Author Topic: 🎄🎁✨ Holiday Season - are you ready to code?  (Read 43411 times)

0 Members and 1 Guest are viewing this topic.

Offline johnno56

  • Forum Resident
  • Posts: 1270
  • Live long and prosper.
    • View Profile
Re: 🎄🎁✨ Holiday Season - are you ready to code?
« Reply #75 on: December 15, 2020, 01:27:36 pm »
  • Best Answer
  • OK. Source code upgraded, font is now in the source code.  Try it again, please.  It is running correctly under Windows. (previous version also)

    Petr,

    Thank you for the upgrade. Everything functions as it should.

    Very nicely done! Great job!

    J
    Logic is the beginning of wisdom.

    Offline Dav

    • Forum Resident
    • Posts: 792
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #76 on: December 15, 2020, 05:44:15 pm »
  • Best Answer
  • @bplus: Glad you enjoy the program!

    Thanks @Dav what's your favorite charity?

    As far as donations go, I don't have a favorite. Me and extra money don't seem to meet that often -- so my contributions are of a local volunteering nature.  Sometime those TV commercials (like for the hospital that helps sick kids 100% free) nearly makes my eyes puddle up and wish I did have money to contribute to charities.

    - Dav

    Offline SierraKen

    • Forum Resident
    • Posts: 1454
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #77 on: December 15, 2020, 06:19:41 pm »
  • Best Answer
  • That's really awesome Dav! When I made my Starfield Clock I thought about something like this for the hourly chimes. Right now it only has the PLAY command. You did an impressive job here. Do you mind if I use 1 or 2 of your .ogg sound files for my clock? But I need to know something first, can the chimes play when there's other things going on in the background? That is why I couldn't use the chime example I found a long time ago.
    « Last Edit: December 15, 2020, 06:22:25 pm by SierraKen »

    Offline Dav

    • Forum Resident
    • Posts: 792
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #78 on: December 15, 2020, 06:39:14 pm »
  • Best Answer
  • @SierraKen: Thanks! Sure, you may use anything i make and post here in your projects.  The way the SPLAY SUB is set up (with my added delay) the sound playback would suspend your clock from running while chiming. But you could easily adapt the code to eliminate that problem. Cool thing about _SNDPLAYCOPY is that it won't suspend your program while playing a sound. All you need to do is call the sounds from in your main program (forget the SPLAY SUB) and perhaps set a special TIMER for calling each note in your main loop.

    - Dav

    Offline SierraKen

    • Forum Resident
    • Posts: 1454
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #79 on: December 15, 2020, 10:17:25 pm »
  • Best Answer
  • Thanks Dav, I tried for a bit tonight but couldn't figure it out, it kept making the sound a bunch of times on each chime. But that's OK, I'm satisfied with what I have. :) If I decide to check it out later I might.

    Offline SMcNeill

    • QB64 Developer
    • Forum Resident
    • Posts: 3972
      • View Profile
      • Steve’s QB64 Archive Forum
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #80 on: December 17, 2020, 03:36:59 pm »
  • Best Answer
  • Here's a little something Xmassy which I tossed together this afternoon.  It works better (as in much faster) with external files, but part of the rules here said we should avoid those, so this is what I've got at the moment:

    [edit]

    Grab the attachment below and run it; the data length in the BAS file is too large for the forum database to handle properly, and can't be included in a post.

    If I'd known I couldn't get this to load as is, I wouldn't have bothered to turn it into a singluar file and would've just kept everything externally available.  :P

    Ah well...  Now I need to decide what I want to do next to it (if anything).  I'm thinking of having Santa fly across the screen in his sled, with a ribbon trailing behind him, showing the words of the song as it plays for us...   And then maybe making this a screensaver for the holidays, on a soundplay loop...

    Or maybe I'll just leave it as it is, and not worry over it...

    I dunno yet what I'm going to end up doing with it, but here's my little Xmas contribution!
     
    Screenshot 2020-12-17 16.01.15.png
    * Xmas.bas (Filesize: 1.11 MB, Downloads: 259)
    « Last Edit: December 17, 2020, 04:01:59 pm by SMcNeill »
    https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

    Offline Dav

    • Forum Resident
    • Posts: 792
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #81 on: December 17, 2020, 04:53:36 pm »
  • Best Answer
  • Well that's pretty, Steve.  I really like the floating snow effect.  Has a nice lilt to it that makes it more like snow than just dropping like rain drops.  Good screensaver.

    Took a while for the converting on my slower laptop, but worth the wait.  Nice converting functions there you made....
     
    - Dav
    « Last Edit: December 17, 2020, 05:02:12 pm by Dav »

    Offline SMcNeill

    • QB64 Developer
    • Forum Resident
    • Posts: 3972
      • View Profile
      • Steve’s QB64 Archive Forum
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #82 on: December 17, 2020, 05:12:26 pm »
  • Best Answer
  • Well that's pretty, Steve.  I really like the floating snow effect.  Has a nice lilt to it that makes it more like snow than just dropping like rain drops.  Good screensaver.

    Took a while for the converting on my slower laptop, but worth the wait.  Nice converting functions there you made....
     
    - Dav

    If you're just interested in the conversion routines, you can find them alone here: https://www.qb64.org/forum/index.php?topic=3379.msg126862#msg126862

    Compress and convert images/sound/resource files to BASE-128 encoding, so we can use them internally in DATA statements without any problems.  They need to be optimized for speed (use of readbit and setbit would help a lot, but they weren't in our language when I first did these routines), but they're functional and usage can often be hidden behind a startup splash screen, or such, in many cases.  ;)
    https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

    Offline SierraKen

    • Forum Resident
    • Posts: 1454
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #83 on: December 17, 2020, 05:24:58 pm »
  • Best Answer
  • Amazing Steve, very nice.

    Offline bplus

    • Global Moderator
    • Forum Resident
    • Posts: 8053
    • b = b + ...
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #84 on: December 18, 2020, 02:31:52 am »
  • Best Answer
  • Beautiful snow scene Steve!

    I finally found an oldie I did in 2015 and translated it to QB64 with some mods:
    Code: QB64: [Select]
    1. _TITLE "Sierpinski Decorated" 'b+ 2020-12-18 finally found this oldie
    2. 'sierpinski decorated.bas 2015-12-06 SmallBASIC 0.12.1 [B+=MGA]
    3. 'Thanks to PeterMaria W for code snips
    4. CONST SSQ = 700, horizon = 485 ' Side of Square Screen
    5. DIM SHARED QB(15) 'modified old QB 16 color system
    6. QB(0) = &HFF000000
    7. QB(1) = &HFF000088
    8. QB(2) = &HFF008800
    9. QB(3) = &HFF008888
    10. QB(4) = &HFF880000
    11. QB(5) = &HFF880088
    12. QB(6) = &HFF888800
    13. QB(7) = &HFFCCCCCC
    14. QB(8) = &HFF888888
    15. QB(9) = &HFF0000FF
    16. QB(10) = &HFF00FF00
    17. QB(11) = &HFF00FFFF
    18. QB(12) = &HFFFF0000
    19. QB(13) = &HFFFF00FF
    20. QB(14) = &HFFFFFF00
    21. QB(15) = &HFFFFFFFF
    22.  
    23. SCREEN _NEWIMAGE(SSQ + 100, SSQ, 32)
    24. _DELAY .25
    25.  
    26. xplus = SSQ
    27. yplus = horizon
    28. nstars = 100
    29. DIM xstar(100), ystar(100), rstar(100)
    30. FOR i = 1 TO 100
    31.     xstar(i) = RND * (SSQ + 100): ystar(i) = RND * horizon:
    32.     IF i < 75 THEN
    33.         rstar(i) = 0
    34.     ELSEIF i < 95 THEN
    35.         rstar(i) = 1
    36.     ELSE
    37.         rstar(i) = 2
    38.     END IF
    39.     CLS
    40.     FOR i = 0 TO horizon
    41.         LINE (0, i)-(SSQ + 100, i), _RGB32(i / horizon * 70, i / horizon * 22, 60 * (i) / horizon)
    42.     NEXT
    43.     land = SSQ - horizon
    44.     FOR i = horizon TO SSQ
    45.         cc = 128 + (i - horizon) / land * 127
    46.         LINE (0, i)-(SSQ + 100, i), _RGB32(cc, cc, cc)
    47.     NEXT
    48.     FOR i = 1 TO 100
    49.         fcirc xstar(i), ystar(i), rstar(i), QB(11)
    50.     NEXT
    51.     pinetree 200, 100
    52.     star xplus, yplus
    53.     xdist = 420 - xplus
    54.     xplus = xplus + .1 * xdist
    55.     ydist = 80 - yplus
    56.     yplus = yplus + .1 * ydist
    57.     _DISPLAY
    58.     _LIMIT 7
    59.  
    60. SUB star (x, y)
    61.     fcirc x, y, 3, &HFFAAFF00
    62.     LINE (x - 7, y)-(x - 3, y), &HFFAAFF00
    63.     LINE (x + 3, y)-(x + 7, y), &HFFAAFF00
    64.     LINE (x, y - 7)-(x, y - 3), &HFFAAFF00
    65.     LINE (x, y + 3)-(x, y + 7), &HFFAAFF00
    66.     IF x > 419 AND x < 421 AND y > 79 AND y < 81 THEN
    67.         CIRCLE (x, y), 12, &HFFAAFF00
    68.         COLOR &HFFFFFF88, &H00000000
    69.         Text 35, horizon + 90, 85, &HFFAA0000, "Seasons Greetings"
    70.         sier 210, 125, 420, 340
    71.         SLEEP
    72.     END IF
    73.  
    74. SUB sier (tlx, tly, width, height)
    75.     ' Sierpinski Christmas.bas modified from Petermaria's
    76.     ax = tlx
    77.     ay = tly + height
    78.     bx = tlx + width
    79.     by = tly + height
    80.     cx = tlx + width / 2
    81.     cy = tly
    82.     px = tlx
    83.     py = tly + height
    84.     FOR n = 0 TO 4000
    85.         fcirc px, py, 2, QB(INT(RND * 16))
    86.         SELECT CASE INT(RND * 3)
    87.             CASE 0
    88.                 px = (px + ax) / 2.0
    89.                 py = (py + ay) / 2.0
    90.             CASE 1
    91.                 px = (px + bx) / 2.0
    92.                 py = (py + by) / 2.0
    93.             CASE 2
    94.                 px = (px + cx) / 2.0
    95.                 py = (py + cy) / 2.0
    96.         END SELECT
    97.     NEXT
    98.     _DISPLAY
    99.  
    100. SUB pinetree (x, y)
    101.     'tannen baum by PeterMaria W  orig 440x460
    102.     bpx = 220: bpy = 410
    103.     tpx = bpx
    104.     FOR aa = -4 TO 4
    105.         bpxx = bpx + aa
    106.         bpyy = bpy - 390
    107.         LINE (x + bpxx, y + bpy)-(x + bpx, y + bpyy), _RGB32(30, 30, 0)
    108.     NEXT
    109.     ra = 160
    110.     tpy = bpy - 40
    111.     FOR ht = 1 TO 40
    112.         FOR xs = -100 TO 100 STEP 40
    113.             xsh = xs / 100
    114.             rs = RND * 4 / 10
    115.             tpxx = tpx + (xsh * ra)
    116.             tpyy = tpy - rs * ra
    117.             LINE (x + tpx, y + tpy)-(x + tpxx, y + tpyy), _RGB32(50, 40, 20)
    118.             FOR aa = 1 TO 30
    119.                 fra = RND * 10 / 10 * ra
    120.                 x1 = tpx + (xsh * fra)
    121.                 y1 = tpy - rs * fra
    122.                 x2 = tpx + xsh * (fra + ra / 5)
    123.                 y2 = tpy - rs * fra + (-rs + (RND * 8) / 10 - 0.4) * (ra / 5)
    124.                 LINE (x + x1, y + y1)-(x + x2, y + y2), _RGB32(RND * 120, RND * 70 + 70, RND * 80)
    125.             NEXT
    126.         NEXT
    127.         ra = ra - 4
    128.         tpy = tpy - 9
    129.     NEXT
    130.     _DISPLAY
    131.  
    132. 'from Steve Gold standard
    133. SUB fcirc (CX AS INTEGER, CY AS INTEGER, R AS INTEGER, C AS _UNSIGNED LONG)
    134.     DIM Radius AS INTEGER, RadiusError AS INTEGER
    135.     DIM X AS INTEGER, Y AS INTEGER
    136.     Radius = ABS(R): RadiusError = -Radius: X = Radius: Y = 0
    137.     IF Radius = 0 THEN PSET (CX, CY), C: EXIT SUB
    138.     LINE (CX - X, CY)-(CX + X, CY), C, BF
    139.     WHILE X > Y
    140.         RadiusError = RadiusError + Y * 2 + 1
    141.         IF RadiusError >= 0 THEN
    142.             IF X <> Y + 1 THEN
    143.                 LINE (CX - Y, CY - X)-(CX + Y, CY - X), C, BF
    144.                 LINE (CX - Y, CY + X)-(CX + Y, CY + X), C, BF
    145.             END IF
    146.             X = X - 1
    147.             RadiusError = RadiusError - X * 2
    148.         END IF
    149.         Y = Y + 1
    150.         LINE (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
    151.         LINE (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
    152.     WEND
    153.  
    154. SUB Text (x, y, textHeight, K AS _UNSIGNED LONG, txt$)
    155.     DIM fg AS _UNSIGNED LONG, cur&, I&, multi, xlen
    156.     fg = _DEFAULTCOLOR
    157.     'screen snapshot
    158.     cur& = _DEST
    159.     I& = _NEWIMAGE(8 * LEN(txt$), 16, 32)
    160.     _DEST I&
    161.     COLOR K, _RGBA32(0, 0, 0, 0)
    162.     _PRINTSTRING (0, 0), txt$
    163.     multi = textHeight / 16
    164.     xlen = LEN(txt$) * 8 * multi
    165.     _PUTIMAGE (x, y)-STEP(xlen, textHeight), I&, cur&
    166.     COLOR fg
    167.     _FREEIMAGE I&
    168.  
    169.  

    Offline Petr

    • Forum Resident
    • Posts: 1720
    • The best code is the DNA of the hops.
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #85 on: December 18, 2020, 02:57:30 am »
  • Best Answer
  • Very nice work, @Steve. Your program is also IDE destructor :) - goto to row with DATA and press "end" key on the keyboard :) That's why I cut long strings to 450 characters per line to do our IDE well.

    @Nice work, BPlus! Better tree than mine :)
    « Last Edit: December 18, 2020, 03:04:52 am by Petr »

    Offline Dav

    • Forum Resident
    • Posts: 792
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #86 on: December 18, 2020, 08:13:09 am »
  • Best Answer
  • Nice one, @bplus

    I was looking for an Christmas oldie to post too, but I think it was on my HD that died.  Anyone happen to save that old school Jingle Bell piano thing I posted a few years ago on the [abandoned, outdated and now likely malicious qb64 dot net website - don’t go there] forum?  It played jingle bells while showing the piano keyboard playing the notes, and displayed the words. 

    - Dav

    Offline _vince

    • Seasoned Forum Regular
    • Posts: 422
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #87 on: December 18, 2020, 09:31:05 am »
  • Best Answer
  • Nice work guys, I hope I can contribute something when I get some free time soon.

    An honorable mention is TheBOB's christmas train.  It would get posted on TQBF every year as a tradition, would be nice if someone can pull it up and/or translate it to QB64. (@Pete ?)

    Offline bplus

    • Global Moderator
    • Forum Resident
    • Posts: 8053
    • b = b + ...
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #88 on: December 18, 2020, 11:09:21 am »
  • Best Answer
  • Nice work guys, I hope I can contribute something when I get some free time soon.

    An honorable mention is TheBOB's christmas train.  It would get posted on TQBF every year as a tradition, would be nice if someone can pull it up and/or translate it to QB64. (@Pete ?)

    Here 'tis: https://www.qb64.org/forum/index.php?topic=820.msg100360#msg100360

    Offline STxAxTIC

    • Library Staff
    • Forum Resident
    • Posts: 1091
    • he lives
      • View Profile
    Re: 🎄🎁✨ Holiday Season - are you ready to code?
    « Reply #89 on: December 18, 2020, 11:20:58 am »
  • Best Answer
  • Here is the Sanctum Winter Wonderland update. Let it load, and walk forward. Look all around, find the snowmen and the christmas trees. When you're bored with that, you can wander some more: through the desert, up in space, inside a planet -- or you may go down, down, down all the way to hell.

    Code: QB64: [Select]
    1. ' Version 2020-12-18
    2.  
    3.  
    4. _TITLE "Sanctum"
    5.  
    6. ' Hardware.
    7. 'SCREEN _NEWIMAGE(640, 480, 32)
    8. SCREEN _NEWIMAGE(800, 600, 32)
    9. 'SCREEN _NEWIMAGE(1024, 768, 32)
    10.  
    11. ' Meta
    12.  
    13. ' Color constants.
    14. CONST Aquamarine = _RGB32(127, 255, 212)
    15. CONST Black = _RGB32(0, 0, 0)
    16. CONST Blue = _RGB32(0, 0, 255)
    17. CONST BlueViolet = _RGB32(138, 43, 226)
    18. CONST Chocolate = _RGB32(210, 105, 30)
    19. CONST Cyan = _RGB32(0, 255, 255)
    20. CONST DarkBlue = _RGB32(0, 0, 139)
    21. CONST DarkGoldenRod = _RGB32(184, 134, 11)
    22. CONST DarkGray = _RGB32(169, 169, 169)
    23. CONST DarkKhaki = _RGB32(189, 183, 107)
    24. CONST DeepPink = _RGB32(255, 20, 147)
    25. CONST DodgerBlue = _RGB32(30, 144, 255)
    26. CONST ForestGreen = _RGB32(34, 139, 34)
    27. CONST Gray = _RGB32(128, 128, 128)
    28. CONST Green = _RGB32(0, 128, 0)
    29. CONST Indigo = _RGB32(75, 0, 130)
    30. CONST Ivory = _RGB32(255, 255, 240)
    31. CONST LightSeaGreen = _RGB32(32, 178, 170)
    32. CONST Lime = _RGB32(0, 255, 0)
    33. CONST LimeGreen = _RGB32(50, 205, 50)
    34. CONST Magenta = _RGB32(255, 0, 255)
    35. CONST PaleGoldenRod = _RGB32(238, 232, 170)
    36. CONST Purple = _RGB32(128, 0, 128)
    37. CONST Red = _RGB32(255, 0, 0)
    38. CONST RoyalBlue = _RGB32(65, 105, 225)
    39. CONST SaddleBrown = _RGB32(139, 69, 19)
    40. CONST Sienna = _RGB32(160, 82, 45)
    41. CONST SlateGray = _RGB32(112, 128, 144)
    42. CONST Snow = _RGB32(255, 250, 250)
    43. CONST Sunglow = _RGB32(255, 207, 72)
    44. CONST SunsetOrange = _RGB32(253, 94, 83)
    45. CONST Teal = _RGB32(0, 128, 128)
    46. CONST White = _RGB32(255, 255, 255)
    47. CONST Yellow = _RGB32(255, 255, 0)
    48.  
    49. ' Mathematical constants.
    50. CONST pi = 4 * ATN(1)
    51. CONST ee = EXP(1)
    52.  
    53. ' Types.
    54.  
    55. TYPE Vector
    56.     x AS DOUBLE
    57.     y AS DOUBLE
    58.     z AS DOUBLE
    59.  
    60. TYPE Camera
    61.     Position AS Vector
    62.     Velocity AS Vector
    63.     Shade AS _UNSIGNED LONG
    64.  
    65. TYPE ClusterElement
    66.     Index AS LONG
    67.     FirstGroup AS LONG
    68.     LastGroup AS LONG
    69.     Centroid AS Vector
    70.     Velocity AS Vector
    71.     Visible AS INTEGER
    72.     MotionType AS INTEGER
    73.  
    74. TYPE GroupElement
    75.     Identity AS LONG
    76.     GroupName AS STRING
    77.     Pointer AS LONG
    78.     Lagger AS LONG
    79.     Volume AS Vector
    80.     FirstVector AS LONG
    81.     LastVector AS LONG
    82.     Centroid AS Vector
    83.     Velocity AS Vector
    84.     Visible AS INTEGER
    85.     Distance2 AS DOUBLE
    86.  
    87. TYPE MissionGroup
    88.     Label AS STRING
    89.     Discovered AS INTEGER
    90.  
    91. ' Scale.
    92. DIM SHARED bignumber AS LONG
    93. bignumber = 6000000
    94.  
    95. ' Cluster setup.
    96. DIM SHARED ClusterIndexTicker AS LONG
    97. DIM SHARED ClusterFillCounter AS INTEGER
    98. DIM SHARED Cluster(bignumber / 100) AS ClusterElement
    99. ClusterIndexTicker = 0
    100. ClusterFillCounter = 0
    101.  
    102. ' Group linked list setup.
    103. DIM SHARED Group(bignumber / 10) AS GroupElement
    104. DIM SHARED GroupIdTicker AS LONG
    105. GroupIdTicker = 0
    106.  
    107. ' Path
    108. DIM SHARED FixedPath(25, 86400) AS Vector
    109. DIM SHARED PathIndexTicker AS LONG
    110. PathIndexTicker = 0
    111.  
    112. ' Basis vectors defined in three-space.
    113. DIM SHARED xhat(3), yhat(3), zhat(3)
    114. xhat(1) = 1: xhat(2) = 0: xhat(3) = 0
    115. yhat(1) = 0: yhat(2) = 1: yhat(3) = 0
    116. zhat(1) = 0: zhat(2) = 0: zhat(3) = 1
    117.  
    118. ' Camera orientation vectors.
    119. DIM SHARED uhat(3), vhat(3), nhat(3)
    120.  
    121. ' Camera position.
    122. DIM SHARED PlayerCamera AS Camera
    123.  
    124. ' Field-of-view distance.
    125. DIM SHARED fovd
    126. fovd = -192
    127.  
    128. ' Clipping planes.
    129. DIM SHARED nearplane(4), farplane(4), rightplane(4), leftplane(4), topplane(4), bottomplane(4)
    130. nearplane(4) = 1
    131. farplane(4) = -180
    132. rightplane(4) = -17 ' (17=35/2=tilesize/2 looks better than 1.)
    133. leftplane(4) = -17
    134. topplane(4) = -17
    135. bottomplane(4) = -17
    136.  
    137. ' World vectors.
    138. DIM SHARED vec(bignumber, 3) ' Relative Position
    139. DIM SHARED vec3Dpos(bignumber, 3) ' Absolute osition
    140. DIM SHARED vec3Dvel(bignumber, 3) ' Linear velocity
    141. DIM SHARED vec3Dvis(bignumber) ' Visible toggle
    142. DIM SHARED vec2D(bignumber, 2) ' Projection onto 2D plane
    143. DIM SHARED vec3Dcolor(bignumber) AS LONG ' Original color
    144. DIM SHARED vec2Dcolor(bignumber) AS LONG ' Projected color
    145.  
    146. ' Mission.
    147. DIM SHARED MissionTicker AS INTEGER
    148. DIM SHARED Mission(20) AS MissionGroup
    149.  
    150. ' Interface.
    151. DIM SHARED ToggleHUD AS INTEGER
    152. DIM SHARED ToggleAnimate AS INTEGER
    153. DIM SHARED FPSReport AS INTEGER
    154. DIM SHARED NumClusterVisible AS LONG
    155. DIM SHARED NumVectorVisible AS LONG
    156. DIM SHARED NumGroupVisible AS LONG
    157. DIM SHARED ClosestGroup AS LONG
    158.  
    159. ' Initialize.
    160. ToggleAnimate = 1
    161. ToggleHUD = 1
    162. PlayerCamera.Position.x = -40
    163. PlayerCamera.Position.y = 500 '30
    164. PlayerCamera.Position.z = 40
    165. uhat(1) = COS(0): uhat(2) = SIN(0): uhat(3) = 0
    166. vhat(1) = 0: vhat(2) = 0: vhat(3) = 1
    167. CALL CalculateScreenVectors
    168.  
    169. ' Prime main loop.
    170. CALL CreateLevel
    171. CALL CreateMission(10)
    172.  
    173. ' Start main loop.
    174. CALL MainLoop
    175.  
    176.  
    177. ' Subs and Functions
    178.  
    179. SUB CreateMission (ListSize AS INTEGER)
    180.     DIM k AS INTEGER
    181.     DIM a AS STRING
    182.     DIM b AS STRING
    183.     a = ""
    184.     b = ""
    185.     REDIM MissionPool(0) AS STRING
    186.  
    187.     '''
    188.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Atmospheric dust"
    189.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Black hole"
    190.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Cloudy sky"
    191.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Comet 1"
    192.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Comet 2"
    193.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Comet 3"
    194.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Demon orb"
    195.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Dirt and sand"
    196.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Grave"
    197.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Hell sparks"
    198.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Laser"
    199.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Lush terrain"
    200.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tornado 1"
    201.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tornado 2"
    202.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tornado 3"
    203.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Planet center"
    204.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Planet column"
    205.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Planet interior"
    206.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Plasma core"
    207.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Pyramid"
    208.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Rain"
    209.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Singularity"
    210.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Snowman 1"
    211.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Snowman 2"
    212.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Snowman 3"
    213.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tree 1"
    214.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tree 2"
    215.     REDIM _PRESERVE MissionPool(UBOUND(MissionPool) + 1): MissionPool(UBOUND(MissionPool)) = "Tree 3"
    216.  
    217.     '''
    218.  
    219.     MissionTicker = 0
    220.     DO
    221.         k = 1 + INT(RND * UBOUND(MissionPool))
    222.         a = MissionPool(k)
    223.         IF (INSTR(b, a) = 0) THEN
    224.             b = b + a
    225.             MissionTicker = MissionTicker + 1
    226.             Mission(MissionTicker).Label = a
    227.             Mission(MissionTicker).Discovered = 0
    228.         END IF
    229.     LOOP UNTIL (MissionTicker = ListSize)
    230.  
    231. SUB CreateLevel
    232.     DIM gtmp AS LONG
    233.     DIM p AS LONG
    234.     DIM n AS SINGLE
    235.     DIM i, j, k AS SINGLE
    236.     DIM u, v, w AS SINGLE
    237.     DIM x0, y0, z0 AS SINGLE
    238.  
    239.     ' Initialize linked list.
    240.     gtmp = CreateNewGroup&(0, 0, 0, 0, 0, 1)
    241.  
    242.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    243.  
    244.     ' Atmospheric dust
    245.     FOR u = -1400 TO 1400 - 350 STEP 350
    246.         FOR v = -1400 TO 1400 - 350 STEP 350
    247.             FOR i = u TO u + 350 STEP 35
    248.                 FOR j = v TO v + 350 STEP 35
    249.                     FOR w = (100 + 35 / 2) TO (800 - 35 + 35 / 2) STEP 35
    250.                         gtmp = NewCube&(gtmp, "Atmospheric dust", 5 * (1 - w / 800), i, j, w, 35, 35, 35, DarkGray, White, Snow, -1)
    251.                     NEXT
    252.                 NEXT
    253.             NEXT
    254.             CALL ClusterPinch(gtmp)
    255.         NEXT
    256.     NEXT
    257.     CALL ClusterPinch(gtmp)
    258.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    259.  
    260.  
    261.  
    262.  
    263.  
    264.  
    265.  
    266.  
    267.  
    268.  
    269.     'GOTO moo
    270.  
    271.  
    272.  
    273.  
    274.  
    275.  
    276.     ' Black hole
    277.     k = RND * 2 * pi
    278.     x0 = 1000 * COS(k)
    279.     y0 = 1000 * SIN(k)
    280.     z0 = 300
    281.     i = 100
    282.     j = 25
    283.     FOR u = -i TO i STEP j
    284.         FOR v = -i TO i STEP j
    285.             FOR w = -i TO i STEP j
    286.                 k = SQR(u * u + v * v + w * w)
    287.                 IF k <= i AND k >= i - j THEN
    288.                     gtmp = NewCube&(gtmp, "Black hole", j, x0 + u, y0 + v, z0 + w, j, j, j, DarkGray, White, Snow, -1)
    289.                     CALL SetParticleVelocity(gtmp, -.25 * u / k, -.25 * v / k, -.25 * w / k)
    290.                 END IF
    291.             NEXT
    292.         NEXT
    293.     NEXT
    294.     gtmp = NewShell&("Singularity", 10, x0, y0, z0, Black, Black, Black, 2, 5, -1)
    295.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    296.  
    297.     ' Comet
    298.     FOR n = 1 TO 3
    299.         u = RND * 2 * pi
    300.         PathIndexTicker = PathIndexTicker + 1
    301.         FOR p = 1 TO 86400
    302.             FixedPath(PathIndexTicker, p).x = .25 * COS(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    303.             FixedPath(PathIndexTicker, p).y = .25 * SIN(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    304.             FixedPath(PathIndexTicker, p).z = .15 * COS(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    305.         NEXT
    306.         x0 = 150 + (RND - .5) * 2 * 550
    307.         y0 = 150 + (RND - .5) * 2 * 550
    308.         z0 = 200 + RND * 100
    309.         gtmp = NewCube&(gtmp, "Comet" + STR$(n), 300, x0, y0, z0, 25, 25, 25, Teal, Cyan, DodgerBlue, PathIndexTicker)
    310.         FOR k = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    311.             vec3Dvel(k, 1) = (RND - .5) * .20
    312.             vec3Dvel(k, 2) = (RND - .5) * .20
    313.             vec3Dvel(k, 3) = (RND - .5) * .20
    314.         NEXT
    315.         gtmp = NewShell&("Comet" + STR$(n), 15, x0, y0, z0, Teal, Cyan, DodgerBlue, 1, 80, PathIndexTicker)
    316.         CALL ClusterPinch(gtmp)
    317.     NEXT
    318.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    319.  
    320.     ' Demon orb
    321.     FOR n = 1 TO 6
    322.         u = RND * 2 * pi
    323.         PathIndexTicker = PathIndexTicker + 1
    324.         FOR p = 1 TO 86400
    325.             FixedPath(PathIndexTicker, p).x = .25 * COS(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    326.             FixedPath(PathIndexTicker, p).y = .25 * SIN(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    327.             FixedPath(PathIndexTicker, p).z = .15 * COS(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    328.         NEXT
    329.         x0 = (RND - .5) * 2 * 1000
    330.         y0 = (RND - .5) * 2 * 1000
    331.         z0 = -700 - RND * 100
    332.         gtmp = NewCube&(gtmp, "Demon orb", 300, x0, y0, z0, 25, 25, 25, Red, SunsetOrange, Sunglow, PathIndexTicker)
    333.         FOR k = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    334.             vec3Dvel(k, 1) = (RND - .5) * .20
    335.             vec3Dvel(k, 2) = (RND - .5) * .20
    336.             vec3Dvel(k, 3) = (RND - .5) * .20
    337.         NEXT
    338.         gtmp = NewShell&("Demon orb", 15, x0, y0, z0, Red, SunsetOrange, Sunglow, 1, 80, PathIndexTicker)
    339.         CALL ClusterPinch(gtmp)
    340.     NEXT
    341.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    342.  
    343.     ' Death Star
    344.     PathIndexTicker = PathIndexTicker + 1
    345.     FOR p = 1 TO 86400
    346.         FixedPath(PathIndexTicker, p).x = -.1 * COS(2 * pi * (24 * 60) * (p - 1) / 86400)
    347.         FixedPath(PathIndexTicker, p).y = -.1 * SIN(2 * pi * (24 * 60) * (p - 1) / 86400)
    348.         FixedPath(PathIndexTicker, p).z = 0
    349.     NEXT
    350.     x0 = 100 + (RND - .5) * 2 * 250
    351.     y0 = 100 + (RND - .5) * 2 * 250
    352.     gtmp = NewShell&("Death Star", 75, x0, y0, 400, Gray, DarkGray, White, 0, 0, PathIndexTicker)
    353.     gtmp = NewShell&("Death Star reactor", 15, x0, y0, 400, Cyan, Teal, Blue, 1, 20, PathIndexTicker)
    354.     gtmp = NewCube&(gtmp, "Plasma core", 300, x0, y0, 400, 25, 25, 25, Cyan, Teal, Blue, PathIndexTicker)
    355.     FOR p = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    356.         vec3Dvel(p, 1) = (RND - .5) * .20
    357.         vec3Dvel(p, 2) = (RND - .5) * .20
    358.         vec3Dvel(p, 3) = (RND - .5) * .20
    359.     NEXT
    360.     FOR n = 10 TO 1 STEP -.5
    361.         gtmp = NewCube&(gtmp, "Laser", 300, x0 + 75 / SQR(2) + (n - 1) * 25, y0 + .5 * n * 25, 400 + 75 / SQR(2) + .5 * (n - 1) * 25, 25, 25, 25, Lime, Green, White, PathIndexTicker)
    362.         FOR p = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    363.             vec3Dvel(p, 1) = (RND - .5) * .50
    364.             vec3Dvel(p, 2) = (RND - .5) * .50
    365.             vec3Dvel(p, 3) = (RND - .5) * .50
    366.         NEXT
    367.     NEXT
    368.     CALL ClusterPinch(gtmp)
    369.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    370.  
    371.     ' Dirt and sand
    372.     FOR u = -1400 TO 1400 - 350 STEP 350
    373.         FOR v = -1400 TO 1400 - 350 STEP 350
    374.             FOR i = u TO u + 350 STEP 35
    375.                 FOR j = v TO v + 350 STEP 35
    376.                     FOR w = 0 - 35 / 2 TO -770 - 35 / 2 STEP -35
    377.                         gtmp = NewCube&(gtmp, "Dirt and sand", 5, i, j, w, 35, 35, 35, SaddleBrown, DarkKhaki, Sienna, -1)
    378.                     NEXT
    379.                 NEXT
    380.             NEXT
    381.             CALL ClusterPinch(gtmp)
    382.         NEXT
    383.     NEXT
    384.     CALL ClusterPinch(gtmp)
    385.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    386.  
    387.     ' Grave and Pyramid (1/2)
    388.     k = RND * pi / 2 - pi / 4
    389.     x0 = 1000 * COS(k)
    390.     y0 = 1000 * SIN(k)
    391.     FOR u = x0 - 160 TO x0 + 160 STEP 80
    392.         FOR v = y0 - 160 TO y0 + 160 STEP 80
    393.             gtmp = NewCube&(gtmp, "Grave", 200, u, v, (1) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    394.             gtmp = NewCube&(gtmp, "Grave", 200, u, v, (2) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    395.             gtmp = NewCube&(gtmp, "Grave", 200, u, v, (3) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    396.             gtmp = NewCube&(gtmp, "Grave", 200, u - 10, v, (4) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    397.             gtmp = NewCube&(gtmp, "Grave", 200, u, v, (4) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    398.             gtmp = NewCube&(gtmp, "Grave", 200, u + 10, v, (4) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    399.             gtmp = NewCube&(gtmp, "Grave", 200, u, v, (5) * 10 - 10 / 2, 10, 10, 10, Gray, SlateGray, DarkGray, -1)
    400.         NEXT
    401.     NEXT
    402.     CALL ClusterPinch(gtmp)
    403.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    404.  
    405.     ' Grave and Pyramid (2/2)
    406.     k = k + pi
    407.     x0 = 1000 * COS(k)
    408.     y0 = 1000 * SIN(k)
    409.     FOR w = 0 TO 75 STEP 15
    410.         FOR u = -75 + w TO 75 - w STEP 15
    411.             FOR v = -75 + w TO 75 - w STEP 15
    412.                 gtmp = NewCube&(gtmp, "Pyramid", 35, x0 + u, y0 + v, w, 13, 13, 13, DarkKhaki, SunsetOrange, DarkGoldenRod, -1)
    413.             NEXT
    414.         NEXT
    415.     NEXT
    416.     CALL ClusterPinch(gtmp)
    417.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    418.  
    419.     ' Hell Sparks
    420.     FOR u = -1400 TO 1400 - 350 STEP 350
    421.         FOR v = -1400 TO 1400 - 350 STEP 350
    422.             FOR i = u TO u + 350 STEP 35
    423.                 FOR j = v TO v + 350 STEP 35
    424.                     gtmp = NewCube&(gtmp, "Hell sparks", 15, i, j, -850, 35, 35, 100, Red, SunsetOrange, Sunglow, -1)
    425.                     CALL SetParticleVelocity(gtmp, 0, 0, .75 + RND * .25)
    426.                 NEXT
    427.             NEXT
    428.         NEXT
    429.     NEXT
    430.     CALL ClusterPinch(gtmp)
    431.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    432.  
    433.     ' Lake of Fire
    434.     gtmp = NewTerrain&("Lake of Fire", 2800, 2800, 35, 0, 0, -900, Red, SunsetOrange, Sunglow, 4, 30)
    435.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    436.  
    437.     ' Planet
    438.     PathIndexTicker = PathIndexTicker + 1
    439.     FOR p = 1 TO 86400
    440.         FixedPath(PathIndexTicker, p).x = .1 * COS(2 * pi * (24 * 60) * (p - 1) / 86400)
    441.         FixedPath(PathIndexTicker, p).y = .1 * SIN(2 * pi * (24 * 60) * (p - 1) / 86400)
    442.         FixedPath(PathIndexTicker, p).z = 0
    443.     NEXT
    444.     x0 = 100 + (RND - .5) * 2 * 250
    445.     y0 = 100 + (RND - .5) * 2 * 250
    446.     gtmp = NewPlanet&(350, 35, x0, y0, 900, PathIndexTicker)
    447.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    448.  
    449.     ' Rain
    450.     FOR u = -350 TO 350 STEP 35
    451.         FOR v = -350 TO 350 STEP 35
    452.             IF (u * u + v * v <= 350 ^ 2) THEN
    453.                 gtmp = NewCube&(gtmp, "Rain", 15, u, v, 50, 35, 35, 100, Blue, DodgerBlue, Blue, -1)
    454.                 CALL SetParticleVelocity(gtmp, 0, 0, -.75 - RND * .25)
    455.             END IF
    456.         NEXT
    457.     NEXT
    458.     CALL ClusterPinch(gtmp)
    459.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    460.  
    461.     ' Realm of Heaven
    462.     gtmp = NewTerrain&("Realm of Heaven", 2800, 2800, 35, 0, 0, 1400, BlueViolet, Cyan, White, 4, 30)
    463.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    464.  
    465.     ' Sky and Terrain: Center
    466.     gtmp = NewTerrain&("Cloudy sky", 1400, 1400, 35, 0, 0, 100, RoyalBlue, DarkGray, Snow, 4, 30)
    467.     gtmp = NewTerrain&("Lush terrain", 1400, 1400, 35, 0, 0, 0, Green, LightSeaGreen, Blue, 4, 30)
    468.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    469.  
    470.     ' Sky and Terrain: North
    471.     gtmp = NewTerrain&("Northern winter sky", 2800, 700, 35, 0, 700 + 350, 100, White, DarkBlue, SlateGray, 4, 30)
    472.     gtmp = NewTerrain&("Northern winter terrain", 2800, 700, 35, 0, 700 + 350, 0, White, Snow, SlateGray, 4, 30)
    473.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    474.  
    475.     ' Sky and Terrain: South
    476.     gtmp = NewTerrain&("Southern desert sky", 2800, 700, 35, 0, -700 - 350, 100, RoyalBlue, SunsetOrange, Indigo, 4, 30)
    477.     gtmp = NewTerrain&("Southern desert terrain", 2800, 700, 35, 0, -700 - 350, 0, DarkKhaki, PaleGoldenRod, Sunglow, 4, 30)
    478.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    479.  
    480.     ' Sky and Terrain: West
    481.     gtmp = NewTerrain&("Western desert sky", 700, 1400, 35, -700 - 350, 0, 100, RoyalBlue, DarkGray, Indigo, 4, 30)
    482.     gtmp = NewTerrain&("Western desert terrain", 700, 1400, 35, -700 - 350, 0, 0, DarkKhaki, PaleGoldenRod, Sunglow, 4, 30)
    483.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    484.  
    485.     ' Sky and Terrain: East
    486.     gtmp = NewTerrain&("Eastern desert sky", 700, 1400, 35, 700 + 350, 0, 100, RoyalBlue, DarkGray, Indigo, 4, 30)
    487.     gtmp = NewTerrain&("Eastern desert terrain", 700, 1400, 35, 700 + 350, 0, 0, DarkKhaki, PaleGoldenRod, Sunglow, 4, 30)
    488.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    489.  
    490.     ' Snow
    491.     FOR u = -175 TO 175 STEP 35
    492.         FOR v = -175 TO 175 STEP 35
    493.             IF (u * u + v * v <= 175 ^ 2) THEN
    494.                 gtmp = NewCube&(gtmp, "Snow", 15, u, v + 1050, 50, 35, 35, 100, White, Snow, SlateGray, -1)
    495.                 CALL SetParticleVelocity(gtmp, 0, 0, -.75 - RND * .25)
    496.             END IF
    497.         NEXT
    498.     NEXT
    499.     CALL ClusterPinch(gtmp)
    500.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    501.  
    502.     ' Snowman
    503.     FOR j = 1 TO 10
    504.         u = 5 + RND * 20
    505.         v = 0
    506.         x0 = RND * 2800
    507.         y0 = RND * 700 + 700
    508.         FOR n = 1 TO 3
    509.             z0 = u + v
    510.             gtmp = NewCube&(gtmp, "Snowman" + STR$(n), 300, x0, y0, z0, u * 2 / SQR(3), u * 2 / SQR(3), u * 2 / SQR(3), Teal, Cyan, DodgerBlue, -1)
    511.             FOR k = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    512.                 vec3Dvel(k, 1) = (RND - .5) * .20
    513.                 vec3Dvel(k, 2) = (RND - .5) * .20
    514.                 vec3Dvel(k, 3) = (RND - .5) * .20
    515.             NEXT
    516.             gtmp = NewShell&("Snowman" + STR$(n), u, x0, y0, z0, Teal, Cyan, DodgerBlue, 1, 80, -1)
    517.             CALL ClusterPinch(gtmp)
    518.             v = v + 2 * u
    519.             u = u / 1.618
    520.         NEXT
    521.     NEXT
    522.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    523.  
    524.     ' Tornado
    525.     FOR n = 1 TO 3
    526.         u = RND * 2 * pi
    527.         PathIndexTicker = PathIndexTicker + 1
    528.         FOR p = 1 TO 86400
    529.             FixedPath(PathIndexTicker, p).x = 1 * COS(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    530.             FixedPath(PathIndexTicker, p).y = 1 * SIN(u + 2 * pi * (24 * 60) * (p - 1) / 86400)
    531.             FixedPath(PathIndexTicker, p).z = 0
    532.         NEXT
    533.         x0 = (RND - .5) * 2 * 750
    534.         y0 = (RND - .5) * 2 * 750
    535.         FOR k = 1 TO 30
    536.             u = RND * 100
    537.             v = RND * u / 3
    538.             w = RND * 2 * pi
    539.             gtmp = NewCube&(gtmp, "Tornado" + STR$(n), 35, x0 + v * COS(w), y0 + v * SIN(w), u, 15, 15, 15, DarkGray, SunsetOrange, DarkGoldenRod, PathIndexTicker)
    540.             CALL SetParticleVelocity(gtmp, -SIN(w), COS(w), 0)
    541.         NEXT
    542.         CALL ClusterPinch(gtmp)
    543.     NEXT
    544.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    545.  
    546.     ' Tree
    547.     FOR n = 1 TO 10
    548.         v = 30 + RND * 60
    549.         u = v / 10
    550.         w = u
    551.         x0 = RND * 2800
    552.         y0 = RND * 700 + 700
    553.         z0 = u / 2
    554.         FOR j = 0 TO v STEP u
    555.             gtmp = NewCube&(gtmp, "Tree" + STR$(n), 5 * (v - j), x0, y0, z0 + j, w, w, u, Chocolate, SaddleBrown, Gray, -1)
    556.             w = w * .9
    557.             CALL ClusterPinch(gtmp)
    558.         NEXT
    559.         w = v / 1.618
    560.         FOR j = u TO (v + 3 * u) STEP u
    561.             gtmp = NewCube&(gtmp, "Tree" + STR$(n), 5 * (v - j), x0, y0, z0 + j, w, w, u, Lime, Green, Teal, -1)
    562.             w = w * .8
    563.             CALL ClusterPinch(gtmp)
    564.         NEXT
    565.         z0 = u / 2 + v
    566.         gtmp = NewCube&(gtmp, "Tree" + STR$(n), 100, x0, y0, z0, u, u, u, White, Yellow, Teal, -1)
    567.         FOR k = Group(gtmp).FirstVector TO Group(gtmp).LastVector
    568.             vec3Dvel(k, 1) = (RND - .5) * .20
    569.             vec3Dvel(k, 2) = (RND - .5) * .20
    570.             vec3Dvel(k, 3) = (RND - .5) * .20
    571.         NEXT
    572.         CALL ClusterPinch(gtmp)
    573.     NEXT
    574.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    575.  
    576.     ' Stellar dust
    577.     FOR u = -1400 TO 1400 - 350 STEP 350
    578.         FOR v = -1400 TO 1400 - 350 STEP 350
    579.             FOR i = u TO u + 350 STEP 35
    580.                 FOR j = v TO v + 350 STEP 35
    581.                     gtmp = NewCube&(gtmp, "Stellar dust", 15, i, j, 1350, 35, 35, 100, BlueViolet, Cyan, White, -1)
    582.                     CALL SetParticleVelocity(gtmp, 0, 0, -.75 - RND * .25)
    583.                 NEXT
    584.             NEXT
    585.         NEXT
    586.     NEXT
    587.     CALL ClusterPinch(gtmp)
    588.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    589.  
    590.     ''' Rocket
    591.     gtmp = NewCube&(gtmp, "Rocket", 200, 0, 0, 0, 15, 15, 35, Green, Green, Green, 0)
    592.     gtmp = NewCube&(gtmp, "Rocket", 200, 0, 0, 0, 15, 15, 15, Red, Red, Red, 0)
    593.     Cluster(ClusterIndexTicker).Velocity.x = 0
    594.     Cluster(ClusterIndexTicker).Velocity.y = 0
    595.     Cluster(ClusterIndexTicker).Velocity.z = 1
    596.     CALL ClusterPinch(gtmp)
    597.     LOCATE 1, 1: PRINT "Loading..." + STR$(INT(100 * Group(gtmp).LastVector / bignumber)) + "%": _DISPLAY
    598.  
    599.     moo:
    600.  
    601.     'PRINT
    602.     'PRINT "Total clusters:  " + STR$(ClusterIndexTicker)
    603.     'PRINT "Total groups:    " + STR$(GroupIdTicker)
    604.     'PRINT "Total particles: " + STR$(Group(gtmp).LastVector)
    605.     '_DISPLAY
    606.     'SLEEP 3
    607.  
    608.  
    609. SUB MainLoop
    610.     DIM fps AS INTEGER
    611.     DIM fpstimer AS INTEGER
    612.     DIM tt AS INTEGER
    613.     fps = 0
    614.     fpstimer = INT(TIMER)
    615.     CALL CalculateScreenVectors
    616.     DO
    617.         CALL PlayerDynamics
    618.         CALL ComputeVisibleScene
    619.         CALL PlotWorld
    620.         CALL DisplayHUD
    621.         CALL KeyProcess
    622.  
    623.         fps = fps + 1
    624.         tt = INT(TIMER)
    625.         IF (tt = fpstimer + 1) THEN
    626.             fpstimer = tt
    627.             FPSReport = fps
    628.             fps = 0
    629.         END IF
    630.  
    631.         _DISPLAY
    632.         _LIMIT 30
    633.     LOOP
    634.  
    635. ' Elevated-order primitive group.
    636.  
    637. FUNCTION NewPlanet& (TheRadius AS DOUBLE, ChunkSize AS DOUBLE, PosX AS DOUBLE, PosY AS DOUBLE, PosZ AS DOUBLE, TheDynamic AS INTEGER)
    638.     DIM ShadeA AS _UNSIGNED LONG
    639.     DIM ShadeB AS _UNSIGNED LONG
    640.     DIM ShadeC AS _UNSIGNED LONG
    641.     DIM gtmp AS LONG
    642.     DIM rtemp AS DOUBLE
    643.     DIM u AS DOUBLE
    644.     DIM v AS DOUBLE
    645.     DIM w AS DOUBLE
    646.     DIM r AS DOUBLE
    647.     FOR rtemp = ChunkSize TO TheRadius STEP ChunkSize
    648.         FOR u = -rtemp TO rtemp STEP ChunkSize
    649.             FOR v = -rtemp TO rtemp STEP ChunkSize
    650.                 FOR w = -rtemp TO rtemp STEP ChunkSize
    651.                     r = SQR(u * u + v * v + w * w)
    652.                     IF r <= rtemp AND r >= rtemp - ChunkSize THEN
    653.                         ShadeA = ShadeMix~&(Red, SaddleBrown, r / TheRadius)
    654.                         ShadeB = ShadeMix~&(SunsetOrange, DarkKhaki, r / TheRadius)
    655.                         ShadeC = DarkKhaki
    656.                         gtmp = NewCube&(1, "Planet Interior", ChunkSize, PosX + u, PosY + v, PosZ + w, ChunkSize, ChunkSize, ChunkSize, ShadeA, ShadeB, ShadeC, TheDynamic)
    657.                         IF (r < TheRadius / 2.5) THEN
    658.                             Group(gtmp).GroupName = "Planet center"
    659.                             CALL SetParticleVelocity(gtmp, .25 * u / r, .25 * v / r, .25 * w / r)
    660.                         END IF
    661.                     END IF
    662.                 NEXT
    663.             NEXT
    664.         NEXT
    665.         CALL ClusterPinch(gtmp)
    666.     NEXT
    667.     CALL ClusterPinch(gtmp)
    668.     gtmp = NewShell&("Planet surface", TheRadius, PosX, PosY, PosZ, Green, DarkKhaki, DodgerBlue, 2, 20, TheDynamic)
    669.     gtmp = NewShell&("Planet core", TheRadius / 10, PosX, PosY, PosZ, Red, Yellow, DarkGoldenRod, 2, 5, TheDynamic)
    670.     FOR w = 0 + ChunkSize / 4 TO PosZ - TheRadius - ChunkSize / 4 STEP ChunkSize / 2
    671.         gtmp = NewCube&(1, "Planet column", 2 * ChunkSize, PosX, PosY, w, ChunkSize / 2, ChunkSize / 2, ChunkSize / 2, Cyan, Teal, Blue, TheDynamic)
    672.         CALL SetParticleVelocity(gtmp, .15 * (RND - .5), .15 * (RND - .5), .25 * RND)
    673.     NEXT
    674.     CALL ClusterPinch(gtmp)
    675.     NewPlanet& = gtmp
    676.  
    677. ' Medium-order primitive group(s).
    678.  
    679. FUNCTION NewTerrain& (TheName AS STRING, SizeX AS DOUBLE, SizeY AS DOUBLE, TheResolution AS DOUBLE, PosX AS DOUBLE, PosY AS DOUBLE, PosZ AS DOUBLE, ShadeA AS _UNSIGNED LONG, ShadeB AS _UNSIGNED LONG, ShadeC AS _UNSIGNED LONG, BumpFactor AS DOUBLE, SmoothFactor AS INTEGER)
    680.     DIM g AS LONG
    681.     DIM q AS LONG
    682.     DIM vindex AS LONG
    683.     DIM k AS INTEGER
    684.     DIM factor1 AS DOUBLE
    685.     DIM factor2 AS DOUBLE
    686.     DIM a AS DOUBLE
    687.     DIM b AS DOUBLE
    688.     DIM i AS DOUBLE
    689.     DIM j AS DOUBLE
    690.     DIM u AS DOUBLE
    691.     DIM v AS DOUBLE
    692.     DIM w AS DOUBLE
    693.     DIM ia AS DOUBLE
    694.     DIM jb AS DOUBLE
    695.     DIM rr AS DOUBLE
    696.     DIM wi AS INTEGER
    697.     DIM wj AS INTEGER
    698.     DIM wa AS INTEGER
    699.     DIM wb AS INTEGER
    700.     DIM ci AS INTEGER
    701.     DIM cj AS INTEGER
    702.     DIM ca AS INTEGER
    703.     DIM cb AS INTEGER
    704.  
    705.     factor1 = TheResolution
    706.     factor2 = factor1 / 10
    707.     wi = 0
    708.     wj = 0
    709.     wa = 0
    710.     wb = 0
    711.     FOR i = -SizeX / 2 + factor1 / 2 TO SizeX / 2 - factor1 / 2 STEP factor1
    712.         wi = wi + 1
    713.     NEXT
    714.     FOR j = -SizeY / 2 + factor1 / 2 TO SizeY / 2 - factor1 / 2 STEP factor1
    715.         wj = wj + 1
    716.     NEXT
    717.     FOR a = 0 TO factor1 - 0 * factor2 STEP factor2
    718.         wa = wa + 1
    719.     NEXT
    720.     FOR b = 0 TO factor1 - 0 * factor2 STEP factor2
    721.         wb = wb + 1
    722.     NEXT
    723.  
    724.     DIM vgrid(wi * wa, wj * wb, 2) AS DOUBLE
    725.     DIM vgrid2(wi * wa, wj * wb) AS DOUBLE
    726.     FOR ia = 1 TO wi * wa
    727.         FOR jb = 1 TO wj * wb
    728.             SELECT CASE (RND)
    729.                 CASE IS < .005
    730.                     vgrid(ia, jb, 1) = factor1 * RND
    731.                     vgrid(ia, jb, 2) = 1
    732.                 CASE IS > 1 - .005
    733.                     vgrid(ia, jb, 1) = -factor1 * RND / 2
    734.                     vgrid(ia, jb, 2) = 1
    735.                 CASE ELSE
    736.                     vgrid(ia, jb, 1) = 0
    737.                     vgrid(ia, jb, 2) = 0
    738.             END SELECT
    739.         NEXT
    740.     NEXT
    741.  
    742.     IF (SmoothFactor > 0) THEN
    743.         FOR k = 1 TO SmoothFactor
    744.             FOR ia = 1 TO wi * wa
    745.                 FOR jb = 1 TO wj * wb
    746.                     vgrid2(ia, jb) = vgrid(ia, jb, 1)
    747.                 NEXT
    748.             NEXT
    749.             FOR ia = 1 + 1 TO wi * wa - 1
    750.                 FOR jb = 1 + 1 TO wj * wb - 1
    751.                     IF (k = SmoothFactor - 5) THEN
    752.                         vgrid(ia, jb, 2) = 0
    753.                     END IF
    754.                     IF vgrid(ia, jb, 2) = 0 THEN
    755.                         vgrid(ia, jb, 1) = (1 / 4) * (vgrid2(ia - 1, jb) + vgrid2(ia + 1, jb) + vgrid2(ia, jb - 1) + vgrid2(ia, jb + 1))
    756.                     END IF
    757.                 NEXT
    758.             NEXT
    759.         NEXT
    760.     END IF
    761.  
    762.     ci = 0
    763.     cj = 0
    764.     ca = 0
    765.     cb = 0
    766.     g = 1
    767.     FOR i = -SizeX / 2 + factor1 / 2 - TheResolution / 2 TO SizeX / 2 - factor1 / 2 - TheResolution / 2 STEP factor1
    768.         ci = ci + 1
    769.         cj = 0
    770.         FOR j = -SizeY / 2 + factor1 / 2 - TheResolution / 2 TO SizeY / 2 - factor1 / 2 - TheResolution / 2 STEP factor1
    771.             cj = cj + 1
    772.             u = i
    773.             v = j
    774.             w = 0
    775.             q = LatestIdentity&(g)
    776.             vindex = Group(q).LastVector
    777.             g = CreateNewGroup&(q, PosX + u, PosY + v, PosZ + w, 0, 24)
    778.             Group(g).GroupName = TheName
    779.             Group(g).Volume.x = SizeX
    780.             Group(g).Volume.y = SizeY
    781.             Group(g).Volume.z = SQR(SizeX * SizeX + SizeY * SizeY)
    782.             Group(g).FirstVector = vindex + 1
    783.             ca = 0
    784.             FOR a = i TO i + factor1 - 0 * factor2 STEP factor2
    785.                 ca = ca + 1
    786.                 cb = 0
    787.                 FOR b = j TO j + factor1 - 0 * factor2 STEP factor2
    788.                     cb = cb + 1
    789.                     rr = vgrid((ci - 1) * wa + ca, (cj - 1) * wb + cb, 1)
    790.                     vindex = vindex + 1
    791.                     vec3Dpos(vindex, 1) = -u + a + BumpFactor * (RND - .5)
    792.                     vec3Dpos(vindex, 2) = -v + b + BumpFactor * (RND - .5)
    793.                     vec3Dpos(vindex, 3) = -w + rr + BumpFactor * (RND - .5)
    794.                     IF (rr > 0) THEN
    795.                         vec3Dcolor(vindex) = ShadeMix~&(ShadeA, ShadeB, rr / 10)
    796.                     ELSE
    797.                         vec3Dcolor(vindex) = ShadeC
    798.                     END IF
    799.                 NEXT
    800.             NEXT
    801.             Group(g).LastVector = vindex
    802.         NEXT
    803.         CALL ClusterPinch(g)
    804.     NEXT
    805.     CALL ClusterPinch(g)
    806.     NewTerrain& = g
    807.  
    808. FUNCTION NewShell& (TheName AS STRING, TheRadius AS DOUBLE, PosX AS DOUBLE, PosY AS DOUBLE, PosZ AS DOUBLE, ShadeA AS _UNSIGNED LONG, ShadeB AS _UNSIGNED LONG, ShadeC AS _UNSIGNED LONG, BumpFactor AS DOUBLE, SmoothFactor AS INTEGER, TheDynamic AS INTEGER)
    809.     DIM g AS LONG
    810.     DIM q AS LONG
    811.     DIM vindex AS LONG
    812.     DIM k AS INTEGER
    813.     DIM factor1 AS DOUBLE
    814.     DIM factor2 AS DOUBLE
    815.     DIM a AS DOUBLE
    816.     DIM b AS DOUBLE
    817.     DIM i AS DOUBLE
    818.     DIM j AS DOUBLE
    819.     DIM u AS DOUBLE
    820.     DIM v AS DOUBLE
    821.     DIM w AS DOUBLE
    822.     DIM ia AS DOUBLE
    823.     DIM jb AS DOUBLE
    824.     DIM rr AS DOUBLE
    825.     DIM wi AS LONG
    826.     DIM wj AS LONG
    827.     DIM wa AS LONG
    828.     DIM wb AS LONG
    829.     DIM ci AS LONG
    830.     DIM cj AS LONG
    831.     DIM ca AS LONG
    832.     DIM cb AS LONG
    833.  
    834.     IF (TheRadius > 200) THEN
    835.         factor1 = (2) * 2 * pi / (10 ^ (INT(LOG(TheRadius) / LOG(10))))
    836.         factor2 = factor1 / (10)
    837.     ELSE
    838.         factor1 = 2 * pi / (10)
    839.         factor2 = factor1 / (5)
    840.     END IF
    841.  
    842.  
    843.     wi = 0
    844.     wj = 0
    845.     wa = 0
    846.     wb = 0
    847.     FOR i = 0 TO 2 * pi - factor1 STEP factor1
    848.         wi = wi + 1
    849.     NEXT
    850.     FOR j = factor2 TO pi - 0 STEP factor1
    851.         wj = wj + 1
    852.     NEXT
    853.     FOR a = 0 TO factor1 STEP factor2
    854.         wa = wa + 1
    855.     NEXT
    856.     FOR b = 0 TO factor1 STEP factor2
    857.         wb = wb + 1
    858.     NEXT
    859.  
    860.     DIM vgrid(wi * wa, wj * wb, 2) AS DOUBLE
    861.     DIM vgrid2(wi * wa, wj * wb) AS DOUBLE
    862.  
    863.     FOR ia = 1 TO wi * wa
    864.         FOR jb = 1 TO wj * wb
    865.             SELECT CASE (RND)
    866.                 CASE IS < .025
    867.                     vgrid(ia, jb, 1) = TheRadius - BumpFactor * (TheRadius / 50)
    868.                     vgrid(ia, jb, 2) = 1
    869.                 CASE IS > 1 - .025
    870.                     vgrid(ia, jb, 1) = TheRadius + BumpFactor * (TheRadius / 50)
    871.                     vgrid(ia, jb, 2) = 1
    872.                 CASE ELSE
    873.                     vgrid(ia, jb, 1) = TheRadius
    874.                     vgrid(ia, jb, 2) = 0
    875.             END SELECT
    876.         NEXT
    877.     NEXT
    878.  
    879.     IF (SmoothFactor > 0) THEN
    880.         FOR k = 1 TO SmoothFactor
    881.             FOR ia = 1 TO wi * wa
    882.                 FOR jb = 1 TO wj * wb
    883.                     vgrid2(ia, jb) = vgrid(ia, jb, 1)
    884.                 NEXT
    885.             NEXT
    886.             FOR ia = 1 + 1 TO wi * wa - 1
    887.                 FOR jb = 1 + 1 TO wj * wb - 1
    888.                     IF (k = SmoothFactor - 5) THEN
    889.                         vgrid(ia, jb, 2) = 0
    890.                     END IF
    891.                     IF vgrid(ia, jb, 2) = 0 THEN
    892.                         vgrid(ia, jb, 1) = (1 / 4) * (vgrid2(ia - 1, jb) + vgrid2(ia + 1, jb) + vgrid2(ia, jb - 1) + vgrid2(ia, jb + 1))
    893.                     END IF
    894.                 NEXT
    895.             NEXT
    896.         NEXT
    897.     END IF
    898.  
    899.     ci = 0
    900.     cj = 0
    901.     ca = 0
    902.     cb = 0
    903.     g = 1
    904.     FOR i = 0 TO 2 * pi - factor1 STEP factor1
    905.         ci = ci + 1
    906.         cj = 0
    907.         FOR j = factor2 TO pi - 0 STEP factor1
    908.             cj = cj + 1
    909.             u = TheRadius * SIN(j) * COS(i)
    910.             v = TheRadius * SIN(j) * SIN(i)
    911.             w = TheRadius * COS(j)
    912.             q = LatestIdentity&(g)
    913.             vindex = Group(q).LastVector
    914.             g = CreateNewGroup&(q, PosX + u, PosY + v, PosZ + w, TheDynamic, 64)
    915.             Group(g).GroupName = TheName
    916.             Group(g).Volume.x = TheRadius
    917.             Group(g).Volume.y = TheRadius
    918.             Group(g).Volume.z = TheRadius
    919.             Group(g).FirstVector = vindex + 1
    920.             ca = 0
    921.             FOR a = i TO i + factor1 STEP factor2
    922.                 ca = ca + 1
    923.                 cb = 0
    924.                 FOR b = j TO j + factor1 STEP factor2
    925.                     cb = cb + 1
    926.                     rr = vgrid((ci - 1) * wa + ca, (cj - 1) * wb + cb, 1)
    927.                     vindex = vindex + 1
    928.                     vec3Dpos(vindex, 1) = -u + rr * SIN(b) * COS(a) + BumpFactor * (RND - .5) * 2
    929.                     vec3Dpos(vindex, 2) = -v + rr * SIN(b) * SIN(a) + BumpFactor * (RND - .5) * 2
    930.                     vec3Dpos(vindex, 3) = -w + rr * COS(b) + BumpFactor * (RND - .5) * 2
    931.                     IF (rr > TheRadius) THEN
    932.                         vec3Dcolor(vindex) = ShadeMix~&(ShadeA, ShadeB, COS(j) ^ 2)
    933.                     ELSE
    934.                         vec3Dcolor(vindex) = ShadeC
    935.                     END IF
    936.                 NEXT
    937.             NEXT
    938.             Group(g).LastVector = vindex
    939.         NEXT
    940.         CALL ClusterPinch(g)
    941.     NEXT
    942.     CALL ClusterPinch(g)
    943.  
    944.     NewShell& = g
    945.  
    946. ' Low-order primitive group(s).
    947.  
    948. FUNCTION NewCube& (StartingIdentity AS LONG, TheName AS STRING, Weight AS INTEGER, PosX AS DOUBLE, PosY AS DOUBLE, PosZ AS DOUBLE, VolX AS DOUBLE, VolY AS DOUBLE, VolZ AS DOUBLE, ShadeA AS _UNSIGNED LONG, ShadeB AS _UNSIGNED LONG, ShadeC AS _UNSIGNED LONG, TheDynamic AS INTEGER)
    949.     DIM k AS INTEGER
    950.     DIM g AS LONG
    951.     DIM q AS LONG
    952.     DIM vindex AS LONG
    953.     q = LatestIdentity&(StartingIdentity)
    954.     vindex = Group(q).LastVector
    955.     g = CreateNewGroup(q, PosX, PosY, PosZ, TheDynamic, 64)
    956.     Group(g).GroupName = TheName
    957.     Group(g).Volume.x = VolX
    958.     Group(g).Volume.y = VolY
    959.     Group(g).Volume.z = VolZ
    960.     Group(g).FirstVector = vindex + 1
    961.     FOR k = 1 TO Weight
    962.         vindex = vindex + 1
    963.         vec3Dpos(vindex, 1) = (RND - .5) * VolX
    964.         vec3Dpos(vindex, 2) = (RND - .5) * VolY
    965.         vec3Dpos(vindex, 3) = (RND - .5) * VolZ
    966.         IF (RND > .5) THEN
    967.             vec3Dcolor(vindex) = ShadeA
    968.         ELSE
    969.             IF (RND > .5) THEN
    970.                 vec3Dcolor(vindex) = ShadeB
    971.             ELSE
    972.                 vec3Dcolor(vindex) = ShadeC
    973.             END IF
    974.         END IF
    975.     NEXT
    976.     Group(g).LastVector = vindex
    977.     NewCube& = g
    978.  
    979. ' Linked list utility.
    980.  
    981. FUNCTION LatestIdentity& (StartingID AS LONG)
    982.     DIM TheReturn AS LONG
    983.     DIM p AS LONG
    984.     DIM q AS LONG
    985.     p = StartingID
    986.     DO
    987.         q = p
    988.         p = Group(q).Pointer
    989.         IF (p = -999) THEN EXIT DO
    990.     LOOP
    991.     TheReturn = q
    992.     LatestIdentity& = TheReturn
    993.  
    994. FUNCTION CreateNewGroup& (TheLaggerIn AS LONG, CenterX AS DOUBLE, CenterY AS DOUBLE, CenterZ AS DOUBLE, TheDynamic AS INTEGER, ClusterSize AS INTEGER)
    995.     GroupIdTicker = GroupIdTicker + 1
    996.     Group(GroupIdTicker).Identity = GroupIdTicker
    997.     Group(GroupIdTicker).Pointer = -999
    998.     Group(GroupIdTicker).Lagger = TheLaggerIn
    999.     Group(GroupIdTicker).Centroid.x = CenterX
    1000.     Group(GroupIdTicker).Centroid.y = CenterY
    1001.     Group(GroupIdTicker).Centroid.z = CenterZ
    1002.     IF (TheLaggerIn <> 0) THEN
    1003.         Group(TheLaggerIn).Pointer = Group(GroupIdTicker).Identity
    1004.     END IF
    1005.     '''
    1006.     ClusterFillCounter = ClusterFillCounter + 1
    1007.  
    1008.     IF (ClusterFillCounter = 1) THEN
    1009.         ClusterIndexTicker = ClusterIndexTicker + 1
    1010.         Cluster(ClusterIndexTicker).FirstGroup = Group(GroupIdTicker).Identity
    1011.         Cluster(ClusterIndexTicker).MotionType = TheDynamic
    1012.     END IF
    1013.  
    1014.     IF (ClusterFillCounter = ClusterSize) THEN
    1015.         CALL ClusterPinch(Group(GroupIdTicker).Identity)
    1016.     END IF
    1017.     '''
    1018.     CreateNewGroup& = Group(GroupIdTicker).Identity
    1019.  
    1020. SUB ClusterPinch (TheLastGroup AS LONG)
    1021.     ClusterFillCounter = 0
    1022.     Cluster(ClusterIndexTicker).LastGroup = TheLastGroup
    1023.     CALL ClusterCentroidCalc(ClusterIndexTicker)
    1024.  
    1025. SUB ClusterCentroidCalc (TheClusterIndex AS LONG)
    1026.     DIM k AS LONG
    1027.     DIM n AS INTEGER
    1028.     Cluster(TheClusterIndex).Centroid.x = 0
    1029.     Cluster(TheClusterIndex).Centroid.y = 0
    1030.     Cluster(TheClusterIndex).Centroid.z = 0
    1031.     k = Cluster(TheClusterIndex).FirstGroup
    1032.     n = 0
    1033.     DO
    1034.         Cluster(TheClusterIndex).Centroid.x = Cluster(TheClusterIndex).Centroid.x + Group(k).Centroid.x
    1035.         Cluster(TheClusterIndex).Centroid.y = Cluster(TheClusterIndex).Centroid.y + Group(k).Centroid.y
    1036.         Cluster(TheClusterIndex).Centroid.z = Cluster(TheClusterIndex).Centroid.z + Group(k).Centroid.z
    1037.         n = n + 1
    1038.         IF (k = Cluster(TheClusterIndex).LastGroup) THEN EXIT DO
    1039.         k = Group(k).Pointer
    1040.     LOOP
    1041.     Cluster(TheClusterIndex).Centroid.x = Cluster(TheClusterIndex).Centroid.x / n
    1042.     Cluster(TheClusterIndex).Centroid.y = Cluster(TheClusterIndex).Centroid.y / n
    1043.     Cluster(TheClusterIndex).Centroid.z = Cluster(TheClusterIndex).Centroid.z / n
    1044.  
    1045. ' Vector manipulation.
    1046.  
    1047. SUB SetParticleVelocity (TheGroup AS LONG, vx AS DOUBLE, vy AS DOUBLE, vz AS DOUBLE)
    1048.     DIM j AS LONG
    1049.     DIM m AS LONG
    1050.     DIM n AS LONG
    1051.     m = Group(TheGroup).FirstVector
    1052.     n = Group(TheGroup).LastVector
    1053.     FOR j = m TO n
    1054.         vec3Dvel(j, 1) = vx
    1055.         vec3Dvel(j, 2) = vy
    1056.         vec3Dvel(j, 3) = vz
    1057.     NEXT
    1058.  
    1059. ' Cluster manipulation.
    1060.  
    1061. SUB EvolveCluster (TheClusterIndex AS LONG)
    1062.     DIM dx, dy, dz, t, u, v AS DOUBLE
    1063.     IF (Cluster(TheClusterIndex).MotionType = 0) THEN
    1064.         dx = Cluster(TheClusterIndex).Velocity.x
    1065.         dy = Cluster(TheClusterIndex).Velocity.y
    1066.         dz = Cluster(TheClusterIndex).Velocity.z
    1067.     ELSE
    1068.         t = TIMER
    1069.         u = INT(t)
    1070.         v = t - u
    1071.         dx = v * FixedPath(Cluster(TheClusterIndex).MotionType, u).x + (1 - v) * FixedPath(Cluster(TheClusterIndex).MotionType, u - 1).x
    1072.         dy = v * FixedPath(Cluster(TheClusterIndex).MotionType, u).y + (1 - v) * FixedPath(Cluster(TheClusterIndex).MotionType, u - 1).y
    1073.         dz = v * FixedPath(Cluster(TheClusterIndex).MotionType, u).z + (1 - v) * FixedPath(Cluster(TheClusterIndex).MotionType, u - 1).z
    1074.     END IF
    1075.     IF ((dx <> 0) OR (dy <> 0) OR (dz <> 0)) THEN
    1076.         CALL TranslateCluster(TheClusterIndex, dx, dy, dz)
    1077.     END IF
    1078.  
    1079. SUB TranslateCluster (TheClusterIndex AS LONG, dx AS DOUBLE, dy AS DOUBLE, dz AS DOUBLE)
    1080.     DIM k AS LONG
    1081.     k = Cluster(TheClusterIndex).FirstGroup
    1082.     DO
    1083.         Group(k).Centroid.x = Group(k).Centroid.x + dx
    1084.         Group(k).Centroid.y = Group(k).Centroid.y + dy
    1085.         Group(k).Centroid.z = Group(k).Centroid.z + dz
    1086.         IF (k = Cluster(TheClusterIndex).LastGroup) THEN EXIT DO
    1087.         k = Group(k).Pointer
    1088.     LOOP
    1089.     Cluster(TheClusterIndex).Centroid.x = Cluster(TheClusterIndex).Centroid.x + dx
    1090.     Cluster(TheClusterIndex).Centroid.y = Cluster(TheClusterIndex).Centroid.y + dy
    1091.     Cluster(TheClusterIndex).Centroid.z = Cluster(TheClusterIndex).Centroid.z + dz
    1092.  
    1093. ' Particle manipulation.
    1094.  
    1095. SUB EvolveParticles (TheGroup AS LONG)
    1096.     DIM xdim AS DOUBLE
    1097.     DIM ydim AS DOUBLE
    1098.     DIM zdim AS DOUBLE
    1099.     DIM dx AS DOUBLE
    1100.     DIM dy AS DOUBLE
    1101.     DIM dz AS DOUBLE
    1102.     DIM px AS DOUBLE
    1103.     DIM py AS DOUBLE
    1104.     DIM pz AS DOUBLE
    1105.     DIM k AS LONG
    1106.  
    1107.     xdim = Group(TheGroup).Volume.x
    1108.     ydim = Group(TheGroup).Volume.y
    1109.     zdim = Group(TheGroup).Volume.z
    1110.  
    1111.     FOR k = Group(TheGroup).FirstVector TO Group(TheGroup).LastVector
    1112.  
    1113.         ' Position update with periodic boundaries inside group volume
    1114.         dx = 1 * vec3Dvel(k, 1)
    1115.         dy = 1 * vec3Dvel(k, 2)
    1116.         dz = 1 * vec3Dvel(k, 3)
    1117.         IF (dx <> 0) THEN
    1118.             px = vec3Dpos(k, 1) + dx
    1119.             IF ABS(px) > xdim / 2 THEN
    1120.                 IF (px > xdim / 2) THEN
    1121.                     px = -xdim / 2
    1122.                 ELSE
    1123.                     px = xdim / 2
    1124.                 END IF
    1125.             END IF
    1126.             vec3Dpos(k, 1) = px
    1127.         END IF
    1128.         IF (dy <> 0) THEN
    1129.             py = vec3Dpos(k, 2) + dy
    1130.             IF ABS(py) > ydim / 2 THEN
    1131.                 IF (py > ydim / 2) THEN
    1132.                     py = -ydim / 2
    1133.                 ELSE
    1134.                     py = ydim / 2
    1135.                 END IF
    1136.             END IF
    1137.             vec3Dpos(k, 2) = py
    1138.         END IF
    1139.         IF (dz <> 0) THEN
    1140.             pz = vec3Dpos(k, 3) + dz
    1141.             IF ABS(pz) > zdim / 2 THEN
    1142.                 IF (pz > zdim / 2) THEN
    1143.                     pz = -zdim / 2
    1144.                 ELSE
    1145.                     pz = zdim / 2
    1146.                 END IF
    1147.             END IF
    1148.             vec3Dpos(k, 3) = pz
    1149.         END IF
    1150.     NEXT
    1151.  
    1152. SUB CalculateScreenVectors
    1153.     DIM uhatmag AS DOUBLE
    1154.     DIM vhatmag AS DOUBLE
    1155.     DIM h2 AS DOUBLE
    1156.     DIM w2 AS DOUBLE
    1157.     DIM mag AS DOUBLE
    1158.     DIM uhatdotvhat AS DOUBLE
    1159.     uhatmag = SQR(uhat(1) * uhat(1) + uhat(2) * uhat(2) + uhat(3) * uhat(3))
    1160.     uhat(1) = uhat(1) / uhatmag: uhat(2) = uhat(2) / uhatmag: uhat(3) = uhat(3) / uhatmag
    1161.     vhatmag = SQR(vhat(1) * vhat(1) + vhat(2) * vhat(2) + vhat(3) * vhat(3))
    1162.     vhat(1) = vhat(1) / vhatmag: vhat(2) = vhat(2) / vhatmag: vhat(3) = vhat(3) / vhatmag
    1163.     uhatdotvhat = uhat(1) * vhat(1) + uhat(2) * vhat(2) + uhat(3) * vhat(3)
    1164.     nhat(1) = uhat(2) * vhat(3) - uhat(3) * vhat(2)
    1165.     nhat(2) = uhat(3) * vhat(1) - uhat(1) * vhat(3)
    1166.     nhat(3) = uhat(1) * vhat(2) - uhat(2) * vhat(1)
    1167.     h2 = _HEIGHT / 2
    1168.     w2 = _WIDTH / 2
    1169.     nearplane(1) = -nhat(1)
    1170.     nearplane(2) = -nhat(2)
    1171.     nearplane(3) = -nhat(3)
    1172.     farplane(1) = nhat(1)
    1173.     farplane(2) = nhat(2)
    1174.     farplane(3) = nhat(3)
    1175.     rightplane(1) = h2 * fovd * uhat(1) - h2 * w2 * nhat(1)
    1176.     rightplane(2) = h2 * fovd * uhat(2) - h2 * w2 * nhat(2)
    1177.     rightplane(3) = h2 * fovd * uhat(3) - h2 * w2 * nhat(3)
    1178.     mag = SQR(rightplane(1) * rightplane(1) + rightplane(2) * rightplane(2) + rightplane(3) * rightplane(3))
    1179.     rightplane(1) = rightplane(1) / mag
    1180.     rightplane(2) = rightplane(2) / mag
    1181.     rightplane(3) = rightplane(3) / mag
    1182.     leftplane(1) = -h2 * fovd * uhat(1) - h2 * w2 * nhat(1)
    1183.     leftplane(2) = -h2 * fovd * uhat(2) - h2 * w2 * nhat(2)
    1184.     leftplane(3) = -h2 * fovd * uhat(3) - h2 * w2 * nhat(3)
    1185.     mag = SQR(leftplane(1) * leftplane(1) + leftplane(2) * leftplane(2) + leftplane(3) * leftplane(3))
    1186.     leftplane(1) = leftplane(1) / mag
    1187.     leftplane(2) = leftplane(2) / mag
    1188.     leftplane(3) = leftplane(3) / mag
    1189.     topplane(1) = w2 * fovd * vhat(1) - h2 * w2 * nhat(1)
    1190.     topplane(2) = w2 * fovd * vhat(2) - h2 * w2 * nhat(2)
    1191.     topplane(3) = w2 * fovd * vhat(3) - h2 * w2 * nhat(3)
    1192.     mag = SQR(topplane(1) * topplane(1) + topplane(2) * topplane(2) + topplane(3) * topplane(3))
    1193.     topplane(1) = topplane(1) / mag
    1194.     topplane(2) = topplane(2) / mag
    1195.     topplane(3) = topplane(3) / mag
    1196.     bottomplane(1) = -w2 * fovd * vhat(1) - h2 * w2 * nhat(1)
    1197.     bottomplane(2) = -w2 * fovd * vhat(2) - h2 * w2 * nhat(2)
    1198.     bottomplane(3) = -w2 * fovd * vhat(3) - h2 * w2 * nhat(3)
    1199.     mag = SQR(bottomplane(1) * bottomplane(1) + bottomplane(2) * bottomplane(2) + bottomplane(3) * bottomplane(3))
    1200.     bottomplane(1) = bottomplane(1) / mag
    1201.     bottomplane(2) = bottomplane(2) / mag
    1202.     bottomplane(3) = bottomplane(3) / mag
    1203.  
    1204. SUB ProjectGroup (TheGroup AS LONG)
    1205.     DIM i AS LONG
    1206.     DIM f AS INTEGER
    1207.     DIM vectorinview AS INTEGER
    1208.     DIM vec3Ddotnhat AS DOUBLE
    1209.     FOR i = Group(TheGroup).FirstVector TO Group(TheGroup).LastVector
    1210.         vec(i, 1) = Group(TheGroup).Centroid.x + vec3Dpos(i, 1) - PlayerCamera.Position.x
    1211.         vec(i, 2) = Group(TheGroup).Centroid.y + vec3Dpos(i, 2) - PlayerCamera.Position.y
    1212.         vec(i, 3) = Group(TheGroup).Centroid.z + vec3Dpos(i, 3) - PlayerCamera.Position.z
    1213.         f = -1
    1214.         vec3Dvis(i) = 0
    1215.         vectorinview = 1
    1216.         IF vec(i, 1) * nearplane(1) + vec(i, 2) * nearplane(2) + vec(i, 3) * nearplane(3) - nearplane(4) < 0 THEN vectorinview = 0
    1217.         'IF vec(i, 1) * farplane(1) + vec(i, 2) * farplane(2) + vec(i, 3) * farplane(3) - farplane(4) < 0 THEN vectorinview = 0
    1218.         IF vec(i, 1) * farplane(1) + vec(i, 2) * farplane(2) + vec(i, 3) * farplane(3) - farplane(4) * .85 < 0 THEN f = 1
    1219.         'IF vec(i, 1) * rightplane(1) + vec(i, 2) * rightplane(2) + vec(i, 3) * rightplane(3) - rightplane(4) < 0 THEN vectorinview = 0
    1220.         'IF vec(i, 1) * leftplane(1) + vec(i, 2) * leftplane(2) + vec(i, 3) * leftplane(3) - leftplane(4) < 0 THEN vectorinview = 0
    1221.         'IF vec(i, 1) * topplane(1) + vec(i, 2) * topplane(2) + vec(i, 3) * topplane(3) - topplane(4) < 0 THEN vectorinview = 0
    1222.         'IF vec(i, 1) * bottomplane(1) + vec(i, 2) * bottomplane(2) + vec(i, 3) * bottomplane(3) - bottomplane(4) < 0 THEN vectorinview = 0
    1223.         IF (vectorinview = 1) THEN
    1224.             vec3Dvis(i) = 1
    1225.             vec3Ddotnhat = vec(i, 1) * nhat(1) + vec(i, 2) * nhat(2) + vec(i, 3) * nhat(3)
    1226.             vec2D(i, 1) = (vec(i, 1) * uhat(1) + vec(i, 2) * uhat(2) + vec(i, 3) * uhat(3)) * fovd / vec3Ddotnhat
    1227.             vec2D(i, 2) = (vec(i, 1) * vhat(1) + vec(i, 2) * vhat(2) + vec(i, 3) * vhat(3)) * fovd / vec3Ddotnhat
    1228.             IF (f = 1) THEN
    1229.                 vec2Dcolor(i) = Gray
    1230.             ELSE
    1231.                 vec2Dcolor(i) = vec3Dcolor(i)
    1232.             END IF
    1233.         END IF
    1234.     NEXT
    1235.  
    1236. ' TheDynamics
    1237.  
    1238. SUB ComputeVisibleScene
    1239.     DIM closestdist2 AS DOUBLE
    1240.     DIM fp42 AS DOUBLE
    1241.     DIM dist2 AS DOUBLE
    1242.     DIM dx AS DOUBLE
    1243.     DIM dy AS DOUBLE
    1244.     DIM dz AS DOUBLE
    1245.     DIM GroupInView AS INTEGER
    1246.     DIM k AS LONG
    1247.     DIM i AS LONG
    1248.     ClosestGroup = 1
    1249.     closestdist2 = 10000000
    1250.     fp42 = farplane(4) * farplane(4)
    1251.     FOR i = 1 TO ClusterIndexTicker
    1252.         dx = Cluster(i).Centroid.x - PlayerCamera.Position.x
    1253.         dy = Cluster(i).Centroid.y - PlayerCamera.Position.y
    1254.         dz = Cluster(i).Centroid.z - PlayerCamera.Position.z
    1255.         dist2 = dx * dx + dy * dy + dz * dz
    1256.         IF (dist2 > 600 * 600) THEN
    1257.             Cluster(i).Visible = 0
    1258.             IF (Cluster(i).MotionType > -1) THEN
    1259.                 CALL EvolveCluster(i)
    1260.             END IF
    1261.         ELSE
    1262.             Cluster(i).Visible = 1
    1263.             k = Cluster(i).FirstGroup
    1264.             IF ((Cluster(i).MotionType > -1) AND (ToggleAnimate = 1)) THEN
    1265.                 CALL EvolveCluster(i)
    1266.             END IF
    1267.             DO
    1268.                 dx = Group(k).Centroid.x - PlayerCamera.Position.x
    1269.                 dy = Group(k).Centroid.y - PlayerCamera.Position.y
    1270.                 dz = Group(k).Centroid.z - PlayerCamera.Position.z
    1271.                 dist2 = dx * dx + dy * dy + dz * dz
    1272.                 Group(k).Visible = 0
    1273.                 IF (dist2 < fp42) THEN
    1274.                     GroupInView = 1
    1275.                     IF dx * nearplane(1) + dy * nearplane(2) + dz * nearplane(3) - nearplane(4) < 0 THEN GroupInView = 0
    1276.                     'IF dx * farplane(1) + dy * farplane(2) + dz * farplane(3) - farplane(4) < 0 THEN groupinview = 0 ''' Redundant
    1277.                     IF dx * rightplane(1) + dy * rightplane(2) + dz * rightplane(3) - rightplane(4) < 0 THEN GroupInView = 0
    1278.                     IF dx * leftplane(1) + dy * leftplane(2) + dz * leftplane(3) - leftplane(4) < 0 THEN GroupInView = 0
    1279.                     IF dx * topplane(1) + dy * topplane(2) + dz * topplane(3) - topplane(4) < 0 THEN GroupInView = 0
    1280.                     IF dx * bottomplane(1) + dy * bottomplane(2) + dz * bottomplane(3) - bottomplane(4) < 0 THEN GroupInView = 0
    1281.                     IF (GroupInView = 1) THEN
    1282.                         Group(k).Visible = 1
    1283.                         IF (dist2 < closestdist2) THEN
    1284.                             closestdist2 = dist2
    1285.                             ClosestGroup = k
    1286.                         END IF
    1287.                         Group(k).Distance2 = dist2
    1288.                         IF (ToggleAnimate = 1) THEN CALL EvolveParticles(k)
    1289.                         CALL ProjectGroup(k)
    1290.                     END IF
    1291.                 END IF
    1292.                 IF (k = Cluster(i).LastGroup) THEN EXIT DO
    1293.                 k = Group(k).Pointer
    1294.             LOOP
    1295.         END IF
    1296.     NEXT
    1297.  
    1298. ' Graphics
    1299.  
    1300. SUB PlotWorld
    1301.     DIM a AS DOUBLE
    1302.     DIM b AS DOUBLE
    1303.     DIM c AS LONG
    1304.     DIM d AS LONG
    1305.     DIM i AS LONG
    1306.     DIM j AS INTEGER
    1307.     DIM k AS LONG
    1308.     DIM x1 AS DOUBLE
    1309.     DIM y1 AS DOUBLE
    1310.     DIM x2 AS DOUBLE
    1311.     DIM y2 AS DOUBLE
    1312.     DIM clrtmp AS _UNSIGNED LONG
    1313.     DIM SortedGroups(5000) AS LONG
    1314.     DIM SortedGroupsCount AS INTEGER
    1315.  
    1316.     NumClusterVisible = 0
    1317.     NumVectorVisible = 0
    1318.  
    1319.     SortedGroupsCount = 0
    1320.     FOR i = 1 TO ClusterIndexTicker
    1321.         IF (Cluster(i).Visible = 1) THEN
    1322.             NumClusterVisible = NumClusterVisible + 1
    1323.             k = Cluster(i).FirstGroup
    1324.             DO
    1325.                 IF (Group(k).Visible = 1) THEN
    1326.                     SortedGroupsCount = SortedGroupsCount + 1
    1327.                     SortedGroups(SortedGroupsCount) = k
    1328.                 END IF
    1329.                 IF (k = Cluster(i).LastGroup) THEN EXIT DO
    1330.                 k = Group(k).Pointer
    1331.             LOOP
    1332.         END IF
    1333.     NEXT
    1334.  
    1335.     NumGroupVisible = SortedGroupsCount
    1336.  
    1337.     ' Bubble sort might be a shitty choice but it works for now. Replace with quicksort prolly.
    1338.     FOR j = SortedGroupsCount TO 1 STEP -1
    1339.         FOR i = 2 TO SortedGroupsCount
    1340.             a = Group(SortedGroups(i - 1)).Distance2
    1341.             b = Group(SortedGroups(i)).Distance2
    1342.             IF (a < b) THEN
    1343.                 c = SortedGroups(i - 1)
    1344.                 d = SortedGroups(i)
    1345.                 SortedGroups(i - 1) = d
    1346.                 SortedGroups(i) = c
    1347.             END IF
    1348.         NEXT
    1349.     NEXT
    1350.  
    1351.     CLS
    1352.     PlayerCamera.Shade = ShadeMix~&(PlayerCamera.Shade, vec3Dcolor(Group(ClosestGroup).FirstVector), .01)
    1353.     PAINT (_WIDTH / 2, _HEIGHT / 2), _RGBA(_RED32(PlayerCamera.Shade), _GREEN32(PlayerCamera.Shade), _BLUE32(PlayerCamera.Shade), 100)
    1354.     FOR j = 1 TO SortedGroupsCount
    1355.         k = SortedGroups(j)
    1356.         FOR i = Group(k).FirstVector TO Group(k).LastVector - 1
    1357.             IF (vec3Dvis(i) = 1) THEN
    1358.                 NumVectorVisible = NumVectorVisible + 1
    1359.                 IF (k = ClosestGroup) THEN
    1360.                     clrtmp = Yellow
    1361.                 ELSE
    1362.                     clrtmp = vec2Dcolor(i)
    1363.                 END IF
    1364.                 x1 = vec2D(i, 1)
    1365.                 y1 = vec2D(i, 2)
    1366.                 x2 = vec2D(i + 1, 1)
    1367.                 y2 = vec2D(i + 1, 2)
    1368.                 IF (((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) < 225) THEN
    1369.                     CALL cline(x1, y1, x2, y2, clrtmp) 'lineSmooth
    1370.                 ELSE
    1371.                     CALL ccircle(x1, y1, 1, clrtmp)
    1372.                 END IF
    1373.             END IF
    1374.         NEXT
    1375.     NEXT
    1376.  
    1377. SUB DisplayHUD
    1378.     DIM a AS STRING
    1379.     DIM k AS INTEGER
    1380.     CALL lineSmooth(0, 0, 25 * (xhat(1) * uhat(1) + xhat(2) * uhat(2) + xhat(3) * uhat(3)), 25 * (xhat(1) * vhat(1) + xhat(2) * vhat(2) + xhat(3) * vhat(3)), _RGBA(255, 0, 0, 200))
    1381.     CALL lineSmooth(0, 0, 25 * (yhat(1) * uhat(1) + yhat(2) * uhat(2) + yhat(3) * uhat(3)), 25 * (yhat(1) * vhat(1) + yhat(2) * vhat(2) + yhat(3) * vhat(3)), _RGBA(0, 255, 0, 200))
    1382.     CALL lineSmooth(0, 0, 25 * (zhat(1) * uhat(1) + zhat(2) * uhat(2) + zhat(3) * uhat(3)), 25 * (zhat(1) * vhat(1) + zhat(2) * vhat(2) + zhat(3) * vhat(3)), _RGBA(30, 144, 255, 200))
    1383.     IF (ToggleHUD = 1) THEN
    1384.         COLOR LimeGreen
    1385.         _PRINTSTRING ((1) * 8, _HEIGHT - (10) * 16), " Position "
    1386.         COLOR Teal
    1387.         _PRINTSTRING ((1) * 8, _HEIGHT - (9) * 16), " x:       "
    1388.         _PRINTSTRING ((1) * 8, _HEIGHT - (8) * 16), " y:       "
    1389.         _PRINTSTRING ((1) * 8, _HEIGHT - (7) * 16), " z:       "
    1390.         _PRINTSTRING ((1) * 8, _HEIGHT - (9) * 16), " x: " + LTRIM$(RTRIM$(STR$(INT(PlayerCamera.Position.x))))
    1391.         _PRINTSTRING ((1) * 8, _HEIGHT - (8) * 16), " y: " + LTRIM$(RTRIM$(STR$(INT(PlayerCamera.Position.y))))
    1392.         _PRINTSTRING ((1) * 8, _HEIGHT - (7) * 16), " z: " + LTRIM$(RTRIM$(STR$(INT(PlayerCamera.Position.z))))
    1393.         COLOR LimeGreen
    1394.         _PRINTSTRING ((1) * 8, _HEIGHT - (6) * 16), " O: Reset "
    1395.         COLOR LimeGreen
    1396.         _PRINTSTRING ((1) * 8, _HEIGHT - (4) * 16), " Move  "
    1397.         COLOR DarkKhaki
    1398.         _PRINTSTRING ((1) * 8, _HEIGHT - (3) * 16), " q W e "
    1399.         _PRINTSTRING ((1) * 8, _HEIGHT - (2) * 16), " A S D "
    1400.         COLOR LimeGreen
    1401.         _PRINTSTRING ((1) * 8, (1) * 16), " Abilities "
    1402.         COLOR DarkKhaki
    1403.         _PRINTSTRING ((1) * 8, (2) * 16), "t = Pause  "
    1404.         _PRINTSTRING ((1) * 8, (3) * 16), "b = Create "
    1405.         _PRINTSTRING ((1) * 8, (4) * 16), "n = Explode"
    1406.         COLOR DarkGray
    1407.         _PRINTSTRING ((1) * 8, (5) * 16), "k = Delete "
    1408.         COLOR DarkKhaki
    1409.         _PRINTSTRING ((1) * 8, (6) * 16), "Esc = Quit "
    1410.         'COLOR LimeGreen
    1411.         '_PRINTSTRING ((1) * 8, (9) * 16), "View"
    1412.         'COLOR DarkKhaki
    1413.         '_PRINTSTRING ((1) * 8, (9) * 16), "FPS: " + LTRIM$(RTRIM$(STR$(FPSReport))) + "/30"
    1414.         '_PRINTSTRING ((1) * 8, (10) * 16), "Clusters: " + LTRIM$(RTRIM$(STR$(NumClusterVisible)))
    1415.         '_PRINTSTRING ((1) * 8, (11) * 16), "Groups: " + LTRIM$(RTRIM$(STR$(NumGroupVisible)))
    1416.         '_PRINTSTRING ((1) * 8, (12) * 16), "Particles: " + LTRIM$(RTRIM$(STR$(NumVectorVisible)))
    1417.         COLOR LimeGreen
    1418.         _PRINTSTRING (_WIDTH - (12) * 8, (1) * 16), " Locations "
    1419.         FOR k = 1 TO MissionTicker
    1420.             a = " " + Mission(k).Label + " "
    1421.             IF (Mission(k).Discovered = 0) THEN
    1422.                 COLOR DarkKhaki
    1423.             ELSE
    1424.                 COLOR DarkGray
    1425.             END IF
    1426.             _PRINTSTRING (_WIDTH - (LEN(a) + 1) * 8, (1 + k) * 16), a
    1427.         NEXT
    1428.         COLOR LimeGreen
    1429.         _PRINTSTRING (_WIDTH - (12) * 8, (k + 1) * 16), " M = Reset "
    1430.         COLOR LimeGreen
    1431.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (10) * 16), " Aim    "
    1432.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (9) * 16), " x      "
    1433.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (8) * 16), " y      "
    1434.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (7) * 16), " z      "
    1435.         COLOR Teal
    1436.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (9) * 16), " x: " + LTRIM$(RTRIM$(STR$(INT(-nhat(1) * 100))))
    1437.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (8) * 16), " y: " + LTRIM$(RTRIM$(STR$(INT(-nhat(2) * 100))))
    1438.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (7) * 16), " z: " + LTRIM$(RTRIM$(STR$(INT(-nhat(3) * 100))))
    1439.         COLOR LimeGreen
    1440.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (5) * 16), " Swivel "
    1441.         COLOR DarkKhaki
    1442.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (4) * 16), " 7 8 9  "
    1443.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (3) * 16), " 4 5 6  "
    1444.         _PRINTSTRING (_WIDTH - (9) * 8, _HEIGHT - (2) * 16), " 1 2 3  "
    1445.         a = " - Closest - "
    1446.         COLOR LimeGreen
    1447.         _PRINTSTRING (_WIDTH / 2 - (LEN(a) / 2) * 8, _HEIGHT - (3) * 16), a
    1448.         a = " SPACE = Hide "
    1449.         COLOR LimeGreen
    1450.         _PRINTSTRING (_WIDTH / 2 - (LEN(a) / 2) * 8, _HEIGHT - (1) * 16), a
    1451.     END IF
    1452.     a = " " + Group(ClosestGroup).GroupName + " "
    1453.     COLOR DarkKhaki
    1454.     _PRINTSTRING (_WIDTH / 2 - (LEN(a) / 2) * 8, _HEIGHT - (2) * 16), a
    1455.  
    1456. ' Interface
    1457.  
    1458. SUB KeyProcess
    1459.     DIM modifier AS DOUBLE
    1460.     modifier = 0.05
    1461.  
    1462.     'IF (_KEYDOWN(100303) <> 0) OR (_KEYDOWN(100304) <> 0) THEN
    1463.     '    modifier = modifier * 10
    1464.     'END IF
    1465.     IF (_KEYDOWN(87) <> 0) OR (_KEYDOWN(119) <> 0) OR (_KEYDOWN(18432) <> 0) THEN ' W or w or uparrow
    1466.         CALL StrafeCameraNhatMinus
    1467.         IF (ToggleAnimate = 1) THEN
    1468.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x - modifier * nhat(1)
    1469.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y - modifier * nhat(2)
    1470.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z - modifier * nhat(3)
    1471.         END IF
    1472.     END IF
    1473.     IF (_KEYDOWN(83) <> 0) OR (_KEYDOWN(115) <> 0) OR (_KEYDOWN(20480) <> 0) THEN ' S or s or downarrow
    1474.         CALL StrafeCameraNhatPlus
    1475.         IF (ToggleAnimate = 1) THEN
    1476.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x + modifier * nhat(1)
    1477.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y + modifier * nhat(2)
    1478.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z + modifier * nhat(3)
    1479.         END IF
    1480.     END IF
    1481.     IF (_KEYDOWN(65) <> 0) OR (_KEYDOWN(97) <> 0) THEN ' A or a
    1482.         CALL StrafeCameraUhatMinus
    1483.         IF (ToggleAnimate = 1) THEN
    1484.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x - modifier * uhat(1)
    1485.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y - modifier * uhat(2)
    1486.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z - modifier * uhat(3)
    1487.         END IF
    1488.     END IF
    1489.     IF (_KEYDOWN(68) <> 0) OR (_KEYDOWN(100) <> 0) THEN ' D or d
    1490.         CALL StrafeCameraUhatPlus
    1491.         IF (ToggleAnimate = 1) THEN
    1492.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x + modifier * uhat(1)
    1493.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y + modifier * uhat(2)
    1494.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z + modifier * uhat(3)
    1495.         END IF
    1496.     END IF
    1497.     IF (_KEYDOWN(81) <> 0) OR (_KEYDOWN(113) <> 0) THEN ' Q or q
    1498.         CALL StrafeCameraVhatMinus
    1499.         IF (ToggleAnimate = 1) THEN
    1500.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x - modifier * vhat(1)
    1501.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y - modifier * vhat(2)
    1502.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z - modifier * vhat(3)
    1503.         END IF
    1504.     END IF
    1505.     IF (_KEYDOWN(69) <> 0) OR (_KEYDOWN(101) <> 0) THEN ' E or e
    1506.         CALL StrafeCameraVhatPlus
    1507.         IF (ToggleAnimate = 1) THEN
    1508.             PlayerCamera.Velocity.x = PlayerCamera.Velocity.x + modifier * vhat(1)
    1509.             PlayerCamera.Velocity.y = PlayerCamera.Velocity.y + modifier * vhat(2)
    1510.             PlayerCamera.Velocity.z = PlayerCamera.Velocity.z + modifier * vhat(3)
    1511.         END IF
    1512.     END IF
    1513.     IF (_KEYDOWN(19200) <> 0) OR (_KEYDOWN(52) <> 0) THEN CALL RotateUhatMinus: CALL CalculateScreenVectors ' 4
    1514.     IF (_KEYDOWN(19712) <> 0) OR (_KEYDOWN(54) <> 0) THEN CALL RotateUhatPlus: CALL CalculateScreenVectors ' 6
    1515.     IF (_KEYDOWN(56) <> 0) THEN CALL RotateVhatPlus: CALL CalculateScreenVectors ' 8
    1516.     IF (_KEYDOWN(50) <> 0) THEN CALL RotateVhatMinus: CALL CalculateScreenVectors ' 2
    1517.     IF (_KEYDOWN(55) <> 0) THEN CALL RotateClockwise ' 7
    1518.     IF (_KEYDOWN(57) <> 0) THEN CALL RotateCounterclockwise ' 9
    1519.     IF (_KEYDOWN(49) <> 0) THEN CALL RotateUhatMinus: CALL CalculateScreenVectors: CALL RotateClockwise ' 1
    1520.     IF (_KEYDOWN(51) <> 0) THEN CALL RotateUhatPlus: CALL CalculateScreenVectors: CALL RotateCounterclockwise ' 3
    1521.  
    1522.     DIM k AS LONG
    1523.     DIM p AS LONG
    1524.     DIM l AS LONG
    1525.     DIM kh AS INTEGER
    1526.     kh = _KEYHIT
    1527.     IF (kh <> 0) THEN
    1528.         SELECT CASE kh
    1529.             CASE 27
    1530.                 SYSTEM
    1531.             CASE ASC(" ")
    1532.                 ToggleHUD = -ToggleHUD
    1533.             CASE ASC("b"), ASC("B")
    1534.                 DIM gtmp AS LONG
    1535.                 gtmp = NewCube&(1, "Custom block", 350, PlayerCamera.Position.x - 40 * nhat(1), PlayerCamera.Position.y - 40 * nhat(2), PlayerCamera.Position.z - 40 * nhat(3), 10, 10, 10, Lime, Purple, Teal, 0)
    1536.                 CALL ClusterPinch(gtmp)
    1537.             CASE ASC("k"), ASC("K")
    1538.                 ' set -999? delete actual vectors? deal with cluster headers
    1539.                 p = Group(ClosestGroup).Pointer
    1540.                 l = Group(ClosestGroup).Lagger
    1541.                 Group(l).Pointer = p
    1542.                 IF (p <> -999) THEN
    1543.                     Group(p).Lagger = l
    1544.                 END IF
    1545.             CASE ASC("m"), ASC("M")
    1546.                 CALL CreateMission(10)
    1547.             CASE ASC("n"), ASC("N")
    1548.                 FOR k = Group(ClosestGroup).FirstVector TO Group(ClosestGroup).LastVector
    1549.                     vec3Dvel(k, 1) = (RND - .5) * .20
    1550.                     vec3Dvel(k, 2) = (RND - .5) * .20
    1551.                     vec3Dvel(k, 3) = (RND - .5) * .20
    1552.                 NEXT
    1553.             CASE ASC("o"), ASC("O")
    1554.                 PlayerCamera.Position.x = -40
    1555.                 PlayerCamera.Position.y = 30
    1556.                 PlayerCamera.Position.z = 40
    1557.                 uhat(1) = -.2078192: uhat(2) = -.9781672: uhat(3) = 0
    1558.                 vhat(1) = 0: vhat(2) = 0: vhat(3) = 1
    1559.                 CALL CalculateScreenVectors
    1560.             CASE ASC("t"), ASC("T")
    1561.                 ToggleAnimate = -ToggleAnimate
    1562.         END SELECT
    1563.     END IF
    1564.     _KEYCLEAR
    1565.  
    1566. ' Color utility.
    1567.  
    1568. FUNCTION ShadeMix~& (Shade1 AS _UNSIGNED LONG, Shade2 AS _UNSIGNED LONG, Weight AS DOUBLE)
    1569.     ShadeMix~& = _RGB32((1 - Weight) * _RED32(Shade1) + Weight * _RED32(Shade2), (1 - Weight) * _GREEN32(Shade1) + Weight * _GREEN32(Shade2), (1 - Weight) * _BLUE32(Shade1) + Weight * _BLUE32(Shade2))
    1570.  
    1571. ' Cartesian plotting.
    1572.  
    1573. SUB cline (x1 AS DOUBLE, y1 AS DOUBLE, x2 AS DOUBLE, y2 AS DOUBLE, col AS _UNSIGNED LONG)
    1574.     LINE (_WIDTH / 2 + x1, -y1 + _HEIGHT / 2)-(_WIDTH / 2 + x2, -y2 + _HEIGHT / 2), col
    1575.  
    1576. SUB cpset (x1, y1, col AS _UNSIGNED LONG)
    1577.     PSET (_WIDTH / 2 + x1, -y1 + _HEIGHT / 2), col
    1578.  
    1579. SUB ccircle (x1 AS DOUBLE, y1 AS DOUBLE, rad AS DOUBLE, col AS _UNSIGNED LONG)
    1580.     CIRCLE (_WIDTH / 2 + x1, -y1 + _HEIGHT / 2), rad, col
    1581.  
    1582. SUB lineSmooth (x0, y0, x1, y1, c AS _UNSIGNED LONG)
    1583.     'Inspiration credit: {(FellippeHeitor)(qb64.org)(2020)}
    1584.     '                    {https://en.wikipedia.org/w/index.php?title=Xiaolin_Wu%27s_line_algorithm&oldid=852445548}
    1585.     'Edit: {(STxAxTIC)(2020-11-20)(Correction to alpha channel.)}
    1586.  
    1587.     DIM plX AS INTEGER, plY AS INTEGER, plI
    1588.  
    1589.     DIM steep AS _BYTE
    1590.     steep = ABS(y1 - y0) > ABS(x1 - x0)
    1591.  
    1592.     IF steep THEN
    1593.         SWAP x0, y0
    1594.         SWAP x1, y1
    1595.     END IF
    1596.  
    1597.     IF x0 > x1 THEN
    1598.         SWAP x0, x1
    1599.         SWAP y0, y1
    1600.     END IF
    1601.  
    1602.     DIM dx, dy, gradient
    1603.     dx = x1 - x0
    1604.     dy = y1 - y0
    1605.     gradient = dy / dx
    1606.  
    1607.     IF dx = 0 THEN
    1608.         gradient = 1
    1609.     END IF
    1610.  
    1611.     'handle first endpoint
    1612.     DIM xend, yend, xgap, xpxl1, ypxl1
    1613.     xend = _ROUND(x0)
    1614.     yend = y0 + gradient * (xend - x0)
    1615.     xgap = (1 - ((x0 + .5) - INT(x0 + .5)))
    1616.     xpxl1 = xend 'this will be used in the main loop
    1617.     ypxl1 = INT(yend)
    1618.     IF steep THEN
    1619.         plX = ypxl1
    1620.         plY = xpxl1
    1621.         plI = (1 - (yend - INT(yend))) * xgap
    1622.         GOSUB plot
    1623.  
    1624.         plX = ypxl1 + 1
    1625.         plY = xpxl1
    1626.         plI = (yend - INT(yend)) * xgap
    1627.         GOSUB plot
    1628.     ELSE
    1629.         plX = xpxl1
    1630.         plY = ypxl1
    1631.         plI = (1 - (yend - INT(yend))) * xgap
    1632.         GOSUB plot
    1633.  
    1634.         plX = xpxl1
    1635.         plY = ypxl1 + 1
    1636.         plI = (yend - INT(yend)) * xgap
    1637.         GOSUB plot
    1638.     END IF
    1639.  
    1640.     DIM intery
    1641.     intery = yend + gradient 'first y-intersection for the main loop
    1642.  
    1643.     'handle second endpoint
    1644.     DIM xpxl2, ypxl2
    1645.     xend = _ROUND(x1)
    1646.     yend = y1 + gradient * (xend - x1)
    1647.     xgap = ((x1 + .5) - INT(x1 + .5))
    1648.     xpxl2 = xend 'this will be used in the main loop
    1649.     ypxl2 = INT(yend)
    1650.     IF steep THEN
    1651.         plX = ypxl2
    1652.         plY = xpxl2
    1653.         plI = (1 - (yend - INT(yend))) * xgap
    1654.         GOSUB plot
    1655.  
    1656.         plX = ypxl2 + 1
    1657.         plY = xpxl2
    1658.         plI = (yend - INT(yend)) * xgap
    1659.         GOSUB plot
    1660.     ELSE
    1661.         plX = xpxl2
    1662.         plY = ypxl2
    1663.         plI = (1 - (yend - INT(yend))) * xgap
    1664.         GOSUB plot
    1665.  
    1666.         plX = xpxl2
    1667.         plY = ypxl2 + 1
    1668.         plI = (yend - INT(yend)) * xgap
    1669.         GOSUB plot
    1670.     END IF
    1671.  
    1672.     'main loop
    1673.     DIM x
    1674.     IF steep THEN
    1675.         FOR x = xpxl1 + 1 TO xpxl2 - 1
    1676.             plX = INT(intery)
    1677.             plY = x
    1678.             plI = (1 - (intery - INT(intery)))
    1679.             GOSUB plot
    1680.  
    1681.             plX = INT(intery) + 1
    1682.             plY = x
    1683.             plI = (intery - INT(intery))
    1684.             GOSUB plot
    1685.  
    1686.             intery = intery + gradient
    1687.         NEXT
    1688.     ELSE
    1689.         FOR x = xpxl1 + 1 TO xpxl2 - 1
    1690.             plX = x
    1691.             plY = INT(intery)
    1692.             plI = (1 - (intery - INT(intery)))
    1693.             GOSUB plot
    1694.  
    1695.             plX = x
    1696.             plY = INT(intery) + 1
    1697.             plI = (intery - INT(intery))
    1698.             GOSUB plot
    1699.  
    1700.             intery = intery + gradient
    1701.         NEXT
    1702.     END IF
    1703.  
    1704.     EXIT SUB
    1705.  
    1706.     plot:
    1707.     ' Change to regular PSET for standard coordinate orientation.
    1708.     CALL cpset(plX, plY, _RGB32(_RED32(c), _GREEN32(c), _BLUE32(c), plI * _ALPHA32(c)))
    1709.     RETURN
    1710.  
    1711. ' Camera transformation
    1712.  
    1713. SUB PlayerDynamics
    1714.     DIM k AS INTEGER
    1715.  
    1716.     IF (ToggleAnimate = 1) THEN
    1717.         PlayerCamera.Velocity.x = .95 * PlayerCamera.Velocity.x
    1718.         PlayerCamera.Velocity.y = .95 * PlayerCamera.Velocity.y
    1719.         PlayerCamera.Velocity.z = .95 * PlayerCamera.Velocity.z
    1720.         PlayerCamera.Position.x = PlayerCamera.Position.x + PlayerCamera.Velocity.x
    1721.         PlayerCamera.Position.y = PlayerCamera.Position.y + PlayerCamera.Velocity.y
    1722.         PlayerCamera.Position.z = PlayerCamera.Position.z + PlayerCamera.Velocity.z
    1723.     END IF
    1724.  
    1725.     FOR k = 1 TO MissionTicker
    1726.         IF (Group(ClosestGroup).GroupName = Mission(k).Label) THEN
    1727.             IF (Mission(k).Discovered = 0) THEN
    1728.                 Mission(k).Discovered = 1
    1729.                 SOUND 500, .25
    1730.             END IF
    1731.         END IF
    1732.     NEXT
    1733.  
    1734. SUB RotateUhatPlus
    1735.     uhat(1) = uhat(1) + nhat(1) * .0333
    1736.     uhat(2) = uhat(2) + nhat(2) * .0333
    1737.     uhat(3) = uhat(3) + nhat(3) * .0333
    1738.  
    1739. SUB RotateUhatMinus
    1740.     uhat(1) = uhat(1) - nhat(1) * .0333
    1741.     uhat(2) = uhat(2) - nhat(2) * .0333
    1742.     uhat(3) = uhat(3) - nhat(3) * .0333
    1743.  
    1744. SUB RotateVhatPlus
    1745.     vhat(1) = vhat(1) + nhat(1) * .0333
    1746.     vhat(2) = vhat(2) + nhat(2) * .0333
    1747.     vhat(3) = vhat(3) + nhat(3) * .0333
    1748.  
    1749. SUB RotateVhatMinus
    1750.     vhat(1) = vhat(1) - nhat(1) * .0333
    1751.     vhat(2) = vhat(2) - nhat(2) * .0333
    1752.     vhat(3) = vhat(3) - nhat(3) * .0333
    1753.  
    1754. SUB RotateCounterclockwise
    1755.     DIM v1 AS DOUBLE
    1756.     DIM v2 AS DOUBLE
    1757.     DIM v3 AS DOUBLE
    1758.     v1 = vhat(1)
    1759.     v2 = vhat(2)
    1760.     v3 = vhat(3)
    1761.     vhat(1) = v1 + uhat(1) * .0333
    1762.     vhat(2) = v2 + uhat(2) * .0333
    1763.     vhat(3) = v3 + uhat(3) * .0333
    1764.     uhat(1) = uhat(1) - v1 * .0333
    1765.     uhat(2) = uhat(2) - v2 * .0333
    1766.     uhat(3) = uhat(3) - v3 * .0333
    1767.  
    1768. SUB RotateClockwise
    1769.     DIM v1 AS DOUBLE
    1770.     DIM v2 AS DOUBLE
    1771.     DIM v3 AS DOUBLE
    1772.     v1 = vhat(1)
    1773.     v2 = vhat(2)
    1774.     v3 = vhat(3)
    1775.     vhat(1) = v1 - uhat(1) * .0333
    1776.     vhat(2) = v2 - uhat(2) * .0333
    1777.     vhat(3) = v3 - uhat(3) * .0333
    1778.     uhat(1) = uhat(1) + v1 * .0333
    1779.     uhat(2) = uhat(2) + v2 * .0333
    1780.     uhat(3) = uhat(3) + v3 * .0333
    1781.  
    1782. SUB StrafeCameraUhatPlus
    1783.     PlayerCamera.Position.x = PlayerCamera.Position.x + uhat(1)
    1784.     PlayerCamera.Position.y = PlayerCamera.Position.y + uhat(2)
    1785.     PlayerCamera.Position.z = PlayerCamera.Position.z + uhat(3)
    1786.  
    1787. SUB StrafeCameraUhatMinus
    1788.     PlayerCamera.Position.x = PlayerCamera.Position.x - uhat(1)
    1789.     PlayerCamera.Position.y = PlayerCamera.Position.y - uhat(2)
    1790.     PlayerCamera.Position.z = PlayerCamera.Position.z - uhat(3)
    1791.  
    1792. SUB StrafeCameraVhatPlus
    1793.     PlayerCamera.Position.x = PlayerCamera.Position.x + vhat(1)
    1794.     PlayerCamera.Position.y = PlayerCamera.Position.y + vhat(2)
    1795.     PlayerCamera.Position.z = PlayerCamera.Position.z + vhat(3)
    1796.  
    1797. SUB StrafeCameraVhatMinus
    1798.     PlayerCamera.Position.x = PlayerCamera.Position.x - vhat(1)
    1799.     PlayerCamera.Position.y = PlayerCamera.Position.y - vhat(2)
    1800.     PlayerCamera.Position.z = PlayerCamera.Position.z - vhat(3)
    1801.  
    1802. SUB StrafeCameraNhatPlus
    1803.     PlayerCamera.Position.x = PlayerCamera.Position.x + nhat(1)
    1804.     PlayerCamera.Position.y = PlayerCamera.Position.y + nhat(2)
    1805.     PlayerCamera.Position.z = PlayerCamera.Position.z + nhat(3)
    1806.  
    1807. SUB StrafeCameraNhatMinus
    1808.     PlayerCamera.Position.x = PlayerCamera.Position.x - nhat(1)
    1809.     PlayerCamera.Position.y = PlayerCamera.Position.y - nhat(2)
    1810.     PlayerCamera.Position.z = PlayerCamera.Position.z - nhat(3)
    1811.  
    1812. '''
    1813.  
    You're not done when it works, you're done when it's right.