PS3D - Polygon Subdivision PS3D - PlayBASIC Devlog

May 20, 2026

 

PS3D - Polygon Subdivision PS3D - PlayBASIC Devlog

In this PlayBASIC development update, we take a deep dive into the latest polygon subdivision experiments inside the PS3D software rendering engine. This tech demo showcases adaptive quad subdivision, near-plane clipping improvements, affine texture mapping, filtered triangles, and transparent rendering running entirely in software.

The new subdivision system dynamically increases polygon detail based on camera distance and face orientation, dramatically improving image quality for close-up geometry without requiring full perspective-correct texture mapping. Instead of relying purely on traditional affine textured triangles, PS3D subdivides quads into smaller faces to better approximate perspective, reducing the heavy distortion commonly seen near the camera.

This video also explores:

  • Adaptive polygon subdivision in a software renderer
  • Quad vs triangle clipping behavior
  • Near Z-plane clipping and projection artifacts
  • Backface culling and object-level visibility rejection
  • Transparent alpha blended rendering
  • Triangle fan generation after clipping
  • Performance tradeoffs between image quality and rendering speed
  • Screen-space clipping limitations
  • Software 3D rendering techniques in PlayBASIC
  • Retro-style rendering pipelines and engine experimentation
  • The demo scene renders thousands of transparent objects while visualizing subdivision levels and clipping behavior in real time. The green wireframe overlays reveal how faces are broken down and processed internally by the renderer.

    PS3D remains a fully software-driven 3D engine written in PlayBASIC, focused on experimentation with classic rendering techniques, optimization strategies, and retro-inspired graphics programming.

    Mini Golf Demo – Conceptual Overview

    February 21, 2026

     

    Mini Golf Demo – Conceptual Overview

    This demo shows a simple but powerful way to build a top-down mini-golf style simulation in PlayBASIC.

    Rather than focusing on complex physics, the code demonstrates how to combine a few clean ideas to create believable behaviour using fast, data-driven techniques.


    1. Separating visuals from gameplay logic

    The program uses two images of the same size, each with a different purpose:

    • Visible Screen

    This is what you actually see: grass, shapes, and moving balls.

    • Zone Screen

    This image is never shown to the player. Each pixel stores a zone value that tells the code what type of surface the ball is currently rolling over.

    This is a very common game-dev trick:

    images aren’t just for graphics – they can also be fast lookup maps for gameplay data.


    2. Zone-based friction using lookup tables

    Each terrain type (short grass, medium grass, long grass) is assigned:

    • A zone ID

    • A friction value

    • A display colour

    When a ball moves, the code reads the pixel value from the zone screen at the ball’s position and uses that value as an index into a friction array.

    This avoids large blocks of conditionals and makes it easy to add or tweak terrain types later.


    3. Direction-based movement

    Each ball stores its:

    • Position

    • Angle

    • Speed

    Movement is calculated using basic trigonometry (sin and cos) to convert angle and speed into X and Y motion.

    The friction value from the current zone scales the speed, giving the impression of different grass lengths slowing the ball down.


    4. Ray-based collision and reflection

    Instead of moving the ball first and checking overlaps later, the code uses ray intersection:

    • A ray is cast from the current position to the next position

    • If it hits geometry, the surface normal is used to calculate a reflection angle

    • The ball bounces naturally off walls and obstacles

    This method is robust, avoids tunnelling, and works well even at higher speeds.


    5. Vector-based world geometry

    The course is built from vector lines and convex shapes, not tiles.

    • Borders are simple line segments

    • Obstacles are procedurally generated convex polygons

    • The world is partitioned to improve collision performance

    This keeps the layout flexible and easy to expand or randomise.


    6. Data-driven design

    One of the key ideas in this demo is data-driven gameplay:

    • Terrain behaviour is defined by data, not hard-coded logic

    • Friction, colour, and movement all come from lookup tables

    • New terrain types can be added with minimal code changes

    This approach scales well and is widely used in real game engines.


    Summary

    At its core, this demo demonstrates how to:

    • Separate visuals from logic

    • Use images as gameplay data

    • Apply simple physics with believable results

    • Handle collision using raycasting

    • Build systems that are easy to extend

    It’s a compact example of practical game programming techniques, written in a way that new coders can understand and build upon.



      Constant Zone_ShortGrass    =ac(1)
      Constant Zone_MedGrass        =ac(1)
      Constant Zone_LongGrass        =ac(1)
    
    
      Dim Palette(100)
      Palette(Zone_ShortGrass)      = rgb(55,200,80)
      Palette(Zone_MedGrass)          = rgbfade(Palette(Zone_ShortGrass),50)
      Palette(Zone_LongGrass)          = rgbfade(Palette(Zone_ShortGrass),25)
     
      dim Friction#(100)
      Friction#(Zone_ShortGrass)    =1
      Friction#(Zone_MedGrass)      =1.5
      Friction#(Zone_LongGrass)      =2
     
     
      ; get the screen size 
      sw=GetScreenWidth()
      sh=GetScreenHeight()
    
      ; create the visible screen
      DisplayScreen      =NewIMage(sw,sh,true)
    
      ; create the zone screen.
      ; the zone screen is the same size as the 'visible'
      ; but is just used to tell what zone the ball is currently over
      ZoneScreen        =GetFreeimage()
      createfximageex ZoneScreen,sw,sh,32
     
    
      // Draw the Visible scene..
      rendertoimage Displayscreen
      c=Palette(Zone_ShortGrass)
      shadebox 0,0,sw,sh,c,rgbfade(c,80),c,rgbfade(c,80)
    
      inkmode 1+32 
      ellipsec sw*0.7,sh*0.5,200,100,true,Palette(Zone_MedGrass)     
      ellipsec sw*0.7,sh*0.5,100,50,true,Palette(Zone_LongGrass)     
      inkmode 1
    
    
      // draw the collision (zone) scene. 
      rendertoimage Zonescreen
      Cls Zone_ShortGrass
      ellipsec sw*0.7,sh*0.5,200,100,true,Zone_MedGrass     
      ellipsec sw*0.7,sh*0.5,100,50,true,Zone_LongGrass     
     
    
    
      // create collision world
      CollisionWorld=CreateVectorWorld()
     
    
     
      rendertoscreen
    
    
      camera=newcamera()
      cameracls camera,off
    
      setfps 60
    
    
    
      Type tBall
            x#,y#,angle#,speed#
      endType
    
      Dim Ball as tball List
    
        AddNewBallTime=0
    
      Do
    
            CT = Timer() and $7fffffff
          if CT>AddNewBallTime
                ball = new Tball
                ball.x#=rnd(sw)
                ball.y#=rnd(sh)
                Ball.Angle#=rnd(360)
                Ball.speed#=rndrange(2,10)
                AddNewBallTime=CT+1000
          endif
    
           
           
    
          capturetoscene
          clsscene
         
          CaptureDepth 100
    
          CameraGrabWorld Camera,CollisionWorld
    
          DrawImage DisplayScreen,0,0,false
    
          CaptureDepth 10
    
          rendertoimage ZoneScreen
    
          for each Ball()
    
            ; check what zone the ball is currently over.       
            ZoneType=Point(Ball.x#,Ball.y#) and 255
    
            ; get the amount of friction this zone has
            Friction#=Friction#(ZoneType)
     
            ; move the ball 
            Speed#=Ball.Speed#*1/Friction#
    
            newx#=ball.x#+(cos(Ball.angle#)*speed#*1)
            newy#=ball.y#+(sin(Ball.angle#)*speed#*1)
    
            if RayIntersectWOrld(CollisionWorld,Ball.x#,Ball.y#,NewX#,NewY#)=true
                  x2#  =getintersectx#(0)
                  y2#  =getintersecty#(0)
                  ; Calc reflection Direction
                  WallAngle#=AtanFull(getnormaly#(0),getnormalx#(0))
                  RayAngle#=GetAngle2d(x2#,y2#,ball.x#,ball.y#)
                  Ball.Angle#=WrapAngle(WallAngle#,WallAngle#-RayAngle#)
    
                  ball.x#=x2#
                  ball.y#=y2#
               
            else
                  ball.x#=newx#
                  ball.y#=newy#
            endif
    
            ; draw the circle to represent the ball
            circle ball.x#,ball.y#,10,true
          next
    
          drawcamera camera
          Sync
      loop
    
    
    
    
    
    Function CreateVectorWorld()
      WorldWidth=GetScreenWidth()
      WorldHeight=GetScreenHeight()
    
    
    ; Create world
      World=NewWorld()
      CaptureToWorld World
    
    ; draw a series of boarder line for this world
      line 0,0,worldwidth,0
      line worldwidth,0,worldwidth,worldheight
      line worldwidth,worldheight,0,worldheight
      line 0,worldheight,0,0
    
      for lp=0 to 7
          x=100+lp*80
          y=100+lp*80
          Make_Convex(4,x,y,50,90+lp*20)
      next
    
      Make_Convex(6,700,100,90,70)
    
      PartitionWorld World,32
      DrawGFXImmediate
    
    EndFunction World
    
    
    
      ; This function creates a convex polygon shape
    
    Function Make_Convex(edges,xpos#,ypos#,Size,angle)
          sa#=360.0/edges
          c=rndrgb()
          for lp=0 to edges-1
            a#=angle+(lp*sa#)
            x1#=xpos#+cosRadius(a#,size)
            y1#=ypos#+SinRadius(a#,size)
            if lp<(edges-1)
                a#=angle+((lp+1)*sa#)
            else
                a#=angle
            endif
            x2#=xpos#+cosRadius(a#,size)
            y2#=ypos#+SinRadius(a#,size)
            line x2#,y2#,x1#,y1#
          next lp
    endfunction i


    At Least We Got Rid of Line Numbers

    February 07, 2026

     

    At Least We Got Rid of Line Numbers

    Modern programming is stressful.

    We’ve got frameworks that change every five minutes, dependencies that break themselves for sport, and build systems that require more configuration than a NBN router in 2011.

    Some days it feels like software development is just trying to keep a wobbly house of cards standing in a cyclone.

    But at least we got rid of line numbers.


    A Stress New Programmers Never Knew

    There’s an anxiety older coders remember that modern developers will never experience.

    Being asked to write real software in a language that required human-generated line numbers.

  • Not line numbers in an editor.
  • Not line numbers for debugging.
  • Actual:

    10 PRINT "HELLO"
    20 GOTO 10
    

    style line numbers.

    If you never had to live like that, congratulations! You skipped a very specific form of programmer misery.


    Coding With Extra Headaches

    Programming was already hard enough.

    You had to think about logic, bugs, memory limits, and whether the computer was going to throw a tantrum.

    And on top of that, you also had to manage imaginary addresses in your head.

    “Right… I’ll start this routine at 3000. That should be plenty of space.”

    It was never plenty of space.

    Add too much code and suddenly you were out of numbers, half your GOTOs were broken, and your “quick change” had just turned into an all-night renumbering party.

    Good times.


    Refactoring Was Basically a Weekend Plan

    Today, refactoring is normal. Back then it was an event!

  • Coffee was made.
  • Backups were taken.
  • Deep breaths were required.
  • Because moving code around wasn’t just tidying things up, it'ss like trying to renovate a house while it was still on fire.

    You didn’t casually improve programs.

    You stared at them and thought:

    “Is this feature really worth ruining my Saturday?”


    Progress… Sort Of

    Of course, modern development replaced those problems with new ones.

    Now we fight with containers, build pipelines, dependency hell, and error messages that look like they were generated by an angry robot having a bad day.

    In many ways we swapped small simple problems for giant complicated ones.

    But still…


    Small Victories Matter

    Whenever I’m knee-deep in some ridiculous modern tech disaster, I try to remember:

  • At least I can add a line of code without planning its numeric future.
  • At least I can move a function without breaking half the program.
  • At least my editor doesn’t force me to think like an accountant.
  • Programming is still stressful.
  • But thank whatever digital gods look after developers…

    At least we got rid of line numbers.