The program builds the Christmas tree procedurally by drawing a spiralling column of small, shaded “needle” particles. Each branch position is calculated using sine and cosine, with a slowly increasing angle (`BaseAngle#`) to create the rotating, corkscrew shape. As the branches move downward, their colour is blended from bright green into a darker green to give depth and the illusion of denser foliage.
Small glowing ornaments are added at regular intervals along the branches. Their colour gently oscillates between gold and red using time-based sine functions, creating a subtle twinkling effect. Depth values are captured per branch so nearer elements render above distant ones, enhancing the 3D feel.
A glowing star is drawn last at the top of the tree, using a flare image that smoothly scales and pulses for a soft halo effect. The background remains a dark midnight blue to contrast the bright tree and lights.
Finally, a large “Merry Christmas” banner is drawn as a sprite that curves upward from the bottom of the screen while fading in and out sinusoidally, giving a slow, festive reveal. Everything is updated each frame, synced with a gentle rotation to keep the scene calm and animated.
PlayBASIC Code:
; PROJECT : Christmas tree; AUTHOR : Kevin Picone - PlayBASIC Tutor (https://PlayBASIC.com); CREATED : 9/12/2025; EDITED : 18/12/2025; ---------------------------------------------------------------------; Christmas colours
TreeGreen =$006618; Deep pine green
TreeDarkGreen =$00330a; Almost black-green for shadows/depth
TrunkBrown =$8B4513; Classic tree trunk brown (or $A0522D for lighter)
SnowWhite =$F0F8FF; Soft alice-white for subtle highlights
GoldOrnament =$FFD700; Warm gold for lights/baubles
RedOrnament =$C41E3A; Deep Christmas red
WarmGlow =$FFAA44; Soft orange-yellow glow (for star/lights)
Background =$101020; Very dark midnight blue (instead of black)
Camera =NewCamera()
Image = MakeParticle(64, TreeGreen); bright green tip of the needlecameracls Camera,offscenecachesize8*(1024*1024)
Offset =GetImageWidth(Image)/-2
StarImage =loadnewimage("gfx/flare.png",2)
BackDropImage =loadNewImage("gfx/Merry-Christmas-LQ2.png",8)
BaseAngle# =0
sw=getscreenwidth()
sh=getscreenheight()
LogoX# = sw*0.52
LogoY#=sh*1.75doinkmode1shadebox0,0,sw,sh,Background,Background,0,0; dark midnight blue sky; Fade the logo in/out using sinus
LogoFade#=wrapangle(LogoFade#,0.2)
Alpha# =0.5+sin(LogoFade#)*0.45
LogoY# =CurveValue(sh*0.45,LogoY# ,50)
DrawFadedSprite(BackdropImage,LogoX#,LogoY#,Alpha#)local x# = sw /2local y# = sh /2local I# =10capturetosceneclssceneinkmode1+64; additive + alpha
Count =0
xpos2# = x#
ypos2# = y# +100; start trunk a bit lower
Current_TIME =timer()
angle# = BaseAngle# +RadToDegree(I#)
Xpos2# = I# *Cos(angle#)+ x#
Ypos2# =20- I# *(Sin(angle#)*0.2-2)While I# < y# -50
angle# = BaseAngle# +RadToDegree(I#)
Xpos# = I# *Cos(angle#)+ x#
Ypos# =20- I# *(Sin(angle#)*0.2-2); Make branches darker the lower they are (more realistic)
depth# = I# /(y#-50)
green =rgbalphablend(TreeGreen, TreeDarkGreen, depth#*80); Twinkling lights effect
twinkle# =Sin(degreeToRad(Count*15+ Current_TIME*0.02))*0.5+0.5
lightCol =rgbalphablend(GoldOrnament, RedOrnament, (Sin(Current_TIME*0.005+ Count)*0.5+0.5)*100)
scale# =0.5-(Sin(angle#)*0.45)
z# =1000+500*(1-Scale#)capturedepth z#
; Main green branchdrawrotatedimage image, Xpos#, Ypos#, 0, Scale#, Scale#, Offset, Offset, true; lights on every 4th branchif(Count and3)=0capturedepth z#-5circlec Xpos#, Ypos#, (rndrange#(2,5))*(Scale#), true,rgbfade(LightCol,50)endif; Branch connection line (slightly darker)linec xpos2#, ypos2#, xpos#, Ypos#, $003308
xpos2# = Xpos#
ypos2# = Ypos#
I# +=1.48
Count++endwhileinkmode1+64capturedepth1000
scale#=10
StarScale# =curvevalue(0.5,STarScale#+rnd#(0.25),10)
io =GetImageWidth(StarImage)/-2drawrotatedimage StarIMage,sw*0.5 , sh*0.02, 0, StarScale#, StarScale#, io,io, falsedrawcamera camera
BaseAngle# +=0.11; nice slow spinink-1inkmode1text10,10, "FPS:"+str$(fps())+" Branches:"+str$(Count)syncloopspacekey()endFunction DrawFadedSprite(ThisImage,x#,y#,Alpha#)
Temp=newsprite(X#,y#,ThisIMage)spritedrawmode temp,2centerspritehandle temp
Level =cliprange(Alpha#*255,0,255)spritetint temp,argb(level,level/2,level,level)drawsprite temp
deletesprite Temp
EndFunctionFunction MakeParticle(Size, Col)
ThisImage =NewFXImage(Size, Size); Brighter tip, darker base ? looks like real pine needlesRenderPhongImage ThisImage, Size/2, Size/2, Col, 255, 300/(Size/2); Add white highlightink$88FF88inkmode1+64circlec ThisImage, Size/2, Size/2, Size*0.15, trueinkmode1EndFunction ThisImage
source code & media attached
#1
2025-12-18 - Christmas Tree-LQ.zip (104.74 KB)
Simple Ray Tracer
By: Kevin Picone | Added: December 14th, 2025
PlayBASIC Math Library Demo - PlayBASIC Math Library Demo - Simple Ray Tracer
This example demonstrates a simple real-time ray tracer written entirely in PlayBASIC, using the new `PB_Vector3` and `PB_Ray` math libraries. Rather than drawing sprites or polygons, the program renders the scene by firing rays into a virtual 3D world and calculating what each ray hits.
While the visuals may look advanced, the goal of this demo is to show how clean and approachable modern 3D techniques become when supported by solid math helpers.
What does this program actually do?
For every pixel on the screen, the program creates a ray that starts at the camera and travels forward into the scene. Each ray is tested against a small list of spheres to see if it intersects any of them.
If a ray hits a sphere, the program:
* Finds the exact point of impact * Calculates the surface direction at that point * Applies lighting, shading, and highlights
If no object is hit, the pixel is coloured with a simple sky gradient.
By repeating this process for every pixel, the program builds a full image entirely from math.
Why vectors are so important
A vector is simply a direction in 3D space. In this example, vectors are used everywhere:
* Ray directions * Surface normals * Light direction * View direction (toward the camera)
The new `PB_Vector3` functions handle all the hard work: normalising directions, adding vectors, taking dot products, and scaling values.
This lets programmers focus on *what they want to do* instead of *how the math works internally*.
Ray casting and intersections
A ray is defined by two things: where it starts and which direction it travels.
Using `RayIntersectSphere`, the program checks whether a ray passes through a sphere and, if so, how far away the collision occurs. The closest hit is always chosen, which ensures objects in front correctly block objects behind them.
This same technique is used both for:
* Camera rays (what the viewer sees) * Shadow rays (checking if a point is in shadow)
Lighting and shadows
Once a ray hits a sphere, lighting calculations take place:
* A small ambient value ensures nothing is completely black * Diffuse lighting depends on how directly the surface faces the light * Specular highlights create shiny reflections
To determine shadows, a second ray is fired toward the light source. If anything blocks it, the point is considered in shadow and only ambient lighting is applied.
This single technique dramatically increases realism while keeping the code understandable.
Why this matters for PlayBASIC users
With the new Vector3 and ray tools, PlayBASIC programmers can now explore:
* Collision detection * Ray-based picking * Lighting models * Physics and camera systems * Reflection and refraction effects
All of this is possible without leaving BASIC, and without relying on external engines or shaders.
For beginners especially, this example shows that modern graphics techniques are not out of reach—they simply require the right building blocks.
The new math libraries provide exactly that foundation.
Note:Requires PlayBASIC V1.65C3 Beta 49
PlayBASIC Code:
Type tSphere
center as PB_Vector3
radius#
colour
specular#
reflective#
EndTypetype tLight
Dir as PB_VEctor3
NegDir as PB_VEctor3 ; negated light direction
Colour
Endtypetype tImpactPoint
Hit as PB_Vector3
Normal as PB_Vector3
EndtypeGlobal Width =400Global Height =300Global Aspect# =float(Width)/Height
Global Fov# =60.0Global HalfWidth# =Tan(Fov#*0.5)Global HalfHeight# = HalfWidth# / Aspect#
Dim Spheres(3)as tSphere
SetupScene()Dim Ray as PB_Ray pointer
Ray =new PB_RAY
ray.origin.x# =0
ray.origin.y# =0
ray.origin.z# =4global ViewDir as PB_Vector3 pointer
ViewDir =new PB_Vector3
global halfVec as PB_Vector3 pointer
halfVec =new PB_Vector3
global TempVec as PB_Vector3 pointer
TempVec =new PB_Vector3
; Simple directional light (sun)global Light as tLight pointer
Light =new tLight
Vector3_Set(LIght.dir , -0.5, -1.0, -0.5)
Vector3_Normalize(light.Dir)
Vector3_NegateTo(light.NegDir, light.Dir)
light.Colour =rgb(255,255,240)global shadowRay as PB_Ray pointer
shadowRay =new PB_Ray
global ImpactPoint as tImpactPoint pointer
ImpactPoint =new tImpactPoint
dim HitResult as PB_HitResult pointer
HitResult =new PB_HitResult
SetFps60
Screen=NewfxIMage(Width,Height)Dorendertoimage screen
lockbuffer
ThisRGB =point(0,0)For y =0To Height-1step1For x =0To Width-1step1
CastRay(ray, x, y)
col = TraceRay(ray, HitResult)fastdot x, y, col
NextNextunlockbuffer
DisplayScreen(Screen)
ray.origin.z# -=0.05SyncLoopFunction DisplayScreen(ThisIMAGE)rendertoscreen
ScaleX#=GetScreenWidth()/float(Width)
ScaleY#=GetScreenHeight()/float(Height)drawrotatedimage ThisIMage,0,0,angle#,ScaleX#,scaleY#,0,0,0EndFunction;=================================================================Function CastRay(Ray as PB_Ray Pointer,x, y)
u# =((x +0.5)/ Width )*2.0-1.0
v# =((y +0.5)/ Height)*2.0-1.0
v# =-v# ; Y is down in screen space
ray.dir.x# = u# * HalfWidth#
ray.dir.y# = v# * HalfHeight#
ray.dir.z# =-1.0
Vector3_Normalize(ray.dir)EndFunctionFunction TraceRay(ray as PB_RAY pointer, HitResult as PB_HitResult pointer)
t# =999999.0
hitSphere =-1
ObjectCount =getarrayelements( Spheres()); Find closest sphere intersection from viewerFor i =0To ObjectCOUNT
result =RayIntersectSphere(ray, Spheres(i).tSphere, HitResult)if result>0
dist#=hitresult.t
If dist# >0.001And dist# < t#
t# = dist#
hitSphere = i
; Copy the impact & Normals
ImpactPoint.hit = HitResult.Hitpoint
ImpactPoint.Normal = HitResult.Normal
EndIfendifNext; if the ray misses all objects, we exitIf hitSphere =-1; Sky gradient
dirY# = ray.dir.y#
dirY# =(dirY# +1.0)*0.5
col =Rgb(255*dirY#, 255*dirY#, 255)exitfunction col
EndIf; The impact point will be ontop of the sphere surface,; we actually don't want this, So lets move it off; just a little to avoid self detection
Vector3_Scale(TempVec, ImpactPoint.Normal, 0.001)
Vector3_Add(ShadowRay.Origin, ImpactPoint.Hit, TempVec); Shadow ray; inShadow = 0
shadowRay.dir = light.negDir // pre negated / normalized// Check for impacts between this ray and existing objectsFor i =0To ObjectCOUNT
result =RayIntersectSphere(ShadowRay, Spheres(i).tSphere, HitResult)if result
if HitResult.tEnter# >0.001
inShadow =1ExitForEndIfEndIfNext
materialColour = Spheres(hitSphere).colour
specular# = Spheres(hitSphere).specular#
reflective# = Spheres(hitSphere).reflective#
; Ambient colour
finalCol# =0.1If inShadow =0; Diffuse
NdotL# = Vector3_Dot(impactpoint.normal, light.NegDir)If NdotL# >0
finalCol# += NdotL# *0.8EndIfIf specular# >0; Step 1: view direction = -ray.dir (from surface toward camera)
Vector3_NegateTo(ViewDir, Ray.Dir); Step 2: half vector = normalize( lightDir + viewDir ); But lightDir points TOWARD the light, so we want -lightDir (from surface to light)
Vector3_Add(TempVec, Light.NegDir, ViewDir); TempVec = L + V
Vector3_NormalizeTo(halfVec, TempVec); halfVec = normalized(L + V); Step 3: specular term
d# = Vector3_Dot(ImpactPoint.Normal, halfVec)
d# =MaxVal#(0.0, d#)
spec# = d# ^32
finalCol# = finalCol# + spec# * specular#
EndIfEndIf
finalCol# =cliprange#(finalCol#,0,1)
col =RgbFade(materialColour,FinalCol#*100)EndFunction Col
Function SetupScene(); Floor sphere (big matte)
Spheres(0).center.x =0
Spheres(0).center.y =-100.5
Spheres(0).center.z =-1
Spheres(0).radius# =100
Spheres(0).colour =rgb(200,200,220)
Spheres(0).specular# =0.1
Spheres(0).reflective# =0.0; Red shiny sphere
Spheres(1).center.x# =-1.0
Spheres(1).center.y# =0
Spheres(1).center.z# =-1
Spheres(1).radius# =0.5
Spheres(1).colour =rgb(220,50,50)
Spheres(1).specular# =0.9
Spheres(1).reflective# =0.8; Green glass sphere
Spheres(2).center.x# =1.0
Spheres(2).center.y# =-0.2
Spheres(2).center.z# =-0.5
Spheres(2).radius# =0.5
Spheres(2).colour =rgb(50,220,100)
Spheres(2).specular# =0.8
Spheres(2).reflective# =0.95; Metal sphere
Spheres(3).center.x# =0
Spheres(3).center.y# =0.3
Spheres(3).center.z# =-1.5
Spheres(3).radius# =0.4
Spheres(3).colour =rgb(220,220,220)
Spheres(3).specular# =1.0
Spheres(3).reflective# =0.99EndFunction
#1
2025-12-06 - Simple Ray Tracer.zip (5.72 KB)
Array Memory Arena Allocation Demo
By: Kevin Picone | Added: December 13th, 2025
AMA – Arena / Array Memory Allocation for PlayBASIC
This library implements a fully custom arena-style memory allocator written entirely in PlayBASIC. Rather than relying on scattered arrays or implicit allocations, it provides a way to manage memory manually from a single contiguous pool, much like a real heap in a low-level engine.
The result is explicit control over allocation, deallocation, fragmentation, and compaction — concepts that are normally hidden from high-level languages.
Core Idea
At the heart of AMA is one large, linear memory buffer that acts as a simulated heap. All memory requests are carved out of this buffer as banks — simple ranges defined by:
• A start address • An end address • A status flag (in use or free)
These banks are not arrays or objects themselves. They are just slices of a shared memory arena. What the memory represents is entirely up to the caller.
This design mirrors how classic engines and operating systems managed memory on constrained hardware.
How Allocation Works (Conceptually)
When a new bank is requested, the allocator:
1. Scans the existing allocations in address order 2. Searches for a free gap large enough to satisfy the request 3. Prefers the *smallest usable gap* (best-fit strategy) 4. Automatically grows the arena if no gap is large enough 5. Inserts the new bank into a sorted allocation list
Because banks are stored in sorted address order, the allocator can reason about gaps, fragmentation, and layout very efficiently — without scanning the entire memory buffer.
Fragmentation Is a First-Class Concept
Deleting banks in arbitrary order will naturally create memory fragmentation. AMA does not hide this — it tracks and exposes it.
The library measures:
• Total free memory • Largest contiguous free gap • A fragmentation percentage derived from these values
This makes it possible to see situations where memory exists, but not in a usable form — a classic real-world heap problem.
Manual Defragmentation
To address fragmentation, AMA includes a defragmentation pass that:
• Moves active banks downward in memory • Removes gaps between allocations • Preserves all data during relocation • Updates internal address tracking
After defragmentation, all free memory becomes one large contiguous block at the end of the arena.
This is effectively a compacting memory manager, similar in spirit to early garbage-collected or frame-based systems — but fully under programmer control.
Debugging and Validation Tools
The allocator includes extensive validation support:
• Memory filling to detect corruption • Verification passes to ensure banks don’t overlap • Integrity checks after defragmentation • Sorted-order diagnostics for allocation state
These tools turn the allocator into something you can inspect and trust, rather than a black box.
What the Demo Program Shows
The demo included with the library is not a gameplay example — it is a stress test and visualiser.
It demonstrates:
• Repeated allocation and deletion of banks • Intentional fragmentation • Manual defragmentation triggers • Real-time memory statistics (used, free, total) • Bank counts and allocation order
Most importantly, it renders a **graphical memory map** where each allocation appears as a coloured region. Larger banks appear brighter, making fragmentation patterns instantly visible.
This turns abstract memory behaviour into something you can actually *see*.
Why This Exists
This library recreates the style of memory management used in:
• Classic game engines • Console and embedded development • Performance-critical tools • Retro systems with strict memory limits
All within PlayBASIC — without native pointers or explicit heap access.
It provides a practical, hands-on way to understand:
• How heaps fragment • Why best-fit allocation matters • When defragmentation becomes necessary • What “contiguous memory” really means
Big Picture
In simple terms:
AMA builds a miniature memory manager inside PlayBASIC, complete with allocation strategies, fragmentation metrics, defragmentation, and debugging support — then proves it works through visualisation and stress testing.
It’s not just a utility library. It’s an educational and experimental framework for exploring how real memory managers behave — from within a high-level language.
Needed a function to find the newest file (via last write date) from a folder while writing a PlayBASIC backup tool.
PlayBASIC Code:
; this will be the folder of this running project
Path$=currentdir$(); Search the this path for the newest PBA file (playbasic source code)
File$ = FindNewestFileInFolder(Path$, "pba")print path$
print File$
Syncwaitkey
#1
2025-11-19 - Find Newest File In Folder.zip (8.08 KB)