.StoreParticleData LDR R8, [R11, #particleCount] \ Set R8 to the number of particles that are \ already on-screen CMP R8, #MAX_PARTICLES \ If R8 >= MAX_PARTICLES then we already LDMHSFD R13!, {R8, PC} \ have the maximum number of particles \ on-screen, so restore the value of R8 from \ the stack and return from the subroutine ADD R8, R8, #1 \ Increment the particle counter to record STR R8, [R11, #particleCount] \ that we have added a new particle LDR R8, [R11, #particleEnd] \ Set R8 to the address of the end of the \ particle data buffer, which is where we \ can store the new batch of data STMIA R8!, {R0-R7} \ Store the eight words of particle data in \ the buffer, updating R8 as we go STR R8, [R11, #particleEnd] \ Update particleEnd with the new address of \ the end of the buffer MOV R9, #0 \ Zero the last word of the next particle's STR R9, [R8, #7*4] \ data after the one we just wrote, so this \ acts as a null terminator LDMFD R13!, {R8, PC} \ Restore the value of R8 from the stack and \ return from the subroutineName: StoreParticleData [Show more] Type: Subroutine Category: Particles Summary: Store the data for a new particle in the particle data buffer Deep dive: Particles and particle cloudsContext: See this subroutine on its own page References: This subroutine is called as follows: * AddBulletParticleToBuffer calls StoreParticleData
Arguments: (R0, R1, R2) Particle coordinate (R3, R4, R5) Particle velocity R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags Stack Contains a value to restore into R8 at the end, and the return address.InitialiseParticleData ADD R0, R11, #particleData \ Set particleEnd to the address of the STR R0, [R11, #particleEnd] \ particle data buffer, so the buffer starts \ off empty MOV R1, #0 \ Zero the last word of the first particle's STR R1, [R0, #7*4] \ data, so it acts as a null terminator STR R1, [R11, #particleCount] \ Set particleCount = 0 to indicate that \ there are no particles on-screen MOV PC, R14 \ Return from the subroutineName: InitialiseParticleData [Show more] Type: Subroutine Category: Start and end Summary: Initialise the particle data buffer and associated variables Deep dive: Particles and particle cloudsContext: See this subroutine on its own page References: This subroutine is called as follows: * Entry calls InitialiseParticleData.AddSmokeParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers AND R0, R0, #7 \ Reduce R0 to a random number in the range ADD R0, R0, #3 \ 3 to 10 MOV R1, R0 \ Set R1 and R2 to the same number, so if MOV R2, R0 \ R0, R1 and R2 represent the three colour \ channels, we have a grey colour of a \ random intensity \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of: AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00080000 \ Set bit 19 of the particle flags, so that \ the particle bounces on the ground should \ it ever reach it MVN R4, #SMOKE_RISING_SPEED \ Set R4 to the speed of the smoke rising \ to pass as the y-axis velocity in the \ particle adding routine below, so the \ particle drifts slowly up and away from \ the ground LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into (R0, R1, R2) MOV R9, #25 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 25), \ i.e. 0 to 128 MOV R8, #13 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 13), \ i.e. -&80000 to +&80000 MOV R6, #15 \ Set the particle's lifespan counter to 15 \ iterations of the main loop B AddRisingParticleToBuffer \ Add a particle to the particle data buffer \ that initially drifts upwards, and with a \ random element being added to its velocity \ and lifespan counter, returning from the \ subroutine using a tail callName: AddSmokeParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a smoke particle to the particle data buffer Deep dive: Particles and particle clouds Screen memory in the ArchimedesContext: See this subroutine on its own page References: This subroutine is called as follows: * AddExplosionToBuffer calls AddSmokeParticleToBuffer * DrawObjects (Part 3 of 3) calls AddSmokeParticleToBuffer
Arguments: (R0, R1, R2) Particle coordinate.AddDebrisParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R1, R1, LSR #29 \ Set R1 to a random number in the range 0 \ to 7 MOV R2, R0, LSR #30 \ Set R2 to a random number in the range 0 \ to 3 AND R0, R0, #7 \ Set R0 to a random number in the range 0 \ to 7 ADD R0, R0, #4 \ Set R0 to a random number in the range 4 \ to 11, to use in the red channel ADD R1, R1, #2 \ Set R1 to a random number in the range 2 \ to 2, to use for the green channel ADD R2, R2, #4 \ Set R2 to a random number in the range 4 \ to 7, to use in the blue channel \ This sets the channels to a randomly \ purple-brownish-green colour \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of: AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&001C0000 \ Set bits 18, 19 and 20 of the particle \ flags, so that's: \ \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into (R0, R1, R2) MOV R9, #26 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 26), \ i.e. 0 to 64 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 10), \ i.e. -&400000 to +&400000 MOV R6, #15 \ Set the particle's lifespan counter to 15 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail callName: AddDebrisParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a debris particle to the particle data buffer, which is a purple-brownish-green particle that bounces out of an explosion Deep dive: Particles and particle clouds Screen memory in the ArchimedesContext: See this subroutine on its own page References: This subroutine is called as follows: * AddExplosionToBuffer calls AddDebrisParticleToBuffer
Arguments: (R0, R1, R2) Particle coordinates.DropARockFromTheSky STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R1, R1, LSR #29 \ Set R1 to a random number in the range 0 \ to 7 MOV R2, R0, LSR #30 \ Set R2 to a random number in the range 0 \ to 3 AND R0, R0, #7 \ Set R0 to a random number in the range 0 \ to 7 ADD R0, R0, #4 \ Set R0 to a random number in the range 4 \ to 11, to use in the red channel ADD R1, R1, #2 \ Set R1 to a random number in the range 2 \ to 2, to use for the green channel ADD R2, R2, #4 \ Set R2 to a random number in the range 4 \ to 7, to use in the blue channel \ This sets the channels to a randomly \ purple-brownish-green colour \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of: AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00FE0000 \ Set bits 17 to 23 of the particle flags, \ so that's: \ \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size is big \ * Bit 24 set = explode on hitting ground LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into (R0, R1, R2) MOV R9, #27 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 27), \ i.e. 0 to 32 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 10), \ i.e. -&400000 to +&400000 MOV R6, #170 \ Set the particle's lifespan counter to \ 170 iterations of the main loop, so it \ won't disappear before it hits the ground B AddStaticParticleToBuffer \ Add a rock to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail callName: DropARockFromTheSky [Show more] Type: Subroutine Category: Particles Summary: Drop a rock from the specified coordinates by spawning it as a particle, albeit a very big particle with an associated 3D object Deep dive: Particles and particle clouds Screen memory in the ArchimedesContext: See this subroutine on its own page References: This subroutine is called as follows: * DropRocksFromTheSky calls DropARockFromTheSky * SpawnRock calls DropARockFromTheSky
Arguments: (R0, R1, R2) The 3D coordinate (x, y, z) where we spawn the rock.AddSparkParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the call to the particle adding \ routine below can return properly MOV R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's: \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle MOV R9, #29 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 29), \ i.e. 0 to 8 MOV R8, #8 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 8), \ i.e. -&1000000 to +&1000000 MOV R6, #8 \ Set the particle's lifespan counter to 8 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail callName: AddSparkParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a spark particle to the particle data buffer that fades from white-hot to red over timeContext: See this subroutine on its own page References: This subroutine is called as follows: * AddExplosionToBuffer calls AddSparkParticleToBuffer * AddSparkCloudToBuffer calls AddSparkParticleToBuffer
Arguments: (R0, R1, R2) Particle coordinates Deep dive: Particles and particle clouds.AddSprayParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers AND R2, R0, #3 \ Set R2 to a random number in the range 12 ADD R2, R2, #12 \ to 15, to use as the blue channel AND R0, R0, #4 \ Set R0 to a random number that's either 8 ADD R0, R0, #8 \ or 12 MOV R1, R0 \ Set R1 to the same as R0, so the red and \ green channels are the same, giving an \ overall colour that combines the high blue \ channel level with one of two greyscale \ levels, giving one of four shades of blue \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of: AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00100000 \ Set bit 20 of the particle flags, so that \ gravity is applied to the particle LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into (R0, R1, R2) MOV R9, #26 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 26), \ i.e. 0 to 64 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 10), \ i.e. -&400000 to +&400000 MOV R6, #20 \ Set the particle's lifespan counter to 20 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail callName: AddSprayParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a spray particle to the particle data buffer Deep dive: Particles and particle clouds Screen memory in the ArchimedesContext: See this subroutine on its own page References: This subroutine is called as follows: * SplashParticleIntoSea calls AddSprayParticleToBuffer
Arguments: (R0, R1, R2) Particle coordinates.AddExplosionToBuffer STMFD R13!, {R3-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved .expl1 \ Each cluster is made up of four particles, \ so we now add them to the particle data \ buffer BL AddSparkParticleToBuffer \ Add a spark particle to the particle data \ buffer (one that fades from white to red) BL AddDebrisParticleToBuffer \ Add a spark particle to the particle data \ buffer (a purple-brownish-green particle \ that flies out and bounces on the ground) BL AddSmokeParticleToBuffer \ Add a smoke particle to the particle data \ buffer (a grey particle that slowly rises) BL AddSparkParticleToBuffer \ Add another spark particle to the particle \ data buffer (one that fades from white to \ red) SUBS R8, R8, #1 \ Decrement the particle cluster counter in \ R8 BPL expl1 \ Loop back until we have drawn all R8 \ particle clusters in the explosion LDMFD R13!, {R3-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: AddExplosionToBuffer [Show more] Type: Subroutine Category: Particles Summary: Create a big explosion of particles and add it to the particle data buffer Deep dive: Particles and particle cloudsContext: See this subroutine on its own page References: This subroutine is called as follows: * AddShipExplosionToBuffer calls AddExplosionToBuffer * AddSmallExplosionToBuffer calls AddExplosionToBuffer * LoseLife calls AddExplosionToBuffer * ProcessObjectDestruction calls AddExplosionToBuffer
Arguments: R8 The number of particle clusters in the explosion (there are four particles per cluster).AddShipExplosionToBuffer STMFD R13!, {R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R8, #50 \ Set R8 = 50 so the explosion contains 50 \ clusters of four particles \ Now we add an explosion to the particle \ buffer at coordinates (R0, R1, R2), which \ are set to PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, so that's \ just in front of the ship (when the ship \ is not close to the ground, in which case \ the explosion would be above the ship as \ the ship moves down the screen) LDR R0, [R11, #xCamera] \ Set R0 = xCamera MOV R1, #0 \ Set R1 = 0 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL AddExplosionToBuffer \ Add an explosion into the particle data \ buffers LDMFD R13!, {PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: AddShipExplosionToBuffer [Show more] Type: Subroutine Category: Particles Summary: An unused routine that adds a 50-cluster explosion cloud to the particle data buffer, just front of the player's ship Deep dive: Unused code in LanderContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.AddSparkCloudToBuffer STMFD R13!, {R6-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R8, #150 \ Set R8 = 150 so the explosion contains 150 \ clusters of four particles \ Now we add an explosion to the particle \ buffer at coordinates (R0, R1, R2), which \ are set to PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, so that's \ just in front of the ship (when the ship \ is not close to the ground, in which case \ the explosion would be above the ship as \ the ship moves down the screen) LDR R0, [R11, #xCamera] \ Set R0 = xCamera MOV R1, #0 \ Set R1 = 0 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z .spcl1 BL AddSparkParticleToBuffer \ Add a spark particle to the particle data \ buffer (one that fades from white to red) SUBS R8, R8, #1 \ Decrement the particle counter in R8 BPL spcl1 \ Loop back until we have drawn all R8 \ particles LDMFD R13!, {R6-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: AddSparkCloudToBuffer [Show more] Type: Subroutine Category: Particles Summary: An unused routine that adds a cloud of 150 spark particles to the particle data buffer, just in front of the player's ship Deep dive: Unused code in LanderContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.SpawnRock STMFD R13!, {R6-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved \ Drop a rock from the sky, spawning the \ rock at coordinates (R0, R1, R2), which \ are set to half the height of a normal \ rock and PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, which is \ fairly high in the sky above the ship's \ current position and one tile in front of \ the ship LDR R0, [R11, #xCamera] \ Set R0 = xCamera MVN R1, #ROCK_HEIGHT / 2 \ Set R1 = ~ROCK_HEIGHT / 2 \ = -(ROCK_HEIGHT + 1) / 2 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL DropARockFromTheSky \ Drop a rock from the coordinates in \ (R0, R1, R2) LDMFD R13!, {R6-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: SpawnRock [Show more] Type: Subroutine Category: Particles Summary: An unused routine that spawns a rock in the sky, at half the altitude of the rocks in the DropRocksFromTheSky routine Deep dive: Unused code in LanderContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.DropRocksFromTheSky LDR R4, [R11, #currentScore] \ Set R4 to the current score minus 800 SUBS R4, R4, #800 MOVMI PC, R14 \ If the result is negative then the current \ score is 800 or less, so return from the \ subroutine without dropping any rocks \ If we get here then the current score is \ greater than 800, so we randomly drop \ rocks from the sky STMFD R13!, {R14} \ Store the return address on the stack BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R0, R0, LSR #18 \ Scale R0 to the range 0 to 16383 CMP R0, R4 \ If R0 >= R4 then return from the LDMHSIA R13!, {PC} \ subroutine without dropping a rock, so the \ chances of a rock dropping from the sky on \ each iteration of the main loop increases \ with higher scores \ If we get here then we drop a rock from \ the sky, spawning the rock at coordinates \ (R0, R1, R2), which are set to ROCK_HEIGHT \ tiles above and PLAYER_FRONT_Z tiles \ forwards from the camera coordinates, \ which is very high in the sky above the \ ship's current position and one tile in \ front of the ship LDR R0, [R11, #xCamera] \ Set R0 = xCamera MVN R1, #ROCK_HEIGHT \ Set R1 = ~ROCK_HEIGHT \ = -(ROCK_HEIGHT + 1) LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL DropARockFromTheSky \ Drop a rock from the coordinates in \ (R0, R1, R2) by spawning it as a \ particle, albeit a very big particle with \ an associated 3D object LDMFD R13!, {PC} \ Return from the subroutineName: DropRocksFromTheSky [Show more] Type: Subroutine Category: Particles Summary: If the score is 800 or more, then randomly drop rocks from the sky by spawning them as particlesContext: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop calls DropRocksFromTheSky.objectTypes EQUD objectPyramid \ 0 = pyramid (unused) EQUD objectSmallLeafyTree \ 1 = small leafy tree EQUD objectTallLeafyTree \ 2 = tall leafy tree EQUD objectSmallLeafyTree \ 3 = small leafy tree EQUD objectSmallLeafyTree \ 4 = small leafy tree EQUD objectGazebo \ 5 = gazebo EQUD objectTallLeafyTree \ 6 = tall leafy tree EQUD objectFirTree \ 7 = fir tree EQUD objectBuilding \ 8 = building EQUD objectRocket \ 9 = rocket EQUD objectRocket \ 10 = rocket EQUD objectRocket \ 11 = rocket EQUD objectRocket \ 12 = smoking but intact rocket (unused) EQUD objectSmokingRemainsRight \ 13 = smoking remains (bends to the right) EQUD objectSmokingRemainsLeft \ 14 = smoking remains (bends to the left) EQUD objectSmokingRemainsLeft \ 15 = smoking remains (bends to the left) EQUD objectSmokingRemainsLeft \ 16 = smoking remains (bends to the left) EQUD objectSmokingGazebo \ 17 = smoking remains of a gazebo EQUD objectSmokingRemainsRight \ 18 = smoking remains (bends to the right) EQUD objectSmokingRemainsRight \ 19 = smoking remains (bends to the right) EQUD objectSmokingBuilding \ 20 = smoking remains of a building EQUD objectSmokingRemainsRight \ 21 = smoking remains (bends to the right) EQUD objectSmokingRemainsLeft \ 22 = smoking remains (bends to the left) EQUD objectSmokingRemainsLeft \ 23 = smoking remains (bends to the left) EQUD objectSmokingRemainsLeft \ 24 = smoking remains (unused)Name: objectTypes [Show more] Type: Variable Category: 3D objects Summary: A table that maps object types to object blueprints Deep dive: Placing objects on the map Object blueprintsContext: See this variable on its own page References: No direct references to this variable in this source file.DrawObjects STMFD R13!, {R5-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved \ We are going to loop through every tile on \ the screen to check whether there are any \ corresponding objects in the object map, \ so we start by working out which entry in \ the object map corresponds to the far left \ corner at the back of the on-screen \ landscape view \ \ We work from back to front so that objects \ get drawn in that order, to ensure that \ distant objects always appear behind \ closer objects LDR R0, [R11, #xCamera] \ Set R0 to the x-coordinate of the camera, \ which is at the back of the landscape and \ in the middle AND R8, R0, #&FF000000 \ Zero the bottom three bytes of R0 and \ store in R8, so R8 contains the \ x-coordinate of the tile below the camera, \ in the middle of the landscape LDR R1, [R11, #zCamera] \ Set R1 to the z-coordinate of the camera AND R9, R1, #&FF000000 \ Zero the bottom three bytes of R1 and \ store in R9, so R9 contains the \ z-coordinate of the tile below the camera, \ at the back of the landscape SUB R8, R8, #LANDSCAPE_X \ Set xCameraTile = R8 - LANDSCAPE_X STR R8, [R11, #xCameraTile] \ \ We already rounded xCamera down to the \ nearest tile, so this moves us to the \ coordinate of the tile corner at the left \ end of the corner row, as subtracting the \ landscape offset effectively moves the \ camera to the left by that amount \ \ We store the result in xCameraTile, so \ this is set to the x-coordinate of the \ left end of the tile row \ \ As we only take the top byte of the \ x-coordinate when reading the object map, \ this moves us to the front-left corner of \ the on-screen landscape, so we can now \ work along the x-axis in the object map \ from left to right in the following loop \ We are now ready to loop through the \ object map, so we set up the loop counters \ for two loops - an outer loop with R7 as \ the counter to work through the z-axis \ from back to front, and an inner loop with \ R6 as the counter to work through the \ x-axis for each row from left to right MOV R7, #TILES_Z \ Set R7 = TILES_Z to act as a loop counter \ as we work our way along the tiles on the \ z-axis (i.e. from back to front on the \ screen) .dobs1 MOV R6, #TILES_X \ Set R6 = TILES_X to act as a loop counter \ as we work our way along the tiles on the \ x-axis (i.e. from left to right on the \ screen) LDR R8, [R11, #xCameraTile] \ Set R8 = xCameraTile, so R8 contains the \ x-coordinate of the leftmost tile of the \ visible landscape \ We now have the coordinates of the far \ left tile in (R8, R9), so we can start to \ iterate through the object map one row at \ a time, checking each tile to see if there \ is an object there (so we can draw it) .dobs2 ADD R14, R11, #objectMap \ Set R14 to the address of the object map ADD R14, R14, R8, LSR #24 \ Set R14 = R6 + (R8 >> 24) + (R9 >> 16) ADD R14, R14, R9, LSR #16 \ \ R8 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R9 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ (R8, R9) LDRB R0, [R14] \ Set R0 to the byte in the object map at \ the coordinate (R8, R9) CMP R0, #&FF \ If the object map entry is not &FF, then BNE dobs4 \ there is an object at this point, so jump \ to dobs4 to consider drawing the object .dobs3 ADD R8, R8, #TILE_SIZE \ Step along the x-axis by one whole tile, \ going from left to right SUBS R6, R6, #1 \ Decrement the x-axis loop counter in R6 BNE dobs2 \ Loop back to check the next tile until we \ have checked along the whole x-axis SUB R9, R9, #TILE_SIZE \ Step along the z-axis by one whole tile, \ going from back to front SUBS R7, R7, #1 \ Decrement the z-axis loop counter in R6 BNE dobs1 \ Loop back to check the next tile until we \ have checked along the whole z-axis LDMFD R13!, {R5-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: DrawObjects (Part 1 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Draw all the objects in the visible portion of the object map, starting by working our way through the map looking for objects Deep dive: Drawing 3D objectsContext: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls DrawObjects * MainLoop calls DrawObjects.dobs4 \ If we get here then there is an object at \ (R8, R9) of type R0 STRB R0, [R11, #objectType] \ Store the object type in objectType ] typeOffset = P% + 8 - objectTypes \ Set typeOffset to the offset back to the \ objectTypes table from the next \ instruction [ OPT pass% ADD R0, PC, R0, LSL #2 \ Set R14 to the address in entry R0 in the LDR R14, [R0, #-typeOffset] \ objectTypes table, which is the address of \ the blueprint for this object type STR R14, [R11, #objectData] \ Store the address of the blueprint in \ objectData BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (R8, R9), which is where we \ are drawing our object MOV R1, R0 \ Make a copy of the altitude in R1, so R1 \ contains the y-coordinate of the object as \ it sits on the landscape CMP R0, #SEA_LEVEL \ If the object is on the sea, jump back to BEQ dobs3 \ dobs3 to move on to the next object, as \ there are no objects on the sea \ \ This check sounds unnecessary as there \ shouldn't be any objects in the sea in the \ object map anyway, as this check is also \ performed when populating the object map \ \ However, because the landscape in Lander \ is infinite but the object map is finite, \ the object map gets repeated every 256 \ tiles in each direction, and objects that \ appear on land in one instance of the \ object may well appear in the sea in \ another instance, so this check prevents \ that from happening LDR R14, [R11, #xCamera] \ Set R0 = R8 - xCamera SUB R0, R8, R14 \ = x - xCamera \ \ So R0 contains the x-coordinate of the \ object relative to the camera LDR R14, [R11, #zCamera] \ Set R2 = R9 - zCamera SUB R2, R9, R14 \ \ So R2 contains the z-coordinate of the \ object relative to the camera ADD R2, R2, #LANDSCAPE_Z \ Move the coordinate back by the landscape \ offset, so (R0, R2) contains the \ coordinate of the particle relative to the \ back-centre point of the landscape LDR R14, [R11, #yCamera] \ Set R1 = R1 - yCamera SUB R1, R1, R14 \ = y - yCamera \ \ So R1 contains the y-coordinate of the \ object relative to the camera LDRB R14, [R11, #objectType] \ If the object type is 12 or more, then it CMP R14, #12 \ represents a destroyed object, so jump to BHS dobs6 \ dobs6 .dobs5 ADD R3, R11, #rotationMatrix \ Set R3 to the address of the object's \ rotation matrix, to pass to DrawObject BL DrawObject \ Draw the object B dobs3 \ Jump back to dobs3 to move on to the next \ objectName: DrawObjects (Part 2 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Draw the object that we have found on the object map Deep dive: Drawing 3D objectsContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.dobs6 \ If we get here then this is a destroyed \ object LDR R14, [R11, #mainLoopCount] \ If either bit 0 or 1 of mainLoopCount are TST R14, #%00000011 \ set, jump to dobs5 to draw the object and BNE dobs5 \ skip the following \ We only get here on one out of every four \ iterations around the main loop STMFD R13!, {R0-R7, R9} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #yCamera] \ Set R1 = R1 + yCamera ADD R1, R1, R14 \ = y + yCamera \ \ This reverts R1 to the altitude of the \ landscape at the object (as we subtracted \ yCamera from the altitude back in part 3) MOV R0, R8 \ Set (R0, R1, R2) = (x, y-SMOKE_HEIGHT, z) MOV R2, R9 \ SUB R1, R1, #SMOKE_HEIGHT \ So this coordinate is SMOKE_HEIGHT above \ the base of the object, or 3/4 of the tile \ size (as the y-axis points downwards), \ which is where we add our smoke particles, \ one on each iteration around the main loop BL AddSmokeParticleToBuffer \ Call AddSmokeParticleToBuffer to draw a \ smoke particle rising from the destroyed \ object LDMFD R13!, {R0-R7, R9} \ Retrieve the registers that we stored on \ the stack B dobs5 \ Jump to dobs5 to draw the objectName: DrawObjects (Part 3 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Draw a destroyed object that we have found on the object map Deep dive: Drawing 3D objectsContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.objectPlayerAddr EQUD objectPlayerName: objectPlayerAddr [Show more] Type: Variable Category: 3D objects Summary: The address of the object blueprint for the player's ship Deep dive: Object blueprintsContext: See this variable on its own page References: This variable is used as follows: * MoveAndDrawPlayer (Part 3 of 5) uses objectPlayerAddr.objectRockAddr EQUD objectRockName: objectRockAddr [Show more] Type: Variable Category: 3D objects Summary: The address of the object blueprint for a rock Deep dive: Object blueprintsContext: See this variable on its own page References: This variable is used as follows: * MoveAndDrawParticles (Part 3 of 4) uses objectRockAddr.DrawObject STMFD R13!, {R6-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R12, graphicsBufferEndAddr \ Set R12 to the address of the table that \ contains the end addresses of the graphics \ buffers, so when we draw the objects, we \ can add them as drawing commands onto the \ ends of the relevant graphics buffers \ \ In other words, R12 = graphicsBuffersEnd \ throughout the following ADD R4, R11, #xObject \ Set R4 to the address of xObject STMIA R4, {R0-R2} \ Store (R0, R1, R2) in the first three \ words of xObject, so we have: \ \ * xObject = x-coordinate of the object \ relative to the camera \ \ * yObject = y-coordinate of the object \ relative to the camera \ \ * zObject = z-coordinate of the object \ relative to the camera \ We now scale up the coordinate (x, y, z) \ in (R0, R1, R2) as high as it can go while \ still staying within 32-bit coordinates MOVS R4, R0 \ Set R4 = |R0| MVNMI R4, R4 \ = |x| \ \ If R0 is negative then R4 is actually set \ ~x, which is -(x+1), but it's close enough MOVS R5, R1 \ Set R5 = |R1| MVNMI R5, R5 \ = |y| \ \ If R1 is negative then R5 is actually set \ ~y, which is -(y+1), but it's close enough ORR R4, R4, R5 \ Set R4 = |R0| OR |R1| OR R2 ORR R4, R4, R2 \ = |x| OR |y| OR z ORRS R4, R4, #1 \ \ And round it up so it is non-zero \ \ So this sets R4 to a number that is \ greater than |x|, |y| and z, so R4 is \ therefore an upper bound on the values of \ all three coordinates MOVPL R4, R4, LSL #1 \ If R4 is positive then we know bit 7 is \ clear, so shift the sign bit off the end \ so we don't include it in the scale factor \ calculation below \ We now work out how many times we can \ scale up R4 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R5, #0 \ Set R5 = 0 to use as the scale factor in \ the following loop STRB R5, [R11, #crashedFlag] \ Set crashedFlag = 0 to indicate that the \ object has not crashed (though we will \ change this later if we find that it has) .dobj1 MOVS R4, R4, LSL #1 \ Shift R4 to the left until the top bit is ADDPL R5, R5, #1 \ set, incrementing R5 for each shift so R5 BPL dobj1 \ contains the scale factor we have applied \ to R4 \ \ So this scales R4 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R5 MOV R0, R0, LSL R5 \ We now scale up the coordinate (x, y, z) MOV R1, R1, LSL R5 \ in (R0, R1, R2) by the scale factor in R5, MOV R2, R2, LSL R5 \ as we know the result will stay within \ 32-bit words ADD R4, R11, #xObjectScaled \ Store (R0, R1, R2) in xObjectScaled, STMIA R4, {R0-R2} \ zObjectScaled and zObjectScaled ADD R4, R11, #rotationMatrix \ If R3 does not already point to the CMP R3, R4 \ rotation matrix at rotationMatrix, copy LDMNEIA R3!, {R0-R2} \ the nine-word matrix from the address in STMNEIA R4!, {R0-R2} \ R3 into rotationMatrix, so rotationMatrix LDMNEIA R3!, {R0-R2} \ now contains the object's rotation matrix, STMNEIA R4!, {R0-R2} \ which is made up of the three orientation LDMNEIA R3!, {R0-R2} \ vectors in: STMNEIA R4!, {R0-R2} \ \ (xNoseV, xRoofV, xSideV) \ (yNoseV, yRoofV, ySideV) \ (zNoseV, zRoofV, zSideV) \ So by this point we have: \ \ * The object's coordinate in: \ \ (xObject, yObject, zObject) \ \ * The scaled-up coordinate in: \ \ (xObjectScaled, yObjectScaled, \ zObjectScaled) \ \ * The object's rotation matrix at \ rotationMatrix, which is made up of \ the orientation vectors as follows: \ \ (xNoseV, xRoofV, xSideV) \ (yNoseV, yRoofV, ySideV) \ (zNoseV, zRoofV, zSideV) \ \ * crashedFlag = 0 \ \ * R12 = graphicsBuffersEnd \ \ We are now ready to move on to processing \ the object's vertices and facesName: DrawObject (Part 1 of 5) [Show more] Type: Subroutine Category: 3D objects Summary: Draw a 3D object and its shadow Deep dive: Drawing 3D objects Object blueprints Flying by mouseContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjects (Part 2 of 3) calls DrawObject * MoveAndDrawParticles (Part 3 of 4) calls DrawObject * MoveAndDrawPlayer (Part 3 of 5) calls DrawObject
Arguments: (R0, R1, R2) The 3D coordinate (x, y, z) of the object relative to the camera R3 The address of the object's rotation matrixLDR R0, [R11, #objectData] \ Set R0 to the address of the blueprint \ for the object that is being drawn LDR R8, [R0] \ Set R8 to the first word of the blueprint, \ which contains the number of vertices ADD R9, R0, #16 \ Set R9 to the address of the vertices data \ in the blueprint, which appears from the \ 16th byte onwards ADD R10, R11, #vertexProjected \ Set R10 to the address of vertexProjected \ which is where we will store the screen \ coordinates of the projected vertices LDRB R1, [R0, #12] \ Set objectFlags to the fourth word of the STRB R1, [R11, #objectFlags] \ blueprint, which contains the object's \ flags \ We now iterate through the vertices in the \ blueprint, using R8 as a loop counter (as \ we set it above to the number of vertices) \ \ As we iterate through the vertices we \ rotate each one by the object's rotation \ matrix (to orientate the object properly) \ and project it onto the screen, saving the \ results in the vertexProjected table .dobj2 LDMIA R9!, {R2-R4} \ Load the coordinates of the next vertex \ from R9 into (R2, R3, R4), and update R9 \ to point to the vertex after that, ready \ for the next iteration ADD R0, R11, #xVertex \ Set R0 to the address of xVertex ADD R1, R11, #xVertexRotated \ Set R1 to the address of xVertexRotated STMIA R0, {R2-R4} \ Store the vertex coordinates in xVertex, \ so (xVertex, yVertex, zVertex) contains \ the vertex coordinates BL MultiplyVectorByMatrix \ If this object is a static object, then \ simply copy the vertex into xVertexRotated \ as follows: \ \ [xVertexRotated] [xVertex] \ [yVertexRotated] = [yVertex] \ [zVertexRotated] [zVertex] \ \ If this is a rotating object, then \ multiply the coordinates at R0 (i.e. the \ vertex coordinates at xVertex) by the \ rotation matrix in rotationMatrix, and \ store the results at R1 (i.e. the rotated \ coordinates at xVertexRotated): \ \ [xVertexRotated] [xVertex] \ [yVertexRotated] = rotMatrix . [yVertex] \ [zVertexRotated] [zVertex] \ \ So this rotates the vertex coordinates by \ the object's rotation matrix ADD R0, R11, #xObject \ Set R0 to the address of xObject BL AddVectorToVertices \ Call AddVectorToVertices to set: \ \ [xCoord] [xObject] [xVertexRotated] \ [yCoord] = [yObject] + [yVertexRotated] \ [zCoord] [zObject] [zVertexRotated] \ \ So (xCoord, yCoord, zCoord) contains the \ coordinates of this vertex in 3D space, \ using the game's coordinate system ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL ProjectVertexOntoScreen \ Project (xCoord, yCoord, zCoord) onto the \ screen, returning the results in (R0, R1) STMIA R10!, {R0-R1} \ Store (R0, R1) in vertexProjected and \ update R10 to point to the next coordinate \ in vertexProjected, ready for us to add \ the shadow's projected vertex next ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL GetLandscapeBelowVertex \ Get the landscape altitude below the \ vertex and return it in R0 STR R0, [R11, #yCoord] \ Set yCoord = R0, so (xCoord, yCoord, \ zCoord) now contains the coordinate on the \ landscape directly below the vertex \ \ We use this coordinate for the object's \ shadow, which we store in vertexProjected \ just after the normally projected vertex ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL ProjectVertexOntoScreen \ Project (xCoord, yCoord, zCoord) onto the \ screen, returning the results in (R0, R1) STMIA R10!, {R0-R1} \ Store (R0, R1) in vertexProjected and \ update R10 to point to the next coordinate \ in vertexProjected, ready for the next \ iteration LDR R14, [R10, #-12] \ Set R14 to the second coordinate from the \ previous projection, i.e. the y-coordinate \ of the projected vertex (so that's the \ object rather than the shadow) CMP R14, R1 \ If R14 >= R1 then the y-coordinate of the MVNHS R14, #0 \ object is bigger than the y-coordinate of STRHSB R14, [R11, #crashedFlag] \ the shadow, which means the object is \ lower down the screen than its shadow \ \ This can only happen if the object has \ "passed through" the ground, so set \ crashedFlag to &FF to indicate that the \ object has crashed SUBS R8, R8, #1 \ Decrement the loop counter, which keeps \ track of the number of vertices we have \ processed BNE dobj2 \ Loop back to dobj2 to move on to the next \ vertex, until we have processed them all \ We now move on to processing the object's \ face data so we can draw the visible facesName: DrawObject (Part 2 of 5) [Show more] Type: Subroutine Category: 3D objects Summary: Process the object's vertices Deep dive: Drawing 3D objects Object blueprints Collisions and bulletsContext: See this subroutine on its own page References: No direct references to this subroutine in this source fileLDR R1, [R11, #objectData] \ Set R1 to the address of the blueprint \ for the object that is being drawn LDR R0, [R1, #8] \ Set R0 to the third word of the blueprint, \ which contains the offset from the start \ of the blueprint to the face data ADD R9, R1, R0 \ Set R9 to the address of the face data in \ the blueprint LDR R8, [R1, #4] \ Set R8 to the second word of the \ blueprint, which contains the number of \ faces \ We now iterate through the faces in the \ blueprint, using R8 as a loop counter (as \ we just set it to the number of faces) \ \ As we iterate through the faces we check \ their visibility, and draw the visible \ faces and (if applicable) their shadows \ into the graphics buffers .dobj3 MOV R0, R9 \ Copy the value of R9 into R0, so R0 points \ to the start of the current face data, \ which is where the face's normal vector is \ stored ADD R9, R9, #12 \ Add 12 to R9 so it points to the fourth \ word in the current face data, which is \ where the vertex numbers are stored ADD R1, R11, #xVertex \ Set R1 to the address of xVertex, so we \ store the results of the following \ calculation here BL MultiplyVectorByMatrix \ If this object is a static object, then \ simply copy the normal vector into xVertex \ as follows: \ \ [xVertex] [xNormal] \ [yVertex] = [yNormal] \ [zVertex] [zNormal] \ \ If this is a rotating object, then \ calculate the following, multiplying the \ vector in R0 by the rotation matrix in \ rotationMatrix: \ \ [xVertex] [xNormal] \ [yVertex] = rotationMatrix . [yNormal] \ [zVertex] [zNormal] \ \ So this rotates the normal vector in R0 by \ the object's rotation matrix, so the \ normal has now been rotated into the same \ frame of reference as the object in the 3D \ world, and we can check the orientation of \ that rotated normal to see if the face is \ visible LDR R1, [R11, #yVertex] \ Set R1 to yVertex, the y-coordinate of the \ rotated normal LDRB R14, [R11, #objectFlags] \ If bit 0 of objectFlags is zero then this TST R14, #%00000001 \ object is static and does not rotate, so MVNEQ R3, #0 \ set R1 and R3 to -1 so this face is always MVNEQ R1, #0 \ visible (as R3 is negative) and always \ casts a shadow in objects with shadows \ as (R1 is negative) ADDNE R2, R11, #xObjectScaled \ Otherwise bit 0 of objectFlags is set, so ADDNE R0, R11, #xVertex \ calculate the dot product of the vectors BLNE GetDotProduct \ at xObjectScaled and xVertex, storing the \ result in R3 \ \ The sign of the dot product depends on the \ angle between the two vectors, so R3 is: \ \ * Negative if angle < 90 degrees \ \ * Positive if angle >= 90 degrees \ \ The vector at xObjectScaled is the vector \ from the camera to the object, so we can \ see faces with normals that are less than \ 90 degrees off this vector, as they point \ towards the camera, while hidden faces \ point away from the camera at angles of \ more than 90 degrees \ \ So if R3 is positive, the face is facing \ away from us and is not visible, and if \ it's negative, the opposite is true STMFD R13!, {R8-R9, R11-R12} \ Store the registers that we want to use on \ the stack so they can be retrieved at \ dobj5 after the face has been processedName: DrawObject (Part 3 of 5) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate the visibility of each of the object's faces Deep dive: Drawing 3D objects Object blueprintsContext: See this subroutine on its own page References: No direct references to this subroutine in this source fileCMP R1, #0 \ If R1 is positive, then the y-coordinate BPL dobj4 \ of the rotated normal vector is positive, \ which means it is pointing down, so jump \ to dobj4 to skip drawing the shadow for \ this face, as we only draw shadows for \ faces that point up, like the roof of the \ gazebo LDRB R14, [R11, #objectFlags] \ If bit 1 of objectFlags is zero then this TST R14, #%00000010 \ object is configured not to have a shadow, BEQ dobj4 \ so jump to dobj4 to skip the following \ We now draw the object's shadow into the \ graphics buffers STMFD R13!, {R3, R9, R11} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R9, {R5-R7} \ Set R5, R6, R7 to the fourth, fifth and \ sixth words of the face data, which \ contain the numbers of the vertices that \ make up the face MOV R8, #0 \ Set R8 = 0, which we will pass to the \ DrawTriangleShadowToBuffer routine as the \ face colour, so the shadow is drawn in \ black ADD R10, R11, #vertexProjected \ Set R10 to the address of the third word ADD R10, R10, #8 \ in vertexProjected \ \ For each vertex we wrote two coordinates \ to vertexProjected, with the second being \ the shadow's projected coordinates, so \ this sets R10 to the address of the second \ projected coordinate's pixel x-coordinate, \ i.e. the pixel coordinate for the shadow \ of the first object vertex ADD R5, R10, R5, LSL #4 \ Set (R0, R1) to the two words at offset LDMIA R5, {R0-R1} \ R5 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R5, i.e. the first vertex \ in this face \ \ We multiply the vertex number by 16 as \ there are four words per vertex in \ vertexProjected, which is 16 bytes per \ face ADD R6, R10, R6, LSL #4 \ Set (R2, R3) to the two words at offset LDMIA R6, {R2-R3} \ R6 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R6, i.e. the second \ vertex in this face ADD R7, R10, R7, LSL #4 \ Set (R4, R5) to the two words at offset LDMIA R7, {R4-R5} \ R7 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R7, i.e. the third vertex \ in this face BL DrawTriangleShadowToBuffer \ Draw the triangle that shows the shadow \ for this face, which uses the vertices of \ the face, dropped directly down onto the \ landscape, all drawn in black LDMFD R13!, {R3, R9, R11} \ Retrieve the registers that we stored on \ the stackName: DrawObject (Part 4 of 5) [Show more] Type: Subroutine Category: 3D objects Summary: Draw the shadow for each of the object's faces Deep dive: Drawing 3D objects Object blueprintsContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.dobj4 CMP R3, #0 \ If R3 is positive then the face is facing BPL dobj5 \ away from us and isn't visible, so jump to \ dobj5 to skip drawing the face \ We now calculate the colour of the face, \ setting the brightness according to the \ direction that the face is pointing (the \ light source is directly above and \ slightly to the left) LDMIA R9, {R4-R7} \ Set R4, R5, R6, R7 to the fourth, fifth, \ sixth and seventh words of face data, \ which contain the numbers of the vertices \ that make up the face (in R4, R5 and R6), \ and the face colour (in R7] ADD R10, R11, #vertexProjected \ Set R10 to the address of vertexProjected \ \ For each vertex we wrote two coordinates \ to vertexProjected, with the first being \ the face's projected coordinates, so this \ sets R10 to the address of the first \ projected coordinate's pixel x-coordinate, \ i.e. the pixel coordinate for the first \ object vertex LDR R1, [R11, #yVertex] \ Set R1 to yVertex, the y-coordinate of the \ rotated normal LDR R0, [R11, #xVertex] \ Set R0 to xVertex, the x-coordinate of the \ rotated normal RSB R1, R1, #&80000000 \ Set R1 = (&80000000 - R1) >> 28 MOV R1, R1, LSR #28 \ = (&80000000 - yVertex) >> 28 \ \ So R1 is in the range 0 to 8 and is \ bigger when the normal is pointing more \ vertically (i.e. when the normal vector \ has a negative y-coordinate with a larger \ magnitude, as the y-axis points down the \ screen) \ \ So a bigger, more negative R1 means \ brighter colours CMP R0, #0 \ If R0 is negative then the normal is ADDMI R1, R1, #1 \ pointing more towards the left then the \ right, so increment R1 to make the face \ slightly brighter (as the light source is \ a little bit to the left) SUBS R1, R1, #5 \ Set R1 = max(0, R1 - 5) MOVMI R1, #0 \ \ So R1 is now between 0 and 3, and can be \ added to the colour numbers below to add \ the correct level of brightness MOV R8, #%00001111 \ Set R0 = bits 8-11 of the colour in R7, so AND R0, R8, R7, LSR #8 \ if the face colour is &rgb, this is &r AND R2, R8, R7, LSR #4 \ Set R2 = bits 4-7 of the colour in R7, so \ if the face colour is &rgb, this is &g AND R7, R7, R8 \ Set R7 = bits 0-3 of the colour in R7, so \ if the face colour is &rgb, this is &b ADD R0, R0, R1 \ Set R0 = R0 + R1 and clip to a maximum CMP R0, #16 \ value of 15, so this adds the face MOVHS R0, #15 \ brightness to the red channel and ensures \ the result fits into four bits ADD R2, R2, R1 \ Set R2 = R2 + R1 and clip to a maximum CMP R2, #16 \ value of %00001111, so this adds the face MOVHS R2, #15 \ brightness to the green channel and \ ensures the result fits into four bits ADD R7, R7, R1 \ Set R7 = R7 + R1 and clip to a maximum CMP R7, #16 \ value of %00001111, so this adds the face MOVHS R7, #15 \ brightness to the blue channel and ensures \ the result fits into four bits \ We now build a VIDC colour number in R8 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R8 from \ the red, green and blue values in R0, R2 \ and R7 ORR R8, R2, R7 \ Set R8 to the bottom three bits of: AND R8, R8, #%00000011 \ ORR R8, R8, R0 \ (the bottom two bits of R2 OR R7) OR R0 AND R8, R8, #%00000111 \ \ So this sets bits 0, 1 and 2 of R8 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R8, R8, #%00010000 \ set bit 4 of R8 AND R2, R2, #%00001100 \ Clear all bits of the green channel in R2 \ except bits 2-3 ORR R8, R8, R2, LSL #3 \ And stick them into bits 5-6 of R8 TST R7, #%00000100 \ If bit 2 of the blue channel in R7 is set, ORRNE R8, R8, #%00001000 \ set bit 3 of R8 TST R7, #%00001000 \ If bit 3 of the blue channel in R7 is set, ORRNE R8, R8, #%10000000 \ set bit 7 of R8 ORR R8, R8, R8, LSL #8 \ Duplicate the lower byte of R8 into the ORR R8, R8, R8, LSL #16 \ other three bytes in the word to produce ORR R8, R8, R8, LSL #24 \ a four-pixel colour word containing four \ pixels of this colour \ Finally we can draw the face, using the \ colour in R8 ADD R4, R10, R4, LSL #4 \ Set (R0, R1) to the two words at offset LDMIA R4, {R0-R1} \ R4 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R4, i.e. the first vertex in this face \ \ We multiply the vertex number by 16 as \ there are four words per vertex in \ vertexProjected, which is 16 bytes per \ face ADD R5, R10, R5, LSL #4 \ Set (R2, R3) to the two words at offset LDMIA R5, {R2-R3} \ R5 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R5, i.e. the second vertex in this face ADD R6, R10, R6, LSL #4 \ Set (R4, R5) to the two words at offset LDMIA R6, {R4-R5} \ R6 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R6, i.e. the third vertex in this face BL DrawTriangleToBuffer \ Draw the triangle for this face using the \ projected vertices for the face, drawn in \ the colour in R8, and draw it one buffer \ nearer the camera than the shadow, so the \ shadow never overlaps the object .dobj5 LDMFD R13!, {R8-R9, R11-R12} \ Retrieve the registers that we stored on \ the stack before processing the face ADD R9, R9, #16 \ Add 16 to R9 so it points to the next \ batch of face data (R9 already points to \ the fourth word in the current face data, \ so this skips the next four, thereby \ skipping all seven words of face data) SUBS R8, R8, #1 \ Decrement the loop counter, which keeps \ track of the number of faces we have \ processed BNE dobj3 \ Loop back to dobj3 to move on to the next \ face, until we have processed them all LDMFD R13!, {R6-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: DrawObject (Part 5 of 5) [Show more] Type: Subroutine Category: 3D objects Summary: Draw each of the object's faces Deep dive: Drawing 3D objects Object blueprints Screen memory in the ArchimedesContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.PrintCurrentScore LDR R0, [R11, #currentScore] \ Set R0 to the current score ADD R1, R11, #stringBuffer \ Set R1 = stringBuffer to use as a string \ buffer for the following call MOV R2, #19 \ Set R2 = 19, to use as the size of the \ string buffer in the following call CMP R0, #1024 \ If R0 >= 1024, set gravity = &50000, so MOVHS R4, #&50000 \ gravity increases from the starting value STRHS R4, [R11, #gravity] \ of &30000 when we reach a score of 1024 CMP R0, #1488 \ If R0 >= 1488, set gravity = &70000, so MOVHS R4, #&70000 \ gravity increases again when we reach a STRHS R4, [R11, #gravity] \ score of 1488 SWI OS_BinaryToDecimal \ Convert the unsigned number in R0 to a \ string and store it in the buffer at R1 \ with a maximum string size of R2 MOV R0, #30 \ Print a VDU 30 command to move the text SWI OS_WriteC \ cursor to the top-left corner of the \ screen MOV R0, #&0A \ Print a line feed (ASCII &0A) to move the SWI OS_WriteC \ cursor down one line, to the start of the \ second line, which is where we print the \ score bar \ We now print the contents of the string \ buffer, which updates the number of \ remaining bullets on-screen .prsc1 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsc1 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV R0, #&20 \ Print two spaces (ASCII &20) to ensure we SWI OS_WriteC \ remove any characters from the previous SWI OS_WriteC \ bullet count MOV PC, R14 \ Return from the subroutineName: PrintCurrentScore [Show more] Type: Subroutine Category: Score bar Summary: Print the current score at the left end of the score barContext: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls PrintCurrentScore * MainLoop calls PrintCurrentScore.PrintScoreInBothBanks STMFD R13!, {R1-R2} \ Store the arguments in R1 and R2 on the \ stack so can retrieve them below ADD R1, R11, #stringBuffer \ Set R1 = stringBuffer to use as a string \ buffer for the following call MOV R2, #19 \ Set R2 = 19, to use as the size of the \ string buffer in the following call SWI OS_BinaryToDecimal \ Convert the unsigned number in R0 to a \ string and store it in the buffer at R1 \ with a maximum string size of R2 MOV R0, #31 \ Start printing the following VDU command: SWI OS_WriteC \ \ VDU 31, x, y \ \ which moves the text cursor to column x \ on row y LDMFD R13!, {R3-R4} \ Fetch R1 and R2 into R3 and R4 and write MOV R0, R3 \ them to complete the VDU 31 command, so SWI OS_WriteC \ this moves the text cursor to the position MOV R0, R4 \ defined in the subroutine arguments, SWI OS_WriteC \ leaving the text coordinates in (R3, R4) STMFD R13!, {R1-R2} \ Store R1 and R2 on the stack so we can \ preserve them through the following \ OS_Byte call MOV R0, #112 \ Set the VDU driver screen bank to bank 1 MOV R1, #1 SWI OS_Byte LDMFD R13, {R1-R2} \ Retrieve R1 and R2 from the stack, so R1 \ points to the string buffer and R2 is the \ buffer size \ \ Note that we don't update the stack \ pointer, so we can retrieve them again \ below .prsb1 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsb1 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV R0, #112 \ Set the hardware and VDU screen banks to MOV R1, #2 \ screen bank 1 SWI OS_Byte MOV R0, #31 \ Print the following VDU command: SWI OS_WriteC \ MOV R0, R3 \ VDU 31, R3, R4 SWI OS_WriteC \ MOV R0, R4 \ which moves the text cursor to column R3 SWI OS_WriteC \ on row R4, i.e. the same text coordinates \ as the text we printed in screen bank 1 \ above LDMFD R13!, {R1-R2} \ Retrieve R1 and R2 from the stack, so R1 \ points to the string buffer and R2 is the \ buffer size .prsb2 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsb2 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV PC, R14 \ Return from the subroutineName: PrintScoreInBothBanks [Show more] Type: Subroutine Category: Score bar Summary: Print a number at a specified text column in the score barContext: See this subroutine on its own page References: This subroutine is called as follows: * PlacePlayerOnLaunchpad calls PrintScoreInBothBanks * StartNewGame calls PrintScoreInBothBanks
Arguments: R0 The unsigned number to print R1 Text column (x-coordinate) R2 Text row (y-coordinate).fuelBarColour EQUD &37373737Name: fuelBarColour [Show more] Type: Variable Category: Score bar Summary: A four-pixel colour word for the colour of the fuel barContext: See this variable on its own page References: This variable is used as follows: * DrawFuelLevel uses fuelBarColour.sinTableAddr EQUD sinTableName: sinTableAddr [Show more] Type: Variable Category: Maths (Geometry) Summary: The address of the sine/cosine lookup tableContext: See this variable on its own page References: No direct references to this variable in this source file.arctanTableAddr EQUD arctanTableName: arctanTableAddr [Show more] Type: Variable Category: Maths (Geometry) Summary: The address of the arctan lookup table Deep dive: Flying by mouseContext: See this variable on its own page References: This variable is used as follows: * GetMouseInPolarCoordinates (Part 1 of 2) uses arctanTableAddr.squareRootTableAddr EQUD squareRootTableName: squareRootTableAddr [Show more] Type: Variable Category: Maths (Arithmetic) Summary: The address of the square root lookup table Deep dive: Flying by mouseContext: See this variable on its own page References: This variable is used as follows: * GetMouseInPolarCoordinates (Part 2 of 2) uses squareRootTableAddr.DrawFuelLevel STMFD R13!, {R10-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R1, [R11, #fuelLevel] \ Set R1 to the current fuel level in \ fuelLevel LDRB R2, [R11, #fuelBurnRate] \ Set R2 to the current fuel burn rate in \ fuelBurnRate SUBS R1, R1, R2 \ Subtract the fuel burn rate from the fuel MOVMI R1, #0 \ level, making sure it doesn't fall below STR R1, [R11, #fuelLevel] \ zero, and store the updated fuel level in \ fuelLevel (and leaving the value in R1) LDR R7, screenAddr \ Set R7 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen ADD R11, R7, #320 \ Set R11 to the address in R7, plus 320 to \ move it down by one pixel line LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the top pixel row of the bar ADD R11, R7, #2*320 \ Set R11 to the address in R7, plus 640 to \ move it down by two pixel lines LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the middle pixel row of the bar ADD R11, R7, #3*320 \ Set R11 to the address in R7, plus 960 to \ move it down by two pixel lines LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the bottom pixel row of the bar LDMFD R13!, {R10-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: DrawFuelLevel [Show more] Type: Subroutine Category: Score bar Summary: Draw the bar at the top of the screen showing the current fuel levelContext: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop calls DrawFuelLevel.graphicsBufferEndAddr EQUD graphicsBuffersEndName: graphicsBufferEndAddr [Show more] Type: Variable Category: Graphics buffers Summary: The address of the table containing the end addresses of the graphics buffers Deep dive: Depth-sorting with the graphics buffersContext: See this variable on its own page References: This variable is used as follows: * AddTerminatorsToBuffers uses graphicsBufferEndAddr * DrawGraphicsBuffer uses graphicsBufferEndAddr * DrawObject (Part 1 of 5) uses graphicsBufferEndAddr.graphicsBufferAddr EQUD graphicsBuffersName: graphicsBufferAddr [Show more] Type: Variable Category: Graphics buffers Summary: The address of the table containing the addresses of the graphics buffers Deep dive: Depth-sorting with the graphics buffersContext: See this variable on its own page References: This variable is used as follows: * AddTerminatorsToBuffers uses graphicsBufferAddr.MultiplyVectorByMatrix LDRB R2, [R11, #objectFlags] \ If bit 0 of objectFlags is clear then this TST R2, #%00000001 \ object is static and does not rotate, so LDMEQIA R0, {R2-R4} \ simply set the following so we do not STMEQIA R1, {R2-R4} \ apply the rotation matrix: MOVEQ PC, R14 \ \ [ R1 ] [ R0 ] \ [ R1+4 ] = [ R0+4 ] \ [ R1+8 ] [ R0+8 ] \ \ and return from the subroutine STMFD R13!, {R6-R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R2, R11, #rotationMatrix \ Set R2 to the address of the rotation \ matrix at rotationMatrix, which contains \ the object's rotation matrix BL GetDotProduct \ Set R1 = R2 . R0, so: STR R3, [R1] \ BL GetDotProduct \ [R1 ] [ R2 R2+4 R2+8 ] [R0 ] STR R3, [R1, #4] \ [R1+4] = [ R2+12 R2+16 R2+20 ] . [R0+4] BL GetDotProduct \ [R1+8] [ R2+24 R2+28 R2+32 ] [R0+8] STR R3, [R1, #8] \ \ which is the result we want LDMFD R13!, {R6-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: MultiplyVectorByMatrix [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Multiply a 3D vector by the rotation matrix in rotationMatrix, if the object is a rotating objectContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject (Part 2 of 5) calls MultiplyVectorByMatrix * DrawObject (Part 3 of 5) calls MultiplyVectorByMatrix
If the object currently being processed is static (i.e. bit 0 of objectFlags is clear), then this routine simply returns the vector in R0 unchanged. If the object is a rotating object, then this routine multiplies the vector at R0 by the rotation matrix at rotationMatrix and store the result at the address in R1: [ R1 ] [ R0 ] [ R1+4 ] = rotationMatrix . [ R0+4 ] [ R1+8 ] [ R0+8 ] [ xNoseV xRoofV xSideV ] [ R0 ] = [ yNoseV yRoofV ySideV ] . [ R0+4 ] [ zNoseV zRoofV zSideV ] [ R0+8 ] So this rotates a coordinate by the object's rotation matrix (i.e. by each of the object's orientation vectors).
Arguments: R0 The address of the vector to multiply R1 The address to store the result
Returns: [R1 R1+4 R1+8] The result of the multiplication as a vector.GetDotProduct LDR R4, [R2], #4 \ Set R4 to the x-coordinate of R2 (let's \ call it xR2), and increment R2 to point to \ the y-coordinate of R2 LDR R5, [R0] \ Set R5 to the x-coordinate of R0 (let's \ call it xR0) \ We now calculate R3 = R4 * R5 using the \ shift-and-add multiplication algorithm EOR R7, R4, R5 \ Set the sign of the result in R7 TEQ R4, #0 \ Set R4 = 4 * |R4| RSBMI R4, R4, #0 MOVS R4, R4, LSL #2 TEQ R5, #0 \ Set R5 = 2 * |R5| RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 \ Zero all but the top byte of R4, ensuring ORR R4, R4, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R3, #0 \ Set R3 = 0 to use for building the sum in \ our shift-and-add multiplication result .dotp1 MOV R5, R5, LSR #1 \ If bit 0 of R5 is set, add R5 to the ADDCS R3, R3, R5 \ result in R3, shifting R5 to the right MOVS R4, R4, LSL #1 \ Shift R4 left by one place BNE dotp1 \ Loop back if R4 is non-zero MOV R3, R3, LSR #1 \ Set R3 = R3 / 2 TEQ R7, #0 \ Apply the sign from R7 to R3 to get the RSBMI R3, R3, #0 \ final result, so: \ \ R3 = R4 * R5 \ = xR0 * xR2 LDR R4, [R2], #4 \ Set R4 to the y-coordinate at R2+4 (let's \ call it yR2), and increment R2 to point to \ the z-coordinate of R2 LDR R5, [R0, #4] \ Set R5 to the y-coordinate at R0+4 (let's \ call it yR0) EOR R7, R4, R5 \ Set R6 = R4 * R5 using the same algorithm TEQ R4, #0 \ as above RSBMI R4, R4, #0 \ MOVS R4, R4, LSL #2 \ This is the first part of the algorithm TEQ R5, #0 RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 ORR R4, R4, #&01000000 MOV R6, #0 .dotp2 MOV R5, R5, LSR #1 \ This is the second part of the algorithm, ADDHS R6, R6, R5 \ so we now have: MOVS R4, R4, LSL #1 \ BNE dotp2 \ R6 = R4 * R5 MOV R6, R6, LSR #1 \ = yR0 * yR2 TEQ R7, #0 RSBMI R6, R6, #0 ADD R3, R3, R6 \ Set R3 = R3 + R6 \ = xR0 * xR2 + yR0 * yR2 LDR R4, [R2], #4 \ Set R4 to the z-coordinate at R2+8 (let's \ call it zR2), and increment R2 to point to \ the next coordinate at R2+12 LDR R5, [R0, #8] \ Set R5 to the z-coordinate at R0+8 (let's \ call it zR0) EOR R7, R4, R5 \ Set R6 = R4 * R5 using the same algorithm TEQ R4, #0 \ as above RSBMI R4, R4, #0 \ MOVS R4, R4, LSL #2 \ This is the first part of the algorithm TEQ R5, #0 RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 ORR R4, R4, #&01000000 MOV R6, #0 .dotp3 MOV R5, R5, LSR #1 \ This is the second part of the algorithm, ADDHS R6, R6, R5 \ so we now have: MOVS R4, R4, LSL #1 \ BNE dotp3 \ R6 = R4 * R5 MOV R6, R6, LSR #1 \ = zR0 * zR2 TEQ R7, #0 RSBMI R6, R6, #0 ADDS R3, R3, R6 \ Set R3 = R3 + R6 \ = xR0 * xR2 + yR0 * yR2 + zR0 * zR2 \ \ which is the result we want \ \ We also set the flags depending on the \ result MOV PC, R14 \ Return from the subroutineName: GetDotProduct [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the dot product of two 3D vectors Deep dive: Drawing 3D objectsContext: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject (Part 3 of 5) calls GetDotProduct * MultiplyVectorByMatrix calls GetDotProduct
Calculate the dot product of the 3D vectors at R0 and R2 as follows: [ R0 ] [ R2 ] R3 = [ R0+4 ] . [ R2+4 ] = (R0 * R2) + (R0+4 * R2+4) + (R0+8 * R2*8) [ R0+8 ] [ R2+8 ] and set the flags according to the result. R2 is updated to the next vector in memory, so repeated calls will work through the following multiplication, returning one row at a time: [ R2 R2+4 R2+8 ] [ R0 ] [ R2+12 R2+16 R2+20 ] . [ R0+4 ] [ R2+24 R2+28 R2+32 ] [ R0+8 ] See the MultiplyVectorByMatrix routine to see this calculation in use.
Arguments: R0 The address of the first vector R2 The address of the second vector
Returns: R2 Updated to the address of the next R2 vector R3 The dot product Flags Set according to the result in R3.TransposeRotationMatrix ADD R2, R11, #rotationMatrix \ Set R2 to the address of the rotation \ matrix LDR R0, [R2, #4] \ Transpose it by swapping coordinates on LDR R1, [R2, #12] \ either side of the diagonal from the STR R0, [R2, #12] \ top-left to bottom-right STR R1, [R2, #4] \ LDR R0, [R2, #8] \ [ x1 x2 x3 ] [ x1 y1 z1 ] LDR R1, [R2, #24] \ [ y1 y2 y3 ] -> [ x2 y2 z2 ] STR R0, [R2, #24] \ [ z1 z2 z3 ] [ x3 y3 z3 ] STR R1, [R2, #8] \ LDR R0, [R2, #20] \ This converts the rotation matrix into the LDR R1, [R2, #28] \ inverse rotation STR R0, [R2, #28] STR R1, [R2, #20] MOV PC, R14 \ Return from the subroutineName: TransposeRotationMatrix [Show more] Type: Subroutine Category: Maths (Geometry) Summary: An unused routine that transposes the rotation matrix Deep dive: Unused code in LanderContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.AddVectorsWithFeedback STMFD R13!, {R5-R6, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R0, {R2-R4} \ Fetch the coordinate into (R2, R3, R4) LDMIA R1, {R5-R6, R14} \ Fetch the delta vector into (R5, R6, R14) ADD R2, R2, R5, ASR #4 \ Set R2 = R2 + R5 / 16 \ \ So this adds a small amount of the x-delta \ to the x-coordinate SUB R5, R5, R2, ASR #4 \ Set R5 = R5 - R2 / 16 \ \ So this updates the x-delta with feedback \ from the x-coordinate ADD R3, R3, R6, ASR #4 \ Set R3 = R3 + R6 / 16 \ \ So this adds a small amount of the y-delta \ to the y-coordinate SUB R6, R6, R3, ASR #4 \ Set R6 = R6 - R3 / 16 \ \ So this updates the y-delta with feedback \ from the y-coordinate ADD R4, R4, R14, ASR #4 \ Set R4 = R4 + R14 / 16 \ \ So this adds a small amount of the z-delta \ to the z-coordinate SUB R14, R14, R4, ASR #4 \ Set R14 = R14 - R4 / 16 \ \ So this updates the z-delta with feedback \ from the z-coordinate STMIA R0, {R2-R4} \ Store the updated coordinate STMIA R1, {R5-R6, R14} \ Store the updated delta vector LDMFD R13!, {R5-R6, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutineName: AddVectorsWithFeedback [Show more] Type: Subroutine Category: Maths (Geometry) Summary: An unused routine that adds a delta vector to a coordinate and updates the delta with feedback from the coordinate value Deep dive: Unused code in LanderContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.CalculateRotationMatrix STMFD R13!, {R0-R1, R14} \ Store the arguments and the return address \ on the stack ADD R0, R0, #&40000000 \ Set R0 = R0 + &40000000 \ = a + &40000000 \ \ As we shift R0 to the right by 20 places \ in the sine lookup below, this is the same \ as adding a value of &40000000 >> 20 to \ the index \ \ &40000000 >> 20 = 1024, so when we add \ this to the index, it skips 256 four-byte \ words compared to looking up R0 without \ the addition \ \ The sine table contains 1024 entries that \ cover the whole circle, so adding 256 to \ the index changes the lookup from sine to \ cosine, as it is effectively adding an \ extra 90-degrees to the lookup index LDR R14, sinTableAddr \ Set R14 to the address of the sine lookup \ table BIC R0, R0, #&00300000 \ Clear bits 21 and 22 of R0 so when we \ shift R0 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R2, [R14, R0, LSR #20] \ Set \ \ R2 = (2^31 - 1) \ * SIN(2 * PI * ((R0 >> 20) / 1024)) \ \ So R2 = sin(a + &40000000) \ = cos(a) ADD R1, R1, #&40000000 \ Using the same approach as above, set: LDR R14, sinTableAddr \ BIC R1, R1, #&00300000 \ R3 = sin(b + &40000000) LDR R3, [R14, R1, LSR #20] \ = cos(b) LDMFD R13!, {R4-R5} \ Set R4 and R5 to the original arguments in \ R0 and R1, so R4 = a and R5 = b LDR R14, sinTableAddr \ Using the same approach as above, set: BIC R4, R4, #&00300000 \ LDR R0, [R14, R4, LSR #20] \ R0 = sin(R4) \ = sin(a) LDR R14, sinTableAddr \ Using the same approach as above, set: BIC R5, R5, #&00300000 \ LDR R1, [R14, R5, LSR #20] \ R1 = sin(R4) \ = sin(b) STMFD R13!, {R0-R3} \ Store R0 to R3 on the stack, so the stack \ contains: \ \ R0 = sin(a) \ R1 = sin(b) \ R2 = cos(a) \ R3 = cos(b) \ We now calculate R4 = R2 * R3 using the \ shift-and-add multiplication algorithm EOR R14, R2, R3 \ Set the sign of the result in R14 TEQ R2, #0 \ Set R2 = 4 * |R2| RSBMI R2, R2, #0 MOVS R2, R2, LSL #2 TEQ R3, #0 \ Set R3 = 2 * |R3| RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R2, R2, #&FE000000 \ Zero all but the top byte of R2, ensuring ORR R2, R2, #&01000000 \ that bit 0 of the top byte is set so the \ value of R2 is non-zero MOV R4, #0 \ Set R4 = 0 to use for building the sum in \ our shift-and-add multiplication result .rmat1 MOV R3, R3, LSR #1 \ If bit 0 of R3 is set, add R3 to the ADDHS R4, R4, R3 \ result in R4, shifting R3 to the right MOVS R2, R2, LSL #1 \ Shift R2 left by one place BNE rmat1 \ Loop back if R4 is non-zero MOV R4, R4, LSR #1 \ Set R4 = R4 / 2 TEQ R14, #0 \ Apply the sign from R14 to R4 to get the RSBMI R4, R4, #0 \ final result, so: \ \ R4 = R2 * R3 \ = cos(a) * cos(b) STR R4, [R11, #xNoseV] \ Store the result in xNoseV, so: \ \ xNoseV = cos(a) * cos(b) EOR R14, R0, R1 \ Set R4 = R0 * R1 using the same algorithm TEQ R0, #0 \ as above RSBMI R0, R0, #0 \ MOVS R0, R0, LSL #2 \ This is the first part of the algorithm TEQ R1, #0 RSBMI R1, R1, #0 MOV R1, R1, LSL #1 AND R0, R0, #&FE000000 ORR R0, R0, #&01000000 MOV R4, #0 .rmat2 MOV R1, R1, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R1 \ so we now have: MOVS R0, R0, LSL #1 \ BNE rmat2 \ R4 = R0 * R1 MOV R4, R4, LSR #1 \ = sin(a) * sin(b) TEQ R14, #0 RSBMI R4, R4, #0 STR R4, [R11, #zRoofV] \ Store the result in zRoofV, so: \ \ zRoofV = sin(a) * sin(b) LDMFD R13, {R0-R3} \ Retrieve R0 to R3 from the stack, leaving \ the stack pointer alone so we can repeat \ the process later, so R0 to R3 once again \ contain the following: \ \ R0 = sin(a) \ R1 = sin(b) \ R2 = cos(a) \ R3 = cos(b) EOR R14, R1, R2 \ Set R4 = R1 * R2 using the same algorithm TEQ R1, #0 \ as above RSBMI R1, R1, #0 \ MOVS R1, R1, LSL #2 \ This is the first part of the algorithm TEQ R2, #0 RSBMI R2, R2, #0 MOV R2, R2, LSL #1 AND R1, R1, #&FE000000 ORR R1, R1, #&01000000 MOV R4, #0 .rmat3 MOV R2, R2, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R2 \ so we now have: MOVS R1, R1, LSL #1 \ BNE rmat3 \ R4 = R1 * R2 MOV R4, R4, LSR #1 \ = sin(b) * cos(a) TEQ R14, #0 \ = cos(a) * sin(b) RSBMI R4, R4, #0 RSB R4, R4, #0 \ Negate the result and store it in zNoseV, STR R4, [R11, #zNoseV] \ so: \ \ zNoseV = -cos(a) * sin(b) EOR R14, R0, R3 \ Set R4 = R0 * R3 using the same algorithm TEQ R0, #0 \ as above RSBMI R0, R0, #0 \ MOVS R0, R0, LSL #2 \ This is the first part of the algorithm TEQ R3, #0 RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R0, R0, #&FE000000 ORR R0, R0, #&01000000 MOV R4, #0 .rmat4 MOV R3, R3, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R3 \ so we now have: MOVS R0, R0, LSL #1 \ BNE rmat4 \ R4 = R0 * R3 MOV R4, R4, LSR #1 \ = sin(a) * cos(b) TEQ R14, #0 RSBMI R4, R4, #0 RSB R4, R4, #0 \ Negate the result and store it in xRoofV, STR R4, [R11, #xRoofV] \ so: \ \ xRoofV = -sin(a) * cos(b) LDMFD R13!, {R0-R3, R14} \ Retrieve R0 to R3 from the stack, so R0 to \ R3 once again contain the following: \ \ R0 = sin(a) \ R1 = sin(b) \ R2 = cos(a) \ R3 = cos(b) \ \ We also retrieve the subroutine return \ address into R14 STR R0, [R11, #yNoseV] \ Set yNoseV = sin(a) STR R1, [R11, #xSideV] \ Set xSideV = sin(b) STR R2, [R11, #yRoofV] \ Set yRoofV = cos(a) STR R3, [R11, #zSideV] \ Set zSideV = cos(b) MOV R0, #0 \ Set ySideV = 0 STR R0, [R11, #ySideV] MOV PC, R14 \ Return from the subroutineName: CalculateRotationMatrix [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the rotation matrix Deep dive: Flying by mouseContext: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls CalculateRotationMatrix * MainLoop calls CalculateRotationMatrix * MoveAndDrawPlayer (Part 1 of 5) calls CalculateRotationMatrix
The rotation matrix is of the form: [ xNoseV xRoofV xSideV ] [ yNoseV yRoofV ySideV ] [ zNoseV zRoofV zSideV ] where the nose, roof and side vectors are the orientation vectors. This routine sets the rotation matrix to the following: [ cos(a) * cos(b) -sin(a) * cos(b) sin(b) ] [ sin(a) cos(a) 0 ] [ -cos(a) * sin(b) sin(a) * sin(b) cos(b) ] This matrix represents a combination of two rotations, one with angle a and the other with angle b.
Arguments: R0 The first rotation angle, a R1 The second rotation angle, b.GetMouseInPolarCoordinates \ In the following, let's call the mouse \ x- and y-coordinates xMouse and yMouse STMFD R13!, {R14} \ Store the return address on the stack MOV R3, #0 \ Set R3 = 0 CMP R0, #0 \ If the mouse x-coordinate in R0 is EORMI R3, R3, #%00000011 \ negative, negate R0 to get |xMouse| and RSBMI R0, R0, #0 \ flip bits 0 and 1 of R3 CMP R1, #0 \ If the mouse y-coordinate in R1 is EORMI R3, R3, #%00000111 \ negative, negate R1 to get |yMouse| and RSBMI R1, R1, #0 \ flip bits 0, 1 and 2 of R3 STMFD R13!, {R0-R1} \ Store |xMouse| and |yMouse| on the stack \ We now divide one coordinate by the other, \ with the smaller value as the numerator, \ so the result is between 0 and 1 \ \ We do this so we can take the arctan of \ the result to calculate the angle in our \ polar coordinate CMP R0, R1 \ If |xMouse| < |yMouse|, flip bit 0 of R3 EORLO R3, R3, #%00000001 \ so we can make sure the angle is in the \ correct quadrant later BHS pole2 \ If |xMouse| >= |yMouse|, jump to pole2 to \ calculate the division with |yMouse| as \ the numerator \ If we get here then |xMouse| < |yMouse| \ so we calculate the division with |xMouse| \ as the numerator \ We now calculate R2 = R0 / R1 using the \ shift-and-subtract division algorithm MOV R2, #0 \ Set R2 = 0 to contain the result MOV R14, #%10000000 \ Set bit 7 of R14 so we can shift it to the \ right in each iteration, using it as a \ counter .pole1 MOVS R0, R0, LSL #1 \ Shift R0 left, moving the top bit into the \ C flag CMPCC R0, R1 \ If we shifted a 0 out of the top of R0, \ test for a possible subtraction SUBCS R0, R0, R1 \ If we shifted a 1 out of the top of R0 or ORRCS R2, R2, R14 \ R0 >= R1, then do the subtraction: \ \ R0 = R0 - R1 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R14 to the \ result in R2) MOVS R14, R14, LSR #1 \ Shift R14 to the right, moving bit 0 into \ the C flag BCC pole1 \ Loop back until we shift the 1 out of the \ right end of R14 (after eight shifts) MOVS R2, R2, LSL #24 \ Scale up the result, so now we have: \ \ R2 = 2^24 * (|xMouse| / |yMouse|) B pole4 \ Jump to pole4 to skip the following .pole2 \ If we get here then |xMouse| >= |yMouse| \ so we calculate the division with |yMouse| \ as the numerator MOV R2, #0 \ Set R2 = R1 / R0 using the same algorithm MOV R14, #&80 \ as above \ \ This is the first part of the algorithm .pole3 MOVS R1, R1, LSL #1 \ This is the second part of the algorithm, CMPLO R1, R0 \ so now we have: SUBHS R1, R1, R0 \ ORRHS R2, R2, R14 \ R2 = 2^24 * (|yMouse| / |xMouse|) MOVS R14, R14, LSR #1 BLO pole3 MOVS R2, R2, LSL #24 .pole4 \ At this point we have calculated one of \ the following, with the smaller coordinate \ on the top of the division: \ \ R2 = 2^24 * (|xMouse| / |yMouse|) \ \ or: \ \ R2 = 2^24 * (|yMouse| / |xMouse|) \ \ So R2 is in the range 0 to 2^24 LDR R14, arctanTableAddr \ Set R14 to the address of the arctan \ lookup table BIC R2, R2, #&01800000 \ Clear bits 23 and 24 of R2 so when we \ shift R2 to the right by 23 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R1, [R14, R2, LSR #23] \ Look up the arctan to get the following: \ \ R1 = ((2^31 - 1) / PI) * ATAN(R2 / 128) \ \ So R1 contains the smaller of the two \ angles in the triangle formed by xMouse \ and yMouse, like this (in this example, \ xMouse >= yMouse, though the ASCII art \ might not make that obvious): \ \ .| \ .´ | \ .´ | y \ .´R1 | \ +´-------+ \ x \ \ So we now have: \ \ R1 = arctan(|xMouse| / |yMouse|) \ \ or: \ \ R1 = arctan(|yMouse| / |xMouse|) TST R3, #%00000001 \ If bit 0 of R3 is set then: ADDEQ R1, R1, R3, LSL #29 \ ADDNE R3, R3, #1 \ R1 = R1 + R3 << 29 RSBNE R1, R1, R3, LSL #29 \ \ otherwise bit 0 of R3 is clear, so: \ \ R1 = (R3 + 1) << 29 - R1 \ \ So this moves the arctan angle into the \ correct circle quadrant, depending on the \ relative sizes and signs of xMouse and \ yMouse \ We now have the angle for our polar \ coordinate in R1, so next we need to \ calculate the distanceName: GetMouseInPolarCoordinates (Part 1 of 2) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Convert the mouse x- and y-coordinates into polar coordinates, starting by calculating the polar angle Deep dive: Flying by mouseContext: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawPlayer (Part 1 of 5) calls GetMouseInPolarCoordinates
This routine converts mouse coordinates (x, y) into polar coordinates, as in this example: .| R0 .´ | .´ | y .´R1 | +´-------+ x R1 is the angle and R0 is the distance.
Arguments: R0 The scaled-up mouse x-coordinate R1 The scaled-up mouse y-coordinate
Returns: R0 The distance of the polar coordinate R1 The angle of the polar coordinateLDMFD R13!, {R0, R2} \ Retrieve the values we put on the stack \ earlier, so: \ \ R0 = |xMouse| \ \ R2 = |yMouse| \ We now calculate R4 = R0 * R0 using the \ shift-and-add multiplication algorithm, \ which calculates: \ \ R4 = R0 ^ 2 \ = |xMouse| ^ 2 MOV R3, R0 \ Set R3 = 2 * R0 MOVS R3, R3, LSL #1 \ = 2 * |xMouse| \ \ So now we calculate R4 = R3 * R0 to get \ our result AND R3, R3, #&FE000000 \ Zero all but the top byte of R3, ensuring ORR R3, R3, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R4, #0 \ Set R4 = 0 to use for building the sum in \ our shift-and-add multiplication result .pole5 MOV R0, R0, LSR #1 \ If bit 0 of R0 is set, add R0 to the ADDHS R4, R4, R0 \ result in R4, shifting R0 to the right MOVS R3, R3, LSL #1 \ Shift R3 left by one place BNE pole5 \ Loop back if R3 is non-zero \ Next we calculate R14 = R2 * R2 using the \ shift-and-add multiplication algorithm, \ which calculates: \ \ R14 = R2 ^ 2 \ = |yMouse| ^ 2 MOV R3, R2 \ Set R3 = 2 * R2 MOVS R3, R3, LSL #1 \ = 2 * |yMouse| \ \ So now we calculate R14 = R3 * R2 to get \ our result AND R3, R3, #&FE000000 \ Zero all but the top byte of R3, ensuring ORR R3, R3, #&01000000 \ that bit 0 of the top byte is set so the \ value of the top byte is at least 1 MOV R14, #0 \ Set R14 = 0 to use for building the sum in \ our shift-and-add multiplication result .pole6 MOV R2, R2, LSR #1 \ If bit 0 of R2 is set, add R2 to the ADDHS R14, R14, R2 \ result in R14, shifting R2 to the right MOVS R3, R3, LSL #1 \ Shift R3 left by one place BNE pole6 \ Loop back if R3 is non-zero \ So by this point we have the following: \ \ R4 = |xMouse| ^ 2 \ \ R14 = |yMouse| ^ 2 \ \ So now we can apply Pythagoras to find the \ length of the hypotenuse, which is the \ distance in our polar coordinate, as in \ the example where x >= y: \ \ .| \ R0 .´ | \ .´ | y \ .´R1 | \ +´-------+ \ x \ \ We calculate the distance in R0 ADD R2, R14, R4 \ Set R2 = R14 + R4 \ \ = |xMouse| ^ 2 + |yMouse| ^ 2 LDR R14, squareRootTableAddr \ Set R14 to the address of the square root \ lookup table BIC R2, R2, #&00300000 \ Clear bits 20 and 21 of R2 so when we \ shift R2 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R0, [R14, R2, LSR #20] \ Set R0 = SQRT(R2) \ = SQRT(|xMouse| ^ 2 + |yMouse| ^ 2) \ \ which is the length of the hypotenuse and \ the distance in our polar coordinate LDMFD R13!, {PC} \ Return from the subroutine LDMIA R0, {R0-R2} \ This instruction appears to be unusedName: GetMouseInPolarCoordinates (Part 2 of 2) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the polar distance Deep dive: Flying by mouseContext: See this subroutine on its own page References: No direct references to this subroutine in this source file[X]Subroutine AddDebrisParticleToBuffer (category: Particles)Add a debris particle to the particle data buffer, which is a purple-brownish-green particle that bounces out of an explosion[X]Subroutine AddExplosionToBuffer (category: Particles)Create a big explosion of particles and add it to the particle data buffer[X]Subroutine AddRisingParticleToBuffer (category: Particles)Add a particle to the particle data buffer that initially drifts up, with a random element to its velocity and lifespan counter[X]Subroutine AddSmokeParticleToBuffer (category: Particles)Add a smoke particle to the particle data buffer[X]Subroutine AddSparkParticleToBuffer (category: Particles)Add a spark particle to the particle data buffer that fades from white-hot to red over time[X]Subroutine AddStaticParticleToBuffer (category: Particles)Add a particle to the particle data buffer that starts off static, adding a random element to its velocity and lifespan counter[X]Subroutine AddVectorToVertices (category: Maths (Geometry))Add a vector to the rotated vertex coordinates to get the vertex coordinates in 3D space[X]Subroutine DrawHorizontalLine (category: Drawing lines)Draw a horizontal line[X]Subroutine DrawObject (Part 1 of 5) (category: 3D objects)Draw a 3D object and its shadow[X]Subroutine DrawTriangleShadowToBuffer (category: Drawing triangles)Draw a triangle shadow into the correct graphics buffer, according to its distance[X]Subroutine DrawTriangleToBuffer (category: Drawing triangles)Draw a coloured triangle into a slightly nearer graphics buffer, according to its distance[X]Subroutine DropARockFromTheSky (category: Particles)Drop a rock from the specified coordinates by spawning it as a particle, albeit a very big particle with an associated 3D object[X]Subroutine GetDotProduct (category: Maths (Geometry))Calculate the dot product of two 3D vectors[X]Subroutine GetLandscapeAltitude (category: Landscape)Calculate the altitude of the landscape for a given coordinate[X]Subroutine GetLandscapeBelowVertex (category: Landscape)Calculate the landscape altitude directly below an object's vertex[X]Subroutine GetRandomNumbers (category: Maths (Arithmetic))Generate pseudo-random numbers from the random number seeds[X]Configuration variable LANDSCAPE_XThe x-coordinate of the landscape offset, which is set to the whole number of tiles that fit into the half the width of the landscape, so we end up looking at the middle point of the landscape[X]Configuration variable LANDSCAPE_ZThe z-coordinate of the landscape offset, which is set to push the landscape away from the viewer by ten tile sizes[X]Configuration variable MAX_PARTICLESThe maximum number of particles at any one time[X]Subroutine MultiplyVectorByMatrix (category: Maths (Geometry))Multiply a 3D vector by the rotation matrix in rotationMatrix, if the object is a rotating object[X]Configuration variable OS_BinaryToDecimalThe operating system call to convert a number into a string[X]Configuration variable OS_ByteGeneral-purpose operating system calls[X]Configuration variable OS_WriteCThe operating system call to write a character to all the output streams[X]Configuration variable PLAYER_FRONT_ZThe distance along the z-axis between the tile in front of the player and the camera, which is where we spawn explosions and falling rocks (set to six tiles forwards from the camera)[X]Subroutine ProjectVertexOntoScreen (category: Maths (Geometry))Project a vertex coordinate from a 3D object onto the screen[X]Configuration variable ROCK_HEIGHTThe height from which we drop rocks from the sky (32 tile sizes)[X]Configuration variable SEA_LEVELThe altitude of sea level[X]Configuration variable SMOKE_HEIGHTThe height at which smoke particles are added above the bottom of a destroyed object (3/4 of a tile)[X]Configuration variable SMOKE_RISING_SPEEDThe speed at which smoke particles rise up from a destroyed object[X]Configuration variable TILES_XThe number of tile corners in a landscape row from left to right (i.e. 12 tiles)[X]Configuration variable TILES_ZThe number of tile corners in a landscape row from front to back (i.e. 10 tiles)[X]Configuration variable TILE_SIZEThe length of one side of a square landscape tile in 3D coordinates[X]Variable arctanTable (category: Maths (Geometry))Arctan lookup table[X]Variable arctanTableAddr (category: Maths (Geometry))The address of the arctan lookup table[X]Configuration variable crashedFlagIf this is non-zero then the object being processed has crashed into the ground[X]Configuration variable currentScoreOur current score, which is displayed at the left end of the score bar[X]Label dobj1 in subroutine DrawObject (Part 1 of 5)[X]Label dobj2 in subroutine DrawObject (Part 2 of 5)[X]Label dobj3 in subroutine DrawObject (Part 3 of 5)[X]Label dobj4 in subroutine DrawObject (Part 5 of 5)[X]Label dobj5 in subroutine DrawObject (Part 5 of 5)[X]Label dobs1 in subroutine DrawObjects (Part 1 of 3)[X]Label dobs2 in subroutine DrawObjects (Part 1 of 3)[X]Label dobs3 in subroutine DrawObjects (Part 1 of 3)[X]Label dobs4 in subroutine DrawObjects (Part 2 of 3)[X]Label dobs5 in subroutine DrawObjects (Part 2 of 3)[X]Label dobs6 in subroutine DrawObjects (Part 3 of 3)[X]Label dotp1 in subroutine GetDotProduct[X]Label dotp2 in subroutine GetDotProduct[X]Label dotp3 in subroutine GetDotProduct[X]Label expl1 in subroutine AddExplosionToBuffer[X]Variable fuelBarColour (category: Score bar)A four-pixel colour word for the colour of the fuel bar[X]Configuration variable fuelBurnRateThe current fuel burn rate (bit 0 is ignored)[X]Configuration variable fuelLevelThe player's fuel level[X]Variable graphicsBufferEndAddr (category: Graphics buffers)The address of the table containing the end addresses of the graphics buffers[X]Variable graphicsBuffers (category: Graphics buffers)The addresses of each of the graphics buffers (these values do not change)[X]Variable graphicsBuffersEnd (category: Graphics buffers)The end addresses of each of the graphics buffers (these values get updated as objects are drawn into the buffers)[X]Configuration variable gravityThe current setting of gravity (which changes on higher levels)[X]Configuration variable mainLoopCountThe main loop counter[X]Variable objectBuilding (category: 3D objects)Object blueprint for the building[X]Configuration variable objectDataThe address of the blueprint for the object currently being drawn[X]Variable objectFirTree (category: 3D objects)Object blueprint for the fir tree[X]Configuration variable objectFlagsThe flags of the object currently being drawn[X]Variable objectGazebo (category: 3D objects)Object blueprint for the gazebo[X]Configuration variable objectMapThe object map determines which objects appear on the landscape, where objects are trees, buildings, rockets and so on (size of object map is 256 * 256 bytes = &10000)[X]Variable objectPlayer (category: 3D objects)Object blueprint for the player's ship[X]Variable objectPyramid (category: 3D objects)Object blueprint for a pyramid[X]Variable objectRock (category: 3D objects)Object blueprint for a rock[X]Variable objectRocket (category: 3D objects)Object blueprint for the rocket[X]Variable objectSmallLeafyTree (category: 3D objects)Object blueprint for the small leafy tree[X]Variable objectSmokingBuilding (category: 3D objects)Object blueprint for the smoking remains of a building[X]Variable objectSmokingGazebo (category: 3D objects)Object blueprint for the smoking remains of a gazebo[X]Variable objectSmokingRemainsLeft (category: 3D objects)Object blueprint for the smoking remains that bend to the left[X]Variable objectSmokingRemainsRight (category: 3D objects)Object blueprint for the smoking remains that bend to the right[X]Variable objectTallLeafyTree (category: 3D objects)Object blueprint for the tall leafy tree[X]Configuration variable objectTypeThe type of the object currently being drawn[X]Configuration variable particleCountThe number of particles currently on-screen[X]Configuration variable particleDataThe particle data buffer, which stores eight data bytes for each on-screen particle[X]Configuration variable particleEndThe address of the end of the particle data in the particle data buffer[X]Label pole1 in subroutine GetMouseInPolarCoordinates (Part 1 of 2)[X]Label pole2 in subroutine GetMouseInPolarCoordinates (Part 1 of 2)[X]Label pole3 in subroutine GetMouseInPolarCoordinates (Part 1 of 2)[X]Label pole4 in subroutine GetMouseInPolarCoordinates (Part 1 of 2)[X]Label pole5 in subroutine GetMouseInPolarCoordinates (Part 2 of 2)[X]Label pole6 in subroutine GetMouseInPolarCoordinates (Part 2 of 2)[X]Label prsb1 in subroutine PrintScoreInBothBanks[X]Label prsb2 in subroutine PrintScoreInBothBanks[X]Label prsc1 in subroutine PrintCurrentScore[X]Label rmat1 in subroutine CalculateRotationMatrix[X]Label rmat2 in subroutine CalculateRotationMatrix[X]Label rmat3 in subroutine CalculateRotationMatrix[X]Label rmat4 in subroutine CalculateRotationMatrix[X]Configuration variable rotationMatrixThe rotation matrix of the object currently being drawn (i.e. the matrix formed from the object's three orientation vectors)[X]Variable screenAddr (category: Drawing the screen)The screen address for the start of the 17th pixel line in the current bank (i.e. the line just below the two rows of text)[X]Variable sinTable (category: Maths (Geometry))Sine/cosine lookup table[X][X]Label spcl1 in subroutine AddSparkCloudToBuffer[X]Variable squareRootTable (category: Maths (Arithmetic))Square root lookup table[X]Variable squareRootTableAddr (category: Maths (Arithmetic))The address of the square root lookup table[X]Configuration variable stringBufferA string buffer that's used when printing the scores[X]Configuration variable typeOffset in subroutine DrawObjects (Part 2 of 3)Set typeOffset to the offset back to the objectTypes table from the next instruction[X]Configuration variable vertexProjectedStorage for projected vertices[X]Configuration variable xCameraThe 3D x-coordinate of the camera position (though note that the camera position is actually at the back of the on-screen landscape, not the front)[X]Configuration variable xCameraTileThe 3D x-coordinate of the camera, clipped to the nearest tile[X]Configuration variable xCoordThe x-coordinate of the vertex being processed, in the game's 3D coordinate system[X]Configuration variable xNoseVThe x-coordinate of the nose vector[X]Configuration variable xObjectThe x-coordinate of the object currently being drawn[X]Configuration variable xObjectScaledThe x-coordinate of the vertex being processed, scaled as high as possible[X]Configuration variable xRoofVThe x-coordinate of the roof vector[X]Configuration variable xSideVThe x-coordinate of the side vector[X]Configuration variable xVertexThe x-coordinate of the vertex being processed, relative to the object's origin[X]Configuration variable xVertexRotatedThe x-coordinate of the vertex after being rotated by the object's rotation matrix[X]Configuration variable yCameraThe 3D y-coordinate of the camera position (though note that the camera position is actually at the back of the on-screen landscape, not the front)[X]Configuration variable yCoordThe y-coordinate of the vertex being processed, in the game's 3D coordinate system[X]Configuration variable yNoseVThe y-coordinate of the nose vector[X]Configuration variable yRoofVThe y-coordinate of the roof vector[X]Configuration variable ySideVThe y-coordinate of the side vector[X]Configuration variable yVertexThe y-coordinate of the vertex being processed, relative to the object's origin[X]Configuration variable zCameraThe 3D z-coordinate of the camera position (though note that the camera position is actually at the back of the on-screen landscape, not the front, so the camera's z-coordinate is larger than it would be for a more traditional camera position; it is more like the camera's focal point than position, in a sense)[X]Configuration variable zNoseVThe z-coordinate of the nose vector[X]Configuration variable zRoofVThe z-coordinate of the roof vector[X]Configuration variable zSideVThe z-coordinate of the side vector