Skip to navigation


Name: StoreParticleData

Name: 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 clouds
Context: 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
.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 subroutine
Name: InitialiseParticleData [Show more] Type: Subroutine Category: Start and end Summary: Initialise the particle data buffer and associated variables Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * Entry calls InitialiseParticleData
.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 subroutine
Name: 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 Archimedes
Context: 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
.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 call
Name: 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 Archimedes
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddExplosionToBuffer calls AddDebrisParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinates
.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 call
Name: 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 Archimedes
Context: 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
.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 call
Name: 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 time
Context: 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
.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 call
Name: 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 Archimedes
Context: See this subroutine on its own page References: This subroutine is called as follows: * SplashParticleIntoSea calls AddSprayParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinates
.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 call
Name: 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 clouds
Context: 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)
.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 subroutine
Name: 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 Lander
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.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 subroutine
Name: 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 Lander
Context: 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 subroutine
Name: 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 Lander
Context: 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 subroutine
Name: 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 particles
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop calls DropRocksFromTheSky
.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 subroutine
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 blueprints
Context: See this variable on its own page References: No direct references to this variable in this source file
.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: 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 objects
Context: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls DrawObjects * MainLoop calls DrawObjects
.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 subroutine
Name: 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 objects
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.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 \ object
Name: 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 objects
Context: 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 object
Name: objectPlayerAddr [Show more] Type: Variable Category: 3D objects Summary: The address of the object blueprint for the player's ship Deep dive: Object blueprints
Context: See this variable on its own page References: This variable is used as follows: * MoveAndDrawPlayer (Part 3 of 5) uses objectPlayerAddr
.objectPlayerAddr EQUD objectPlayer
Name: objectRockAddr [Show more] Type: Variable Category: 3D objects Summary: The address of the object blueprint for a rock Deep dive: Object blueprints
Context: See this variable on its own page References: This variable is used as follows: * MoveAndDrawParticles (Part 3 of 4) uses objectRockAddr
.objectRockAddr EQUD objectRock
Name: 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 mouse
Context: 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 matrix
.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 faces
Name: 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 bullets
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDR 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 faces
Name: 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 blueprints
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDR 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 processed
Name: 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 blueprints
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
CMP 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 stack
Name: 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 Archimedes
Context: 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 subroutine
Name: PrintCurrentScore [Show more] Type: Subroutine Category: Score bar Summary: Print the current score at the left end of the score bar
Context: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls PrintCurrentScore * MainLoop calls PrintCurrentScore
.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 subroutine
Name: PrintScoreInBothBanks [Show more] Type: Subroutine Category: Score bar Summary: Print a number at a specified text column in the score bar
Context: 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)
.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 subroutine
Name: fuelBarColour [Show more] Type: Variable Category: Score bar Summary: A four-pixel colour word for the colour of the fuel bar
Context: See this variable on its own page References: This variable is used as follows: * DrawFuelLevel uses fuelBarColour
.fuelBarColour EQUD &37373737
Name: sinTableAddr [Show more] Type: Variable Category: Maths (Geometry) Summary: The address of the sine/cosine lookup table
Context: See this variable on its own page References: No direct references to this variable in this source file
.sinTableAddr EQUD sinTable
Name: arctanTableAddr [Show more] Type: Variable Category: Maths (Geometry) Summary: The address of the arctan lookup table Deep dive: Flying by mouse
Context: See this variable on its own page References: This variable is used as follows: * GetMouseInPolarCoordinates (Part 1 of 2) uses arctanTableAddr
.arctanTableAddr EQUD arctanTable
Name: squareRootTableAddr [Show more] Type: Variable Category: Maths (Arithmetic) Summary: The address of the square root lookup table Deep dive: Flying by mouse
Context: See this variable on its own page References: This variable is used as follows: * GetMouseInPolarCoordinates (Part 2 of 2) uses squareRootTableAddr
.squareRootTableAddr EQUD squareRootTable
Name: DrawFuelLevel [Show more] Type: Subroutine Category: Score bar Summary: Draw the bar at the top of the screen showing the current fuel level
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop calls DrawFuelLevel
.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 subroutine
Name: 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 buffers
Context: 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
.graphicsBufferEndAddr EQUD graphicsBuffersEnd
Name: 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 buffers
Context: See this variable on its own page References: This variable is used as follows: * AddTerminatorsToBuffers uses graphicsBufferAddr
.graphicsBufferAddr EQUD graphicsBuffers
Name: 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 object
Context: 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
.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 subroutine
Name: GetDotProduct [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the dot product of two 3D vectors Deep dive: Drawing 3D objects
Context: 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
.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 subroutine
Name: TransposeRotationMatrix [Show more] Type: Subroutine Category: Maths (Geometry) Summary: An unused routine that transposes the rotation matrix Deep dive: Unused code in Lander
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.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 subroutine
Name: 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 Lander
Context: 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 subroutine
Name: CalculateRotationMatrix [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the rotation matrix Deep dive: Flying by mouse
Context: 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
.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 subroutine
Name: 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 mouse
Context: 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 coordinate
.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 distance
Name: GetMouseInPolarCoordinates (Part 2 of 2) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the polar distance Deep dive: Flying by mouse
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDMFD 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 unused