autocam off sync rate 60 sync on hide mouse global gParticleColour as DWORD `------------------ ` Config variables ` If set to 1 we'll use cubes, otherwise we'll use spheres global gUseCubes = 0 ` If set to 1 keep the particles rotating to add some movement to the static numbers global gRotateParticles = 0 ` Colour of the particles gParticleColour = rgb(0, 196, 255) ` Fade the particles with distance from the numbers - NOTE: This will make all the particles white again for some reason global gFadeParticles = 1 ` Controls the scale of the numbers global gCameraHeight# = 125.0 ` End of config `------------------ `set display mode 1600, 1200, 32 global gCurrentFreeObject = 0 ` Keep track of the object numbers we've used global gTimePassed# ` Can't assign an expression on the same line as a global variable definition gTimePassed# = 1.0/60.0 global gRotSpeed# = 2.8 ` How fast the particles can turn towards their destination global gMoveSpeed# = 35.0 ` How fast the particles can move towards their destination global gRotSpeed_Seconds# = 12.0 ` How fast the particles used in the last digit can turn towards their destination global gMoveSpeed_Seconds# = 150.0 ` How fast the particles used in the last digit can move towards their destination global gSecondsSpeedScale# = 0.0 ` The seconds move faster, but we want to scale them so they don't jump ` into position as soon as the clock starts global gTimeZPos# = -20.0 ` Z Coordinate for the numbers global gSpareZOffset# = 0.0 ` Z Coordinate for the spare particles ` Simple 3D vertex type type Vertex x as float y as float z as float endtype ` Type for each number that will appear on the screen, there will be 8 of these, 6 numbers ` for hours, minutes and seconds and a couple of colons to separate them. type Number Position as Vertex NumPositions as integer StartObject as integer endtype ` Type for the particles type Particle Position as Vertex ` Position in 3D space Velocity as Vertex ` How much we move per frame Rotation as Vertex ` Which direction we're currently pointing RotationVelocity as Vertex ` How fast we're rotating if gRotateParticles is 1 Destination as Vertex ` Where we're heading in 3D space ObjectIndex as integer ` The object index that we're controlling UsedInLastDigit as integer `Whether this particles needs to use different speed and rotation values endtype ` There can only be a maximum of 94 particles in use (00:00:00), add a couple of spares for effect #constant NumParticles 96 ` 8 characters on screen "00:00:00" #constant NumNumbers 8 ` Create and initialise the numbers dim Numbers(NumNumbers) as Number ` Set the positions of each of the numbers in 3D space NumberStartX# = -65.0 NumberSpacing# = 17.0 current# = NumberStartX# for i=1 to NumNumbers Number_Construct(i,current#,-10) current# = current# + NumberSpacing# next i `Can't have arrays inside types so we'll have to keep it external dim Number_Positions(NumNumbers, 25) as Vertex ` Read the data statements containing the number pixel offsets into an array dim NumberTemplates(11, 25) as integer restore NumberData for i=1 to 11 for j = 1 to 25 read NumberTemplates(i, j) next j next i ` Create and initialise the particles dim Particles(NumParticles) as Particle for i=1 to NumParticles Particle_Construct(i) next i ` Make a top down camera position camera 0, 0, gCameraHeight#, 0 point camera 0, 0, 0, 0 color backdrop RGB(0,0,0) ` Store the minute count last frame so we can toggle the clock between the top and the bottom of the screen LastMinute = -1 Pressed1LastFrame = 0 Pressed2LastFrame = 0 Pressed3LastFrame = 0 ` ---------------------------------- ` ---------------------------------- ` Main loop ` ---------------------------------- ` ---------------------------------- do ` Get the time as a string t$ = get time$() ` Run along the string character by character assigning the correct number template to each of our 8 numbers for i=1 to NumNumbers char$ = mid$(t$, i) if char$ = ":" Number_SetTemplate(i, 1) else index = val(char$)+2 Number_SetTemplate(i, index) endif next i ` Move the clock on the screen every minute minute = val(mid$(t$,5)) if minute <> LastMinute if gTimeZPos# = -20.0 gTimeZPos# = 30.0 ` Clock at the top of the screen gSpareZOffset# = -50.0 ` Spare particles at the bottom else gTimeZPos# = -20.0 ` Clock at the bottom of the screen gSpareZOffset# = 0.0 ` Spare particles at the top endif ` Set the positions of the numbers for i=1 to NumNumbers Numbers(i).Position.z = gTimeZPos# next i ` Slow down the rate of the seconds particles so they don't jump to their new position immediately gSecondsSpeedScale# = 0.0 endif LastMinute = minute ` Assign particles to the valid positions in the numbers current = 1; for i=1 to NumNumbers for j=1 to Numbers(i).NumPositions Particle_SetDestination(current, Number_Positions(i, j).x, Number_Positions(i, j).z) if i = 8 Particles(current).UsedInLastDigit = 1 else Particles(current).UsedInLastDigit = 0 endif current = current + 1 next j next i ` Send the rest of the particles off into the spare particles area for i=current to NumParticles Particle_SetDestination(i, Rnd(50)-25.0, Rnd(50)+gSpareZOffset#) Particles(i).UsedInLastDigit = 0 next i ` Scale up the speed of the particles displaying the seconds over time if gSecondsSpeedScale# < 1.0 gSecondsSpeedScale# = gSecondsSpeedScale# + gTimePassed#/4.0 if gSecondsSpeedScale# > 1.0 gSecondsSpeedScale# = 1.0 endif endif ` Update all of the particles for i=1 to NumParticles Particle_Tick(i) next i ` Process the keys ` Key '1' toggle between cubes and spheres if Keystate(2) = 1 if Pressed1LastFrame = 0 gUseCubes = 1-gUseCubes Particle_ChangeObjects() endif Pressed1LastFrame = 1 else Pressed1LastFrame = 0 endif ` Key '2' toggles between rotating and not rotating if Keystate(3) = 1 if Pressed2LastFrame = 0 gRotateParticles = 1-gRotateParticles endif Pressed2LastFrame = 1 else Pressed2LastFrame = 0 endif ` Key '3' toggles between fading and not fading the particles if Keystate(4) = 1 if Pressed3LastFrame = 0 gFadeParticles = 1-gFadeParticles Particle_ResetFade() endif Pressed3LastFrame = 1 else Pressed3LastFrame = 0 endif ` PGUP to move the camera in if Keystate(201) = 1 gCameraHeight# = gCameraHeight# - 50.0 * gTimePassed# position camera 0, 0, gCameraHeight#, 0 endif ` PGDN to move the camera out if Keystate(209) = 1 gCameraHeight# = gCameraHeight# + 50.0 * gTimePassed# position camera 0, 0, gCameraHeight#, 0 endif sync loop `---------------------- ` End of Mainloop `---------------------- ` Initialisation function for the Number type function Number_Construct(i as integer, x as float, z as float) Numbers(i).Position.x = x Numbers(i).Position.z = z Numbers(i).StartObject = GetFreeObject() gCurrentFreeObject = gCurrentFreeObject-1 endfunction ` Function to work out the 3D positions of the pixels of a number and store them in the Number_Positions array function Number_SetTemplate(i as integer, number as integer) numberDotSpacing# = 3.0 Numbers(i).NumPositions = 0 j=1 for z = 0 to 4 for x = 0 to 4 if NumberTemplates(number, j) = 1 Number_Positions(i, Numbers(i).NumPositions+1).x = Numbers(i).Position.x + x*numberDotSpacing# Number_Positions(i, Numbers(i).NumPositions+1).z = Numbers(i).Position.z + -(z*numberDotSpacing#) Numbers(i).NumPositions = Numbers(i).NumPositions + 1 endif j = j + 1 next x next y endfunction ` Initialisation function for the particles function Particle_Construct(i as integer) Particles(i).ObjectIndex = GetFreeObject() if gUseCubes = 1 Make Object Cube Particles(i).ObjectIndex, 2 else Make Object Sphere Particles(i).ObjectIndex, 2.5 endif Color Object Particles(i).ObjectIndex, gParticleColour Ghost Object On Particles(i).ObjectIndex Particles(i).Position.x = 0 Particles(i).Position.z = 0 Particles(i).Velocity.x = Particles(i).Velocity.z = 0 Particles(i).Destination.x = Particles(i).Destination.z = 0 Particles(i).Rotation.x = Particles(i).Rotation.y = Particles(i).Rotation.z = 0 Particles(i).RotationVelocity.x = Rnd(40)+10 Particles(i).RotationVelocity.z = Rnd(40)+10 endfunction ` Remake all of the objects function Particle_ChangeObjects() for i=1 to NumParticles delete object Particles(i).ObjectIndex if gUseCubes = 1 Make Object Cube Particles(i).ObjectIndex, 2 else Make Object Sphere Particles(i).ObjectIndex, 2.5 endif Color Object Particles(i).ObjectIndex, gParticleColour Ghost Object On Particles(i).ObjectIndex next i endfunction ` Reset the fade values of all of the particles function Particle_ResetFade() for i=1 to NumParticles Fade Object Particles(i).ObjectIndex, 100 ` Need to reset the colour as the fade turns it off Color Object Particles(i).ObjectIndex, gParticleColour next i endfunction ` Update function for the paritlces function Particle_Tick(i as integer) arrivedDist# = 0.1 ` How far away from the destination we need to be before we say we're there diff as Vertex angle as float angleDiff as float ` Work out our distance from the destinayion diff.x = Particles(i).Destination.x - Particles(i).Position.x diff.z = Particles(i).Destination.z - Particles(i).Position.z dist# = Sqrt(diff.x*diff.x + diff.z*diff.z) if gFadeParticles = 1 ` Alter the alpha based on distance from destination diffZ# = abs(gTimeZPos# - Particles(i).Position.z) if diffZ# > 60.0 Fade Object Particles(i).ObjectIndex, 40 else if diffZ# < 10.0 Fade Object Particles(i).ObjectIndex, 100 else alpha# = (60.0-diffZ#) + 40.0 Fade Object Particles(i).ObjectIndex, alpha# endif endif endif ` Clamp the distance between 0.01 and 10 so we can vary the approach speed within a sensible range if dist# < 0.01 dist# = 0.01 endif if dist# > 10.0 dist# = 10.0 endif if dist# > arrivedDist# ` Work out the angle we need to be facing to point at our destination angle = AtanFull(diff.x, diff.z) angle = WrapValue(angle) angleDiff = angle-Particles(i).Rotation.y angleDiff = WrapAngleForInterpolation(angleDiff) if Particles(i).UsedInLastDigit = 1 ` Ease up to our top rotation speed over a few seconds tempRotSpeed# = (gRotSpeed_Seconds#-gRotSpeed#) * gSecondsSpeedScale# + gRotSpeed# else tempRotSpeed# = gRotSpeed# endif ` Rotate towards the destination Particles(i).Rotation.y = Particles(i).Rotation.y + angleDiff * gTimePassed# * tempRotSpeed# Particles(i).Rotation.y = WrapValue(Particles(i).Rotation.y) ` Work out how fast we want to be moving. The closer we are the slower we go tempMoveSpeed# = dist# / 10.0 if Particles(i).UsedInLastDigit = 1 ` Ease up to our top move speed over a few seconds interpPos# = (gMoveSpeed_Seconds#-gMoveSpeed#) * gSecondsSpeedScale# + gMoveSpeed# tempMoveSpeed# = tempMoveSpeed# * interpPos# else tempMoveSpeed# = tempMoveSpeed# * gMoveSpeed# endif ` Get a vector pointing in the same dierection as us and apply velocity ` in that direction pointing as Vertex rot# = -Particles(i).Rotation.y pointing.x = -sin(rot#) pointing.z = cos(rot#) Particles(i).Velocity.x = pointing.x * tempMoveSpeed# Particles(i).Velocity.z = pointing.z * tempMoveSpeed# Particles(i).Position.x = Particles(i).Position.x + (Particles(i).Velocity.x * gTimePassed#) Particles(i).Position.z = Particles(i).Position.z + (Particles(i).Velocity.z * gTimePassed#) endif ` Put the 3D object in the right place Position Object Particles(i).ObjectIndex, Particles(i).Position.x, 0, Particles(i).Position.z ` Rotate the particles if we've got it set on if gRotateParticles = 1 Particles(i).Rotation.x = Particles(i).Rotation.x + Particles(i).RotationVelocity.x * gTimePassed# Particles(i).Rotation.x = WrapValue(Particles(i).Rotation.x) Particles(i).Rotation.z = Particles(i).Rotation.z + Particles(i).RotationVelocity.z * gTimePassed# Particles(i).Rotation.z = WrapValue(Particles(i).Rotation.z) Rotate Object Particles(i).ObjectIndex, Particles(i).Rotation.x, Particles(i).Rotation.y, Particles(i).Rotation.z endif endfunction ` Function to set the destination coordinate of a particle function Particle_SetDestination(i as integer, x as float, z as float) Particles(i).Destination.x = x Particles(i).Destination.z = z endfunction ` Return the next free object number function GetFreeObject() gCurrentFreeObject = gCurrentFreeObject + 1 endfunction gCurrentFreeObject ` Wrap an angle to fall in the range -180 to 180 degrees. Avoids having to deal with wrap around from 359->0 function WrapAngleForInterpolation(angle as float) sanityCheck = 0 ` This will stop us getting into an infinite loop if we get a bad angle from ATanFull while angle < -180.0 angle = angle + 360.0 sanityCheck = sanityCheck + 1 if sanityCheck > 5 exitfunction 0.0 endif endwhile sanityCheck = 0 while angle > 180.0 angle = angle - 360.0 sanityCheck = sanityCheck + 1 if sanityCheck > 5 exitfunction 0.0 endif endwhile endfunction angle ` Data for a 5x5 number only font NumberData: Data 0,0,0,0,0, 0,0,1,0,0, 0,0,0,0,0, 0,0,1,0,0, 0,0,0,0,0 ` : Data 0,1,1,1,0, 1,0,0,1,1, 1,0,1,0,1, 1,1,0,0,1, 0,1,1,1,0 ` 0 Data 0,0,1,0,0, 0,1,1,0,0, 0,0,1,0,0, 0,0,1,0,0, 1,1,1,1,1 ` 1 Data 0,1,1,1,0, 1,0,0,0,1, 0,0,1,1,0, 0,1,0,0,0, 1,1,1,1,1 ` 2 Data 1,1,1,1,0, 0,0,0,0,1, 0,1,1,1,0, 0,0,0,0,1, 1,1,1,1,0 ` 3 Data 1,0,0,0,1, 1,0,0,0,1, 0,1,1,1,1, 0,0,0,0,1, 0,0,0,0,1 ` 4 Data 1,1,1,1,1, 1,0,0,0,0, 1,1,1,1,0, 0,0,0,0,1, 1,1,1,1,0 ` 5 Data 0,1,1,1,0, 1,0,0,0,0, 1,1,1,1,0, 1,0,0,0,1, 0,1,1,1,0 ` 6 Data 1,1,1,1,1, 0,0,0,0,1, 0,0,0,1,0, 0,0,0,1,0, 0,0,1,0,0 ` 7 Data 0,1,1,1,0, 1,0,0,0,1, 0,1,1,1,0, 1,0,0,0,1, 0,1,1,1,0 ` 8 Data 0,1,1,1,0, 1,0,0,0,1, 0,1,1,1,1, 0,0,0,0,1, 0,1,1,1,0 ` 9