PLAYBASIC SOURCE CODES


Merry Christmas Tree from PlayBASIC




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 needle
    cameracls Camera,off
    scenecachesize 8 * (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.75

    do
        inkmode 1
      shadebox 0,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 / 2 
        local y# = sh / 2
        local I# = 10
      capturetoscene
      clsscene
      inkmode 1+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 branch
        drawrotatedimage image, Xpos#, Ypos#, 0, Scale#, Scale#, Offset, Offset, true

        ; lights on every 4th branch
        if (Count and 3) = 0
              capturedepth z#-5
            circlec 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++
    endwhile

    inkmode 1+64
        capturedepth 1000
        scale#=10

        StarScale# =curvevalue(0.5,STarScale#+rnd#(0.25),10)
        io                =GetImageWidth(StarImage)/-2    

        drawrotatedimage StarIMage,sw*0.5 , sh*0.02, 0, StarScale#, StarScale#, io,io, false
        drawcamera camera

    BaseAngle# += 0.11  ; nice slow spin

    ink -1
    inkmode 1
    text 10,10, "FPS:"+str$(fps())+"  Branches:"+str$(Count)
    
    sync
loop spacekey()


    end
    

Function DrawFadedSprite(ThisImage,x#,y#,Alpha#)
        Temp=newsprite(X#,y#,ThisIMage)
        spritedrawmode temp,2
        centerspritehandle temp
        Level = cliprange(Alpha#*255,0,255)
        spritetint temp,argb(level,level/2,level,level)
        drawsprite temp
        deletesprite Temp
EndFunction 


Function MakeParticle(Size, Col)
    ThisImage = NewFXImage(Size, Size)    
    ; Brighter tip, darker base ? looks like real pine needles
    RenderPhongImage ThisImage, Size/2, Size/2, Col, 255, 300/(Size/2)
    ; Add  white highlight
    ink $88FF88
    inkmode 1+64
    circlec ThisImage, Size/2, Size/2, Size*0.15, true
    inkmode 1
EndFunction ThisImage


source code & media attached

#1
2025-12-18 - Christmas Tree-LQ.zip (104.74 KB)

Simple Ray Tracer

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#
    EndType


    type tLight
        Dir as PB_VEctor3
        NegDir as PB_VEctor3            ; negated light direction
        Colour    
    Endtype


    type tImpactPoint
        Hit        as PB_Vector3    
        Normal    as PB_Vector3    
    Endtype

    Global Width          = 400
    Global Height        = 300
    Global Aspect#    = float(Width)/Height
    Global Fov#        = 60.0
    Global 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# = 4

    global 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

        SetFps 60

        Screen=NewfxIMage(Width,Height)

        Do

            rendertoimage screen
            lockbuffer
            ThisRGB =point(0,0)
            For y = 0 To Height-1 step 1
                    For x = 0 To Width-1 step 1
                            CastRay(ray, x, y)
                        col = TraceRay(ray, HitResult)
                            fastdot x, y, col
                      Next
                  Next
            unlockbuffer
            
            DisplayScreen(Screen)

            ray.origin.z# -= 0.05
        Sync    
    Loop

Function DisplayScreen(ThisIMAGE)
        rendertoscreen
        ScaleX#=GetScreenWidth()/float(Width)
        ScaleY#=GetScreenHeight()/float(Height)
        drawrotatedimage ThisIMage,0,0,angle#,ScaleX#,scaleY#,0,0,0 
EndFunction 


;=================================================================
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)
    
EndFunction



Function TraceRay(ray as PB_RAY pointer,  HitResult as PB_HitResult pointer)
    t# = 999999.0
    hitSphere = -1

    ObjectCount = getarrayelements( Spheres())

    ; Find closest sphere intersection from viewer
    For i = 0 To ObjectCOUNT
            result =RayIntersectSphere(ray, Spheres(i).tSphere, HitResult) 
            if result>0
                    dist#=hitresult.t    
                  If dist# > 0.001 And dist# < t#
                        t# = dist#
                            hitSphere = i
                            ;  Copy the impact & Normals 
                            ImpactPoint.hit            = HitResult.Hitpoint
                            ImpactPoint.Normal        = HitResult.Normal
                    EndIf
            endif    
      Next

    ; if the ray misses all objects, we exit
    If 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 objects
    For i = 0 To ObjectCOUNT
              result =RayIntersectSphere(ShadowRay, Spheres(i).tSphere, HitResult) 
            if result
                if HitResult.tEnter# >0.001
                    inShadow = 1
                    ExitFor
            EndIf
        EndIf
    Next
    
    materialColour = Spheres(hitSphere).colour

    specular#        = Spheres(hitSphere).specular#

    reflective#    = Spheres(hitSphere).reflective#

    ; Ambient colour
    finalCol# = 0.1
    
    If inShadow = 0
        ; Diffuse
        NdotL# = Vector3_Dot(impactpoint.normal, light.NegDir)
        If NdotL# > 0
            finalCol# += NdotL# * 0.8
        EndIf


        If 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#
        EndIf
    EndIf


        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.99
EndFunction


#1
2025-12-06 - Simple Ray Tracer.zip (5.72 KB)

Array Memory Arena Allocation Demo

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.






#1
2025-12-04 - PB_Anera Memory Manager V018.zip (14.3 KB)

Wave Image Demo

Wave Image Demo (Horizontal, Vertical, Combined & Double Ripple Effects)


This example perform the classic sinus wave distortion along the X / Y and combined axis's.

  * CopyStripV (vertical ripple)
  * CopyStripH (horizontal ripple)



PlayBASIC Code:
    
    setfps 100

    LoadFont "courier new",1,32,8
    fontdrawmode 1,1  
    
    ThisIMAGE = LoadNewFXImage("FILE-NAME-OF_IMAGE-TO_LOAD")
    
    Width    = GetImageWidth(ThisIMAGE)
    Height    =    GetImageHeight(ThisIMAGE)
    
    TempImage = NewfxImage(Width,Height)    
    TempImage2 = NewfxImage(Width,Height)    
    
    
    Do

        cls        
        
        select Method

            ; --------------------------------------------------------
            case 0            
            ; --------------------------------------------------------
                HoriWave(ThisIMAGE,0,Angle#    ,0.1    ,100)

            ; --------------------------------------------------------
            case 1
            ; --------------------------------------------------------
                VertWave(ThisIMAGE,0,Angle#    ,0.11    ,100)
    
            ; --------------------------------------------------------
            case 2
            ; --------------------------------------------------------
                HoriWave(ThisIMAGE,TempIMAGE    ,Angle#        ,0.2        ,200)
                VertWave(TempIMAGE,0                ,Angle#        ,0.11        ,200)
                CLsIMAGE(TempIMAGE,0)
                

            ; --------------------------------------------------------
            case 3
            ; --------------------------------------------------------

                HoriWave(ThisIMAGE,TempIMAGE    ,Angle#        ,1        ,10)
                VertWave(TempIMAGE,TempIMAGE2    ,Angle2#    ,1        ,10)
                CLsIMAGE(TempIMAGE,0)

                HoriWave(TempIMAGE2,TempIMAGE    ,Angle2#        ,0.2    , 100+sin(angle2#)*50)
                VertWave(TempIMAGE,0                ,Angle2#        ,0.11    , 50)
                CLsIMAGE(TempIMAGE2,0)

        endselect
    
    
        if ScanCode()
            if enterkey()
                Method=wrapvalue(Method+1,0,4)
                flushkeys
            endif
        endif            
    
        print Fps()

        Angle#    =wrapangle(Angle#,1.1)
        Angle2#    =wrapangle(Angle2#,0.35)
        
        Sync        
    loop spacekey()






Function CLsIMAGE(ThisIMAGE,ThisCOLOUR)
            rendertoimage ThisIMAGE
            Cls ThisCOLOUR
            rendertoscreen
ENdFunction 




Function HoriWave(SrcIMAGE,DestIMAGE,Angle#,AngleStep#,Radius#)

            Width    =GetImageWidth(SrcIMage)
            Height=GetImageHeight(SrcIMage)
    
            lockbuffer
                for ylp=0 to Height-1
                    x =sin(Angle#+(ylp*AngleStep#))*Radius#
                    CopyStripH SrcIMAGE,ylp,0,Width , DestIMAGE, X , ylp  
                next
            unlockbuffer
    
EndFunction 


Function VertWave(SrcIMAGE,DestIMAGE,Angle#,AngleStep#,Radius#)

            Width    =GetImageWidth(SrcIMage)
            Height=GetImageHeight(SrcIMage)
            lockbuffer
                for Xlp=0 to Width-1
                    y =sin(Angle#+(Xlp*angleStep#))*Radius#
                    CopyStripV SrcIMAGE,Xlp,0,Height , DestIMAGE, xlp,y
                next
            unlockbuffer
EndFunction




Find Newest File Within Folder

Find Newest File Within Folder

  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$

    Sync
    waitkey
COMMANDS USED: CURRENTDIR$ | PRINT | SYNC | WAITKEY |

#1
2025-11-19 - Find Newest File In Folder.zip (8.08 KB)