REM  Donut
     REM  Version 1.2 // 16-Mar-2012


     REM
     REM  Disable Escape key
     REM

     
*ESC OFF


     REM
     REM  Set up a simple error handler
     REM

     
ON ERROR OSCLI "REFRESH ON" : MODE 8 : REPORT : PRINT " at line ";ERL : END


     
REM
     REM  Select 64-bit floating point mode (double-precision maths)
     REM

     
*FLOAT 64


     REM
     REM  Prevent the program window from being resized by the user
     REM

     
PROCfixWindowSize


     REM
     REM  Select a 640x512 display mode and switch off the flashing cursor
     REM

     
MODE 8
     OFF


     
REM
     REM  Install and initialise GFXLIB and required external modules
     REM

     
INSTALL @lib$ + "GFXLIB2.BBC"
     PROCInitGFXLIB

     INSTALL @lib$ + "GFXLIB_modules\PlotTint.BBC"
     PROCInitModule


     REM
     REM  Install and initialise SORTLIB (which will be used to depth-sort the
     REM  'vector balls' according to their Z coordinate)
     REM

     
INSTALL @lib$+"SORTLIB"
     sort% = FN_sortinit(1, 0)


     REM
     REM  Load-in the ball sprite (32x32 pixels, 24bpp) and convert to 32bpp
     REM

     
ballBm% = FNLoadImg( @dir$ + "sphere003_32x32x24.BMP", 0 )


     REM
     REM  Define our donut ring variables
     REM

     
ballsPerRing% = 12
     ringRadius% = 50
     ringDist% = 180
     numRings% = 30

     numBalls% = numRings% * ballsPerRing%


     REM
     REM  Arrays to hold the ball's position in 3D space,
     REM  and declare the rotation matrix arrays
     REM

     
DIM p(2, numBalls%-1), q(2, numBalls%-1)

     DIM m1(2,2), m2(2,2), m3(2,2)


     REM
     REM  Define our 3D donut object
     REM

     
N% = 0
     FOR T% = 0 TO numRings%-1
       FOR A% = 0 TO ballsPerRing%-1
         x = ringDist% + ringRadius% * SIN( A% * 2*PI/ballsPerRing% )
         y = ringRadius% * COS( A% * 2*PI/ballsPerRing% )
         z = 0
         PROCrotatePoint( x, y, z, 0, T%*(2*PI/numRings%), 0, x`, y`, z` )
         p(0, N%) = x`
         p(1, N%) = y`
         p(2, N%) = z`
         N% += 1
       NEXT A%
     NEXT T%


     REM
     REM  Define the initial rotation angles, and the 'colour-change' variables
     REM

     
R% = RND(-TIME)

     a = 2.0 * PI*RND(1) : REM  \
     
b = 2.0 * PI*RND(1) : REM   >---  rotation angles
     
c = 2.0 * PI*RND(1) : REM  /


     
d = 2.0 * PI*RND(1) : REM  \
     
e = 2.0 * PI*RND(1) : REM   >---  colour-change vars
     
f = 2.0 * PI*RND(1) : REM  /


     REM
     REM  Load some frequently accessed values into static vars
     REM  for faster speed of access
     REM

     
B% = ballBm%
     D% = dispVars{}
     P% = GFXLIB_PlotTint%


     REM
     REM  Disable automatic program window refresh
     REM

     
*REFRESH OFF


     REPEAT


       
REM
       REM  Clear the viewport (fill it with slowly changing colour)
       REM

       
red = 127 + 128*SINd
       green = 127 + 128*COSe
       blue = 127 + 128*SINf*COSe
       bgCol% = FNrgb( red, green, blue )

       SYS GFXLIB_Clr%, dispVars{}, bgCol%


       REM
       REM  Rotate the 3D positions of the balls
       REM

       
cosa = COSa
       cosb = COSb
       cosc = COSc
       sina = SINa
       sinb = SINb
       sinc = SINc

       m1() = 1, 0, 0, 0, cosa, -sina, 0, sina, cosa
       m2() = cosb, 0, sinb, 0, 1, 0, -sinb, 0, cosb
       m3() = cosc, -sinc, 0, sinc, cosc, 0, 0, 0, 1

       q() = m1().p()
       q() = m2().q()
       q() = m3().q()


       REM
       REM  Sort the rotated ball positions according to their Z-coordinate
       REM

       
C% = DIM(q(),DIM(q()))+1
       CALL sort%, q(2,0), q(0,0), q(1,0)


       REM  ===========================
       REM  Draw the depth-sorted balls
       REM  ===========================

       REM
       REM  Reminder:  P% = GFXLIB_PlotTint%
       REM             D% = dispVars{}
       REM             B% = ballSprAddr%
       REM

       
FOR I%=0 TO numBalls%-1

         REM  Calc. perspective factor
         
z = 680 / (600 + q(2,I%))

         REM  Calc. the depth-dependent colour tinting factor
         
T% = 255 * (160 + q(2,I%))/320

         REM  Calc. ball's screen coordinates
         
X% = 304 + q(0,I%)*z
         Y% = 240 + q(1,I%)*z

         REM  Plot the ball sprite
         REM
         REM  P% = GFXLIB_PlotTint%,  D% = dispVars{},  B% = ballBm%

         
SYS P%, D%, B%, 32, 32, X%, Y%, bgCol%, T%

       NEXT


       
REM
       REM  Bump and check the rotation angles and the background colour-change vars
       REM

       
a += 0.0292710182113
       b += 0.0263168891711
       c += 0.0221941538383

       d += 0.0021771091552
       e += 0.0050891118723
       f += 0.0039187106721

       IF a > 2*PI THEN a -= 2*PI
       IF
b > 2*PI THEN b -= 2*PI
       IF
c > 2*PI THEN c -= 2*PI

       IF
d > 2*PI THEN d -= 2*PI
       IF
e > 2*PI THEN e -= 2*PI
       IF
f > 2*PI THEN f -= 2*PI


       
REM
       REM  Update the screen (program window)
       REM

       
PROCdisplay

     UNTIL FALSE


     
DEF PROCrotatePoint( x%, y%, z%, a, b, c, RETURN x`, RETURN y`, RETURN z` )
     LOCAL x1, y1, z1, x2, y2, z2, x3, y3, z3
     LOCAL cosa, cosb, cosc, sina, sinb, sinc

     cosa = COSa
     cosb = COSb
     cosc = COSc
     sina = SINa
     sinb = SINb
     sinc = SINc

     REM X rotation
     
y1 = y%*cosa - z%*sina
     z1 = y%*sina + z%*cosa
     x1 = x%

     REM Y rotation
     
z2 = z1*cosb - x1*sinb
     x2 = z1*sinb + x1*cosb
     y2 = y1

     REM Z rotation
     
x3 = x2*cosc - y2*sinc
     y3 = x2*sinc + y2*cosc
     z3 = z2

     x` = x3
     y` = y3
     z` = z3
     ENDPROC


     
DEF PROCfixWindowSize
     LOCAL GWL_STYLE, WS_THICKFRAME, WS_MAXIMIZEBOX, ws%
     GWL_STYLE = -16
     WS_THICKFRAME = &40000
     WS_MAXIMIZEBOX = &10000
     SYS "GetWindowLong", @hwnd%, GWL_STYLE TO ws%
     SYS "SetWindowLong", @hwnd%, GWL_STYLE, ws% AND NOT (WS_THICKFRAME+WS_MAXIMIZEBOX)
     ENDPROC