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.
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)shadebox0,0,sw,sh,c,rgbfade(c,80),c,rgbfade(c,80)inkmode1+32ellipsec 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)inkmode1// 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,offsetfps60Type tBall
x#,y#,angle#,speed#
endTypeDim Ball as tball List
AddNewBallTime=0Do
CT =Timer()and$7fffffffif CT>AddNewBallTime
ball =new Tball
ball.x#=rnd(sw)
ball.y#=rnd(sh)
Ball.Angle#=rnd(360)
Ball.speed#=rndrange(2,10)
AddNewBallTime=CT+1000endifcapturetosceneclssceneCaptureDepth100CameraGrabWorld Camera,CollisionWorld
DrawImage DisplayScreen,0,0,falseCaptureDepth10rendertoimage ZoneScreen
foreach Ball(); check what zone the ball is currently over.
ZoneType=Point(Ball.x#,Ball.y#)and255; 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)ifRayIntersectWOrld(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 ballcircle ball.x#,ball.y#,10,truenextdrawcamera camera
SyncloopFunction CreateVectorWorld()
WorldWidth=GetScreenWidth()
WorldHeight=GetScreenHeight(); Create world
World=NewWorld()CaptureToWorld World
; draw a series of boarder line for this worldline0,0,worldwidth,0line worldwidth,0,worldwidth,worldheight
line worldwidth,worldheight,0,worldheight
line0,worldheight,0,0for lp=0to7
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,32DrawGFXImmediateEndFunction World
; This function creates a convex polygon shapeFunction Make_Convex(edges,xpos#,ypos#,Size,angle)
sa#=360.0/edges
c=rndrgb()for lp=0to 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
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…