\ ****************************************************************************** \ \ LANDER GAME SOURCE \ \ Lander was written by David Braben and is copyright D.J.Braben 1987 \ \ The code on this site has been reconstructed from a disassembly of the game on \ the Arthur, RISC OS 2 and RISC OS 3.00 application discs \ \ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes \ in the documentation are entirely my fault \ \ The terminology and notations used in this commentary are explained at \ https://lander.bbcelite.com/terminology \ \ The deep dive articles referred to in this commentary can be found at \ https://lander.bbcelite.com/deep_dives \ \ ------------------------------------------------------------------------------ \ \ This source file produces the following binary file: \ \ * GameCode.bin \ \ ****************************************************************************** CODE = &00008000 \ The build address for the game code \ ****************************************************************************** \ \ Configuration variables \ \ ****************************************************************************** TILE_SIZE = &01000000 \ The length of one side of a square \ landscape tile in 3D coordinates TILES_X = 13 \ The number of tile corners in a landscape \ row from left to right (i.e. 12 tiles) TILES_Z = 11 \ The number of tile corners in a landscape \ row from front to back (i.e. 10 tiles) MAX_PARTICLES = 484 \ The maximum number of particles at any one \ time LAUNCHPAD_OBJECT = 9 \ The type of object along the right edge of \ the launchpad (i.e. the three rockets) LAUNCHPAD_ALTITUDE = &03500000 \ The altitude of the launchpad SEA_LEVEL = &05500000 \ The altitude of sea level LANDING_SPEED = &00200000 \ The maximum safe landing speed SMOKE_RISING_SPEED = &00080000 \ The speed at which smoke particles rise up \ from a destroyed object UNDERCARRIAGE_Y = &00640000 \ The height of the ship's undercarriage \ (0.390625 tiles), which is the distance \ from the centre of the ship to the bottom \ of the ship BUFFER_SIZE = 4308 \ The size of each of the graphics buffers \ in bytes \ ****************************************************************************** \ \ Calculated constants \ \ ****************************************************************************** STORE = 256 * INT(TILES_X / 32) \ We need 256 bytes of cornerStore for every \ 32 tiles across the landscape, so STORE \ contains the number of extra bytes of data \ we need for each extra set of 32 tiles on \ top of the default 256 byte buffers (so \ STORE is zero for the default landscape) LAUNCHPAD_Y = LAUNCHPAD_ALTITUDE - UNDERCARRIAGE_Y \ The y-coordinate of the \ ship as it sits on the \ launchpad, and on top of \ its undercarriage LAUNCHPAD_SIZE = TILE_SIZE * 8 \ Size of the launchpad (8 tile sizes) HIGHEST_ALTITUDE = TILE_SIZE * 52 \ Highest altitude for the engines to work \ (52 tile sizes, but as a positive number; \ the altitude is actually -TILE_SIZE * 52) SPLASH_HEIGHT = TILE_SIZE / 16 \ The height above the sea at which splash \ particles are added when a particle \ splashes into the sea (1/16 of a tile) CRASH_CLOUD_Y = TILE_SIZE * 5 / 16 \ The vertical distance above the \ player's ship where we add the \ explosion cloud when they crash \ (5/16-th of a tile) SMOKE_HEIGHT = TILE_SIZE * 3 / 4 \ The height at which smoke particles are \ added above the bottom of a destroyed \ object (3/4 of a tile) SAFE_HEIGHT = TILE_SIZE * 3 / 2 \ The minimum safe height for avoiding \ objects on the ground (1.5 tile sizes) CAMERA_PLAYER_Z = (TILES_Z - 6) * TILE_SIZE \ The distance along the z-axis \ between the player and the \ camera position (which is at \ the back of the landscape, in \ the middle) LAND_MID_HEIGHT = TILE_SIZE * 5 \ The altitude of the mid-point of the \ generated landscape, to which the height \ of +/- 5 tiles is added by the Fourier \ synthesis in GetLandscapeAltitude PLAYER_FRONT_Z = (TILES_Z - 5) * TILE_SIZE \ The 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) ROCK_HEIGHT = TILE_SIZE * 32 \ The height from which we drop rocks from \ the sky (32 tile sizes) LANDSCAPE_X_WIDTH = TILE_SIZE * (TILES_X - 2) \ The width of the visible \ landscape in x-coordinates, \ counting whole tiles only, \ and ignoring the centre tile LANDSCAPE_Z_DEPTH = TILE_SIZE * (TILES_Z - 1) \ The depth of the visible \ landscape in z-coordinates, \ counting whole tiles only LANDSCAPE_X = LANDSCAPE_X_WIDTH / 2 \ The 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 LANDSCAPE_Y = 0 \ The y-coordinate of the landscape offset, \ which is zero so the landscape remains at \ the same height LANDSCAPE_Z = LANDSCAPE_Z_DEPTH + (10 * TILE_SIZE) \ The z-coordinate of the \ landscape offset, which is \ set to push the landscape \ away from the viewer by \ ten tile sizes HALF_TILES_X = INT(TILES_X / 2) \ Half the number of tiles from left to \ right in the landscape LANDSCAPE_X_HALF = TILE_SIZE * HALF_TILES_X \ The width of half the number \ of tiles from left to right in \ the landscape in x-coordinates LANDSCAPE_Z_BEYOND = LANDSCAPE_Z_DEPTH + TILE_SIZE \ The depth of the visible \ landscape in z-coordinates \ plus one more tile LANDSCAPE_Z_FRONT = LANDSCAPE_Z - LANDSCAPE_Z_DEPTH \ The z-coordinate of \ the front of the \ visible landscape LANDSCAPE_Z_MID = LANDSCAPE_Z - CAMERA_PLAYER_Z \ The z-coordinate of the \ mid-point of the landscape \ depth (i.e. the player) \ ****************************************************************************** \ \ Operating system SWI numbers \ \ ****************************************************************************** OS_WriteC = &00 \ The operating system call to write a \ character to all the output streams OS_WriteS = &01 \ The operating system call to write a \ string to all the output streams OS_ReadC = &04 \ The operating system call to read a key \ press OS_Byte = &06 \ General-purpose operating system calls OS_Word = &07 \ General-purpose operating system calls OS_Mouse = &1C \ The operating system call to read the \ mouse status OS_BinaryToDecimal = &28 \ The operating system call to convert a \ number into a string \ ****************************************************************************** \ \ Workspace variables \ \ ****************************************************************************** stack = &000121FC \ The stack is after the end of the main \ game code and descends from this address \ down to &00012000 (to give a stack size of \ 512 32-bit entries) workspace = stack + 4 \ The variable workspace starts just after \ the top of the stack \ R11 typically contains the address of the \ workspace, so each of the variables in the \ workspace can be referred to as: \ \ [R11, #offset] \ \ where #offset is one of the following \ variables xObject = &00 \ The x-coordinate of the object currently \ being drawn yObject = &04 \ The y-coordinate of the object currently \ being drawn zObject = &08 \ The z-coordinate of the object currently \ being drawn \ Offsets &0C to &2C appear to be unused rotationMatrix = &30 \ The rotation matrix of the object \ currently being drawn (i.e. the matrix \ formed from the object's three orientation \ vectors) xNoseV = &30 \ The x-coordinate of the nose vector xRoofV = &34 \ The x-coordinate of the roof vector xSideV = &38 \ The x-coordinate of the side vector yNoseV = &3C \ The y-coordinate of the nose vector yRoofV = &40 \ The y-coordinate of the roof vector ySideV = &44 \ The y-coordinate of the side vector zNoseV = &48 \ The z-coordinate of the nose vector zRoofV = &4C \ The z-coordinate of the roof vector zSideV = &50 \ The z-coordinate of the side vector xVertex = &54 \ The x-coordinate of the vertex being \ processed, relative to the object's origin yVertex = &58 \ The y-coordinate of the vertex being \ processed, relative to the object's origin zVertex = &5C \ The z-coordinate of the vertex being \ processed, relative to the object's origin \ Offset &60 appears to be unused xVertexRotated = &64 \ The x-coordinate of the vertex after being \ rotated by the object's rotation matrix yVertexRotated = &68 \ The y-coordinate of the vertex after being \ rotated by the object's rotation matrix zVertexRotated = &6C \ The z-coordinate of the vertex after being \ rotated by the object's rotation matrix \ Offset &70 appears to be unused xCoord = &74 \ The x-coordinate of the vertex being \ processed, in the game's 3D coordinate \ system yCoord = &78 \ The y-coordinate of the vertex being \ processed, in the game's 3D coordinate \ system zCoord = &7C \ The z-coordinate of the vertex being \ processed, in the game's 3D coordinate \ system \ Offset &80 appears to be unused xObjectScaled = &84 \ The x-coordinate of the vertex being \ processed, scaled as high as possible yObjectScaled = &88 \ The y-coordinate of the vertex being \ processed, scaled as high as possible zObjectScaled = &8C \ The z-coordinate of the vertex being \ processed, scaled as high as possible \ Offset &90 appears to be unused xPlayer = &94 \ The x-coordinate of the player's ship yPlayer = &98 \ The y-coordinate of the player's ship zPlayer = &9C \ The z-coordinate of the player's ship xVelocity = &A0 \ The player's velocity in the x-axis yVelocity = &A4 \ The player's velocity in the y-axis zVelocity = &A8 \ The player's velocity in the z-axis xExhaust = &AC \ The x-coordinate of the ship's exhaust \ vector, which points along the exhaust \ plume yExhaust = &B0 \ The y-coordinate of the ship's exhaust \ vector, which points along the exhaust \ plume zExhaust = &B4 \ The z-coordinate of the ship's exhaust \ vector, which points along the exhaust \ plume \ Offsets &B8 to &C0 appear to be unused previousColumn = &C4 \ The corner coordinates of the previous \ column while working along a row from left \ to right tileCornerRow = &E4 \ The number of the landscape tile corner \ row that is currently being processed \ Offset &E8 appears to be unused unusedConfig = &F0 \ Storage for the first configuration value \ in the landscapeConfig table, which is \ stored but not used objectType = &F4 \ The type of the object currently being \ drawn tileRowOddEven = &F8 \ A counter that flips every time we process \ a tile corner row when drawing the \ landscape, so we can trigger actions \ accordingly \ Offset &FC appears to be unused altitude = &100 \ The altitude of the landscape at the \ most recently calculated coordinate prevAltitude = &104 \ The altitude of the landscape at the \ previously calculated coordinate particleEnd = &108 \ The address of the end of the particle \ data in the particle data buffer particleCount = &10C \ The number of particles currently \ on-screen objectData = &110 \ The address of the blueprint for the \ object currently being drawn objectFlags = &114 \ The flags of the object currently being \ drawn mainLoopCount = &118 \ The main loop counter crashLoopCount = &11C \ The loop counter for the crash animation crashedFlag = &120 \ If this is non-zero then the object being \ processed has crashed into the ground currentScore = &124 \ Our current score, which is displayed at \ the left end of the score bar \ \ It is initialised to initialScore at the \ start of each new game \ \ It goes down by one each time we fire a \ bullet, and goes up by 20 for each object \ we destroy fuelLevel = &128 \ The player's fuel level gravity = &12C \ The current setting of gravity (which \ changes on higher levels) playingGame = &130 \ A flag to determine whether the game is \ being player, or whether this is the crash \ animation: \ \ * 0 = this is the crash animation \ \ * -1 = game is being played remainingLives = &134 \ The number of remaining lives, which is \ displayed towards the right end of the \ score bar, just before the high score \ \ It is initialised to 3 at the start of \ each new game highScore = &138 \ The high score, which is displayed at the \ right end of the score bar \ \ It is initialised to initialHighScore when \ the game is loaded xCamera = &13C \ The 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) yCamera = &140 \ The 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) zCamera = &144 \ The 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) xCameraTile = &148 \ The 3D x-coordinate of the camera, clipped \ to the nearest tile yCameraTile = &14C \ The 3D y-coordinate of the camera, clipped \ to the nearest tile zCameraTile = &150 \ The 3D z-coordinate of the camera, clipped \ to the nearest tile shipDirection = &154 \ The direction in which the player's ship \ faces, which is angle b in the rotation \ matrix, and which is determined by the \ angle of the mouse from the centre point shipPitch = &158 \ The pitch of the player's ship, which is \ angle a in the rotation matrix, and which \ is determined by the distance of the mouse \ from the centre point fuelBurnRate = &15C \ The current fuel burn rate (bit 0 is \ ignored) xLandscapeRow = &160 \ The x-coordinate of the far-left corner \ of the landscape row that we are drawing, \ where rows run from left to right across \ the screen (this is a relative coordinate) yLandscapeRow = &164 \ The y-coordinate of the far-left corner \ of the landscape row that we are drawing, \ where rows run from left to right across \ the screen (this is a relative coordinate) zLandscapeRow = &168 \ The z-coordinate of the far-left corner \ of the landscape row that we are drawing, \ where rows run from left to right across \ the screen (this is a relative coordinate) \ Offset &16C appears to be unused xLandscapeCol = &170 \ The x-coordinate of the far-left corner \ of the column (i.e. tile) that we are \ drawing in the current landscape row, \ where columns run in and out of the screen \ (this is a relative coordinate) yLandscapeCol = &174 \ The y-coordinate of the far-left corner \ of the column (i.e. tile) that we are \ drawing in the current landscape row, \ where columns run in and out of the screen \ (this is a relative coordinate) zLandscapeCol = &178 \ The z-coordinate of the far-left corner \ of the column (i.e. tile) that we are \ drawing in the current landscape row, \ where columns run in and out of the screen \ (this is a relative coordinate) \ Offsets &17C to &1FC appear to be unused stringBuffer = &200 \ A string buffer that's used when printing \ the scores cornerStore1 = &400 \ Storage for coordinates of tile corners as \ we work our way through the landscape cornerStore2 = &500 + STORE \ Storage for coordinates of tile corners as \ we work our way through the landscape vertexProjected = &600 + STORE * 2 \ Storage for projected vertices particleData = &700 + STORE * 2 \ The particle data buffer, which stores \ eight data bytes for each on-screen \ particle objectMap = &4400 + STORE * 2 \ The 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) buffers = &14400 + STORE * 2 \ The graphics buffers, one for each tile \ corner row (so there are TILE_Z of them) \ ****************************************************************************** \ \ LANDER MAIN GAME CODE \ \ Produces the binary file GameCode.bin. \ \ ****************************************************************************** DIM CODE% &A000 + STORE * 2 \ Reserve a block in memory for the \ assembled code FOR pass% = 4 TO 6 STEP 2 \ Perform a two-pass assembly, using both \ P% and O%, with errors enabled on the \ second pass only O% = CODE% \ Assemble the code for deployment to \ address O% P% = CODE \ Assemble the code into the block at P% [ \ Switch from BASIC into assembly language OPT pass% \ Set the assembly option for this pass \ ****************************************************************************** \ \ Name: landscapeOffset \ Type: Variable \ Category: Landscape \ Summary: The offset we apply to the on-screen landscape to push it away \ from us and to the left, so the visible tiles fit nicely on-screen \ \ ****************************************************************************** .landscapeOffset EQUD -LANDSCAPE_X EQUD LANDSCAPE_Y EQUD LANDSCAPE_Z \ ****************************************************************************** \ \ Name: landscapeOffsetAddr \ Type: Variable \ Category: Landscape \ Summary: The address of the landscape offset \ \ ****************************************************************************** .landscapeOffsetAddr EQUD landscapeOffset \ ****************************************************************************** \ \ Name: landscapeConfig \ Type: Variable \ Category: Landscape \ Summary: The configuration data for each tile row in the landscape \ \ ------------------------------------------------------------------------------ \ \ This table contains configuration data for the tile rows that make up the \ landscape. \ \ The first byte is read but is never used. \ \ The second byte is the number of points (i.e. corners) in each tile row. This \ is the same for all tile rows, so while this table would allow us to tailor \ the number of tiles plotted on each row, perhaps to make them taper off into \ the distance, this isn't actually done. \ \ ****************************************************************************** .landscapeConfig \ We need a configuration for each tile \ corner row EQUB &3A, TILES_X \ Tile row #0 ] FOR I% = 1 TO (TILES_Z - 1) / 2 [ OPT pass% EQUB &E3, TILES_X \ Tile row data (even) EQUB &E4, TILES_X \ Tile row data (odd) ] NEXT [ OPT pass% EQUB &E3, TILES_X \ Tile row #TILES_Z ALIGN \ ****************************************************************************** \ \ Name: landscapeConfigAddr \ Type: Variable \ Category: Landscape \ Summary: The address of the landscapeConfig table \ \ ****************************************************************************** .landscapeConfigAddr EQUD landscapeConfig \ ****************************************************************************** \ \ Name: graphicsBuffers \ Type: Variable \ Category: Graphics buffers \ Summary: The addresses of each of the graphics buffers (these values do not \ change) \ Deep dive: Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffers \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 (and the game \ also includes an extra buffer that is \ unused) ] buffer = workspace + buffers \ The graphics buffers live at the address \ given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 \ Add a buffer for each corner row (plus 1) [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE \ Move on to the next buffer NEXT \ Repeat until we have inserted addresses of \ all the graphics buffers [ OPT pass% \ ****************************************************************************** \ \ Name: graphicsBuffersEnd \ Type: Variable \ Category: Graphics buffers \ Summary: The end addresses of each of the graphics buffers (these values \ get updated as objects are drawn into the buffers) \ Deep dive: Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffersEnd \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 (and the game \ also includes an extra buffer that is \ unused) ] buffer = workspace + buffers \ The graphics buffers live at the address \ given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 \ Add a buffer for each corner row (plus 1) [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE \ Move on to the next buffer NEXT \ Repeat until we have inserted addresses of \ all the graphics buffers [ OPT pass% \ ****************************************************************************** \ \ Name: DrawLandscapeAndBuffers (Part 1 of 4) \ Type: Subroutine \ Category: Landscape \ Summary: Draw the landscape and the contents of the graphics buffers, from \ the back of the screen to the front \ Deep dive: Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ Both the landscape and objects are drawn one tile row at a time, working from \ the back to the front. In each iteration, we process a horizontal row of tile \ corners, working from left to right and back to front. For each corner we draw \ a landscape tile where possible (i.e. when we have already processed the other \ three corners in that tile, so we don't start drawing until we reach the \ second corner on the second row). \ \ Objects are drawn after the tiles on which they sit. This is achieved by \ staggering the drawing of objects so they are drawn two rows later than the \ landscape, so we draw an object two iterations after we have finished drawing \ all four of its surrounding tiles. This ensures that the landscape always \ appears behind the objects that sit on it. \ \ More specifically, we do the following: \ \ * Part 1: Start by setting up all the variables \ \ * We now step through each row of tile corners, working through the tile \ corners from left to right, one row at a time, keeping track of the row \ number in tileCornerRow = 0 to 10 \ \ For each row of tile corners, we do the following: \ \ * Part 2: Work along the current row of tile corners, from left to right, \ one corner coordinate at a time, and draw each tile as a \ quadrilateral once we have four valid corner coordinates from \ the previous row and previous column \ \ * Part 3: If tileCornerRow >= 2, also draw the contents of the graphics \ buffer with number tileCornerRow - 2 \ \ * Part 4: Finish by drawing the objects in graphics buffer 9 and graphics \ buffer 10 \ \ So we draw the objects in graphics buffer 0 just after we process tile corner \ row 2, so that's just after we draw the tiles between corner rows 1 and 2. \ \ Note that the game allocates memory to an extra graphics buffer, but only \ buffers numbers 0 to TILE_Z are used. \ \ ****************************************************************************** .DrawLandscapeAndBuffers STMFD R13!, {R5-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #zCamera] \ Set R3 to the z-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R9, R3, #&FF000000 \ Set R9 to the z-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R3, R3, R9 \ Set R3 = R3 - R9 \ = zCamera - tile containing zCamera \ \ So R3 contains the fractional element of \ zCamera in terms of whole tiles STR R9, [R11, #zCameraTile] \ Set zCameraTile to the z-coordinate of the \ tile containing the camera (though this \ isn't actually used - the value of R9 is \ used below, though) LDR R4, [R11, #xCamera] \ Set R4 to the x-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R8, R4, #&FF000000 \ Set R8 to the x-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R4, R4, R8 \ Set R4 = R4 - R8 \ = xCamera - tile containing xCamera \ \ So R4 contains the fractional element of \ xCamera in terms of whole tiles STR R8, [R11, #xCameraTile] \ Set xCameraTile to the x-coordinate of the \ tile containing the camera ADD R5, R11, #xLandscapeRow \ Set R5 to the address of xLandscapeRow LDR R0, landscapeOffsetAddr \ Fetch the landscape offset vector into LDMFD R0, {R0-R2} \ (R0, R1, R2), which defines the offset \ that we apply to the on-screen landscape \ to move it away from the viewer so it \ fits nicely on-screen SUB R0, R0, R4 \ Subtract the fractional element of xCamera \ from the landscape offset x-coordinate in \ R0 SUB R2, R2, R3 \ Subtract the fractional element of zCamera \ from the landscape offset z-coordinate in \ R2 STMIA R5, {R0-R2} \ (R0, R1, R2) now contains the coordinate \ of the far-left corner of the back row of \ the landscape, which is the first corner \ row that we will process, so store it in \ (xLandscapeRow, yLandscapeRow, \ zLandscapeRow) MOV R0, #0 \ Set tileCornerRow = 0 to use as the STRB R0, [R11, #tileCornerRow] \ number of the tile corner row we are \ currently processing, working through the \ corner rows from row 0 (at the back) to \ row 10 (at the front) MOV R6, #0 \ Set R6 = 0 to use as the address of the \ previous row's coordinates, which we set \ to zero as we don't have a previous row \ yet ADD R7, R11, #cornerStore1 \ Set R7 to the address of cornerStore1 STRB R6, [R11, #tileRowOddEven] \ Set tileRowOddEven = 0, which we will flip \ between 0 and 1 for each tile corner row \ that we process \ So we now have the following variables set \ up, ready for the iteration through each \ row of tile corners, stepping from left to \ right, column by column: \ \ * R6 = 0 \ \ R6 always points to the set of corner \ pixel coordinates from the previous \ tile corner row, so it starts out as \ zero as there is no previous row at \ this point \ \ * R7 = address of cornerStore1 \ \ R7 always points to the place where we \ store the pixel coordinates for the \ current row corner as we work our way \ along the row it (we store these \ coordinates so we can draw the tiles \ when we're on the next corner row in \ the next iteration) \ \ * R9 = the z-coordinate of the tile \ containing the camera \ \ So (x, R9) is the coordinate of each \ corner on the corner row we're working \ along, starting from the far-left \ corner (as the camera position is \ actually at the back of the on-screen \ landscape) \ \ * zLandscapeRow = the z-coordinate of \ the back corner row of the landscape \ \ So zLandscapeRow keeps track of the \ z-coordinate of the tile corner row \ that we are processing, decreasing as \ we move towards the front of the tile \ landscape \ \ * tileRowOddEven = 0 \ \ tileRowOddEven flips between 0 and 1 \ for each tile corner row, so we can \ set the correct values of R6 and R7 at \ the end of each iteration \ \ * tileCornerRow = 0 \ \ tileCornerRow contains the number of \ the tile corner row that we are \ currently processing, which increments \ on each iteration as we move forwards \ \ * xCameraTile is the x-coordinate of the \ tile containing the camera \ \ This is the same as the x-coordinate of \ the middle of each tile corner row, \ rounded down to the nearest tile \ \ We now fall through into part 2 \ ****************************************************************************** \ \ Name: DrawLandscapeAndBuffers (Part 2 of 4) \ Type: Subroutine \ Category: Landscape \ Summary: Draw a row of landscape tiles \ Deep dive: Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** \ We're now ready to iterate through the \ landscape, from left to right and back to \ front, looping back to land1 after \ processing each corner row .land1 LDRB R1, [R11, #tileCornerRow] \ Set R1 to the number of the tile corner \ row we are currently processing LDR R0, landscapeConfigAddr \ Set R0 to the address of the pair of ADD R0, R0, R1, LSL #1 \ bytes in the landscapeConfig table for the \ tile row number in R1 LDRB R10, [R0, #1] \ Set R10 to the second byte from the pair \ in landscapeConfig, which is the number \ of tile corners in the row \ \ We now use R10 as a loop counter when \ working our way along the row, counting \ tile corners as we progress LDRB R0, [R0] \ Set unusedConfig to the first byte from STRB R0, [R11, #unusedConfig] \ the pair in landscapeConfig, though this \ value is not used, so this has no effect ADD R0, R11, #xLandscapeRow \ Fetch the coordinate from (xLandscapeRow, LDMIA R0, {R0-R2} \ yLandscapeRow, zLandscapeRow) into \ (R0, R1, R2), so they contain the \ coordinates of the left end of the corner \ row that we need to process ADD R4, R11, #xLandscapeCol \ Store the coordinates in (xLandscapeCol, STMIA R4, {R0-R2} \ yLandscapeCol, zLandscapeCol) so we can \ start processing from the left end of this \ tile corner row, using these coordinates \ as we work through the columns, updating \ yLandscapeCol with the landscape height as \ we go LDR R8, [R11, #xCameraTile] \ Set R8 = xCameraTile - LANDSCAPE_X, so SUB R8, R8, #LANDSCAPE_X \ R8 is now the coordinate of the tile \ corner at the left end of the corner row, \ as subtracting the offset of the landscape \ effectively moves the camera to the left \ by that amount MOV R0, #&80000000 \ Set previousColumn = &80000000 to denote STR R0, [R11, #previousColumn] \ that we don't have corner coordinates from \ the previous column yet (as we are \ starting a new row) \ We now do the following loop R10 times, \ once for each of the tile corners in the \ row, looping back to land2 as we move \ right through the columns .land2 BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9), which is \ the current corner on this row LDR R14, [R11, #yCamera] \ Set yLandscapeCol = R0 - yCamera SUB R0, R0, R14 \ = altitude - yCamera STR R0, [R11, #yLandscapeCol] \ \ So this stores the y-coordinate of the \ landscape at this point, as we're seeing \ it from the point of view of the camera \ (so this will move it down the screen if \ the player is flying high, for example) ADD R0, R11, #xLandscapeCol \ Set R0 to the address of xLandscapeCol BL ProjectVertexOntoScreen \ Project (xLandscapeCol, yLandscapeCol, \ zLandscapeCol) onto the screen, returning \ the results in (R0, R1), so this projects \ the tile corner as it sits on the \ landscape \ \ This also sets the C flag if the vertex is \ too far away to draw BCS land4 \ If the vertex is too far away to draw then \ jump to land4 to skip the following STMIA R7!, {R0-R1} \ Store the corner coordinates in (R0, R1) \ in R7, updating R7 as we go, as R7 is \ where we store the tile corner pixel \ coordinates as we go (so this stores the \ corner coordinates in either cornerStore1 \ or cornerStore2, so we can fetch them when \ we process the next tile corner row) CMP R6, #0 \ If R6 = 0 then this is either the very BEQ land4 \ first tile corner row, or we have already \ fetched all the data for the previous row \ from the storage at R6 \ \ In either case, jump to land4 to skip \ drawing this tile as we can't draw tiles \ without the corresponding corner \ coordinates from the previous row and \ previous column LDMIA R6!, {R2-R3} \ Load the corner pixel coordinates from R6 \ into (R2, R3), updating R6 as we go, so \ this fetches the corresponding corner \ pixel coordinates from the previous tile \ corner row (i.e. from the opposite corner \ store to the one where we just stored this \ row's coordinate) CMP R2, #&80000000 \ If R2 = &80000000 then we have reached the MOVEQ R6, #0 \ end of the storage in R6, so set R6 = 0 BEQ land4 \ to prevent any more access attempts from \ the storage in R6, and jump to land4 to \ skip drawing this tile \ If we get here then we have the \ following pixel corner coordinates \ calculated: \ \ (R0, R1) = new corner from this row \ \ (R2, R3) = corresponding corner from \ previous row (i.e. the corner \ that's back by one row) STMFD R13!, {R6-R8} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R14, R11, #previousColumn \ We now need to fetch the previous corners, LDMIA R14, {R4-R7} \ looking left along the tile row to the \ previous column of corners, so we can draw \ a tile from there to the new corner \ \ We will have stored these previous corners \ in the previousColumn table in the \ previous iteration, so fetch them into \ R4 to R7 like this: \ \ (R4, R5) = corner from the column to the \ left on this row \ \ (R6, R7) = corresponding corner from the \ previous row STMIA R14, {R0-R3} \ Store the new corners from this corner row \ in (R0, R1) and (R2, R3) in previousColumn \ so we can fetch them in the next iteration \ in the same way CMP R4, #&80000000 \ If R4 = &80000000 then this is actually BEQ land3 \ the first column of corner coordinates in \ this row, so we can't yet draw the tile, \ so jump to land3 to skip the drawing part \ If we get here then we actually have a \ tile to draw BL GetLandscapeTileColour \ Calculate the tile colour, depending on \ the slope, and return it in R8 BL DrawQuadrilateral \ Draw the tile by drawing a quadrilateral \ in colour R8 with corners at: \ \ (R0, R1) = the corner we're processing \ (R2, R3) = same corner in previous row \ (R4, R5) = previous column in this row \ (R6, R7) = previous row, previous column \ \ So this draws the tile we want on-screen .land3 LDMFD R13!, {R6-R8} \ Retrieve the registers that we stored on \ the stack .land4 SUBS R10, R10, #1 \ Decrement the loop counter in R10, which \ counts the number of tile corners in this \ row LDRNE R0, [R11, #xLandscapeCol] \ If we haven't yet processed all the tile ADDNE R0, R0, #TILE_SIZE \ corners in this row, add a tile's width to STRNE R0, [R11, #xLandscapeCol] \ xLandscapeCol and R8 to move them along ADDNE R8, R8, #TILE_SIZE \ the row to the next tile corner, and jump BNE land2 \ back to land2 to process the next corner \ By this point have drawn all the tiles in \ this row, so we now move on to the objects \ in the graphics buffers \ ****************************************************************************** \ \ Name: DrawLandscapeAndBuffers (Part 3 of 4) \ Type: Subroutine \ Category: Landscape \ Summary: Draw the objects in the graphics buffers for two rows behind the \ current corner row \ Deep dive: Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing SUBS R0, R0, #2 \ Subtract 2 from the corner row to get the \ number of the row that's two tile rows \ behind the current row BLPL DrawGraphicsBuffer \ If this results in a valid graphics buffer \ number, i.e. one that is positive, draw \ the contents of the buffer LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing ADDS R0, R0, #1 \ Increment the tile row number in R0 to \ move forwards by one tile row CMP R0, #TILES_Z \ If R0 = TILES_Z then we just drew the last BEQ land5 \ tile row, so jump to land5 to finish off \ by drawing graphics buffers 9 and 10 STRB R0, [R11, #tileCornerRow] \ Otherwise store the updated tile corner \ row number in tileCornerRow, ready for the \ next corner row LDR R0, [R11, #zLandscapeRow] \ Subtract a tile's width from zLandscapeRow SUB R0, R0, #TILE_SIZE \ to move the coordinates of the current row STR R0, [R11, #zLandscapeRow] \ forward by one whole tile SUB R9, R9, #TILE_SIZE \ Subtract a tile's width from R9 to step \ along the z-axis by one whole tile, going \ from back to front MOV R0, #&80000000 \ Store &80000000 in the address in R7 to STR R0, [R7] \ reset the store, so it's ready to be used \ to store the new row's pixel corner \ coordinates LDRB R0, [R11, #tileRowOddEven] \ Flip the value of tileRowOddEven between EORS R0, R0, #1 \ 0 and 1, setting the flags to feed into STRB R0, [R11, #tileRowOddEven] \ the following logic ADDNE R6, R11, #cornerStore1 \ Do the following after drawing the first ADDEQ R6, R11, #cornerStore2 \ tile row and then every other row: ADDEQ R7, R11, #cornerStore1 \ ADDNE R7, R11, #cornerStore2 \ * R6 = address of cornerStore1 \ * R7 = address of cornerStore2 \ \ Or do the following after drawing the \ second tile row and then every other row: \ \ * R6 = address of cornerStore2 \ * R7 = address of cornerStore1 \ \ So R6 and R7 swap over after each row, so \ R6 always points to the set of pixel \ corner coordinates from the previous row, \ and R7 points to the place where we store \ this row's pixel corner coordinates B land1 \ Loop back to land1 to process the next row \ of tile corners \ ****************************************************************************** \ \ Name: DrawLandscapeAndBuffers (Part 4 of 4) \ Type: Subroutine \ Category: Landscape \ Summary: Draw the remaining graphics buffers \ Deep dive: Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** .land5 MOV R0, #TILES_Z - 2 \ Draw the contents of the penultimate BL DrawGraphicsBuffer \ graphics buffer MOV R0, #TILES_Z - 1 \ Draw the contents of the last graphics BL DrawGraphicsBuffer \ buffer LDMFD R13!, {R5-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: GetLandscapeAltitude \ Type: Subroutine \ Category: Landscape \ Summary: Calculate the altitude of the landscape for a given coordinate \ Deep dive: Generating the landscape \ Drawing the landscape \ \ ------------------------------------------------------------------------------ \ \ This routine calculates the altitude of the landscape at a given coordinate. \ The altitude is inverse, with lower values indicating higher altitudes. The \ launchpad is at LAUNCHPAD_ALTITUDE, while the sea is at SEA_LEVEL. \ \ The altitude at landscape coordinate (x, z) is calculated as follows: \ \ LAND_MID_HEIGHT - ( 2*sin(x - 2z) + 2*sin(4x + 3z) + 2*sin(3z - 5x) \ + 2*sin(3x + 3z) + sin(5x + 11z) + sin(10x + 7z) \ ) / 256 \ \ Note that the object map is flat, like a paper map, so the x- and z-axes on \ the map correspond to the x- and z-axes in the three-dimensional space when \ the map is laid out on the landscape (as the 3D z-axis goes into the screen). \ When talking about the map, we are talking about (x, z) coordinates that are \ a bit like longitude and latitude, and this routine returns the y-coordinate \ of the point on the landscape (as the y-axis goes down the screen and \ determines the altitude). \ \ Note that more negative values denote lower altitudes, as the y-axis goes down \ the screen, working down from zero at the very highest altitude in space. This \ is the opposite to conventional altitudes in the real world. \ \ The altitude is capped to a maximum value of SEA_LEVEL, and the altitude on \ the launchpad is set to LAUNCHPAD_ALTITUDE. The launchpad is defined as (x, z) \ where 0 <= x < LAUNCHPAD_SIZE and 0 <= z < LAUNCHPAD_SIZE, so that's tiles 0 \ to 7 along each axis (with the origin being at the front-left corner of the \ launchpad). \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R8 The x-coordinate of the landscape coordinate \ \ R9 The z-coordinate of the landscape coordinate \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R0 The altitude (y-coordinate) of the landscape \ at these coordinates, with more negative \ values denoting lower altitudes, as the \ y-axis points downwards \ \ ****************************************************************************** .GetLandscapeAltitude LDR R0, [R11, #altitude] \ Set prevAltitude = altitude, so we store STR R0, [R11, #prevAltitude] \ the altitude from the previous calculation \ In the following commentary, we will refer \ to the coordinates in R8 and R9 as x and z SUB R0, R8, R9, LSL #1 \ Set R0 = R8 - R9 << 1 \ = x - 2z LDR R2, sinTableAddr \ Set R2 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 R0, [R2, R0, LSR #20] \ Set \ \ R0 = (2^31 - 1) \ * SIN(2 * PI * ((R0 >> 20) / 1024)) \ \ So R0 = sin(R0) \ = sin(x - 2z) MOV R0, R0, ASR #7 \ Set R0 = R0 >> 7 \ = sin(x - 2z) / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = 2x + z ADD R1, R9, R1, LSL #1 \ R1 = R9 + R1 << 1 \ = z + (2x + z) << 1 \ = z + 4x + 2z \ = 4x + 3z ADD R3, R1, R8 \ R3 = R1 + R8 \ = 4x + 3z + x \ = 5x + 3z \ So the above gives us: \ \ R0 = sin(x - 2z) / 128 \ R1 = 4x + 3z \ R3 = 5x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(4x + 3z) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z)) / 128 SUB R1, R9, R8, LSL #1 \ R1 = R9 - R8 << 1 \ = z - 2x RSB R1, R8, R1, LSL #1 \ R1 = (R1 << 1) - R8 \ = (z - 2x) << 1 - x \ = 2z - 4x - x \ = 2z - 5x ADD R1, R1, R9 \ R1 = R1 + R9 \ = 2z - 5x + z \ = 3z - 5x \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z)) / 128 \ R1 = 3z - 5x LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(3z - 5x) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x)) / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = z + 2x \ = 2x + z ADD R1, R9, R1, LSL #2 \ R1 = R9 + R1 << 2 \ = z + (2x + z) << 2 \ = z + 4x + 2z \ = 4x + 3z SUB R1, R1, R8 \ R1 = R1 - R8 \ = 4x + 3z - x \ = 3x + 3z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x)) / 128 \ R1 = 3x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(3x + 3z) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 ADD R1, R3, R9, LSL #3 \ R1 = R3 + R9 << 3 \ = 5x + 3z + 8z \ = 5x + 11z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ R1 = 5x + 11z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(5x + 11z) ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + sin(5x + 11z) / 256 ADD R1, R9, R3, LSL #1 \ R1 = R9 + R3 << 1 \ = z + (5x + 3z) << 1 \ = z + 10x + 6z \ = 10x + 7z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + sin(5x + 11z) / 256 \ R1 = 10x + 7z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(10x + 7z) ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + \ (sin(5x + 11z) + sin(10x + 7z)) / 256 RSB R0, R0, #LAND_MID_HEIGHT \ R0 = LAND_MID_HEIGHT - R0 \ \ = LAND_MID_HEIGHT - \ (2*sin(x - 2z) + 2*sin(4x + 3z) \ + 2*sin(3z - 5x) \ + 2*sin(3x + 3z) \ + sin(5x + 11z) \ + sin(10x + 7z)) / 256 \ \ which is the result that we want CMP R0, #SEA_LEVEL \ If R0 > SEA_LEVEL, set R0 = SEA_LEVEL so MOVGT R0, #SEA_LEVEL \ we don't create landscapes lower then sea \ level CMP R8, #LAUNCHPAD_SIZE \ If R8 and R9 are both < LAUNCHPAD_SIZE, CMPLO R9, #LAUNCHPAD_SIZE \ then this coordinate is on the launchpad, MOVLO R0, #LAUNCHPAD_ALTITUDE \ so set R0 = LAUNCHPAD_ALTITUDE STR R0, [R11, #altitude] \ Set altitude = R0, so we return the result \ in R0 and set the altitude variable MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: GetLandscapeBelowVertex \ Type: Subroutine \ Category: Landscape \ Summary: Calculate the landscape altitude directly below an object's vertex \ Deep dive: Generating the landscape \ Drawing 3D objects \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R0 The address containing the object's vertex \ (x, y, z), relative to the camera position \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R0 The altitude (y-coordinate) of the landscape \ directly below the coordinate \ \ ****************************************************************************** .GetLandscapeBelowVertex STMFD R13!, {R8-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R8, [R0] \ Add xCamera to the x-coordinate in R8 to LDR R1, [R11, #xCamera] \ get the vertex position in the game's ADD R8, R8, R1 \ world coordinate system LDR R9, [R0, #8] \ Add zCamera to the z-coordinate in R8 to LDR R1, [R11, #zCamera] \ get the vertex position in the game's ADD R9, R9, R1 \ world coordinate system SUB R9, R9, #LANDSCAPE_Z \ Move the z-coordinate forward by the \ landscape offset, as the altitude \ calculation needs the coordinate to be \ relative to the front-centre point of the \ landscape \ The (x, z) coordinate in (R8, R9) is now \ relative to the game's coordinate system, \ rather than the camera or the landscape \ offset, which is what we need in order \ to calculate the altitude of the landscape \ at this point BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9), which is \ the point directly below the vertex LDR R14, [R11, #yCamera] \ Subtract yCamera from the altitude so the SUB R0, R0, R14 \ result is relative to the camera position LDMFD R13!, {R8-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: GetLandscapeTileColour \ Type: Subroutine \ Category: Landscape \ Summary: Calculate the colour of the landscape tile currently being drawn \ Deep dive: Drawing the landscape \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R8 The colour of the landscape tile \ \ ****************************************************************************** .GetLandscapeTileColour STMFD R13!, {R0-R4, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #prevAltitude] \ Set R3 to the altitude of the previous \ point on the landscape LDR R4, [R11, #altitude] \ Set R4 to the altitude of the current \ point on the landscape SUBS R14, R3, R4 \ Set R14 to the slope of the tile, which we MOVMI R14, #0 \ get from the change of altitude from the \ previous point to this one, making sure \ the slope value is greater than zero: \ \ R14 = min(0, prevAltitude - altitude) \ \ The y-axis that measures altitude goes \ down the screen (counterintuitively), so \ if prevAltitude has a bigger value than \ altitude, this means the previous point \ is lower down than the current point \ \ As we draw the landscape from left to \ right, this means the slope value will be \ non-zero for tiles that face left, with a \ greater value for steeper slopes, while \ tiles that face right will have a slope \ value of zero \ We now calculate the colour, with the red, \ green and blue channels in R0, R1 and R2 \ respectively AND R2, R4, #&10 \ This instruction appears to have no \ effect, as we overwrite the result in the \ next instruction, but it looks like it's \ all that remains of an experiment to add \ blue to the landscape MOV R2, #0 \ Set the blue channel in R2 to zero, as we \ only use blue for the sea \ We now set the green and red channels \ depending on bits 2 and 3 of the altitude, \ for red and green respectively \ \ This makes the green channel change more \ slowly between neighbouring tiles, with \ the red channel changing more quickly, \ giving the overall effect of a gentle \ green landscape pockmarked with small \ groups of red-brown dirt AND R1, R4, #%00001000 \ Set the green channel in R1 to bit 3 of MOV R1, R1, LSR #1 \ the current point's altitude, as follows: ADD R1, R1, #4 \ \ R1 = (bit 3) * 4 + 4 \ \ So it's 4 if bit 3 is clear, or 8 if bit 3 \ is set AND R0, R4, #%00000100 \ Set the red channel in R0 to bit 2 of the \ current point's altitude CMP R4, #LAUNCHPAD_ALTITUDE \ If the current point is on the launchpad, MOVEQ R0, #4 \ set the colour to grey, i.e. red, green MOVEQ R1, #4 \ and blue all have the same value of 4 MOVEQ R2, #4 CMP R4, #SEA_LEVEL \ If both the previous and current points CMPEQ R3, #SEA_LEVEL \ are at sea level, set the colour to blue, MOVEQ R1, #0 \ i.e. the blue channel has value 4 while MOVEQ R2, #4 \ red and green are zero MOVEQ R0, #0 LDRB R8, [R11, #tileCornerRow] \ Set R8 to the number of the tile corner \ row that we're processing, so it goes from \ 1 at the back to TILES_Z - 1 at the front, \ or 1 to 10 (it doesn't start at zero as we \ don't draw any tiles for the very first \ row of tile corners, so we don't call this \ routine) ADD R3, R8, R14, LSR #22 \ Set the tile's brightness in R3 to: \ \ tileCornerRow + slope >> 22 \ \ where tileCornerRow is 1 at the back and \ TILES_Z - 1 at the front \ \ As we draw the landscape from back to \ front and left to right, this means that: \ \ * Tiles nearer the front will have a \ higher brightness level than those at \ the back (as tileCornerRow will be \ higher for closer tiles) \ \ * Tiles that face to the left will have \ a higher brightness level than those \ that face to the right, with steeper \ sloping tiles being brighter than \ shallow ones (as slope will be higher \ for steeper sloping tiles) \ \ * Tiles that face to the right will have \ fixed brightness levels that only vary \ with distance and not with slope (as \ slope is zero for tiles that face \ right) \ \ This implements a light source that is \ directly above and slightly to the left ADD R0, R0, R3 \ Add the brightness in R3 to all three ADD R1, R1, R3 \ channels ADD R2, R2, R3 CMP R0, #16 \ Ensure that the red channel in R0 fits MOVHS R0, #15 \ into four bits CMP R1, #16 \ Ensure that the green channel in R1 fits MOVHS R1, #15 \ into four bits CMP R2, #16 \ Ensure that the blue channel in R2 fits MOVHS R2, #15 \ 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, R1 \ and R2 ORR R8, R1, R2 \ Set R8 to the bottom three bits of: AND R8, R8, #%00000011 \ ORR R8, R8, R0 \ (the bottom two bits of R1 OR R2) 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 R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R8, R8, R1, LSL #3 \ And stick them into bits 5-6 of R8 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R8, R8, #%00001000 \ set bit 3 of R8 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 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 LDMFD R13!, {R0-R4, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: MoveAndDrawPlayer (Part 1 of 5) \ Type: Subroutine \ Category: Player \ Summary: Process player movement and draw the player's ship into the \ graphics buffers, starting with reading the mouse position \ Deep dive: Flying by mouse \ \ ****************************************************************************** .MoveAndDrawPlayer STMFD R13!, {R14} \ Store the return address on the stack \ We start by reading the position of the \ mouse and generating a rotation matrix for \ the player's ship, based on its new \ orientation SWI OS_Mouse \ Read the mouse coordinates, returning: \ \ R0 = x-coordinate \ \ R1 = y-coordinate \ \ R2 = mouse buttons (%lmr) STRB R2, [R11, #fuelBurnRate] \ Set the fuel burn rate to the mouse button \ result, so it's set to: \ \ * %000 (0) if no buttons are being \ pressed \ \ * %001 (1) if the right button is being \ pressed (i.e. fire bullets) \ \ * %010 (2) if the middle button is being \ pressed (i.e. hover) \ \ * %100 (4) if the left button is being \ pressed (i.e. full thrust) \ \ Bit 0 of the fuel rate is ignored in the \ fuel calculations, so firing bullets does \ not burn fuel, even though fuelBurnRate is \ set to a non-zero value LDR R14, [R11, #fuelLevel] \ If the fuel level is zero, also zero the CMP R14, #0 \ fuel burn rate, as we can't burn fuel that STREQB R14, [R11, #fuelBurnRate] \ we don't have \ At the start of each new life, the mouse \ is initialised to coordinates (511, 511) CMP R0, #1024 \ Cap the mouse x-coordinate in R0 so it's MOVHS R0, #1024 \ in the range 0 to 1023 SUBHS R0, R0, #1 SUB R0, R0, #512 \ Convert R0 into the range -512 to +511 RSB R1, R1, #1024 \ Set R1 = 1024 - R1 - 512 SUB R1, R1, #512 \ = 512 - R1 \ \ So the mouse y-coordinate in R1 is now in \ the range -512 to +512 MOV R0, R0, LSL #22 \ Scale both mouse coordinates up as far as MOV R1, R1, LSL #22 \ possible without losing data (512 << 22 is \ &80000000, so this is as high as we can \ go) BL GetMouseInPolarCoordinates \ Convert the mouse coordinates into polar \ coordinates, returning the polar angle in \ R1 and the polar distance in R0 CMP R0, #&40000000 \ Cap the polar distance in R0 so it's in MOVHS R0, #&40000000 \ the range 0 to &3FFFFFFF SUBHS R0, R0, #1 MOV R0, R0, LSL #1 \ Scale R0 to the range 0 to &7FFFFFFE LDR R2, [R11, #shipPitch] \ Set R2 = shipPitch LDR R3, [R11, #shipDirection] \ Set R3 = shipDirection SUBS R4, R3, R1 \ Set R4 = R3 - R1 \ = shipDirection - polar angle BMI ship1 \ If the result is negative, jump to ship1 \ so we cap R4 against negative values CMP R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MOVHS R4, #&30000000 \ of &30000001 B ship2 \ Jump to ship2 to skip the following and \ keep going .ship1 CMN R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MVNLO R4, #&30000000 \ of -&30000001 .ship2 SUBS R5, R2, R0 \ Set R5 = R2 - R0 \ = shipPitch - polar distance BLE ship3 \ If the result is zero or negative, jump to \ ship3 so we cap R5 against negative values CMP R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MOVHS R5, #&30000000 \ of &30000001 B ship4 \ Jump to ship4 to skip the following and \ keep going .ship3 CMN R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MVNLO R5, #&30000000 \ of -&30000001 .ship4 \ We now update the rotation angles with the \ latest mouse values (in the form of the \ distance and angle) \ \ We do this by adding half of the new value \ and half of the old value, which seems to \ apply some kind of damping to the controls SUB R0, R2, R5, ASR #1 \ Set R0 = R2 - R5 / 2 \ = shipPitch \ - (shipPitch - distance) / 2 SUB R1, R3, R4, ASR #1 \ Set R1 = R3 - R4 / 2 \ = shipDirection \ - (shipDirection - angle) / 2 STR R0, [R11, #shipPitch] \ Store the updated value in shipPitch STR R1, [R11, #shipDirection] \ Store the updated value in shipDirection BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ updated angles given in R0 and R1, so \ this sets the rotation matrix for the \ player's ship \ Now that we have the player's rotation \ matrix, we can move on to the ship's \ movement in space \ ****************************************************************************** \ \ Name: MoveAndDrawPlayer (Part 2 of 5) \ Type: Subroutine \ Category: Player \ Summary: Update the player's velocity and coordinates \ Deep dive: Flying by mouse \ \ ****************************************************************************** ADD R14, R11, #xPlayer \ Set R0 to R5 as follows: LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So (R0, R1, R2) is the player's coordinate \ and (R3, R4, R5) is the player's velocity \ vector \ \ We now update the velocity by adding in \ various factors such as friction, thrust \ and gravity CMN R1, #HIGHEST_ALTITUDE \ If R1 is higher than the altitude where LDRLTB R9, [R11, #fuelBurnRate] \ the engines cut out, clear bits 2 and 3 of BICLT R9, R9, #%00000110 \ the fuel burn rate to cut the engines STRLTB R9, [R11, #fuelBurnRate] LDR R6, [R11, #xRoofV] \ Set (R6, R7, R8) to the roof vector from LDR R7, [R11, #yRoofV] \ the rotation matrix, which is the vector LDR R8, [R11, #zRoofV] \ that points directly down through the \ ship's floor as the y-axis is inverted \ (so it's in the direction of thrust, as \ the thrusters are on the bottom of the \ ship) \ \ Let's refer to this thrust vector as \ follows: \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust LDRB R9, [R11, #fuelBurnRate] \ Set R9 to the fuel burn rate TST R9, #%00000100 \ Set the flags according to bit 2 of the \ fuel burn rate, which is set if the left \ button is being pressed (i.e. full thrust) \ We start by updating xVelocity in R3 SUB R3, R3, R3, ASR #6 \ Set R3 = R3 - R3 >> 6 \ = xVelocity - (xVelocity / 64) \ \ This reduces the velocity along the x-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R3, R3, R6, ASR #11 \ Increase R3 by a further xExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R0, R0, R3 \ Set R0 = R0 + R3 \ = xPlayer + R3 \ \ This applies the newly calculated velocity \ to the player's x-coordinate, which moves \ the player in 3D space \ Next we update yVelocity in R4 SUB R4, R4, R4, ASR #6 \ Set R4 = R4 - R4 >> 6 \ = yVelocity - (yVelocity / 64) \ \ This reduces the velocity along the y-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R4, R4, R7, ASR #11 \ Increase R3 by a further yExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R1, R1, R4 \ Set R1 = R1 + R4 \ = yPlayer + R4 \ \ This applies the newly calculated velocity \ to the player's y-coordinate, which moves \ the player in 3D space \ And finally we update zVelocity in R4 SUB R5, R5, R5, ASR #6 \ Set R5 = R5 - R5 >> 6 \ = zVelocity - (zVelocity / 64) \ \ This reduces the velocity along the z-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R5, R5, R8, ASR #11 \ Increase R3 by a further zExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R2, R2, R5 \ Set R2 = R2 + R5 \ = zPlayer + R5 \ \ This applies the newly calculated velocity \ to the player's z-coordinate, which moves \ the player in 3D space \ By this point we have updated the player's \ coordinates in (R0, R1, R2) by the new \ velocity in (R3, R4, R5) TST R9, #%00000010 \ Set the flags according to bit 1 of the \ fuel burn rate, which is set if the middle \ button is being pressed (i.e. hover) SUBNE R3, R3, R6, ASR #13 \ If the middle button is being pressed, for SUBNE R4, R4, R7, ASR #13 \ hovering mode, then apply a quarter of the SUBNE R5, R5, R8, ASR #13 \ full thrust vector to the velocity, rather \ than the full thrust vector we apply for \ the left button \ \ Note that the hover thrust is applied \ after we applied the velocity vector to \ the player's coordinates, so hovering has \ a slightly delayed impact on the ship, to \ simulate the effects of inertia LDR R9, [R11, #gravity] \ Add the effect of gravity to yVelocity in ADD R4, R4, R9 \ R4, adding it to R4 as it is a downwards \ force along the z-axis STMIA R14, {R0-R8} \ Store the updated player coordinates, ship \ velocity and thrust vector as follows: \ \ xPlayer = R0 \ yPlayer = R1 \ zPlayer = R2 \ \ xVelocity = R3 \ yVelocity = R4 \ zVelocity = R5 \ \ xExhaust = R6 \ yExhaust = R7 \ zExhaust = R8 \ \ We use these values in part 4 \ ****************************************************************************** \ \ Name: MoveAndDrawPlayer (Part 3 of 5) \ Type: Subroutine \ Category: Player \ Summary: Check for collisions and draw the ship \ Deep dive: Drawing 3D objects \ Collisions and bullets \ Flying by mouse \ \ ****************************************************************************** CMP R1, #0 \ If R1 (i.e. the y-coordinate of the ship) MOVPL R1, #0 \ is positive, then set it to zero \ \ As we store this value as the camera's \ y-coordinate below, this ensures that the \ camera doesn't drop down all the way with \ the ship in last few moments before it \ lands, and similarly it doesn't start \ rising up with the ship as it takes off \ until the ship is at a reasonable height ADD R2, R2, #CAMERA_PLAYER_Z \ Set R2 to the z-coordinate we want to use \ for the camera, which is at the back of \ the landscape ADD R14, R11, #xCamera \ Set (xCamera, yCamera, zCamera) to the STMIA R14, {R0-R2} \ coordinates in (R0, R1, R2), which is at \ the back of the landscape and in the \ middle \ \ This sets the camera so that it follows \ the player's ship as it flies around, \ and is positioned at the back of the \ visible screen MOV R8, R0 \ Set (R8, R9) to the point on the landscape MOV R9, R2 \ that's directly below the player, by SUB R9, R9, #CAMERA_PLAYER_Z \ setting (R8, R9) to the ship's (x, z) \ coordinates in (R0, R2) and subtracting \ the five tiles we added above BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9) SUB R0, R0, #UNDERCARRIAGE_Y \ Set R0 to the altitude of a point that's \ the height of the ship's undercarriage \ above the point on the landscape beneath \ the player, which is the lowest altitude \ the player can be without being in danger \ of hitting the ground with the bottom of \ the ship LDR R1, [R11, #yPlayer] \ Set R14 to the y-coordinate of the player \ in yPlayer SUB R14, R0, R1 \ If R0 - yPlayer >= SAFE_HEIGHT (1.5 tile CMP R14, #SAFE_HEIGHT \ sizes) then this means the bottom of the BHS ship6 \ player's ship is safely clear of objects \ on the ground, as SAFE_HEIGHT is the \ minimum safe height for avoiding objects \ on the ground, and the undercarriage of \ the player's ship is above this height \ \ So if this is the case, jump to ship6 to \ skip the following set of collision \ checks, as we know we are safe STMFD R13!, {R0} \ Store R0 on the stack so we can retrieve \ it below ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R9, R9, #&FF000000 \ Clip R9 down to the nearest tile 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, #0 \ If the object map entry is zero, jump to BEQ ship5 \ ship5 to skip the following (though this \ shouldn't ever happen, as an object type \ of zero is never added to the object map) CMP R0, #12 \ If R0 < 12 then the object map entry is a ADDLO R13, R13, #4 \ valid object number in the range 1 to 11, BLO LoseLife \ so the player's ship just hit the object \ below it on the ground, so jump to \ LoseLife to show the explosion animation \ and lose a life .ship5 LDMFD R13!, {R0} \ Retrieve R0 from the stack, so it is once \ again the lowest altitude the player can \ be without being in danger of hitting the \ ground .ship6 \ If we get here then R0 is the lowest \ altitude the player can be without being \ in danger of hitting the ground, and R1 is \ the y-coordinate of the player, i.e. the \ player's altitude CMP R1, R0 \ If R1 > R0 then the player is lower down BLGT LandOnLaunchpad \ then the safe altitude, so call \ LandOnLaunchpad to check whether we have \ landed on the launchpad (as we need to be \ within the danger zone in order to land) CMP R1, #0 \ If R1 (i.e. the y-coordinate of the ship) MOVMI R1, #0 \ is negative, then set it to zero \ \ This is the converse of the test we did at \ the start of this part, as it sets R1 to \ zero only when the altitude of the player \ is higher than zero, so this only keeps \ R1 set to the player's y-coordinate when \ they are really close to the ground \ \ As we pass this value to DrawObject below, \ this means we draw the player's ship in \ the middle of the screen most of the time \ (by passing an R1 of zero to DrawObject), \ but when the ship is close to the ground, \ we draw the ship lower down the screen ADD R3, R11, #rotationMatrix \ Set R3 to the address of the ship's \ rotation matrix to pass to DrawObject LDR R14, objectPlayerAddr \ Set objectData to the object blueprint STR R14, [R11, #objectData] \ for the player's ship MOV R0, #0 \ Set the coordinates in (R0, R1, R2) so MOV R2, #LANDSCAPE_Z_MID \ the ship gets drawn in the middle of the \ screen or slightly below if the ship is \ near the ground (as R0 = 0 and R1 is set \ as described above), at a position into \ the screen of LANDSCAPE_Z_MID, which \ places it above the middle of the \ landscape \ \ This works because the landscape offset \ pushes the far edge of the landscape 20 \ tiles into the screen, and the landscape \ is ten tiles deep, so the centre is 15 \ tiles into the screen BL DrawObject \ Draw the player's ship with the correct \ orientation and position LDRB R10, [R11, #crashedFlag] \ DrawObject sets crashedFlag to -1 if the CMP R10, #0 \ ship is lower down than its shadow, to BLNE LoseLife \ indicate that it has crashed, so jump to \ LoseLife is this is the case \ ****************************************************************************** \ \ Name: MoveAndDrawPlayer (Part 4 of 5) \ Type: Subroutine \ Category: Player \ Summary: Spawn the particles in the exhaust plume if the engine is engaged \ Deep dive: Flying by mouse \ \ ****************************************************************************** LDRB R10, [R11, #fuelBurnRate] \ If both bits 1 and 2 of the fuel burn rate TST R10, #%00000110 \ are clear then the engine is not running, BEQ ship7 \ so jump to ship7 to skip generating an \ exhaust plume ADD R14, R11, #xVelocity \ Set R0 to R2 and R6 to R8 as follows by LDMIA R14, {R0-R2, R6-R8} \ fetching the values we stored in part 2: \ \ R0 = xVelocity \ R1 = yVelocity \ R2 = zVelocity \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust \ \ So (R0, R1, R2) is the player's velocity \ and (R6, R7, R8) is the player's thrust \ vector ADD R3, R0, R6, ASR #7 \ Set (R3, R4, R5) as follows: ADD R4, R1, R7, ASR #7 \ ADD R5, R2, R8, ASR #7 \ [ (xVelocity + xExhaust / 128) / 2 ] MOV R3, R3, ASR #1 \ [ (yVelocity + yExhaust / 128) / 2 ] MOV R4, R4, ASR #1 \ [ (zVelocity + zExhaust / 128) / 2 ] MOV R5, R5, ASR #1 \ \ So this sets (R3, R4, R5) to a vector in \ the direction of the player's velocity \ (so the particles move along with the \ ship) and in the direction of the exhaust \ plume (so that's heading away from the \ engine, in the direction that it's \ pointing), and we halve the result so they \ shoot out of the engine but soon get left \ behind as we blast away ADD R0, R11, #xPlayer \ Set R0 to R2 as follows: LDMIA R0, {R0-R2} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ So (R0, R1, R2) is the player's coordinate SUB R0, R0, R3 \ Set (R0, R1, R2) as follows: SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + (xExhaust / 128) ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + (yExhaust / 128) ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + (zExhaust / 128) ] ADD R2, R2, R8, ASR #7 \ \ So this sets (R0, R1, R2) to the position \ of the player's ship, but a little way in \ the direction of the exhaust plume (so \ that's below the engine in the direction \ that it's pointing), and we also subtract \ the velocity in (R3, R4, R5) because the \ first thing that happens when we process \ the particle in MoveAndDrawParticles is \ to add the velocity, so this cancels that \ out to ensure the particle starts out \ along the line of the exhaust plume \ By this stage we have: \ \ (R0, R1, R2) = particle coordinate \ \ (R3, R4, R5) = particle velocity \ \ We now set the values of R6 to R9 to pass \ to the AddExhaustParticleToBuffer routine 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 R6, #8 \ Set the particle's lifespan counter to 8 \ iterations of the main loop MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 10), \ i.e. -&400000 to +&400000 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 STMFD R13!, {R0-R9} \ Store R0 to R9 on the stack so we can \ fetch this set of values before each call \ to AddExhaustParticleToBuffer \ We now call the AddExhaustParticleToBuffer \ routine eight times if full thrust is \ engaged, or twice if hover mode is being \ used (so the exhaust plume is four times \ denser when full thrust is engaged) BL AddExhaustParticleToBuffer \ Call AddExhaustParticleToBuffer with the \ set of parameters in R0 to R9 to add the \ first exhaust plume particle to the \ particle data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the second particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the third particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fourth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fifth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the sixth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the seventh particle to the particle \ data buffer LDMFD R13!, {R0-R9} \ Fetch the same parameters and add the BL AddExhaustParticleToBuffer \ final particle to the particle data buffer \ ****************************************************************************** \ \ Name: MoveAndDrawPlayer (Part 5 of 5) \ Type: Subroutine \ Category: Player \ Summary: Spawn a bullet particle if the fire button is being pressed \ Deep dive: Collisions and bullets \ \ ****************************************************************************** .ship7 TST R10, #%00000001 \ If bit 0 of the fuel burn rate is clear BEQ ship8 \ then the fire button is not being pressed, \ so jump to ship8 to skip the bullet-firing \ process and return from the subroutine LDR R8, [R11, #currentScore] \ Decrement the current score by one, as we SUB R8, R8, #1 \ are about to fire a bullet STR R8, [R11, #currentScore] ADD R14, R11, #xPlayer \ Set R0 to R5 as follows: LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So (R0, R1, R2) is the player's coordinate \ and (R3, R4, R5) is the player's velocity LDR R6, [R11, #xNoseV] \ Set (R6, R7, R8) to the nose vector from LDR R7, [R11, #yNoseV] \ the rotation matrix, which is the vector LDR R8, [R11, #zNoseV] \ that points out through the ship's nose, \ just like the ship's gun \ \ Let's refer to this gun vector as follows: \ \ R6 = xGun \ R7 = yGun \ R8 = zGun ADD R3, R3, R6, ASR #8 \ Set (R3, R4, R5) as follows: ADD R4, R4, R7, ASR #8 \ ADD R5, R5, R8, ASR #8 \ [ xVelocity + xGun / 256 ] \ [ yVelocity + yGun / 256 ] \ [ zVelocity + zGun / 256 ] \ \ So this sets (R3, R4, R5) to a vector in \ the direction of the player's velocity \ (so the bullet particles move along with \ the ship from which they are fired) and \ then in the direction that the gun is \ pointing (so they leave the barrel in the \ correct direction) SUB R0, R0, R3 \ Set (R0, R1, R2) as follows: SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + (xGun / 128) ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + (yGun / 128) ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + (zGun / 128) ] ADD R2, R2, R8, ASR #7 \ \ So this sets (R0, R1, R2) to the position \ of the player's ship, but a little way in \ the direction of the gun (so the bullet \ fires out of the end of the gun), and we \ also subtract the velocity in (R3, R4, R5) \ because the first thing that happens when \ we process the particle in \ MoveAndDrawParticles is to add the \ velocity, so this cancels that out to \ ensure the particle starts out at the end \ of the gun MOV R6, #20 \ Set the bullet particle's lifespan \ counter to 20 iterations of the main loop MOV R7, #&01BC0000 \ Set bits 18, 19, 20, 21, 23 and 24 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 \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size is big \ * Bit 24 set = explode on hitting ground ORR R7, R7, #&FF \ Set the particle colour to white in bits 0 \ to 7 of the particle flag BL AddBulletParticleToBuffer \ Add a bullet particle to the particle data \ buffer .ship8 LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name: LandOnLaunchpad \ Type: Subroutine \ Category: Player \ Summary: Check to see if the player has landed on the launchpad \ Deep dive: Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ We call this routine from MoveAndDrawPlayer when the player's altitude matches \ that of the launchpad, so this performs additional checks to see if the player \ just landed. \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R1 The y-coordinate of the player's ship \ \ ****************************************************************************** .LandOnLaunchpad ADD R3, R11, #xPlayer \ Fetch the six words at xPlayer as follows: LDMIA R3, {R0-R5} \ \ (R0, R1, R2) = the player's coordinates \ from (xPlayer, yPlayer, zPlayer) \ \ (R3, R4, R5) = the player's velocity \ from (xVelocity, yVelocity, zVelocity) CMP R0, #LAUNCHPAD_SIZE \ If either of xPlayer or zPlayer is >= CMPLO R2, #LAUNCHPAD_SIZE \ LAUNCHPAD_SIZE then we are not over the BHS LoseLife \ launchpad, as the launchpad is defined as \ this part of the map: \ \ 0 <= x = LANDING_SPEED, then: ADD R3, R3, R5 \ CMP R3, #LANDING_SPEED \ |xVelocity| + |yVelocity| + |zVelocity| MOVHS PC, R14 \ \ is greater than the safe landing speed, so \ we can't be performing a safe landing, so \ return from the subroutine \ If we get here then we have landed, as the \ altitude checks were made before this \ routine was called, we know we are above \ the launchpad, and our speed is slow \ enough to land \ \ So now we start refuelling MOV R1, #LAUNCHPAD_Y \ Set R1 to the y-coordinate of the player's \ ship as it sits on the launchpad (which is \ set to the launchpad altitude plus the \ height of the ship's undercarriage) LDR R3, [R11, #fuelLevel] \ Bump up the fuel level by 1/160 of a full ADD R3, R3, #&20 \ tank CMP R3, #&1400 STRLO R3, [R11, #fuelLevel] MOV R3, #0 \ Zero the player's velocity MOV R4, #0 MOV R5, #0 STR R3, [R11, #xVelocity] STR R4, [R11, #yVelocity] STR R5, [R11, #zVelocity] STR R1, [R11, #yPlayer] \ Set the y-coordinate of the player to the \ y-coordinate of the launchpad, so the \ player's ship is resting correctly on the \ pad MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: LoseLifeFromParticleLoop \ Type: Subroutine \ Category: Main loop \ Summary: Lose a life when a crash is detected in the particle processing \ loop \ Deep dive: Collisions and bullets \ \ ****************************************************************************** .LoseLifeFromParticleLoop LDMFD R13!, {R10, R12} \ Remove the R10 and R12 registers that the \ MoveAndDrawParticles put on the stack, \ leaving just the return address \ Fall through into LoseLife to lose a life \ ****************************************************************************** \ \ Name: LoseLife \ Type: Subroutine \ Category: Main loop \ Summary: Display a crash animation when we lose a life and end the game if \ this is our last life \ Deep dive: The main game loop \ Collisions and bullets \ \ ****************************************************************************** .LoseLife MOV R0, #0 \ Set playingGame = 0 to flag that the game STR R0, [R11, #playingGame] \ is no longer being played and that this is \ the crash animation MOV R0, #30 \ Set crashLoopCount = 30 to act as a loop STR R0, [R11, #crashLoopCount] \ the crash animation below MOV R8, #81 \ Set R8 = 81 to use as the size of the \ explosion in AddExplosionToBuffer ADD R0, R11, #xPlayer \ Set (R0, R1, R2) to the coordinate in LDMIA R0, {R0-R2} \ xPlayer SUB R1, R1, #CRASH_CLOUD_Y \ Subtract CRASH_CLOUD_Y from the ship's \ y-coordinate so the explosion occurs just \ above the player's ship (5/16 tile sizes \ above the ship, to be precise) BL AddExplosionToBuffer \ Draw a large explosion in place of the \ player's ship .lose1 \ We now run a cut-down version of the main \ loop to display the crash animation (this \ is like the main loop but without the \ calls to drop rocks from the sky, draw the \ player's ship or update the fuel level) \ We now set up the rotation matrix for the \ rocks, using the main loop counter to \ generate rotation angles that change along \ with the main loop (so the rocks spin at a \ nice steady speed) LDR R0, [R11, #mainLoopCount] \ Set R0 = mainLoopCount << 24 MOV R0, R0, LSL #24 MOV R1, R0, LSL #1 \ Set R1 = mainLoopCount << 25 BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ "angles" given in R0 and R1, which we can \ apply to any rocks we draw in the \ MoveAndDrawParticles routine (as rocks are \ only rotating 3D objects apart from the \ player, and the player calculates its own \ rotation matrix) BL MoveAndDrawParticles \ Move and draw all the particles, such as \ smoke clouds and bullets, into the \ graphics buffers BL DrawObjects \ Draw all the objects, such as trees and \ buildings, into the graphics buffers BL AddTerminatorsToBuffers \ Add terminators to the ends of the \ graphics buffers so we know when to stop \ drawing BL DrawLandscapeAndBuffers \ Draw the landscape and the contents of the \ graphics buffers BL PrintCurrentScore \ Print the number of remaining bullets at \ the left end of the score bar BL SwitchScreenBank \ Switch screen banks and clear the newly \ hidden screen bank to black LDR R0, [R11, #mainLoopCount] \ Increment the main loop counter ADD R0, R0, #1 STR R0, [R11, #mainLoopCount] LDR R0, [R11, #crashLoopCount] \ Decrement the loop counter for the crash SUBS R0, R0, #1 \ animation above STR R0, [R11, #crashLoopCount] BPL lose1 \ Loop back to keep running the crash \ animation until the loop counter runs down LDR R0, [R11, #remainingLives] \ Decrement the number of remaining lives SUBS R0, R0, #1 \ and set the flags accordingly STR R0, [R11, #remainingLives] \ ADD R13, R13, #4 \ Increment the stack pointer by one word so \ we discard the return address from the top \ of the stack, so we rejoin the main loop \ without keeping the return address of the \ subroutine we were in before we jumped \ here (i.e. MoveAndDrawPlayer or \ LandOnLaunchpad) BNE PlacePlayerOnLaunchpad \ If we still have one or more lives left, \ jump to PlacePlayerOnLaunchpad to play the \ next life \ ****************************************************************************** \ \ Name: GameOver \ Type: Subroutine \ Category: Main loop \ Summary: Print a Game Over message and start a new game \ \ ****************************************************************************** .GameOver MOV R0, #112 \ Set the VDU driver screen bank to bank 1 MOV R1, #1 SWI OS_Byte MOV R0, #31 \ Print the following VDU command: SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, halfway down the screen SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 ALIGN MOV R0, #112 \ Set the VDU driver screen bank to bank 2 MOV R1, #2 SWI OS_Byte MOV R0, #31 \ Print the following VDU command: SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, i.e. the same text coordinates as \ the text we printed in screen bank 1 above SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 ALIGN SWI OS_ReadC \ Wait for a key press B StartNewGame \ Jump to StartNewGame to start a brand new \ game \ ****************************************************************************** \ \ Name: graphicsBuffEndAddr2 \ Type: Variable \ Category: Graphics buffers \ Summary: The addresses of the tables containing the graphics buffer \ addresses (same as graphicsBufferEndAddr and graphicsBufferAddr) \ Deep dive: Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffEndAddr2 EQUD graphicsBuffersEnd EQUD graphicsBuffers \ ****************************************************************************** \ \ Name: MoveAndDrawParticles (Part 1 of 4) \ Type: Subroutine \ Category: Particles \ Summary: Process particle movement and draw the particles into the graphics \ buffers, starting with the movement of particles \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ dpar1 The start of the main particle-processing \ loop \ \ ****************************************************************************** .MoveAndDrawParticles STMFD R13!, {R10, R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R10, R11, #particleData \ Set R10 to the address of the particle \ data buffer, which we update as we work \ through the particle data buffer, so this \ points to the data of the particle being \ processed LDR R12, graphicsBuffEndAddr2 \ Set R1 to the address of the table that \ contains the end addresses of the graphics \ buffers \ We now work our way through the particle \ data buffer, processing each particle in \ turn .dpar1 LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ from the particle data buffer at R10, so \ this sets the following: \ \ (R0, R1, R2) = particle coordinate \ \ (R3, R4, R5) = particle velocity \ \ R6 = particle lifespan counter \ \ R7 = particle flags CMP R7, #0 \ If the last word of the data is zero, then LDMEQFD R13!, {R10, R12, PC} \ this is a null terminator and we have \ reached the end of the buffer, so retrieve \ the registers that we stored on the stack \ and return from the subroutine .dpar2 SUBS R6, R6, #1 \ Decrement the particle's lifespan counter \ in R6 BEQ DeleteParticleData \ If R6 is now zero then the particle just \ expired, so jump to DeleteParticleData to \ delete this particle from the particle \ data buffer \ \ DeleteParticleData then jumps back to \ dpar1 to move on to the next particle ADD R0, R0, R3 \ Move the particle by its current velocity ADD R1, R1, R4 \ by adding the particle's velocity vector ADD R2, R2, R5 \ in (R3, R4, R5) to the particle's \ coordinates in (R0, R1, R2) TST R7, #&00100000 \ If bit 20 of the particle flags is set, LDRNE R14, [R11, #gravity] \ add gravity to the velocity's y-coordinate ADDNE R4, R4, R14 \ in R4, so the particle accelerates towards \ the ground TST R7, #&00010000 \ If bit 16 of the particle flags is set, BLNE SetParticleColourToFade \ call SetParticleColourToFade to fade the \ particle colour from white to red, \ according to the particle's age, and store \ the colour in the bottom byte of the \ particle flags (bits 0 to 7) MOV R8, R0 \ Set (R8, R9) = (R0, R2) so we can fetch MOV R9, R2 \ the landscape altitude directly below the \ particle STMFD R13!, {R0-R3} \ Store R0 to R3 on the stack so they don't \ get corrupted by the following call to \ GetLandscapeAltitude BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9) MOV R9, R0 \ Copy the altitude into R9 LDMFD R13!, {R0-R3} \ Retrieve the values of R0 to R3 that we \ stored above TST R7, #&00200000 \ If bit 21 of the particle flags is set BLNE ProcessObjectDestruction \ then the particle can destroy objects that \ it hits, so call ProcessObjectDestruction \ to process this CMP R1, R9 \ If R1 > R9 then the particle is below the BLGT BounceParticle \ level of the landscape, so bounce the \ particle off the ground STMIA R10!, {R0-R7} \ Store the updated particle data in the \ particle data buffer, updating R10 so it \ points to the next particle, ready for the \ next iteration around the loop \ ****************************************************************************** \ \ Name: MoveAndDrawParticles (Part 2 of 4) \ Type: Subroutine \ Category: Particles \ Summary: Draw particles (including rocks) into the graphics buffers \ Deep dive: Particles and particle clouds \ \ ****************************************************************************** LDR R8, [R11, #xCamera] \ Set R0 = R0 - xCamera SUB R0, R0, R8 \ = x - xCamera \ \ So R0 contains the x-coordinate of the \ particle relative to the camera LDR R8, [R11, #zCamera] \ Set R2 = R2 - zCamera SUB R2, R2, R8 \ \ So R2 contains the z-coordinate of the \ particle 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 R8, [R11, #yCamera] \ Set R1 = R1 - yCamera SUB R1, R1, R8 \ = y - yCamera \ \ So R1 contains the y-coordinate of the \ particle relative to the camera CMP R2, #LANDSCAPE_Z \ If the z-coordinate of the particle in R2 BHS dpar1 \ is further into the screen than the \ landscape offset in LANDSCAPE_Z, then it \ is beyond the back of the visible \ landscape, so jump to dpar1 to move on to \ the next particle in the buffer CMP R2, #LANDSCAPE_Z_FRONT \ If the z-coordinate of the particle in R2 BLO dpar1 \ is closer to us than LANDSCAPE_Z_FRONT, \ then it is closer than the front of the \ visible landscape, so jump to dpar1 to \ move on to the next particle in the buffer MOVS R14, R0 \ Set R14 = |R0| RSBMI R14, R0, #0 \ = |particle x-coordinate| CMP R14, #LANDSCAPE_X_HALF \ If the x-coordinate of the particle in R14 BHS dpar1 \ is more than half the x-axis width of the \ landscape to the left or right, then it is \ past the edge of the landscape and is too \ far to be drawn, so jump to dpar1 to move \ on to the next particle in the buffer TST R7, #&00020000 \ If bit 17 of the particle flags is clear BEQ dpar4 \ then this is not a rock, so jump to dpar4 \ to draw the particle and its shadow into \ the graphics buffers in part 4 \ Otherwise this is a rock, so fall through \ into part 3 to process collisions \ ****************************************************************************** \ \ Name: MoveAndDrawParticles (Part 3 of 4) \ Type: Subroutine \ Category: Particles \ Summary: Process rocks by checking for collisions and drawing them as 3D \ objects \ Deep dive: Drawing 3D objects \ Particles and particle clouds \ Collisions and bullets \ \ ****************************************************************************** \ If we get here then the particle is a \ rock, so we need to check whether it has \ hit the player's ship STMFD R13!, {R0-R2} \ Store the rock coordinates in (R0, R1, R2) \ on the stack so we can retrieve them below LDR R14, [R11, #playingGame] \ If playingGame = 0 then this is the crash CMP R14, #0 \ animation, so jump to dpar3 to draw the BEQ dpar3 \ rock, skipping the call to lose a life (as \ the EQ condition is true, which does not \ match BLO) CMP R0, #0 \ Set R0 = |R0| RSBMI R0, R0, #0 \ = |rock x-coordinate| SUBS R2, R2, #LANDSCAPE_Z_MID \ Set R2 = |R2 - LANDSCAPE_Z_MID| RSBMI R2, R2, #0 \ = |rock z-coordinate - 15 tiles| \ \ Because the player's ship is always at \ x-coordinate 0 and the z-coordinate at \ the mid-point of the landscape, this works \ out the rock's coordinate relative to the \ ship (for the x- and z-coordinates) \ \ The rock object is one tile in size along \ each axis, so we can do a check against \ the file size to see if we are being hit \ by the rock CMP R0, #TILE_SIZE \ If either of R0 or R2 is bigger than one CMPLO R2, #TILE_SIZE \ tile size, jump to dpar3 as the rock is BHS dpar3 \ missing us, skipping the call to lose a \ life (as the HS condition is true, which \ does not match BLO) \ The rock is overlapping the player in \ either the x- or z-coordinate, so now we \ need to check its altitude LDR R0, [R11, #yCamera] \ Set R1 = |R1 + yCamera - yPlayer| ADD R1, R1, R0 \ LDR R0, [R11, #yPlayer] \ So R1 contains the y-coordinate of the SUBS R1, R1, R0 \ rock relative to the player, which we can RSBMI R1, R1, #0 \ also test against the tile size to check \ for a collision CMP R1, #TILE_SIZE \ If R1 < TILE_SIZE then the LO condition \ will be true, which will send us to \ LoseLifeFromParticleLoop below to lose a \ life, as the rock has hit us .dpar3 LDMFD R13!, {R0-R2} \ Retrieve the rock coordinates that we \ stored above into (R0, R1, R2) BLO LoseLifeFromParticleLoop \ Jump to LoseLifeFromParticleLoop if the LO \ condition is true, which will only be the \ case if we reached the CMP just before \ dpar3 and R1 < TILE_SIZE LDR R14, objectRockAddr \ Store the address of the rock's object STR R14, [R11, #objectData] \ blueprint in objectData to pass to the \ DrawObject routine ADD R3, R11, #rotationMatrix \ Set R3 to the address of the rock's \ rotation matrix, to pass to DrawObject BL DrawObject \ Draw the rock into the graphics buffers B dpar1 \ Loop back to dpar1 to process the next \ particle in the particle data buffer \ ****************************************************************************** \ \ Name: MoveAndDrawParticles (Part 4 of 4) \ Type: Subroutine \ Category: Particles \ Summary: Draw particles into the graphics buffers \ Deep dive: Particles and particle clouds \ \ ****************************************************************************** .dpar4 \ If we get here then we draw the particle \ and its shadow into the graphics buffers STMFD R13!, {R0-R2, R7} \ Store R0, R1, R2 and R7 on the stack so \ they don't get corrupted by the following \ calls LDR R14, [R11, #yCamera] \ Set R1 = R9 - yCamera SUB R1, R9, R14 \ = landscape altitude - yCamera \ \ We set R9 in part 1 to the altitude of the \ landscape below the particle, so this sets \ R1 to the y-coordinate of the particle's \ shadow relative to the camera MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle's \ shadow in (R0, R1, R2) onto the screen, \ returning the results in (R0, R1) \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleShadowToBuffer \ If the projected coordinates fit onto the \ screen, draw the particle's projected \ shadow into the graphics buffers LDMFD R13!, {R0-R2, R7} \ Retrieve the values of R0, R1, R2 and R7 \ that we stored above MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle in \ (R0, R1, R2) onto the screen, returning \ the results in (R0, R1) \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleToBuffer \ If the projected coordinates fit onto the \ screen, draw the projected particle into \ the graphics buffers LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ for the next particle from the particle \ data buffer at R10 CMP R7, #0 \ If the last word of the data is not zero BNE dpar2 \ then this is a valid particle rather than \ a null terminator, so loop back to dpar2 \ to process the next particle LDMFD R13!, {R10, R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: SetParticleColourToFade \ Type: Subroutine \ Category: Particles \ Summary: Set the flags for a particle whose colour fades from white to red \ over time, to give a white-hot explosion particle that cools down \ Deep dive: Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R6 Particle lifespan counter (i.e. how many \ iterations around the main loop before the \ particle expires) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R7 Particle flags for a fading colour particle \ \ * Bits 0-7 = particle colour \ * 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 \ \ ****************************************************************************** .SetParticleColourToFade STMFD R13!, {R0-R2, R6} \ Store R0, R1, R2 and R6 on the stack so \ they don't get corrupted by the following \ 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 MOV R0, #15 \ Set the red channel to 15 CMP R6, #8 \ If R6 >= 8, set: MOVHS R1, #15 \ MOVLO R1, R6, LSL #1 \ Green channel in R1 = 15 SUBHS R2, R6, #8 \ Blue channel in R2 = (R6 - 8) * 2 MOVHS R2, R2, LSL #1 \ MOVLO R2, #0 \ otherwise set: \ \ Green channel in R1 = R6 * 2 \ Blue channel in R2 = 0 \ \ So particles start out white (R6 > 8) \ with a fading level of blue, until the \ blue disappears entirely (R6 = 8) and \ then the green fades away (R6 < 8) to \ leave a pure red particle (R6 = 0) \ \ So this is a fading particle from white \ to red, so this looks like a burning \ particle from an explosion that cools \ over time \ 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, #&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 LDMFD R13!, {R0-R2, R6} \ Retrieve the registers that we stored on \ the stack above MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: BounceParticle \ Type: Subroutine \ Category: Particles \ Summary: Bounce a particle off the ground \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinates \ \ (R3, R4, R5) Particle velocity \ \ R7 Particle flags \ \ R9 The altitude of the landscape directly below \ the particle \ \ ****************************************************************************** .BounceParticle MOV R1, R9 \ Set R1 to R9, so (R0, R1, R2) now contains \ the coordinates of the point on the \ landscape directly below the particle CMP R9, #SEA_LEVEL \ If the particle is above the sea, jump to BEQ SplashParticleIntoSea \ SplashParticleIntoSea to splash the \ particle into the sea, returning from the \ subroutine using a tail call TST R7, #&00080000 \ If bit 19 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without bouncing or exploding TST R7, #&01000000 \ If bit 24 of the particle flags is set, BNE AddSmallExplosionToBuffer \ jump to AddSmallExplosionToBuffer to \ destroy the particle in a small explosion MOV R3, R3, ASR #1 \ Otherwise we bounce the particle off the MOV R4, R4, ASR #1 \ ground by setting the particle's velocity MOV R5, R5, ASR #1 \ vector to half its previous speed, and in RSB R4, R4, #0 \ the opposite direction in the y-axis MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessObjectDestruction \ Type: Subroutine \ Category: Particles \ Summary: If this particle has hit an object, destroy the object and the \ particle in an explosion, scoring points if it's a bullet \ Deep dive: Particles and particle clouds \ Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) The particle's coordinates \ \ R7 Particle flags \ \ R9 The altitude of the landscape directly below \ the particle \ \ ****************************************************************************** .ProcessObjectDestruction SUB R8, R9, R1 \ Set R8 = altitude - y-coordinate \ \ So R8 contains the vertical distance \ between the particle and the ground CMP R8, #SAFE_HEIGHT \ If R8 is higher than the minimum safe MOVHS PC, R14 \ height for avoiding objects on the ground \ in SAFE_HEIGHT (which is set to 1.5 tile \ sizes) then return from the subroutine as \ the particle is too high off the ground to \ be hitting any objects STMFD R13!, {R2} \ Store R2 on the stack so we can retrieve \ it below STR R14, [R11, #objectType] \ Store the return address in objectType \ (the choice of variable is not important, \ we are just using it as temporary storage \ here) ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R2, R2, #&FF000000 \ Set the bottom three bytes of R2 to zero, \ leaving just the top byte, so we can use \ it in the following ADD R14, R14, R0, LSR #24 \ Set R14 = R14 + (R0 >> 24) + (R2 >> 16) ADD R14, R14, R2, LSR #16 \ \ R0 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R2 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ (R0, R2) LDMFD R13!, {R2} \ Retrieve the value of R2 that we stored \ above, so it contains the particle \ y-coordinate once again LDRB R8, [R14] \ Set R8 to the byte in the object map at \ the coordinate (R0, R2) CMP R8, #&FF \ If the object map entry is &FF, then there LDREQ PC, [R11, #objectType] \ is no object on the map at the particle's \ location, so we haven't hit anything, so \ return from the subroutine by fetching the \ return address that we stored above CMP R8, #12 \ If the object being hit by the particle is BHS AddSmallExplosionToBuffer \ 12 or greater, then the object has already \ been destroyed, so draw a small explosion \ by calling AddSmallExplosionToBuffer, and \ return from the subroutine using a tail \ call ADD R8, R8, #12 \ Otherwise the particle has just hit an STRB R8, [R14] \ undestroyed object, so add 12 to the \ object's type in the object map to denote \ that it has been destroyed TST R7, #&00020000 \ If bit 17 the particle flags is clear then LDREQ R8, [R11, #currentScore] \ the particle doing the hitting is not a ADDEQ R8, R8, #20 \ rock, so it must be a bullet, so increment STREQ R8, [R11, #currentScore] \ the current score by 20 MOV R8, #20 \ Set R8 = 20 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a medium-sized explosion where the \ particle hit B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: AddSmallExplosionToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add a small explosion to the particle data buffer \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Explosion coordinates \ \ R7 Particle flags \ \ ****************************************************************************** .AddSmallExplosionToBuffer MOV R8, #3 \ Set R8 = 3 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a small explosion at the given \ coordinates B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: SplashParticleIntoSea \ Type: Subroutine \ Category: Particles \ Summary: Splash a particle into the sea, creating a spray particle \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinates (on the sea's surface) \ \ R7 Particle flags \ \ ****************************************************************************** .SplashParticleIntoSea TST R7, #&00040000 \ If bit 18 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without splashing TST R7, #&00800000 \ If bit 23 of the particle flags is set, MOVNE R8, #65 \ set the number of spray particles in R8 MOVEQ R8, #4 \ to 65, otherwise set it to 4 SUB R1, R1, #SPLASH_HEIGHT \ Set R1 to a point just above the sea's \ surface (R1 is on the surface, so this \ moves it to SPLASH_HEIGHT above the waves, \ or 1/16 of a tile size) .psea1 BL AddSprayParticleToBuffer \ Add a spray particle to the particle data \ buffer SUBS R8, R8, #1 \ Decrement the particle counter in R8 BNE psea1 \ Loop back until we have added all the \ spray particles \ Fall through into DeleteParticleData to \ delete the particle and return from the \ subroutine, as the particle has now \ crashed into the sea \ ****************************************************************************** \ \ Name: DeleteParticleData \ Type: Subroutine \ Category: Particles \ Summary: Delete a particle from the particle data buffer and move the last \ particle's data down to take its place \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R10 The address in the particle data buffer of \ the particle to delete \ \ ****************************************************************************** .DeleteParticleData LDR R8, [R11, #particleEnd] \ Decrease the address of the end of the SUB R8, R8, #8*4 \ particle data buffer by eight words, so it STR R8, [R11, #particleEnd] \ points to the last particle's data in the \ buffer LDMIA R8, {R0-R7} \ Copy the eight bytes of particle data from STMIA R10, {R0-R7} \ the end of the buffer to the address in \ R10 MOV R7, #0 \ Zero the last word of the last particle's STR R7, [R8, #7*4] \ data, so the last particle in the buffer \ now acts as a null terminator LDR R8, [R11, #particleCount] \ Decrement the particle counter to reflect SUB R8, R8, #1 \ the new number of particles on-screen STR R8, [R11, #particleCount] B dpar1 \ Jump to the start of the main loop in \ MoveAndDrawParticles to process the next \ particle in the particle data buffer \ ****************************************************************************** \ \ Name: AddBulletParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add a bullet particle to the particle data buffer \ Deep dive: Particles and particle clouds \ Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ 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 \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^(32 - R8) \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^(32 - R9) \ \ ****************************************************************************** .AddBulletParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the call to StoreParticleData \ can return properly B StoreParticleData \ Jump to StoreParticleData to store the \ particle in the particle data buffer, \ returning from the subroutine using a tail \ call \ ****************************************************************************** \ \ Name: AddExhaustParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add one of the moving particles in the exhaust plume to the \ particle data buffer \ Deep dive: Particles and particle clouds \ Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ 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 \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^(32 - R8) \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^(32 - R9) \ \ ****************************************************************************** .AddExhaustParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the following call to the \ particle adding routine below can return \ properly B AddMovingParticleToBuffer \ Add a moving particle to the particle data \ buffer that starts off moving along the \ exhaust vector, and with a random element \ being added to its velocity and lifespan \ counter, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: AddStaticParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add a particle to the particle data buffer that starts off static, \ adding a random element to its velocity and lifespan counter \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinate \ \ R6 Particle lifespan counter (i.e. how many \ iterations around the main loop before the \ particle expires) \ \ R7 Particle flags: \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size (big when set) \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^(32 - R8) \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^(32 - R9) \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddStaticParticleToBuffer MOV R4, #0 \ Set R4 = 0 \ Fall into AddRisingParticleToBuffer so we \ pass a velocity of (0, 0, 0) to the \ AddMovingParticleToBuffer routine \ ****************************************************************************** \ \ Name: AddRisingParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add a particle to the particle data buffer that initially drifts \ up, with a random element to its velocity and lifespan counter \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinate \ \ R4 Particle velocity in the y-axis (up-down) \ \ R6 Particle lifespan counter (i.e. how many \ iterations around the main loop before the \ particle expires) \ \ R7 Particle flags: \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size (big when set) \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^(32 - R8) \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^(32 - R9) \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddRisingParticleToBuffer MOV R3, #0 \ Set R3 = 0 MOV R5, #0 \ Set R5 = 0 \ Fall into AddMovingParticleToBuffer with \ a velocity of (0, R4, 0) \ ****************************************************************************** \ \ Name: AddMovingParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Add a moving particle to the particle data buffer, adding a random \ element to its velocity and lifespan counter \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ 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: \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size (big when set) \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^(32 - R8) \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^(32 - R9) \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddMovingParticleToBuffer STMFD R13!, {R0-R1} \ Store R0 and R1 on the stack so we can \ restore them after the following \ We now add a random signed element to the \ particle velocity in (R3, R4, R5), with \ the scale of the random element determined \ by R8 (so a larger R8 adds a smaller \ random element to the velocity) BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R3, R3, R0, ASR R8 \ Set R3 = R3 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R3 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R4, R4, R0, ASR R8 \ Set R4 = R4 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R4 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R5, R5, R0, ASR R8 \ Set R5 = R5 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R5 \ We now add a random positive element to \ the particle's lifespan counter in R6, \ with the scale determined by R9 (so a \ larger R9 adds a smaller random element \ to the particle's lifespan) BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R6, R6, R0, LSR R9 \ Set R6 = R6 + R0 >> R9 \ \ We do not keep the sign in R0, so the \ addition is always positive LDMFD R13!, {R0-R1} \ Retrieve the values of R0 and R1 that we \ stored above \ ****************************************************************************** \ \ Name: StoreParticleData \ Type: Subroutine \ Category: Particles \ Summary: Store the data for a new particle in the particle data buffer \ Deep dive: Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Start and end \ Summary: Initialise the particle data buffer and associated variables \ Deep dive: Particles and particle clouds \ \ ****************************************************************************** .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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Particles \ Summary: Add a spark particle to the particle data buffer that fades from \ white-hot to red over time \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ****************************************************************************** .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 \ 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 \ \ ****************************************************************************** .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 \ 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 \ \ ****************************************************************************** .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 \ Type: Subroutine \ Category: Particles \ Summary: If the score is 800 or more, then randomly drop rocks from the sky \ by spawning them as particles \ \ ****************************************************************************** .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 \ Type: Variable \ Category: 3D objects \ Summary: A table that maps object types to object blueprints \ Deep dive: Placing objects on the map \ Object blueprints \ \ ****************************************************************************** .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) \ 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 \ \ ****************************************************************************** .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) \ Type: Subroutine \ Category: 3D objects \ Summary: Draw the object that we have found on the object map \ Deep dive: Drawing 3D objects \ \ ****************************************************************************** .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) \ Type: Subroutine \ Category: 3D objects \ Summary: Draw a destroyed object that we have found on the object map \ Deep dive: Drawing 3D objects \ \ ****************************************************************************** .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 \ Type: Variable \ Category: 3D objects \ Summary: The address of the object blueprint for the player's ship \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectPlayerAddr EQUD objectPlayer \ ****************************************************************************** \ \ Name: objectRockAddr \ Type: Variable \ Category: 3D objects \ Summary: The address of the object blueprint for a rock \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectRockAddr EQUD objectRock \ ****************************************************************************** \ \ Name: DrawObject (Part 1 of 5) \ Type: Subroutine \ Category: 3D objects \ Summary: Draw a 3D object and its shadow \ Deep dive: Drawing 3D objects \ Object blueprints \ Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: 3D objects \ Summary: Process the object's vertices \ Deep dive: Drawing 3D objects \ Object blueprints \ Collisions and bullets \ \ ****************************************************************************** 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) \ Type: Subroutine \ Category: 3D objects \ Summary: Calculate the visibility of each of the object's faces \ Deep dive: Drawing 3D objects \ Object blueprints \ \ ****************************************************************************** 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) \ Type: Subroutine \ Category: 3D objects \ Summary: Draw the shadow for each of the object's faces \ Deep dive: Drawing 3D objects \ Object blueprints \ \ ****************************************************************************** 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) \ 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 \ \ ****************************************************************************** .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 \ Type: Subroutine \ Category: Score bar \ Summary: Print the current score at the left end of the score bar \ \ ****************************************************************************** .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 \ Type: Subroutine \ Category: Score bar \ Summary: Print a number at a specified text column in the score bar \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Score bar \ Summary: A four-pixel colour word for the colour of the fuel bar \ \ ****************************************************************************** .fuelBarColour EQUD &37373737 \ ****************************************************************************** \ \ Name: sinTableAddr \ Type: Variable \ Category: Maths (Geometry) \ Summary: The address of the sine/cosine lookup table \ \ ****************************************************************************** .sinTableAddr EQUD sinTable \ ****************************************************************************** \ \ Name: arctanTableAddr \ Type: Variable \ Category: Maths (Geometry) \ Summary: The address of the arctan lookup table \ Deep dive: Flying by mouse \ \ ****************************************************************************** .arctanTableAddr EQUD arctanTable \ ****************************************************************************** \ \ Name: squareRootTableAddr \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: The address of the square root lookup table \ Deep dive: Flying by mouse \ \ ****************************************************************************** .squareRootTableAddr EQUD squareRootTable \ ****************************************************************************** \ \ Name: DrawFuelLevel \ Type: Subroutine \ Category: Score bar \ Summary: Draw the bar at the top of the screen showing the current fuel \ level \ \ ****************************************************************************** .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 \ 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 \ \ ****************************************************************************** .graphicsBufferEndAddr EQUD graphicsBuffersEnd \ ****************************************************************************** \ \ Name: graphicsBufferAddr \ 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 \ \ ****************************************************************************** .graphicsBufferAddr EQUD graphicsBuffers \ ****************************************************************************** \ \ Name: MultiplyVectorByMatrix \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Multiply a 3D vector by the rotation matrix in rotationMatrix, if \ the object is a rotating object \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Calculate the dot product of two 3D vectors \ Deep dive: Drawing 3D objects \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: An unused routine that transposes the rotation matrix \ Deep dive: Unused code in Lander \ \ ****************************************************************************** .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 \ 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 \ \ ****************************************************************************** .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 \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Calculate the rotation matrix \ Deep dive: Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Calculate the polar distance \ Deep dive: Flying by mouse \ \ ****************************************************************************** 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 \ ****************************************************************************** \ \ Name: ProjectParticleOntoScreen \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Project a 3D particle coordinate onto the screen \ Deep dive: Particles and particle clouds \ Projecting onto the screen \ \ ------------------------------------------------------------------------------ \ \ This routine projects a 3D coordinate (x, y, z) into screen coordinates, as \ follows: \ \ screen x-coordinate = 160 + x / z \ \ screen y-coordinate = 64 + y / z \ \ The screen coordinate is deemed on-screen when it is within the following \ valid ranges: \ \ screen x is in the range 0 to 319 \ \ screen y is in the range 0 to 238 \ \ and is at less than a 45-degree viewing angle (i.e. within a viewing cone of \ 45 degrees). \ \ The particle is deemed visible if its z-coordinate is less than &80000000, and \ is not projected if it is further away than this. \ \ The y-coordinate is the screen height (256 pixels) minus the two text lines \ that are reserved for the score bar at the top of the screen (16 pixels). \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinates in (x, y, z) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (R0, R1) Projected screen coordinates (x, y) \ \ C flag C flag is set if the particle is off-screen, \ clear if it is on-screen \ \ ****************************************************************************** .ProjectParticleOntoScreen CMN R2, #&80000000 \ If R2 + &80000000 produces a carry, then MOVCS PC, R14 \ the particle is too far away to be \ visible, so return from the subroutine \ with the C flag set MOVS R3, R0 \ Set R3 = |R0| RSBMI R3, R3, #0 \ = |x| MVNMI R5, R3 \ If R0 is negative, set R5 = ~R3 MOVPL R5, R3 \ If R0 is positive, set R5 = R3 MOVS R4, R1 \ Set R4 = |R1| RSBMI R4, R4, #0 \ = |y| MVNMI R6, R4 \ If R1 is negative, set R6 = ~R4 MOVPL R6, R4 \ If R1 is positive, set R6 = R4 ORR R5, R5, R6 \ Set R5 = R5 OR R6 OR R2 ORR R5, R5, R2 \ = |R0| OR |R1| OR R2 ORR R5, R5, #1 \ = |x| OR |y| OR z \ \ And round it up so it is non-zero \ \ So this sets R5 to a number that is \ greater than |x|, |y| and z, so R5 is \ therefore an upper bound on the values of \ all three coordinates \ We now work out how many times we can \ scale up R5 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R6, #0 \ Set R6 = 0 to use as the scale factor in \ the following loop .ppar1 MOVS R5, R5, LSL #1 \ Shift R5 to the left until the top bit is ADDPL R6, R6, #1 \ set, incrementing R6 for each shift so R6 BPL ppar1 \ contains the scale factor we have applied \ to R5 \ \ So this scales R5 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R6 MOV R2, R2, LSL R6 \ We now scale up the updated coordinate MOV R3, R3, LSL R6 \ (|x|, |y|, z) in (R3, R4, R2) by the scale MOV R4, R4, LSL R6 \ factor in R6, as we know the result will \ stay within 32-bit words CMP R3, R2 \ If R3 >= R2 or R4 >= R2, then at least one CMPCC R4, R2 \ of these is true: MOVCS PC, R14 \ \ |x| >= z or |y| >= z \ \ so return from the subroutine with C flag \ set to indicate the particle is off-screen \ \ This is the case when the particle has a \ greater viewing angle then 45 degrees in \ either direction, in which case we don't \ draw the particle on-screen STMFD R13!, {R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R7, R2 \ Set R7 = R2 \ = z \ \ where the value of z is scaled up \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&200 \ Set bit 9 in R14 to act as our shifted \ division bit, so we populate ten bits of \ the result .ppar2 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC ppar2 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #22 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits \ (as the result contains ten bits), so now \ we have: \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above \ \ This is the first part of the algorithm .ppar3 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have: SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC ppar3 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor MOV R5, R5, LSR #24 \ Shift both division results down so we MOV R6, R6, LSR #24 \ only keep the top byte of each result TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set: RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set: \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates: \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set: RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set: \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates: \ \ R1 = 64 + y / z CMP R0, #320 \ Set the C flag if either of these is true: CMPCC R1, #239 \ \ screen x-coordinate in R0 >= 320 \ \ screen y-coordinate in R1 >= 239 \ \ to indicate that the particle is \ off-screen LDMFD R13!, {R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: ProjectVertexOntoScreen \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Project a vertex coordinate from a 3D object onto the screen \ Deep dive: Drawing 3D objects \ Drawing the landscape \ Projecting onto the screen \ \ ------------------------------------------------------------------------------ \ \ This routine projects a 3D coordinate (x, y, z) into screen coordinates, as \ follows: \ \ screen x-coordinate = 160 + x / z \ \ screen y-coordinate = 64 + y / z \ \ The vertex is deemed visible if its z-coordinate is less than &80000000, and \ is not projected if it is further away than this. \ \ This routine differs from the particle projection routine in that it projects \ vertices irrespective of their viewing angle. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1, R2) Particle coordinates in (x, y, z) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (R0, R1) Projected screen coordinates (x, y) \ \ C flag C flag is set if the vertex is too far away \ to draw, clear if it is close enough \ \ ****************************************************************************** .ProjectVertexOntoScreen LDMIA R0, {R0-R2} \ Fetch the coordinates we want to project \ from (R0, R1, R2), which we'll call \ (x, y, z) in the following CMN R2, #&80000000 \ If R2 + &80000000 produces a carry, then MOVCS PC, R14 \ the vertex is too far away to be visible, \ so return from the subroutine with the \ C flag set STMFD R13!, {R5-R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOVS R3, R0 \ Set R3 = |R0| RSBMI R3, R3, #0 \ = |x| MVNMI R5, R3 \ If R0 is negative, set R5 = ~R3 MOVPL R5, R3 \ If R0 is positive, set R5 = R3 MOVS R4, R1 \ Set R4 = |R1| RSBMI R4, R4, #0 \ = |y| MVNMI R6, R4 \ If R1 is negative, set R6 = ~R4 MOVPL R6, R4 \ If R1 is positive, set R6 = R4 ORR R5, R5, R6 \ Set R5 = R5 OR R6 OR R2 ORR R5, R5, R2 \ = |R0| OR |R1| OR R2 ORR R5, R5, #1 \ = |x| OR |y| OR z \ \ And round it up so it is non-zero \ \ So this sets R5 to a number that is \ greater than |x|, |y| and z, so R5 is \ therefore an upper bound on the values of \ all three coordinates \ We now work out how many times we can \ scale up R5 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R6, #0 \ Set R6 = 0 to use as the scale factor in \ the following loop .pver1 MOVS R5, R5, LSL #1 \ Shift R5 to the left until the top bit is ADDPL R6, R6, #1 \ set, incrementing R6 for each shift so R6 BPL pver1 \ contains the scale factor we have applied \ to R5 \ \ So this scales R5 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R6 MOV R2, R2, LSL R6 \ We now scale up the updated coordinate MOV R3, R3, LSL R6 \ (|x|, |y|, z) in (R3, R4, R2) by the scale MOV R4, R4, LSL R6 \ factor in R6, as we know the result will \ stay within 32-bit words CMP R2, R3 \ If R2 < R3 or R2 < R4, then at least one CMPHS R2, R4 \ of these is true: BLO pver4 \ \ |x| > z or |y| > z \ \ so jump to pver4 to deal with this special \ case, when the vertex has a greater \ viewing angle then 45 degrees in either \ direction MOV R7, R2 \ Set R7 = R2 \ = z \ \ where the value of z is scaled up \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&100 \ Set bit 8 in R14 to act as our shifted \ division bit, so we populate nine bits of \ the result .pver2 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver2 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #23 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ (as the result contains nine bits), so \ now we have: \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the \ result \ \ This is the first part of the algorithm .pver3 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have: SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver3 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor MOV R5, R5, LSR #24 \ Shift both division results down so we MOV R6, R6, LSR #24 \ only keep the top byte of each result TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set: RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set: \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates: \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set: RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set: \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates: \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen (this CMN instruction \ is a hacky way of clearing the C flag) LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine .pver4 \ If we get here then: \ \ |x| > z or |y| > z \ \ So the vertex has a greater viewing angle \ then 45 degrees in either direction \ \ The following registers are also set: \ \ (R3, R4, R2) = (|x|, |y|, z) MOV R5, #24 \ Set R5 = 24 CMP R3, R4 \ Set R6 = R3 / 2 MOVHS R6, R3, LSR #1 \ = |x| / 2 MOVLO R6, R3, LSR #1 \ \ The comparison seems to be unnecessary as \ both conditions do the same thing (if the \ shifts used the C flag then this would \ make more sense, but LSR overwrites the C \ flag rather than using it) .pver5 CMP R2, R6 \ Shift R6 to the right and increment R5 ADDLO R5, R5, #1 \ until R2 >= R6, so this scales R6 down, MOV R6, R6, LSR #1 \ counting the scale factor in R5 BLO pver5 \ \ Specifically, this scales |x| / 2 down \ until it is smaller than z MOV R2, R2, LSR #23 \ We now scale down the updated coordinate MOV R3, R3, LSR R5 \ (|x|, |y|, z) in (R3, R4, R2) by the scale MOV R4, R4, LSR R5 \ factor in R5 (for x and y) and by 23 \ places (for z) MOV R7, R2 \ Set R7 = R2 STMFD R13!, {R5} \ Store the scale factor in R5 on the stack \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm, scaling both \ the numerator and denominator up by 24 \ places before doing the division MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&80 \ Set bit 7 in R14 to act as our shifted \ division bit, so we populate eight bits of \ the result MOV R4, R4, LSL #24 \ Scale the numerator and denominator up by MOV R7, R7, LSL #24 \ 24 places to ensure more accuracy .pver6 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver6 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #24 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ (as the result contains eight bits), so \ now we have: \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the MOV R3, R3, LSL #22 \ result MOV R2, R2, LSL #22 \ \ This is the first part of the algorithm .pver7 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have: SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver7 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor LDMFD R13!, {R14} \ Fetch the scale factor that we stored on \ the stack into R14 SUB R14, R14, #23 \ I have no idea what this does, as it RSB R14, R14, #23 \ appears to have no effect at all MOV R5, R5, LSR R14 \ Apply the scale factor in R14 to R5 MOV R6, R6, LSR R14 \ Apply the scale factor in R14+1 to R6 MOV R6, R6, LSR #1 TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set: RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set: \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates: \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set: RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set: \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates: \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen (this CMN instruction \ is a hacky way of clearing the C flag) LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: AddVectors \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: An unused routine that adds two vectors and uses self-modifying \ code to store the results in a specified location \ Deep dive: Unused code in Lander \ \ ****************************************************************************** .vmod1 ADD R4, R11, #xVertexRotated \ Modification code to store the result of \ the vector addition in xVertexRotated .vmod2 ADD R4, R11, #xCoord \ Modification code to restore the result of \ the vector addition to xCoord .vmod3 MOV R4, R12 \ Modification code to store the result of \ the vector addition in R12 .vmod4 ADD R4, R11, #xLandscapeCol \ Modification code to store the result of \ the vector addition in xLandscapeCol .vmod5 ADD R4, R11, #xLandscapeRow \ Modification code to store the result of \ the vector addition in xLandscapeRow .AddVectors STMFD R13!, {R14} \ Store the return address on the stack \ We now modify the following instruction in \ the AddVectorToVertices routine to change \ the way it works: \ \ ADD R3, R11, #xCoord \ \ Interestingly, though, none of the \ modifications above will work properly, as \ they all change R4 rather than R3, so \ presumably the modified subroutine was \ refactored and this unused routine wasn't \ updated \ \ If this routine worked, it would use the \ modifications to change the address where \ we stored the result LDR R1, vmod3 \ Modify the eighth instruction in the STR R1, AddVectorToVertices+28 \ AddVectorToVertices routine to the \ instruction in vmod3 LDMIA R0, {R0-R2} \ Fetch the coordinate from R0 into \ (R0, R1, R2) MOV R3, R12 \ Set R3 = R12 to use as the address of the \ second vector to add BL AddVectorToVertices+8 \ Add the vector (R0, R1, R2) to the vector \ at the address in R3 (by skipping the \ first two instructions in the modified \ routine LDR R1, vmod2 \ Revert the eighth instruction in the STR R1, AddVectorToVertices+28 \ AddVectorToVertices routine to the \ instruction in vmod2 (though note that \ this doesn't actually revert the \ instruction, it corrupts it, for the \ reasons noted above) LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name: AddVectorToVertices \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: Add a vector to the rotated vertex coordinates to get the vertex \ coordinates in 3D space \ \ ------------------------------------------------------------------------------ \ \ Calculate the following: \ \ [ xCoord ] [ R0 ] + [ xVertexRotated ] \ [ yCoord ] = [ R0+4 ] + [ yVertexRotated ] \ [ zCoord ] [ R0+8 ] + [ zVertexRotated ] \ \ This routine is only ever called with R0 = xObject, so this adds the rotated \ vertex coordinate to the object's coordinate, giving the vertex's coordinate \ in the game's coordinate system. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R0 The address of the 3D vector to add in the \ above calculation \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ AddVectorToVertices+8 Enter the routine with R0 to R3 already set \ \ ****************************************************************************** .AddVectorToVertices LDMIA R0, {R0-R2} \ Fetch the coordinate from R0 into \ (R0, R1, R2) ADD R3, R11, #xVertexRotated \ Set R3 to the address of the coordinates \ in xVertexRotated STMFD R13!, {R5-R6} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R3, {R3-R5} \ Fetch the vector at xVertexRotated into \ (R3, R4, R5) ADD R0, R0, R3 \ Set R0 = R0 + R3 \ = R0 + xVertexRotated ADD R1, R1, R4 \ Set R1 = R1 + R4 \ = R0+4 + yVertexRotated ADD R2, R2, R5 \ Set R2 = R2 + R5 \ = R0+8 + zVertexRotated ADD R3, R11, #xCoord \ Store (R0, R1, R2) in (xCoord, yCoord, STMIA R3, {R0-R2} \ zCoord) LDMFD R13!, {R5-R6} \ Retrieve the registers that we stored on \ the stack MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: MultiplyVectorByConstant \ Type: Subroutine \ Category: Maths (Geometry) \ Summary: An unused routine that multiplies a vector by a constant value \ Deep dive: Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R0 The constant value to multiply the vector by \ \ R1 The address of the three-word vector, plus a \ counter word \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ xVertexRotated The result of the multiplication, followed \ by the incremented counter \ \ ****************************************************************************** .MultiplyVectorByConstant STMFD R13!, {R5-R8, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R1, {R1-R3, R14} \ Fetch R1, R2, R3 and R14 from address R1 MOV R8, R0 \ Set R8 = R0 \ We now calculate R5 = R8 * R1 using the \ shift-and-add multiplication algorithm EOR R4, R8, R1 \ Set the sign of the result in R4 TEQ R8, #0 \ Set R8 = 4 * |R8| RSBMI R8, R8, #0 MOVS R8, R8, LSL #2 TEQ R1, #0 \ Set R1 = 2 * |R1| RSBMI R1, R1, #0 MOV R1, R1, LSL #1 AND R8, R8, #&FE000000 \ Zero all but the top byte of R8, ensuring ORR R8, R8, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R5, #0 \ Set R5 = 0 to use for building the sum in \ our shift-and-add multiplication result .vcon1 MOV R1, R1, LSR #1 \ If bit 0 of R1 is set, add R1 to the ADDCS R5, R5, R1 \ result in R5, shifting R1 to the right MOVS R8, R8, LSL #1 \ Shift R8 left by one place BNE vcon1 \ Loop back if R8 is non-zero MOV R5, R5, LSR #1 \ Set R5 = R5 / 2 TEQ R4, #0 \ Apply the sign from R4 to R5 to get the RSBMI R5, R5, #0 \ final result, so: \ \ R5 = R8 * R1 \ = R0 * R1 MOV R8, R0 \ Set R8 = R0 EOR R4, R8, R2 \ Set R6 = R8 * R2 using the same algorithm TEQ R8, #0 \ as above RSBMI R8, R8, #0 \ MOVS R8, R8, LSL #2 \ This is the first part of the algorithm TEQ R2, #0 RSBMI R2, R2, #0 MOV R2, R2, LSL #1 AND R8, R8, #&FE000000 ORR R8, R8, #&01000000 MOV R6, #0 .vcon2 MOV R2, R2, LSR #1 \ This is the second part of the algorithm, ADDCS R6, R6, R2 \ so we now have: MOVS R8, R8, LSL #1 \ BNE vcon2 \ R4 = R8 * R2 MOV R6, R6, LSR #1 \ = R0 * R2 TEQ R4, #0 RSBMI R6, R6, #0 MOV R8, R0 \ Set R8 = R0 EOR R4, R8, R3 \ Set R7 = R8 * R3 using the same algorithm TEQ R8, #0 \ as above RSBMI R8, R8, #0 \ MOVS R8, R8, LSL #2 \ This is the first part of the algorithm TEQ R3, #0 RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R8, R8, #&FE000000 ORR R8, R8, #&01000000 MOV R7, #0 .vcon3 MOV R3, R3, LSR #1 \ This is the second part of the algorithm, ADDCS R7, R7, R3 \ so we now have: MOVS R8, R8, LSL #1 \ BNE vcon3 \ R7 = R8 * R3 MOV R7, R7, LSR #1 \ = R0 * R3 TEQ R4, #0 RSBMI R7, R7, #0 ADD R14, R14, #2 \ Set R14 = R14 + 2 ADD R8, R11, #xVertexRotated \ Store (R5, R6, R7) and the updated R14 in STMIA R8, {R5-R7, R14} \ xVertexRotated LDMFD R13!, {R5-R8, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: randomSeed1 \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: The first random seed for the random number generator \ \ ****************************************************************************** .randomSeed1 EQUD &4F9C3490 \ ****************************************************************************** \ \ Name: randomSeed2 \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: The second random seed for the random number generator \ \ ****************************************************************************** .randomSeed2 EQUD &DA0383CF \ ****************************************************************************** \ \ Name: GetRandomNumbers \ Type: Subroutine \ Category: Maths (Arithmetic) \ Summary: Generate pseudo-random numbers from the random number seeds \ Deep dive: Random numbers \ \ ------------------------------------------------------------------------------ \ \ This algorithm appears in the original 1986 ARM Assembler manual that came \ with Acorn's ARM Evaluation System (it's in section 11.2 on page 96). It also \ appears in later Acorn manuals, such as Acorn Desktop Assembler (release 2), \ where it's on page 186. \ \ Here's the algorithm's description from the manual: \ \ "It is often necessary to generate (pseudo-)random numbers and the most \ efficient algorithms are based on shift generators with exclusive-or feedback \ rather like a cyclic redundancy check generator. Unfortunately the sequence of \ a 32-bit generator needs more than one feedback tap to be maximal length (that \ is 2^32-1 cycles before repetition). A 33-bit shift generator with taps at \ bits 20 and 33 is required. The basic algorithm is newbit := bit33 eor bit20, \ shift left the 33 bit number and put in newbit at the bottom. Then do this for \ all the newbits needed, that is 32 of them. Luckily this can all be done in \ five S cycles." \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R0 A random number \ \ R1 A random number \ \ ****************************************************************************** .GetRandomNumbers STMFD R13!, {R14} \ Store the return address on the stack LDR R0, randomSeed1 \ Set R0 = randomSeed1 LDR R1, randomSeed2 \ Set R1 = randomSeed2 TST R1, R1, LSR #1 \ Set flags on R1 AND (R1 >> 1) MOVS R14, R0, RRX \ Rotate R0 right, incorporating the C flag, \ and store the result in R14, replacing the \ C flag with bit 0 from R0 ADC R1, R1, R1 \ R1 = R1 + R1 + C EOR R14, R14, R0, LSL #12 \ R14 = R14 EOR (R0 << 12) EOR R0, R14, R14, LSR #20 \ R0 = R14 EOR (R14 >> 20) STR R1, randomSeed1 \ Set randomSeed1 = R1 STR R0, randomSeed2 \ Set randomSeed2 = R0 LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name: screenAddr \ Type: Variable \ Category: Drawing the screen \ Summary: 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) \ Deep dive: Screen memory in the Archimedes \ \ ****************************************************************************** .screenAddr EQUD &01FD8000 + 16 * 320 \ ****************************************************************************** \ \ Name: greyColourWords \ Type: Variable \ Category: Drawing the screen \ Summary: An unused table of grey four-pixel colour words \ Deep dive: Unused code in Lander \ \ ****************************************************************************** .greyColourWords EQUD &00000000 EQUD &01010101 EQUD &02020202 EQUD &03030303 EQUD &2C2C2C2C EQUD &2D2D2D2D EQUD &2E2E2E2E EQUD &2F2F2F2F EQUD &D0D0D0D0 EQUD &D1D1D1D1 EQUD &D2D2D2D2 EQUD &D3D3D3D3 EQUD &FCFCFCFC EQUD &FDFDFDFD EQUD &FEFEFEFE EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF \ ****************************************************************************** \ \ Name: greyColourWordsAddr \ Type: Variable \ Category: Drawing the screen \ Summary: The unused address of the unused table of grey four-pixel colour \ words \ Deep dive: Unused code in Lander \ \ ****************************************************************************** .greyColourWordsAddr EQUD greyColourWords \ ****************************************************************************** \ \ Name: Draw1x1ParticleFromBuffer \ Type: Subroutine \ Category: Particles \ Summary: Process the "draw 1x1-pixel specified" command from the graphics \ buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ This command draws a 1x1-pixel particle at the given coordinates in the \ specified colour. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw1x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + ((R1 + R1 << 2) << 6) ADD R3, R3, R1, LSL #6 \ = R3 + ((R1 + 4 * R1) * 64) \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 1x1-pixel particle in the colour \ specified in R7 B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: Draw2x1ParticleFromBuffer \ Type: Subroutine \ Category: Particles \ Summary: Process the "draw 2x1-pixel particle" command from the graphics \ buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ This command draws a 2x1-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the left end of the \ particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw2x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + ((R1 + R1 << 2) << 6) ADD R3, R3, R1, LSL #6 \ = R3 + ((R1 + 4 * R1) * 64) \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 2x1-pixel particle in the colour STRB R7, [R3, #1] \ specified in R7 B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: Draw2x2ParticleFromBuffer \ Type: Subroutine \ Category: Particles \ Summary: Process the "draw 2x2-pixel particle" command from the graphics \ buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ This command draws a 2x2-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the top-left corner of the \ particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw2x2ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + ((R1 + R1 << 2) << 6) ADD R3, R3, R1, LSL #6 \ = R3 + ((R1 + 4 * R1) * 64) \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 2x2-pixel particle in the colour STRB R7, [R3, #1] \ specified in R7 STRB R7, [R3, #320] STRB R7, [R3, #320+1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: Draw3x2ParticleFromBuffer \ Type: Subroutine \ Category: Particles \ Summary: Process the "draw 3x2-pixel particle" command from the graphics \ buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ This command draws a 3x2-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the pixel in the \ bottom-middle of the particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw3x2ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + ((R1 + R1 << 2) << 6) ADD R3, R3, R1, LSL #6 \ = R3 + ((R1 + 4 * R1) * 64) \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3, #-1] \ Draw a 3x2-pixel particle in the colour STRB R7, [R3] \ specified in R7 STRB R7, [R3, #1] STRB R7, [R3, #320-1] STRB R7, [R3, #320] STRB R7, [R3, #320+1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: Draw3x1ParticleFromBuffer \ Type: Subroutine \ Category: Particles \ Summary: Process the "draw 3x1-pixel particle" command from the graphics \ buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ This command draws a 3x1-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the pixel in the middle of \ the particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw3x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + ((R1 + R1 << 2) << 6) ADD R3, R3, R1, LSL #6 \ = R3 + ((R1 + 4 * R1) * 64) \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3, #-1] \ Draw a 3x1-pixel particle in the colour STRB R7, [R3] \ specified in R7 STRB R7, [R3, #1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: DrawTriangleFromBuffer \ Type: Subroutine \ Category: Drawing triangles \ Summary: Process the "draw triangle" command from the graphics buffer \ Deep dive: Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ The seven parameter words for this command match the arguments for the \ DrawTriangle routine: \ \ * Pixel coordinate of corner 1 (x1, y1) \ * Pixel coordinate of corner 2 (x2, y2) \ * Pixel coordinate of corner 3 (x3, y3) \ * Colour \ \ This command draws a triangle of the specified shape and colour. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R9 The address of the seven parameter words in \ the graphics buffer \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleFromBuffer LDMIA R9!, {R0-R5, R8} \ Fetch the seven parameter words from the \ graphics buffer into R0 to R5 and R8, \ incrementing R9 to point to the next \ command in the buffer STMFD R13!, {R9, R12} \ Store R9 and R12 on the stack so they \ don't get corrupted by the following call \ to DrawTriangle BL DrawTriangle \ Draw a triangle at the coordinates \ specified in R0 to R5 and R8 LDMFD R13!, {R9, R12} \ Retrieve the values of R9 and R12 that we \ stored above B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name: DrawParticleToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Draw a large coloured particle into a slightly nearer graphics, \ buffer according to its distance \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This command draws a large coloured particle into a graphics buffer, with the \ buffer being chosen according to the distance of the particle in the z-axis, \ plus one (so this routine draws the particle into a slightly nearer graphics \ buffer than DrawParticleShadowToBuffer, so shadows never overlap their \ corresponding particles). \ \ This command is used for drawing particles, while DrawParticleShadowToBuffer \ is used for drawing their shadows. \ \ The command numbers for drawing large particles are 0 to 8, with higher \ numbers giving smaller particles (so particles are smaller when they are \ further away, as the command number is based on the z-coordinate). This is \ followed by the pixel coordinates, composed into one 32-bit parameter word as \ follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (0-255) (0-255) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) The pixel coordinate of the particle \ \ R7 The particle colour \ \ R8 The 3D z-coordinate of the particle \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ (i.e. graphicsBuffersEnd) \ \ ****************************************************************************** .DrawParticleToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved RSB R14, R8, #LANDSCAPE_Z \ Set R14 = landscape offset - R8 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant particles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa (so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer) ADD R14, R14, #TILE_SIZE \ Add the size of one tile to R14 so we draw \ the particle one tile nearer than we would \ otherwise (i.e. one buffer nearer) BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our particle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R8, R8, LSR #25 \ Scale R8 from a 3D z-coordinate into a CMP R8, #8 \ number between 0 and 8, which we can use MOVHS R8, #8 \ as the drawing command number, as drawing \ commands 0 to 8 draw particles with higher \ numbers giving smaller particles (see \ bufferJump for a full list of drawing \ commands) AND R7, R7, #&FF \ Encode the two pixel coordinates in R0 and ADD R1, R1, R7, LSL #12 \ R1 and the colour in R7 into one parameter ADD R1, R1, R0, LSL #20 \ word in R1, by inserting the value of R0 \ into bits 20-31 of R1 and the value of R7 \ into bits 12-19 STR R8, [R9], #4 \ Store the drawing command number in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R1, [R9], #4 \ Store the parameter word in R1 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: DrawParticleShadowToBuffer \ Type: Subroutine \ Category: Particles \ Summary: Draw a small black particle into the correct graphics buffer, \ according to its distance \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This command draws a small black particle into a graphics buffer, with the \ buffer being chosen according to the distance of the particle in the z-axis. \ \ This command is used for drawing particle shadows, while the particles \ themselves are drawn by DrawParticleToBuffer. \ \ The command numbers for drawing small particles are 9 to 17, with higher \ numbers giving smaller particles (so particles are smaller when they are \ further away, as the command number is based on the z-coordinate). This is \ followed by the pixel coordinates, composed into one 32-bit parameter word as \ follows: \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ (0-319) (always 0) (0-255) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) The pixel coordinate of the particle \ \ R8 The 3D z-coordinate of the particle \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ (i.e. graphicsBuffersEnd) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawParticleShadowToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved RSB R14, R8, #LANDSCAPE_Z \ Set R14 = landscape offset - R8 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant particles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa (so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer) BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our particle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R8, R8, LSR #25 \ Scale R8 from a 3D z-coordinate into a CMP R8, #8 \ number between 9 and 17, which we can use MOVHS R8, #8 \ as the drawing command number, as drawing ADD R8, R8, #9 \ commands 9 to 17 draw shadow particles \ with higher numbers giving smaller \ particles (see bufferJump for a full list \ of drawing commands) ADD R1, R1, R0, LSL #20 \ Encode the two pixel coordinates in R0 and \ R1 into one parameter word in R1, by \ inserting the value of R0 into bits 20-31 \ of R1 \ \ The colour in bits 12-19 is left at zero, \ so the particle is always black STR R8, [R9], #4 \ Store the drawing command number in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R1, [R9], #4 \ Store the parameter word in R1 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: DrawTriangleShadowToBuffer \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a triangle shadow into the correct graphics buffer, according \ to its distance \ Deep dive: Drawing 3D objects \ Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ This command draws a triangle into a graphics buffer, with the buffer being \ chosen according to the distance of the triangle in the z-axis. \ \ This command is used for drawing triangle shadows, while the triangles \ themselves are drawn by DrawTriangleToBuffer. It is always called with a \ colour value of 0 in R8, so shadows are always black. \ \ The command number for drawing a triangle is 18, followed by seven parameter \ words that match the arguments for the DrawTriangle routine: \ \ * Command number (18) \ \ * Pixel coordinate of corner 1 (x1, y1) \ \ * Pixel coordinate of corner 2 (x2, y2) \ \ * Pixel coordinate of corner 3 (x3, y3) \ \ * Colour \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) Pixel coordinate of corner 1 \ \ (R2, R2) Pixel coordinate of corner 2 \ \ (R4, R5) Pixel coordinate of corner 4 \ \ R8 Colour (always 0 for black) \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ (i.e. graphicsBuffersEnd) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleShadowToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #zObject] \ Set R14 to the z-coordinate of the \ triangle RSB R14, R14, #LANDSCAPE_Z \ Set R14 = landscape offset - R14 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant triangles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa (so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer) CMP R14, #LANDSCAPE_Z_BEYOND \ If R14 is a z-distance beyond the back of MOVHS R14, #LANDSCAPE_Z_DEPTH \ the visible landscape, cap it to the depth \ of the landscape so it fits into the range \ of graphics buffers (as there is one \ buffer for each tile row, going into the \ screen) BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our triangle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R7, #18 \ The command number for drawing a triangle STR R7, [R9], #4 \ is 18, so we store a value of 18 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer (see bufferJump for a full list of \ drawing commands) STMIA R9!, {R0-R5, R8} \ Store the three pixel coordinates from R0 \ to R5, and then the colour in R8, in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: DrawTriangleToBuffer \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a coloured triangle into a slightly nearer graphics buffer, \ according to its distance \ Deep dive: Drawing 3D objects \ Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ This command draws a triangle into a graphics buffer, with the buffer being \ chosen according to the distance of the triangle in the z-axis, plus one (so \ this routine draws the triangle into a slightly nearer graphics buffer than \ DrawTriangleShadowToBuffer, so shadows never overlap their corresponding \ triangles). \ \ This command is used for drawing triangles, while DrawTriangleShadowToBuffer \ is used to draw their shadows. \ \ The command number for drawing a triangle is 18, followed by seven parameter \ words that match the arguments for the DrawTriangle routine: \ \ * Command number (18) \ \ * Pixel coordinate of corner 1 (x1, y1) \ \ * Pixel coordinate of corner 2 (x2, y2) \ \ * Pixel coordinate of corner 3 (x3, y3) \ \ * Colour \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) Pixel coordinate of corner 1 \ \ (R2, R2) Pixel coordinate of corner 2 \ \ (R4, R5) Pixel coordinate of corner 4 \ \ R8 Colour \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ (i.e. graphicsBuffersEnd) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #zObject] \ Set R14 to the z-coordinate of the \ triangle RSB R14, R14, #LANDSCAPE_Z \ Set R14 = landscape offset - R14 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant triangles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa (so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer) ADD R14, R14, #TILE_SIZE \ Add the size of one tile to R14 so we draw \ the triangle one tile nearer than we would \ otherwise (i.e. one buffer nearer) CMP R14, #LANDSCAPE_Z_BEYOND \ If R14 is a z-distance beyond the back of MOVHS R14, #LANDSCAPE_Z_DEPTH \ the visible landscape, cap it to the depth \ of the landscape so it fits into the range \ of graphics buffers (as there is one \ buffer for each tile row, going into the \ screen) BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our triangle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R7, #18 \ The command number for drawing a triangle STR R7, [R9], #4 \ is 18, so we store a value of 18 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer (see bufferJump for a full list of \ drawing commands) STMIA R9!, {R0-R5, R8} \ Store the three pixel coordinates from R0 \ to R5, and then the colour in R8, in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: bufferJump \ Type: Variable \ Category: Graphics buffers \ Summary: The jump table for drawing commands that we store in the graphics \ buffers \ Deep dive: Depth-sorting with the graphics buffers \ \ ****************************************************************************** .bufferJump EQUD Draw3x2ParticleFromBuffer \ Drawing commands 0 to 8 draw large EQUD Draw3x2ParticleFromBuffer \ particles (for particles) EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw2x2ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw1x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer \ Drawing commands 9 to 17 draw small EQUD Draw3x1ParticleFromBuffer \ particles (for shadows) EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw1x1ParticleFromBuffer EQUD DrawTriangleFromBuffer \ Drawing command 18 draws a triangle EQUD TerminateGraphicsBuffer \ Drawing command 19 terminates the buffer \ ****************************************************************************** \ \ Name: DrawGraphicsBuffer \ Type: Subroutine \ Category: Graphics buffers \ Summary: Draw the contents of the specified graphics buffer \ Deep dive: Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This routine draws the contents of a specified graphics buffer. There are 11 \ buffers that are used to layer the game's graphics correctly according to an \ object's distance. This routine draws the contents of a specific buffer onto \ the screen. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R0 The number of the graphics buffer to draw \ (0 to 10) \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ DrawNextFromGraphicsBuffer Process the next command from the graphics \ buffer \ \ TerminateGraphicsBuffer Terminate drawing from the graphics buffer \ by returning from the subroutine \ \ ****************************************************************************** .DrawGraphicsBuffer STMFD R13!, {R6-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R1, graphicsBufferEndAddr \ Set R1 to the address of the table that \ contains the end addresses of the graphics \ buffers LDR R9, [R1, R0, LSL #2] \ Set R9 to the R0-th entry from the \ graphicsBuffersEnd table, which contains \ the end address for graphics buffer R0 LDR R12, screenAddr \ Set R12 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 .DrawNextFromGraphicsBuffer LDR R0, [R9], #4 \ Fetch the address of the next free byte in \ the graphics buffer (which is the same as \ the end address of the buffer) from the \ graphicsBuffersEnd table, put it into R0 \ and increment R9 to point to the next \ buffer's end address ] labOffset = P% + 8 - bufferJump \ Set labOffset to the offset back to the \ bufferJump table from the next instruction [ OPT pass% ADD R0, PC, R0, LSL #2 \ Jump to the address in entry R0 in the LDR PC, [R0, #-labOffset] \ bufferJump table .TerminateGraphicsBuffer LDMFD R13!, {R6-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: AddTerminatorsToBuffers \ Type: Subroutine \ Category: Graphics buffers \ Summary: Add terminators to the ends of the graphics buffers so we know \ when to stop drawing \ Deep dive: Depth-sorting with the graphics buffers \ \ ****************************************************************************** .AddTerminatorsToBuffers STMFD R13!, {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 LDR R14, graphicsBufferAddr \ Set R14 to the address of the table that \ contains the start addresses of the \ graphics buffers MOV R0, #TILES_Z \ We are going to loop through all of the \ graphics buffers, so set a counter in R0 \ to the number of tile corners in the \ visible landscape, going back to front (as \ there is one graphics buffer for each \ corner row) MOV R1, #19 \ The command number for terminating a \ graphics buffer is 19, so put this in R1 \ (see bufferJump for a full list of drawing \ commands) .term1 LDR R2, [R12, R0, LSL #2] \ Set R2 to the end address of graphics \ buffer number R0 STR R1, [R2] \ Store the termination command number at \ the end of the buffer LDR R2, [R14, R0, LSL #2] \ Reset the graphicsBufferEnd entry for this STR R2, [R12, R0, LSL #2] \ buffer back to the start address from the \ graphicsBuffer table, so when we draw the \ next frame, we fill up the graphics \ buffers from the start again SUBS R0, R0, #1 \ Decrement the loop counter BPL term1 \ Loop back until we have terminated all 12 \ graphics buffers LDMFD R13!, {R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: screenBank2Addr \ Type: Variable \ Category: Drawing the screen \ Summary: The screen address for the start of the 17th pixel line in screen \ bank 0 (i.e. the line just below the two rows of text) \ Deep dive: Screen memory in the Archimedes \ \ ****************************************************************************** .screenBank2Addr EQUD &01FD8000 + 320 * 16 \ ****************************************************************************** \ \ Name: screenBank1Addr \ Type: Variable \ Category: Drawing the screen \ Summary: The screen address for the start of the 17th pixel line in screen \ bank 1 (i.e. the line just below the two rows of text) \ Deep dive: Screen memory in the Archimedes \ \ ****************************************************************************** .screenBank1Addr EQUD &01FEC000 + 320 * 16 \ ****************************************************************************** \ \ Name: screenBankNumber \ Type: Variable \ Category: Drawing the screen \ Summary: The number of the current screen bank (0 or 1) \ Deep dive: Screen memory in the Archimedes \ \ ****************************************************************************** .screenBankNumber EQUD 0 \ Stores the details of the current bank, as \ follows: \ \ * 0 = draw bank 1, show bank 2 \ \ * 1 = draw bank 2, show bank 1 \ ****************************************************************************** \ \ Name: divisionTableAddr \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: The address of the division lookup table \ \ ****************************************************************************** .divisionTableAddr EQUD divisionTable \ ****************************************************************************** \ \ Name: SwitchScreenBank \ Type: Subroutine \ Category: Drawing the screen \ Summary: Switch screen banks and clear the newly hidden screen bank to \ black \ Deep dive: Screen memory in the Archimedes \ \ ****************************************************************************** .SwitchScreenBank LDRB R0, screenBankNumber \ Flip the current screen bank number in EORS R0, R0, #1 \ screenBankNumber between bank 0 and bank 1 STRB R0, screenBankNumber \ which means: \ \ * 0 = draw bank 1, show bank 2 \ \ * 1 = draw bank 2, show bank 1 LDRNE R1, screenBank2Addr \ Set screenAddr as follows: LDREQ R1, screenBank1Addr \ STR R1, screenAddr \ * If screenBankNumber = 0, set it to the \ address of bank 2 in screenBank2Addr \ \ * If screenBankNumber = 1, set it to the \ address of bank 1 in screenBank1Addr \ \ So screenAddr points to the screen that is \ no longer being shown, i.e. the bank that \ we should now draw into TEQ R0, #0 \ Set R1 = screenBankNumber + 1 MOVEQ R1, #1 \ MOVNE R1, #2 \ So this sets R1 as follows: \ \ * If screenBankNumber = 0, R1 = 1 \ \ * If screenBankNumber = 1, R1 = 2 \ \ So R1 is the number of the opposite bank \ to the one in screenAddr, i.e. the bank \ that we should now show on-screen MOV R0, #113 \ Set the display hardware to display the SWI OS_Byte \ screen bank in R1, so the correct bank is \ shown on-screen \ \ This call returns the old bank number in \ R1, so R1 now contains the number of the \ screen bank that is not being displayed MOV R0, #112 \ Set the VDU driver screen bank to the bank SWI OS_Byte \ number in R1, so this directs VDU calls \ (such as the text routines that update the \ score bar) so they update the hidden \ screen bank MOV R0, #19 \ Wait for the vertical sync SWI OS_Byte \ We now clear the screen bank that isn't \ being shown, i.e. the bank at screenAddr STMFD R13!, {R9, R11-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R0, #0 \ Set R0 to R9 to zero so we can use them to MOV R1, #0 \ clear the screen bank to black (as black MOV R2, #0 \ is represented by zero in screen memory) MOV R3, #0 MOV R4, #0 MOV R5, #0 MOV R6, #0 MOV R7, #0 MOV R8, #0 MOV R9, #0 LDR R11, screenAddr \ Set R11 = screenAddr \ \ So R11 contains the address of the screen \ bank we want to clear, pointing just below \ the two lines of text at the top of the \ screen ADD R12, R11, #&12C00 \ Set R12 = screenAddr + 320 * 240 \ \ So R12 points to the end of screen memory \ for the bank at screenAddr, as the address \ in R11 skips the first 16 pixel rows of \ the 320 * 256 screen \ We now zero screen memory from R12 down to \ R11, which clears the screen bank that we \ are no longer showing on-screen .bank1 STMDB R12!, {R0-R9} \ Zero 40 x 32-bit words of screen memory, STMDB R12!, {R0-R9} \ decreasing the address in R12 as we go STMDB R12!, {R0-R9} STMDB R12!, {R0-R9} CMP R12, R11 \ If R12 hasn't yet reached the start of the BNE bank1 \ bank's screen memory in R11, loop back to \ keep zeroing screen memory until it is all \ cleared to black, leaving just the two \ lines of text at the top LDMFD R13!, {R9, R11-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: DrawQuadrilateral \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a quadrilateral (i.e. two triangles) \ Deep dive: Drawing the landscape \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) Pixel coordinate of corner 1 \ \ (R2, R3) Pixel coordinate of corner 2 \ \ (R4, R5) Pixel coordinate of corner 3 \ \ (R6, R7) Pixel coordinate of corner 3 \ \ R8 Colour \ \ ****************************************************************************** .DrawQuadrilateral STMFD R13!, {R2-R7, R9-R12, R14} \ Store R2 to R7 on the stack, as well as \ the registers that we want to use on the \ stack so they can be preserved BL DrawTriangle \ Draw a triangle in colour R8 with the \ following corner coordinates: \ \ (R0, R1) \ (R2, R3) \ (R4, R5) LDMFD R13!, {R0-R5} \ Set R0 to R5 to the original values of \ R2 to R7, to (R0, R2) is the pixel \ coordinate of corner 2, and so on BL DrawTriangle \ Draw a triangle in colour R8 with the \ following corner coordinates, in terms \ of the original arguments to the routine: \ \ (R2, R3) \ (R4, R5) \ (R6, R7) \ \ So overall this draws a quadrilateral in \ colour R8 with corners at: \ \ (R0, R1) \ (R2, R3) \ (R4, R5) \ (R6, R7) LDMFD R13!, {R9-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: DrawTriangle (Part 1 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a triangle, starting by ordering the coordinates and jumping \ to the relevant part of the routine \ Deep dive: Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (R0, R1) Pixel coordinate of corner 1 in (x1, y1) \ \ (R2, R3) Pixel coordinate of corner 2 in (x2, y2) \ \ (R4, R5) Pixel coordinate of corner 3 in (x3, y3) \ \ R8 Colour \ \ ****************************************************************************** .DrawTriangle CMP R0, #320 \ Check to see whether any of the triangle CMPLO R1, #239 \ corners are off-screen, in which case at CMPLO R2, #320 \ least one of x1 to x3 will be >= 320, or CMPLO R3, #239 \ at least one of y1 to y3 will be >= 239 CMPLO R4, #320 \ (the y-coordinate is checked against 239 CMPLO R5, #239 \ rather than 255 as the top 16 pixel lines \ of the 320x256 screen are taken up by the \ score bar) \ \ This also catches any negative pixel \ coordinates, as they will be treated as \ extremely large positive values in the \ comparisons (as the LO condition is used \ for unsigned comparisons) BHS trin23 \ If any of the corners are off-screen, jump \ to part 6 to draw a clipped triangle STMFD R13!, {R14} \ Store the return address on the stack LDR R12, screenAddr \ Set R12 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 \ We start by ordering the triangle corners \ by y-coordinate, so (x1, y1) is the \ furthest down the screen with the highest \ y-coordinate, followed by (x2, y2) then \ (x3, y3), at the same or higher level, and \ in that order CMP R1, R3 \ If R1 >= R3, jump to trin1 to skip the BHS trin1 \ following, as y1 >= y2 already MOV R6, R0 \ Swap (x1, y1) and (x2, y2), so we now have MOV R0, R2 \ y1 >= y2 MOV R2, R6 MOV R6, R1 MOV R1, R3 MOV R3, R6 .trin1 CMP R1, R5 \ If R1 >= R5, jump to trin2 to skip the BHS trin2 \ following, as y1 >= y3 already MOV R6, R0 \ Swap (x1, y1) and (x3, y3), so we now have MOV R0, R4 \ y1 >= y3 MOV R4, R6 MOV R6, R1 MOV R1, R5 MOV R5, R6 .trin2 CMP R3, R5 \ If R3 >= R5, jump to trin3 to skip the BHS trin3 \ following, as y2 >= y3 already MOV R6, R2 \ Swap (x2, y2) and (x3, y3), so we now have MOV R2, R4 \ y2 >= y3 MOV R4, R6 MOV R6, R3 MOV R3, R5 MOV R5, R6 \ So the triangle coordinates are ordered \ like this on-screen: \ \ (x3, y3) \ (x2, y2) \ (x1, y1) .trin3 ADD R11, R1, R1, LSL #2 \ Set R11 = R1 + R1 * 4 \ = 5 * y1 ADD R12, R12, R11, LSL #6 \ Set R12 = R12 + R11 * 64 \ = screenAddr + 5 * y1 * 64 \ = screenAddr + 320 * y1 \ \ So R12 is the screen address of the start \ of the pixel row containing (x1, y1), at \ the bottom of the triangle on-screen \ ****************************************************************************** \ \ Name: DrawTriangle (Part 2 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Calculate the slope of (x1, y1) to (x2, y2) \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope of the first \ side of the triangle, from (x1, y1) to \ (x2, y2), as follows: \ \ R6 = (y1 - y2) / (x2 - x1) SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2 BEQ trin16 \ If (x1, y1) and (x2, y2) share the same \ y-coordinate then the triangle has a \ horizontal edge between (x1, y1) and \ (x2, y2), so jump to part 5 to process \ this special case STMFD R13!, {R4-R5} \ Store the third corner's coordinates in \ (x3, y3) on the stack to we can retrieve \ them later SUBS R14, R2, R0 \ Set R14 = R2 - R0 \ = x2 - x1 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ x-coordinate between the two points \ \ This will be positive if the line from \ (x1, y1) to (x2, y2) slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R2, R0 \ If R14 is negative, set R14 = x1 - x2, so \ we have the following: \ \ R14 = |x2 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin4 \ \ R14 = |x2 - x1| >= 64 \ \ R9 = (y1 - y2) >= 64 \ \ so jump to trin4 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R6 = R9 / R14 \ \ = (y1 - y2) / |x2 - x1| B trin6 \ Jump to trin6 to skip the following and \ keep going .trin4 \ If we get here then at least one of these \ is true: \ \ R14 = |x2 - x1| >= 64 \ \ R9 = (y1 - y2) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin5 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R6) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin5 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R6 = R9 / R14 \ \ = (y1 - y2) / |x2 - x1| .trin6 CMP R2, R0 \ If R2 - R0 < 0 then: RSBMI R6, R6, #0 \ \ x2 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation: \ \ R6 = (y1 - y2) / (x2 - x1) \ \ So R6 contains the slope of the first side \ of the triangle, from (x1, y1) to (x2, y2) \ ****************************************************************************** \ \ Name: DrawTriangle (Part 3 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Calculate the slope of (x1, y1) to (x3, y3) \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope for the second \ side of the triangle, from (x1, y1) to \ (x3, y3), as follows: \ \ R7 = (y1 - y3) / (x3 - x1) SUBS R9, R1, R5 \ Set R9 = R1 - R5 \ = y1 - y3 SUBS R14, R4, R0 \ Set R14 = R4 - R0 \ = x3 - x1 RSBMI R14, R4, R0 \ If R14 is negative, set R4 = x1 - x3, so \ we have the following: \ \ R14 = |x3 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin7 \ \ R14 = |x3 - x1| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ so jump to trin7 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R7 = R9 / R14 \ \ = (y1 - y3) / |x3 - x1| B trin9 \ Jump to trin9 to skip the following and \ keep going .trin7 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x1| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin8 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R7) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin8 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R7 = R9 / R14 \ \ = (y1 - y3) / |x3 - x1| .trin9 CMP R4, R0 \ If R4 - R0 < 0 then: RSBMI R7, R7, #0 \ \ x3 - x1 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation: \ \ R7 = (y1 - y3) / (x3 - x1) \ \ So R7 contains the slope of the second \ side of the triangle, from (x1, y1) to \ (x3, y3) \ ****************************************************************************** \ \ Name: DrawTriangle (Part 4 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a triangle that isn't clipped and has a sloping first side \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ By this point we have the following: \ \ (R0, R1) = (x1, y1) \ \ (R2, R3) = (x2, y2) \ \ (x3, y3) is on the stack \ \ R6 = (y1 - y2) / (x2 - x1) \ = slope of (x1, y1) to (x2, y2) \ \ R7 = (y1 - y3) / (x3 - x1) \ = slope of (x1, y1) to (x3, y3) \ \ R12 = screen address of the start of the \ pixel row containing (x1, y1) \ \ (x1, y1) is the point lowest down the \ screen and (x3, y3) is the highest up the \ screen, with (x2, y2) the point in the \ middle (in terms of y-coordinate) \ \ We now draw the triangle in two parts, \ effectively slicing the triangle in half \ with a horizontal line at y-coordinate y2, \ leaving two triangles to draw: \ \ 1. The triangle from (x1, y1) at the \ bottom up to the horizontal line with \ (x2, y2) at one end \ \ 2. The triangle from the horizontal line \ with (x2, y2) at one end, up to \ (x3, y3) at the top \ \ We start at the bottom of the triangle, at \ (x1, y1), and step upwards by one pixel \ row at a time, drawing a horizontal line \ between the two sides, until we reach the \ level of (x2, y2) \ \ As we step up each pixel row, we calculate \ the x-coordinates of each row we draw by \ adding the slopes in R6 and R7 \ \ We store the x-coordinates of the current \ horizontal line in R4 and R5, so these are \ the registers we update with the slope \ values SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y1 and y2 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw ORR R4, R4, #&8000 \ Set R5 to x1, with the top bit of its MOV R5, R4 \ fractional part set (so that's 0.5) \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw CMP R6, R7 \ If R6 > R7, swap R6 and R7, so we know MOVGT R14, R6 \ that R6 <= R7, i.e. R6 contains the side MOVGT R6, R7 \ with the lesser slope, which will be the MOVGT R7, R14 \ slope along the left edge of the triangle .trin10 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row ADD R11, R12, R4, LSR #16 \ Set R11 = R12 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOV R10, R5, LSR #16 \ Set R10 = R5 >> 16 - R4 >> 16 SUBS R10, R10, R4, LSR #16 \ = x-coordinate of right edge \ - x-coordinate of left edge \ \ So R10 contains the length of the line \ from the left edge to the right edge BLPL DrawHorizontalLine \ If R10 is positive then we draw a line \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 SUB R12, R12, #320 \ Subtract 320 from R12 so that R12 now \ points to the start of the pixel row above \ the one we just drew SUBS R9, R9, #1 \ Decrement the pixel row counter in R9, \ which contains the number of pixel rows \ between y1 and y2 BNE trin10 \ Loop back until we have drawn all the \ horizontal lines in the first part of the \ triangle, from (x1, y1) at the bottom, up \ to the level of (x2, y2) \ So now we need to draw the rest of the \ triangle, from (x2, y2) up to (x3, y3) LDMFD R13!, {R0-R1} \ Fetch the coordinates for the third point \ (x3, y3) from the stack and into (R0, R1) SUBS R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 LDMEQIA R13!, {PC} \ If R9 is zero then (x2, y2) and (x3, y3) \ are at the same y-coordinate, so there is \ nothing to draw in the top part of the \ triangle, so return from the subroutine as \ we are done \ We now calculate the slope for the third \ side of the triangle, from (x2, y2) to \ (x3, y3), as follows: \ \ R14 = (y2 - y3) / (x3 - x2) SUBS R14, R0, R2 \ Set R14 = R0 - R2 \ = x3 - x2 RSBMI R14, R0, R2 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following: \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin11 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ so jump to trin11 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R11, [R10, R9, LSL #2] \ Set R11 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R11 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| B trin13 \ Jump to trin13 to skip the following and \ keep going .trin11 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ We now calculate R11 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R11, #0 \ Set R11 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin12 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R11, R11, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R11) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin12 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R11 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| .trin13 CMP R0, R2 \ If R0 - R2 < 0 then: RSBMI R11, R11, #0 \ \ x3 - x2 < 0 \ \ so negate R11 to give R11 the correct sign \ for the following calculation: \ \ R11 = (y2 - y3) / (x3 - x2) \ \ So R11 contains the slope of the third \ side of the triangle, from (x2, y2) to \ (x3, y3) \ By this point we have the following: \ \ (R0, R1) = (x3, y3) \ \ (R2, R3) = (x2, y2) \ \ R4 = x-coordinate of the left edge for \ the last line that we drew \ \ R5 = x-coordinate of the right edge for \ the last line that we drew \ \ R7 = (y1 - y3) / (x3 - x1) \ = slope of (x1, y1) to (x3, y3) \ \ R11 = (y2 - y3) / (x3 - x2) \ = slope of (x2, y2) to (x3, y3) \ \ R12 = screen address of the start of the \ pixel row containing (x2, y2) SUB R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 SUBS R14, R2, R4, LSR #16 \ Set: \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ So this will be zero if (x2, y2) is at the \ left edge of the horizontal line SUBNES R10, R2, R5, LSR #16 \ If the above is non-zero, set: \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ So this will be zero if (x2, y2) is at the \ right edge of the horizontal line BNE trin15 \ If both of the above are non-zero, jump to \ trin15, as (x2, y2) doesn't match either \ edge of the horizontal line we just drew CMP R2, R4, LSR #16 \ Set the flags on R2 - R4 >> 16 MOVEQ R6, R11 \ If R2 = R4 >> 16, then x2 = left edge, so: BICEQ R4, R4, #&FF00 \ so x2 must be at the left edge, so we set BICEQ R4, R4, #&00FF \ the slope of the left edge in R6 to R11, ORREQ R4, R4, #&8000 \ as it's the left edge that is changing \ slope as we move into the top part of the \ triangle: \ \ R6 = R11 \ = slope of (x2, y2) to (x3, y3) \ \ We also reset the fractional part of R4 \ (the left edge x-coordinate) to just the \ top bit set (so that's 0.5) \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before MOVNE R7, R11 \ If R2 <> R4 >> 16, then x2 <> left edge, BICNE R5, R5, #&FF00 \ so x2 must be at the right edge, so we set BICNE R5, R5, #&00FF \ the slope of the right edge in R7 to R11, ORRNE R5, R5, #&8000 \ as it's the right edge that is changing \ slope as we move into the top part of the \ triangle: \ \ R7 = R11 \ = slope of (x2, y2) to (x3, y3) \ \ We also reset the fractional part of R5 \ (the right edge x-coordinate) to just the \ top bit set (so that's 0.5) \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before .trin14 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row ADD R11, R12, R4, LSR #16 \ Set R11 = R12 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOV R10, R5, LSR #16 \ Set R10 = R5 >> 16 - R4 >> 16 SUBS R10, R10, R4, LSR #16 \ = x-coordinate of right edge \ - x-coordinate of left edge \ \ So R10 contains the length of the line \ from the left edge to the right edge BLPL DrawHorizontalLine \ If R10 is positive then we draw a line \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 SUB R12, R12, #320 \ Subtract 320 from R12 so that R12 now \ points to the start of the pixel row above \ the one we just drew SUBS R9, R9, #1 \ Decrement the pixel row counter in R9, \ which contains the number of pixel rows \ between y1 and y2 BNE trin14 \ Loop back until we have drawn all the \ horizontal lines in the second part of \ the triangle, from (x2, y2) at the bottom, \ up to the level of (x3, y3) LDMFD R13!, {PC} \ Return from the subroutine .trin15 \ We jump here following these two \ calculations, if neither of them are zero: \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ I am not sure what this signifies! RSBMI R10, R10, #0 \ The last calculation was for R10, so this \ makes R10 positive if the above result is \ negative, so it does this: \ \ R10 = |R10| CMP R14, #0 \ Set the following: RSBMI R14, R14, #0 \ \ R14 = |R14| \ So R10 and R14 are set to the magnitudes \ of the distances between (x2, y2) and each \ edge of the horizontal line (R14 for left, \ R10 for right) CMP R14, R10 \ If |R14| >= |R10| then (x2, y2) is nearer MOVHS R6, R11 \ the right edge, so set: \ \ R6 = R11 \ = slope of (x2, y2) to (x3, y3) \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before (though I'm not sure why?) MOVLO R7, R11 \ Otherwise |R14| < |R10| and (x2, y2) is \ nearer the left edge, so set: \ \ R7 = R11 \ = slope of (x2, y2) to (x3, y3) \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before (though I'm not sure why?) B trin14 \ Jump up to trin14 to draw the top part of \ the triangle \ ****************************************************************************** \ \ Name: DrawTriangle (Part 5 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a triangle with a horizontal edge between (x1, y1) and \ (x2, y2) \ Deep dive: Drawing triangles \ \ ****************************************************************************** .trin16 \ If we get here then y1 = y2, so (x1, y1) \ and (x2, y2) share the same y-coordinate \ and the triangle has a horizontal edge \ between (x1, y1) and (x2, y2) SUBS R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3 \ \ We also know that y1 = y2, so: \ \ R9 = y2 - y3 \ = y1 - y3 LDMEQIA R13!, {PC} \ If R9 = 0 then y2 = y3, which means all \ three corners share the same y-coordinate \ and are at the same height on-screen \ \ This means there is no triangle to draw, \ so return from the subroutine SUBS R14, R0, R4 \ Set R14 = R0 - R4 \ = x1 - x3 \ \ So this is the vertical distance between \ (x1, y1) and (x3, y3), i.e. the delta \ x-coordinate between the two points \ \ This will be negative if the line from \ (x1, y1) to (x3, y3) slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R0, R4 \ If R14 is negative, set R14 = x1 - x3, so \ we have the following: \ \ R14 = |x1 - x3| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin17 \ \ R14 = |x1 - x3| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ so jump to trin17 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R6 = R9 / R14 \ \ = (y3 - y1) / |x3 - x1| B trin19 \ Jump to trin19 to skip the following and \ keep going .trin17 \ If we get here then at least one of these \ is true: \ \ R14 = |x1 - x3| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin18 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R6) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin18 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R6 = R9 / R14 \ \ = (y3 - y1) / |x3 - x1| .trin19 CMP R4, R0 \ If R4 - R0 < 0 then: RSBMI R6, R6, #0 \ \ x3 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation: \ \ R6 = (y3 - y1) / (x3 - x1) \ \ So R6 contains the slope of the first side \ of the triangle, from (x1, y1) to (x3, y3) \ We now calculate the slope for the second \ side of the triangle, from (x2, y2) to \ (x3, y3), as follows: \ \ R7 = (y2 - y3) / (x2 - x3) SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 SUBS R14, R2, R4 \ Set R14 = R4 - R2 \ = x3 - x2 RSBMI R14, R2, R4 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following: \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin20 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ so jump to trin20 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R7 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| B trin22 \ Jump to trin22 to skip the following and \ keep going .trin20 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin21 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R7) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin21 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R7 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| .trin22 CMP R4, R2 \ If R4 - R2 < 0 then: RSBMI R7, R7, #0 \ \ x3 - x2 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation: \ \ R7 = (y2 - y3) / (x3 - x2) \ \ So R7 contains the slope of the second \ side of the triangle, from (x2, y2) to \ (x3, y3) SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw (after \ swapping the ends below if necessary) MOV R5, R2, LSL #16 \ Set R5 = R0 << 16 \ = x2 << 16 \ \ We scale up R5 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R5 >> 16 gives us the integral part \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw (after \ swapping the ends below if necessary) ORR R4, R4, #&8000 \ Set the top bit of the fractional parts of ORR R5, R5, #&8000 \ both R4 and R5 (so that's 0.5) CMP R4, R5 \ If R4 >= R5 then x1 >= x2, so we need to MOVHS R14, R6 \ swap R6 and R7, and swap R4 and R5, to MOVHS R6, R7 \ ensure that R4 is the left end of the line MOVHS R7, R14 \ and R5 is the right end, and that the MOVHS R14, R4 \ slopes are swapped accordingly MOVHS R4, R5 MOVHS R5, R14 B trin14 \ Jump to trin14 in part 4 to draw the \ triangle \ ****************************************************************************** \ \ Name: DrawTriangle (Part 6 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a clipped triangle that's partly off-screen \ Deep dive: Drawing triangles \ \ ****************************************************************************** .trin23 CMP R1, #239 \ If all of the y-coordinates are off-screen CMPHS R3, #239 \ then return from the subroutine CMPHS R5, #239 MOVHS PC, R14 CMP R0, #320 \ If all of the x-coordinates are off-screen CMPHS R2, #320 \ then return from the subroutine CMPHS R4, #320 MOVHS PC, R14 STMFD R13!, {R14} \ Store the return address on the stack LDR R12, screenAddr \ Set R12 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 \ We start by ordering the triangle corners \ by y-coordinate, so (x1, y1) is the \ furthest down the screen with the highest \ y-coordinate, followed by (x2, y2) then \ (x3, y3), at the same or higher level, and \ in that order CMP R1, R3 \ If R1 > R3, jump to trin24 to skip the BGT trin24 \ following, as y1 > y2 already MOV R6, R0 \ Swap (x1, y1) and (x2, y2), so we now have MOV R0, R2 \ y1 >= y2 MOV R2, R6 MOV R6, R1 MOV R1, R3 MOV R3, R6 .trin24 CMP R1, R5 \ If R1 > R5, jump to trin25 to skip the BGT trin25 \ following, as y1 > y3 already MOV R6, R0 \ Swap (x1, y1) and (x3, y3), so we now have MOV R0, R4 \ y1 >= y3 MOV R4, R6 MOV R6, R1 MOV R1, R5 MOV R5, R6 .trin25 CMP R3, R5 \ If R3 > R5, jump to trin26 to skip the BGT trin26 \ following, as y2 > y3 already MOV R6, R2 \ Swap (x2, y2) and (x3, y3), so we now have MOV R2, R4 \ y2 >= y3 MOV R4, R6 MOV R6, R3 MOV R3, R5 MOV R5, R6 \ So the triangle coordinates are ordered \ like this on-screen: \ \ (x3, y3) \ (x2, y2) \ (x1, y1) .trin26 MOV R11, R1 \ Set R11 = R1 \ = y1 \ \ We use the value of R11 in part 11, when \ drawing the clipped triangle \ ****************************************************************************** \ \ Name: DrawTriangle (Part 7 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Calculate the slopes of (x1, y1) to (x2, y2) and (x1, y1) to \ (x3, y3) for a clipped triangle \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope of the first \ side of the triangle, from (x1, y1) to \ (x2, y2), as follows: \ \ R6 = (y1 - y2) / (x2 - x1) SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2 BEQ trin38 \ If (x1, y1) and (x2, y2) share the same \ y-coordinate then the triangle has a \ horizontal edge between (x1, y1) and \ (x2, y2), so jump to part 10 to process \ this special case STMFD R13!, {R4-R5} \ Store the third corner's coordinates in \ (x3, y3) on the stack to we can retrieve \ them later SUBS R14, R2, R0 \ Set R14 = R2 - R0 \ = x2 - x1 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ x-coordinate between the two points \ \ This will be positive if the line from \ (x1, y1) to (x2, y2) slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R2, R0 \ If R14 is negative, set R14 = x1 - x2, so \ we have the following: \ \ R14 = |x2 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin27 \ \ R14 = |x2 - x1| >= 64 \ \ R9 = (y1 - y2) >= 64 \ \ so jump to trin27 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R6 = R9 / R14 \ \ = (y1 - y2) / |x2 - x1| B trin29 \ Jump to trin29 to skip the following and \ keep going .trin27 \ If we get here then at least one of these \ is true: \ \ R14 = |x2 - x1| >= 64 \ \ R9 = (y1 - y2) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin28 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R6) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin28 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R6 = R9 / R14 \ \ = (y1 - y2) / |x2 - x1| .trin29 CMP R2, R0 \ If R2 - R0 < 0 then: RSBMI R6, R6, #0 \ \ x2 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation: \ \ R6 = (y1 - y2) / (x2 - x1) \ \ So R6 contains the slope of the first side \ of the triangle, from (x1, y1) to (x2, y2) \ We now calculate the slope for the second \ side of the triangle, from (x1, y1) to \ (x3, y3), as follows: \ \ R7 = (y1 - y3) / (x3 - x1) SUBS R9, R1, R5 \ Set R9 = R1 - R5 \ = y1 - y3 SUBS R14, R4, R0 \ Set R14 = R4 - R0 \ = x3 - x1 RSBMI R14, R4, R0 \ If R14 is negative, set R4 = x1 - x3, so \ we have the following: \ \ R14 = |x3 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin30 \ \ R14 = |x3 - x1| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ so jump to trin30 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R7 = R9 / R14 \ \ = (y1 - y3) / |x3 - x1| B trin32 \ Jump to trin32 to skip the following and \ keep going .trin30 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x1| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin31 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R7) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin31 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R7 = R9 / R14 \ \ = (y1 - y3) / |x3 - x1| .trin32 CMP R4, R0 \ If R4 - R0 < 0 then: RSBMI R7, R7, #0 \ \ x3 - x1 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation: \ \ R7 = (y1 - y3) / (x3 - x1) \ \ So R7 contains the slope of the second \ side of the triangle, from (x1, y1) to \ (x3, y3) \ ****************************************************************************** \ \ Name: DrawTriangle (Part 8 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw the bottom part of a clipped triangle \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ By this point we have the following: \ \ (R0, R1) = (x1, y1) \ \ (R2, R3) = (x2, y2) \ \ (x3, y3) is on the stack \ \ R6 = (y1 - y2) / (x2 - x1) \ = slope of (x1, y1) to (x2, y2) \ \ R7 = (y1 - y3) / (x3 - x1) \ = slope of (x1, y1) to (x3, y3) \ \ R12 = screen address of the start of the \ pixel row containing (x1, y1) \ \ (x1, y1) is the point lowest down the \ screen and (x3, y3) is the highest up the \ screen, with (x2, y2) the point in the \ middle (in terms of y-coordinate) \ \ We now draw the triangle in two parts, \ effectively slicing the triangle in half \ with a horizontal line at y-coordinate y2, \ leaving two triangles to draw: \ \ 1. The triangle from (x1, y1) at the \ bottom up to the horizontal line with \ (x2, y2) at one end \ \ 2. The triangle from the horizontal line \ with (x2, y2) at one end, up to \ (x3, y3) at the top \ \ We start at the bottom of the triangle, at \ (x1, y1), and step upwards by one pixel \ row at a time, drawing a horizontal line \ between the two sides, until we reach the \ level of (x2, y2) \ \ As we step up each pixel row, we calculate \ the x-coordinates of each row we draw by \ adding the slopes in R6 and R7 \ \ We store the x-coordinates of the current \ horizontal line in R4 and R5, so these are \ the registers we update with the slope \ values SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ (x1, y1) and (x2, y2), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y1 and y2 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw ORR R4, R4, #&8000 \ Set R5 to x1, with the top bit of its MOV R5, R4 \ fractional part set (so that's 0.5) \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw CMP R6, R7 \ If R6 > R7, swap R6 and R7, so we know MOVGT R14, R6 \ that R6 <= R7, i.e. R6 contains the side MOVGT R6, R7 \ with the lesser slope, which will be the MOVGT R7, R14 \ slope along the left edge of the triangle BL trin45 \ Call the subroutine in part 11 to draw the \ bottom part of the triangle, clipping it \ to the screen as we go LDMFD R13!, {R0-R1} \ Fetch the coordinates for the third point \ (x3, y3) from the stack and into (R0, R1) SUBS R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 LDMEQIA R13!, {PC} \ If R9 is zero then (x2, y2) and (x3, y3) \ are at the same y-coordinate, so there is \ nothing to draw in the top part of the \ triangle, so return from the subroutine as \ we are done \ We now calculate the slope for the third \ side of the triangle, from (x2, y2) to \ (x3, y3), as follows: \ \ R14 = (y2 - y3) / (x2 - x1) SUBS R14, R0, R2 \ Set R14 = R0 - R2 \ = x3 - x2 RSBMI R14, R0, R2 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following: \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin33 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ so jump to trin33 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R12, [R10, R9, LSL #2] \ Set R12 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R12 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| B trin35 \ Jump to trin35 to skip the following and \ keep going .trin33 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ We now calculate R12 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R12, #0 \ Set R12 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin34 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R12, R12, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R12) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin34 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R12 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| .trin35 CMP R0, R2 \ If R0 - R2 < 0 then: RSBMI R12, R12, #0 \ \ x3 - x2 < 0 \ \ so negate R12 to give R12 the correct sign \ for the following calculation: \ \ R12 = (y2 - y3) / (x3 - x2) \ \ So R12 contains the slope of the third \ side of the triangle, from (x2, y2) to \ (x3, y3) \ ****************************************************************************** \ \ Name: DrawTriangle (Part 9 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw the top part of a clipped triangle \ Deep dive: Drawing triangles \ \ ****************************************************************************** \ By this point we have the following: \ \ (R0, R1) = (x3, y3) \ \ (R2, R3) = (x2, y2) \ \ R4 = x-coordinate of the left edge for \ the last line that we drew \ \ R5 = x-coordinate of the right edge for \ the last line that we drew \ \ R7 = (y1 - y3) / (x3 - x1) \ = slope of (x1, y1) to (x3, y3) \ \ R12 = (y2 - y3) / (x3 - x2) \ = slope of (x2, y2) to (x3, y3) SUB R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 \ The following calculations are very \ similar to those in part 4 for the \ unclipped triangle, but the shifts keep \ the sign of the shifted register (they use \ ASR instead of LSR) SUBS R14, R2, R4, ASR #16 \ Set: \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ So this will be zero if (x2, y2) is at the \ left edge of the horizontal line SUBNES R10, R2, R5, ASR #16 \ If the above is non-zero, set: \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ So this will be zero if (x2, y2) is at the \ right edge of the horizontal line BNE trin37 \ If both of the above are non-zero, jump to \ trin37, as (x2, y2) doesn't match either \ edge of the horizontal line we just drew CMP R2, R4, ASR #16 \ Set the flags on R2 - R4 >> 16 MOVEQ R6, R12 \ If R2 = R4 >> 16, then x2 = left edge, so: BICEQ R4, R4, #&FF00 \ so x2 must be at the left edge, so we set BICEQ R4, R4, #&00FF \ the slope of the left edge in R6 to R12, ORREQ R4, R4, #&8000 \ as it's the left edge that is changing \ slope as we move into the top part of the \ triangle: \ \ R6 = R12 \ = slope of (x2, y2) to (x3, y3) \ \ We also reset the fractional part of R4 \ (the left edge x-coordinate) to just the \ top bit set (so that's 0.5) \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before MOVNE R7, R12 \ If R2 <> R4 >> 16, then x2 <> left edge, BICNE R5, R5, #&FF00 \ so x2 must be at the right edge, so we set BICNE R5, R5, #&00FF \ the slope of the right edge in R7 to R12, ORRNE R5, R5, #&8000 \ as it's the right edge that is changing \ slope as we move into the top part of the \ triangle: \ \ R7 = R12 \ = slope of (x2, y2) to (x3, y3) \ \ We also reset the fractional part of R5 \ (the right edge x-coordinate) to just the \ top bit set (so that's 0.5) \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before .trin36 LDR R12, screenAddr \ Set R12 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 BL trin45 \ Call the subroutine in part 11 to draw the \ top part of the triangle, clipping it to \ the screen as we go LDMFD R13!, {PC} \ Return from the subroutine .trin37 \ We jump here following these two \ calculations, if neither of them are zero: \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ I am not sure what this signifies! RSBMI R10, R10, #0 \ The last calculation was for R10, so this \ makes R10 positive if the above result is \ negative, so it does this: \ \ R10 = |R10| CMP R14, #0 \ Set the following: RSBMI R14, R14, #0 \ \ R14 = |R14| \ So R10 and R14 are set to the magnitudes \ of the distances between (x2, y2) and each \ edge of the horizontal line (R14 for left, \ R10 for right) CMP R14, R10 \ If |R14| >= |R10| then (x2, y2) is nearer MOVHS R6, R12 \ the right edge, so set: \ \ R6 = R12 \ = slope of (x2, y2) to (x3, y3) \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before (though I'm not sure why?) MOVLO R7, R12 \ Otherwise |R14| < |R10| and (x2, y2) is \ nearer the left edge, so set: \ \ R7 = R12 \ = slope of (x2, y2) to (x3, y3) \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before (though I'm not sure why?) B trin36 \ Jump up to trin14 to draw the top part of \ the triangle \ ****************************************************************************** \ \ Name: DrawTriangle (Part 10 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a clipped triangle with a horizontal edge between (x1, y1) \ and (x2, y2) \ Deep dive: Drawing triangles \ \ ****************************************************************************** .trin38 \ If we get here then y1 = y2, so (x1, y1) \ and (x2, y2) share the same y-coordinate \ and the triangle has a horizontal edge \ between (x1, y1) and (x2, y2) SUBS R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 \ \ So this is the vertical distance between \ (x2, y2) and (x3, y3), i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3 \ \ We also know that y1 = y2, so: \ \ R9 = y2 - y3 \ = y1 - y3 LDMEQIA R13!, {PC} \ If R9 = 0 then y2 = y3, which means all \ three corners share the same y-coordinate \ and are at the same height on-screen \ \ This means there is no triangle to draw, \ so return from the subroutine SUBS R14, R0, R4 \ Set R14 = R0 - R4 \ = x1 - x3 \ \ So this is the vertical distance between \ (x1, y1) and (x3, y3), i.e. the delta \ x-coordinate between the two points \ \ This will be negative if the line from \ (x1, y1) to (x3, y3) slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R0, R4 \ If R14 is negative, set R14 = x1 - x3, so \ we have the following: \ \ R14 = |x1 - x3| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin39 \ \ R14 = |x1 - x3| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ so jump to trin39 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R6 = R9 / R14 \ \ = (y3 - y1) / |x3 - x1| B trin41 \ Jump to trin41 to skip the following and \ keep going .trin39 \ If we get here then at least one of these \ is true: \ \ R14 = |x1 - x3| >= 64 \ \ R9 = (y1 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin40 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R6) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin40 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R6 = R9 / R14 \ \ = (y3 - y1) / |x3 - x1| .trin41 CMP R4, R0 \ If R4 - R0 < 0 then: RSBMI R6, R6, #0 \ \ x3 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation: \ \ R6 = (y3 - y1) / (x3 - x1) \ \ So R6 contains the slope of the first side \ of the triangle, from (x1, y1) to (x3, y3) \ We now calculate the slope for the second \ side of the triangle, from (x2, y2) to \ (x3, y3), as follows: \ \ R7 = (y2 - y3) / (x2 - x3) SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 SUBS R14, R2, R4 \ Set R14 = R4 - R2 \ = x3 - x2 RSBMI R14, R2, R4 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following: \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true: BHS trin42 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ so jump to trin42 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is: \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following: \ \ R7 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| B trin44 \ Jump to trin44 to skip the following and \ keep going .trin42 \ If we get here then at least one of these \ is true: \ \ R14 = |x3 - x2| >= 64 \ \ R9 = (y2 - y3) >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin43 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction: \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ (i.e. apply the set bit in R10 to the \ result in R7) MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin43 \ Loop back until we shift the 1 out of the \ right end of R10 (after 32 shifts) \ So we now have the following result: \ \ R7 = R9 / R14 \ \ = (y2 - y3) / |x3 - x2| .trin44 CMP R4, R2 \ If R4 - R2 < 0 then: RSBMI R7, R7, #0 \ \ x3 - x2 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation: \ \ R7 = (y2 - y3) / (x3 - x2) \ \ So R7 contains the slope of the second \ side of the triangle, from (x2, y2) to \ (x3, y3) SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw (after \ swapping the ends below if necessary) MOV R5, R2, LSL #16 \ Set R5 = R0 << 16 \ = x2 << 16 \ \ We scale up R5 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R5 >> 16 gives us the integral part \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw (after \ swapping the ends below if necessary) ORR R4, R4, #&8000 \ Set the top bit of the fractional parts of ORR R5, R5, #&8000 \ both R4 and R5 (so that's 0.5) CMP R4, R5 \ If R4 > R5 then x1 > x2, so we need to MOVGT R14, R6 \ swap R6 and R7, and swap R4 and R5, to MOVGT R6, R7 \ ensure that R4 is the left end of the line MOVGT R7, R14 \ and R5 is the right end, and that the MOVGT R14, R4 \ slopes are swapped accordingly MOVGT R4, R5 MOVGT R5, R14 B trin36 \ Jump to trin36 in part 9 to draw the \ triangle \ ****************************************************************************** \ \ Name: DrawTriangle (Part 11 of 11) \ Type: Subroutine \ Category: Drawing triangles \ Summary: Draw a triangle, clipping it to the screen as we go \ Deep dive: Drawing triangles \ \ ****************************************************************************** .trin45 \ We call this part of the routine as a \ subroutine, so returning from the \ subroutine in this context means returning \ to the main triangle code above \ \ We call it with the following values: \ \ R4 = x-coordinate of the left end of the \ horizontal line to draw \ \ R5 = x-coordinate of the right end of \ the horizontal line to draw \ \ R6 = left edge slope \ \ R7 = right edge slope \ \ R8 = four-pixel colour word \ \ R9 = the delta y-coordinate between \ (x1, y1) and (x2, y2), which \ defines the height of the triangle \ to draw \ \ R11 = y1, so that's the y-coordinate of \ the bottom point of the triangle \ to draw \ \ R12 = screen address of the start of the \ pixel row containing (x1, y1) CMP R9, #256 \ If R9 >= 256 then the triangle to draw is MOVHS PC, R14 \ taller than the screen, which is just too \ big, so return from the subroutine without \ drawing this triangle STMFD R13!, {R14} \ Store the return address on the stack \ We now draw the triangle from bottom to \ top, keeping track of the y-coordinate of \ the current pixel row in R11 and counting \ down the pixel rows in R9 .trin46 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row CMP R11, #0 \ If R11 < 0 then we are already off the top LDMMIIA R13!, {PC} \ of the screen, so return from the \ subroutine CMP R11, #239 \ If R11 >= 239 then we are off the bottom BHS trin48 \ of the screen, so jump to trin48 to move \ on to the next row above in the triangle, \ so the triangle is clipped to the bottom \ of the screen STMFD R13!, {R11} \ Store the y-coordinate of the current row \ on the stack so we can retrieve it below ADD R11, R11, R11, LSL #2 \ Set R11 = R11 + R11 * 4 \ = y-coord of row * 5 ADD R11, R12, R11, LSL #6 \ Set R11 = R12 + R11 * 64 \ = screen address + 320 * y-coord \ \ So R11 points to the start of the pixel \ row in screen memory CMP R4, #&01400000 \ If R4 > &01400000, then the left end of BPL trin47 \ the line is greater than &140 (as the \ fractional number &01400000 represents the \ integer &0140), so it is greater than 320 \ and past the right edge of the screen \ \ This means the whole line is off-screen, \ so jump to trin47 to move on to the next \ row above in the triangle CMP R5, #0 \ If R5 < 0, then the right end of the line BMI trin47 \ is off the left edge of the screen, so \ jump to trin47 to move on to the next row \ above in the triangle CMP R4, #0 \ If the left end of the line is positive, MOVPL R0, R4, LSR #16 \ then it's on-screen, so set: \ \ R0 = R4 >> 16 \ = x-coordinate of left end of line \ \ The shift removes the fractional part from \ R4 ADDPL R11, R11, R4, LSR #16 \ If the left end of the line is positive, \ set R11 = R11 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOVMI R0, #0 \ If the left end of the line is negative, \ i.e. off the left edge of the screen, then \ set R0 = 0 \ So R0 is set to the x-coordinate of the \ left end of the line we want to draw CMP R5, #&01400000 \ If R5 < &01400000, then the right end of RSBLO R10, R0, R5, LSR #16 \ the line is less than &140 (as the \ fractional number &01400000 represents the \ integer &0140), so it is less than 320 \ and is not past the right edge of the \ screen, so set: \ \ R10 = R5 >> 16 - R0 \ = x-coordinate of right end of line \ - x-coordinate of left end of line RSBHS R10, R0, #320 \ If the right end of the line is off the \ right side of the screen, set: \ \ R10 = 320 - x-coordinate of left end of \ line \ So R10 contains the length of the line \ from the left edge to the right edge CMP R10, #0 \ If R10 is positive then we draw a line BLPL DrawHorizontalLine \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 .trin47 LDMFD R13!, {R11} \ Retrieve the y-coordinate of the current \ row from the stack, which we stored above, \ and put it into R11 once more .trin48 SUB R11, R11, #1 \ Decrement R11 so we move one pixel line up \ the screen SUBS R9, R9, #1 \ Decrement the triangle height counter in \ R9 BNE trin46 \ Loop back to draw the next line in the \ triangle until we have drawn all R9 \ horizontal lines LDMFD R13!, {PC} \ Return from the subroutine (and rejoin the \ main triangle routine) \ ****************************************************************************** \ \ Name: lineJump \ Type: Variable \ Category: Drawing lines \ Summary: Jump table for drawing a horizontal line of between 0 and 17 \ pixels using the relevant entry point in DrawLineSegment \ Deep dive: Drawing triangles \ \ ****************************************************************************** .lineJump EQUD DrawLineSegment + 17 * 4 \ Draw a horizontal line of 0 pixels EQUD DrawLineSegment + 16 * 4 \ Draw a horizontal line of 1 pixels EQUD DrawLineSegment + 15 * 4 \ Draw a horizontal line of 2 pixels EQUD DrawLineSegment + 14 * 4 \ Draw a horizontal line of 3 pixels EQUD DrawLineSegment + 13 * 4 \ Draw a horizontal line of 4 pixels EQUD DrawLineSegment + 12 * 4 \ Draw a horizontal line of 5 pixels EQUD DrawLineSegment + 11 * 4 \ Draw a horizontal line of 6 pixels EQUD DrawLineSegment + 10 * 4 \ Draw a horizontal line of 7 pixels EQUD DrawLineSegment + 9 * 4 \ Draw a horizontal line of 8 pixels EQUD DrawLineSegment + 8 * 4 \ Draw a horizontal line of 9 pixels EQUD DrawLineSegment + 7 * 4 \ Draw a horizontal line of 10 pixels EQUD DrawLineSegment + 6 * 4 \ Draw a horizontal line of 11 pixels EQUD DrawLineSegment + 5 * 4 \ Draw a horizontal line of 12 pixels EQUD DrawLineSegment + 4 * 4 \ Draw a horizontal line of 13 pixels EQUD DrawLineSegment + 3 * 4 \ Draw a horizontal line of 14 pixels EQUD DrawLineSegment + 2 * 4 \ Draw a horizontal line of 15 pixels EQUD DrawLineSegment + 1 * 4 \ Draw a horizontal line of 16 pixels EQUD DrawLineSegment \ Draw a horizontal line of 17 pixels \ ****************************************************************************** \ \ Name: DrawHorizontalLine \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a horizontal line \ Deep dive: Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R8 Line colour (in the form of a four-pixel \ colour word) \ \ R10 Line length \ \ R11 Screen address of the left end of the line \ \ ****************************************************************************** .DrawHorizontalLine CMP R10, #18 \ If R10 < 18 then set the flags so the next \ set of instructions are run (so for lines \ of length 17 pixels and fewer, we jump to \ the relevant routine in the lineJump to \ draw the line ] varOffset = P% + 8 - lineJump \ Set labOffset to the offset back to the \ lineJump table from the next instruction [ OPT pass% ADDLO R0, PC, R10, LSL #2 \ If R10 < 18, jump to the address in entry LDRLO PC, [R0, #-varOffset] \ R10 in the lineJump table, which will draw \ a horizontal line R10 pixels long and \ return from the subroutine using a tail \ call \ If we get here then we need to draw a line \ of 18 pixels or more ADD R10, R11, R10 \ Set R10 to the screen address of the right \ end of the line (i.e. the address of the \ left end in R11 plus the width in R10) BIC R0, R10, #%00000011 \ Set R0 to the screen address with bits 0 \ and 1 cleared, so R0 points to the word in \ screen memory that contains the right end \ of the line (the "right cap") \ So by this point R11 points to the word in \ screen memory that contains the left cap \ of the line (which is the word containing \ the left end of the line), and R0 points \ to the right cap \ \ The caps can contain 1, 2, 3 or 4 pixels, \ as each 32-bit word contains four one-byte \ pixels \ \ If a cap contains four pixels, then bits 0 \ and 1 of the cap address will be zero, so \ in the following we draw the line as \ follows: \ \ * If the left cap contains 1, 2 or 3 \ pixels, draw those pixels \ \ * Draw the portion of the line between \ the left and right caps, drawing it \ one word (four pixels) at a time \ \ * If the right cap contains 1, 2 or 3 \ pixels, draw those pixels \ \ We start with the left cap at screen \ address R11 TST R11, #%00000011 \ If R11 is not a multiple of 4 then one or STRNEB R8, [R11], #1 \ both of bits 0 and 1 will be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the pixel at the end of the \ line, and it increments R11 so R11 now \ points to the next pixel along TSTNE R11, #%00000011 \ If R11 is still not a multiple of 4 then STRNEB R8, [R11], #1 \ bits 0 and 1 will still be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the second pixel in the line \ if drawing the first one didn't take us to \ a word boundary, and it increments R11 so \ R11 now points to the next pixel along TSTNE R11, #%00000011 \ If R11 is still not a multiple of 4 then STRNEB R8, [R11], #1 \ bits 0 and 1 will still be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the third pixel in the line \ if drawing the first two didn't take us to \ a word boundary, and it increments R11 so \ R11 now points to the next pixel along \ By this point R11 will definitely be on a \ word boundary and we have successfully \ drawn the left cap of the line, so now we \ draw the centre portion of the line, all \ the way to the right cap \ \ We do this in a loop that is unrolled once \ to speed things up a little but .hlin1 STR R8, [R11], #4 \ Draw a full word (four pixels) in the \ colour in R8 at screen address R11 and \ increment R11 by 4 to move on to the next \ word CMP R11, R0 \ If R11 < R0 then we have not yet reached STRLO R8, [R11], #4 \ the right cap, so draw another word in \ memory and increment R11 again CMPLO R11, R0 \ If R11 < R0 then we have still not reached BLO hlin1 \ the right cap, so loop back to hlin1 to \ keep drawing the centre portion of the \ line until we do reach the right cap CMP R11, R10 \ If R11 < R10 then we have not yet reached STRLOB R8, [R11], #1 \ the end of the line, so we draw up to CMPLO R11, R10 \ three pixels in the final word of the line STRLOB R8, [R11], #1 \ by simply drawing each pixel, incrementing CMPLO R11, R10 \ R11 and then re-checking whether R11 has STRLOB R8, [R11], #1 \ reached R10 (and drawing the next pixel if \ it hasn't) MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawLineSegment \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a horizontal line of between 0 and 17 pixels by jumping to \ the relevant entry point \ Deep dive: Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R8 Line colour (in the form of a four-pixel \ colour word) \ \ R11 Screen address of left end of line \ \ ****************************************************************************** .DrawLineSegment STRB R8, [R11, #16] \ Draw a horizontal line of 17 pixels STRB R8, [R11, #15] \ Draw a horizontal line of 16 pixels STRB R8, [R11, #14] \ Draw a horizontal line of 15 pixels STRB R8, [R11, #13] \ Draw a horizontal line of 14 pixels STRB R8, [R11, #12] \ Draw a horizontal line of 13 pixels STRB R8, [R11, #11] \ Draw a horizontal line of 12 pixels STRB R8, [R11, #10] \ Draw a horizontal line of 11 pixels STRB R8, [R11, #9] \ Draw a horizontal line of 10 pixels STRB R8, [R11, #8] \ Draw a horizontal line of 9 pixels STRB R8, [R11, #7] \ Draw a horizontal line of 8 pixels STRB R8, [R11, #6] \ Draw a horizontal line of 7 pixels STRB R8, [R11, #5] \ Draw a horizontal line of 6 pixels STRB R8, [R11, #4] \ Draw a horizontal line of 5 pixels STRB R8, [R11, #3] \ Draw a horizontal line of 4 pixels STRB R8, [R11, #2] \ Draw a horizontal line of 3 pixels STRB R8, [R11, #1] \ Draw a horizontal line of 2 pixels STRB R8, [R11] \ Draw a horizontal line of 1 pixel MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: workspaceAddr \ Type: Variable \ Category: Start and end \ Summary: The address of the game's variable workspace \ \ ****************************************************************************** .workspaceAddr EQUD workspace \ ****************************************************************************** \ \ Name: stackAddr \ Type: Variable \ Category: Start and end \ Summary: The address of the game's stack \ \ ****************************************************************************** .stackAddr EQUD stack \ ****************************************************************************** \ \ Name: memoryTestAddr \ Type: Variable \ Category: Start and end \ Summary: The memory location to check to ensure we have enough memory for \ the game \ \ ****************************************************************************** .memoryTestAddr EQUD 0 \ ****************************************************************************** \ \ Name: initialScore \ Type: Variable \ Category: Score bar \ Summary: The score at the start of each game \ \ ****************************************************************************** .initialScore EQUD 500 \ ****************************************************************************** \ \ Name: initialHighScore \ Type: Variable \ Category: Score bar \ Summary: The high score when we first load the game \ \ ****************************************************************************** .initialHighScore EQUD 500 \ ****************************************************************************** \ \ Name: initialFuelLevel \ Type: Variable \ Category: Score bar \ Summary: The fuel level at the start of each new game \ \ ****************************************************************************** .initialFuelLevel EQUD 3413 \ ****************************************************************************** \ \ Name: stackPointerOnEntry \ Type: Variable \ Category: Start and end \ Summary: Stores the stack pointer from when the game was run \ \ ****************************************************************************** .stackPointerOnEntry EQUD 0 \ ****************************************************************************** \ \ Name: mouseParameters \ Type: Variable \ Category: Player \ Summary: The parameters for OS_Word 21,3 for resetting the mouse position \ \ ****************************************************************************** .mouseParameters EQUB 3 \ This is OS_Word 21,3, so the reason code \ in the first byte is 3 EQUW 511 \ The X position for the mouse EQUW 511 \ The Y position for the mouse ALIGN \ ****************************************************************************** \ \ Name: mouseParametersAddr \ Type: Variable \ Category: Player \ Summary: The address of the OS_Word block for resetting the mouse position \ \ ****************************************************************************** .mouseParametersAddr EQUD mouseParameters \ ****************************************************************************** \ \ Name: ResetMousePosition \ Type: Subroutine \ Category: Player \ Summary: Reset the mouse position to (511, 511), ready for the game \ \ ****************************************************************************** .ResetMousePosition LDR R1, mouseParametersAddr \ Call OS_Word 21,3 to reset the mouse MOV R0, #21 \ position to (511, 511) SWI OS_Word MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: AbortWithMemoryError \ Type: Subroutine \ Category: Start and end \ Summary: Show a memory error and abort the game \ \ ****************************************************************************** .AbortWithMemoryError STRB R1, [R0] \ Restore the byte that we have been poking \ in the Entry routine to determine whether \ there is enough memory SWI OS_WriteS \ Print an error explaining that there isn't EQUS "There is not enough " \ enough free memory to run the game EQUS "memory to run this " EQUS "game." EQUW &0D0A EQUS "Please *CONFIGURE your " EQUS "machine to free more " EQUS "memory." EQUB 0 ALIGN B ReturnToDesktop \ Jump to ReturnToDesktop to quit the game \ and return to the desktop \ ****************************************************************************** \ \ Name: Entry \ Type: Subroutine \ Category: Start and end \ Summary: The main entry point for the game \ \ ****************************************************************************** .Entry MOV R0, #22 \ Change to screen mode 15, which uses 160K SWI OS_WriteC \ of screen memory, the same amount that we MOV R0, #15 \ need for the game (though in the game we SWI OS_WriteC \ actually use two mode 13 screens, each of \ which is 80K) STR R13, stackPointerOnEntry \ Store the stack pointer from when the game \ started in stackPointerOnEntry, so we can \ retore it when we quit the game LDR R13, stackAddr \ Set the stack pointer in R13 to stackAddr, \ so the 512 bytes descending from stackAddr \ can be used as the game's stack LDR R11, workspaceAddr \ Set R11 to the address of the workspace, \ which is stored in workspaceAddr, so we \ can access workspace variables by applying \ an offset to R11 using [R11, #offset] STMFD R13!, {R14} \ R14 contains the address we should return \ to when quitting the game, so store it on \ the stack so we can retrieve it later LDR R0, memoryTestAddr \ Set R0 to the address in memoryTestAddr, \ which points to a byte that will be \ writable if there is enough free memory \ for running the game LDRB R1, [R0] \ Set R1 to the current contents of memory \ at R0, so we can restore it after running \ our memory tests MOV R2, #&AA \ Store the value &AA in our test memory STRB R2, [R0] \ location in R0, read it back, and if the LDRB R3, [R0] \ returned value is different to &AA, jump CMP R2, R3 \ to AbortWithMemoryError to abort the game, BNE AbortWithMemoryError \ as there isn't enough free memory MOV R2, #&55 \ Store the value &55 in our test memory STRB R2, [R0] \ location in R0, read it back, and if the LDRB R3, [R0] \ returned value is different to &55, jump CMP R2, R3 \ to AbortWithMemoryError to abort the game, BNE AbortWithMemoryError \ as there isn't enough free memory STRB R1, [R0] \ Restore the original contents of address \ R0 so it's unchanged by our memory tests MOV R0, #4 \ Call OS_Byte 4 to set the cursor keys to MOV R1, #1 \ return ASCII values, so they don't move MOV R2, #0 \ the cursor during the game SWI OS_Byte BL InitialiseParticleData \ Initialise the particle data buffer and \ associated variables MOV R0, #22 \ Change to screen mode 13 (320×256 pixels SWI OS_WriteC \ with 256 colours), which will display MOV R0, #13 \ screen bank 1 SWI OS_WriteC SWI OS_WriteS \ Write the game's title into the top line EQUS "Lander Demo/Practice " \ of the screen, where it remains for the EQUS "(C) D.J.Braben 1987" \ whole game EQUB 0 ALIGN BL SwitchScreenBank \ Switch screen bank, so VDU commands go to \ screen bank 2 MOV R0, #22 \ Change to screen mode 13 with shadow SWI OS_WriteC \ memory enabled (i.e. mode 128 + 13), which MOV R0, #128+13 \ creates two mode 13 screen banks SWI OS_WriteC \ MOV R0, #23 \ Start printing the following VDU command: SWI OS_WriteC \ MOV R0, #1 \ VDU 23, 1, 0, 0, 0, 0, 0, 0, 0, 0 SWI OS_WriteC \ \ which disables the cursor MOV R8, #8 \ We now want to print the eight zeroes in \ the above command, so set a loop counter \ in R8 .entr1 MOV R0, #0 \ This loop prints the eight zeroes in the SWI OS_WriteC \ above VDU command SUBS R8, R8, #1 \ Decrement the loop counter BNE entr1 \ Loop back until we have printed all eight \ zeroes SWI OS_WriteS \ Write the game's title into the top line EQUS "Lander Demo/Practice " \ of screen bank 2, so the same title EQUS "(C) D.J.Braben 1987" \ appears at the top of both screen banks EQUB 0 ALIGN LDR R0, initialScore \ Initialise currentScore to the score that STR R0, [R11, #currentScore] \ we start each game with, which is set in \ initialScore LDR R0, initialHighScore \ Initialise highScore to the high score STR R0, [R11, #highScore] \ that we start the game with, which is set \ in initialHighScore \ ****************************************************************************** \ \ Name: StartNewGame \ Type: Subroutine \ Category: Main loop \ Summary: Start a brand new game with a full set of lives and a newly \ generated set of objects \ \ ****************************************************************************** .StartNewGame \ We start by initialising the scores and \ printing them on the score bar LDR R0, [R11, #highScore] \ Set R0 to the current high score LDR R1, [R11, #currentScore] \ Set R1 to our current score CMP R1, R0 \ If R1 - R0 is positive, i.e. R1 >= R0, MOVPL R0, R1 \ then our latest score is higher than the \ high score, so set R0 to our latest score \ \ So R0 is set to the maximum of highScore \ and currentScore, which is the new high \ score STRHS R0, [R11, #highScore] \ If R1 >= R0 then we just updated the high \ score and the new high score is in R0, so \ store the new high score in highScore MOV R1, #35 \ Set (R1, R2) = (35, 1) so the following MOV R2, #1 \ call to PrintScoreInBothBanks prints the \ high score at column 35 on row 1 BL PrintScoreInBothBanks \ Print the high score in R0 at column 35 on \ row 1, at the right end of the score bar LDR R0, initialScore \ Initialise currentScore to the score that STR R0, [R11, #currentScore] \ we start each game with, which is set in \ initialScore \ We now initialise more game variables LDR R0, initialFuelLevel \ Initialise fuelLevel to the fuel level STR R0, [R11, #fuelLevel] \ that we start each game with, which is set \ in initialFuelLevel MOV R0, #&30000 \ Initialise gravity to &30000 STR R0, [R11, #gravity] MOV R0, #3 \ Initialise the number of lives to 3 STR R0, [R11, #remainingLives] \ ****************************************************************************** \ \ Name: PlaceObjectsOnMap \ Type: Subroutine \ Category: 3D objects \ Summary: Randomly place a number of objects on the map, avoiding the sea \ and the launchpad \ Deep dive: Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The object map at objectMap contains one byte for each tile on the landscape. \ This byte determines which object (if any) appears on that tile, where objects \ are trees, buildings, rockets and so on. \ \ ****************************************************************************** .PlaceObjectsOnMap \ We start by initialising the object map \ at objectMap with values of &FF, which \ indicates no objects on the map MVN R0, #0 \ Set R0 to R3 to &FF so we can poke them MVN R1, #0 \ into memory at objectMap (this sets each MVN R2, #0 \ 32-bit register to &FFFFFFFF, which is the MVN R3, #0 \ same as four bytes, each of which is &FF) MOV R4, #256*256 \ Set R4 to use as a byte counter in the \ following loop, which works through each \ of the coordinates in the 256x256 map ADD R6, R11, #objectMap \ Set R6 to the address of the object map .snew1 STMIA R6!, {R0-R3} \ Store four words, or 16 bytes, at R6, \ updating R6 as we go, with each byte \ containing &FF SUBS R4, R4, #16 \ Subtract 16 from the byte counter in R4 \ as we just initialised 16 bytes BNE snew1 \ Loop back until we have set all bytes in \ the object map to &FF \ We now add 2048 randomly chosen 3D objects \ to the object map, each one at a random \ coordinate and of a random type ADD R6, R11, #objectMap \ Set R6 to the address of the object map MOV R5, #2048 \ Set R5 to a loop counter as we work \ through all 2048 objects .snew2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R8, R0 \ Set R8 = R0, so R8 is a random number \ \ We use the top byte of R8 below as the \ x-coordinate of the 3D object MOV R9, R0, LSL #8 \ Set R9 = R0 << 8 \ \ We use the top byte of R9 below as the \ z-coordinate of the 3D object, so the \ shift ensures that the top bytes of R8 \ and R9 are different STMFD R13!, {R0} \ Store R0 on the stack so it doesn't get \ corrupted by the following call to \ GetLandscapeAltitude BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9) MOV R14, R0 \ Set R14 to the landscape altitude returned \ in R0 LDMFD R13!, {R0} \ Retrieve the value of R0 from the stack \ that we stored above CMP R14, #SEA_LEVEL \ If R14 = LAUNCHPAD_ALTITUDE or SEA_LEVEL, CMPNE R14, #LAUNCHPAD_ALTITUDE \ jump to snew3 to skip the following, so we BEQ snew3 \ do not place any objects on the sea or the \ launchpad AND R0, R0, #7 \ Reduce R0 to the range 1 to 8, so this is ADD R0, R0, #1 \ a random number that we use to determine \ the type of object we're adding (so there \ are lots of trees): \ \ * 1 = small leafy tree \ * 2 = tall leafy tree \ * 3 = small leafy tree \ * 4 = small leafy tree \ * 5 = gazebo \ * 6 = tall leafy tree \ * 7 = fir tree \ * 8 = building \ \ See the objectTypes table for details of \ object types AND R9, R9, #&FF000000 \ Set the bottom three bytes of R9 to zero, \ leaving just the top byte, so we can use \ it in the following \ The object at coordinate (x, z) is stored \ at offset &zzxx within objectMap, where \ &xx and &zz are the top bytes of the full \ 32-bit coordinates \ \ In the following we set this address in \ R14: \ \ objectMap + (R8 >> 24) + (R9 >> 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 ADD R14, R6, R8, LSR #24 \ Set R14 = R6 + (R8 >> 24) + (R9 >> 16) ADD R14, R14, R9, LSR #16 STRB R0, [R14] \ Store R0 in the address in R14 to add the \ object to the map at the coordinates given \ by the top bytes of R8 and R9, at (R8, R9) .snew3 SUBS R5, R5, #1 \ Decrement the loop counter BPL snew2 \ Loop back until we have done all &800 \ iterations \ We now place three rockets along the right \ edge of the launchpad, with a rocket on \ every other tile, working from front to \ back, into the screen and parallel to the \ z-axis, and with each one having an \ x-coordinate of 7 MOV R0, #LAUNCHPAD_OBJECT \ Set R0 to the type of object along the \ right edge of the launchpad, which is a \ rocket of type 9 STRB R0, [R6, #&0107] \ Add the front rocket to coordinate (7, 1) STRB R0, [R6, #&0307] \ Add the middle rocket to coordinate (7, 3) STRB R0, [R6, #&0507] \ Add the rear rocket to coordinate (7, 5) \ ****************************************************************************** \ \ Name: PlacePlayerOnLaunchpad \ Type: Subroutine \ Category: Player \ Summary: The main entry point for the game \ \ ****************************************************************************** .PlacePlayerOnLaunchpad LDR R0, [R11, #remainingLives] \ Set R0 to the number of remaining lives MOV R1, #30 \ Set (R1, R2) = (30, 1) so the following MOV R2, #1 \ call to PrintScoreInBothBanks prints the \ number of lives at column 30 on row 1 BL PrintScoreInBothBanks \ Print the number of lives in R0 at column \ 30 on row 1, just before the high score \ towards the right end of the score bar MVN R0, #0 \ Set playingGame = -1 to flag that the game STR R0, [R11, #playingGame] \ is being played and that this is not the \ crash animation MOV R0, #0 \ Set xCamera = 0 and zCamera = 0 STR R0, [R11, #xCamera] \ STR R0, [R11, #zCamera] \ This doesn't have any effect as the camera \ position is set at the start of the main \ loop by the call to MoveAndDrawPlayer, \ which overwrites these values STR R0, [R11, #shipDirection] \ Set shipDirection = 0 so the ship faces \ right when the game starts (though this is \ quickly corrected when the game uses the \ mouse coordinates to calculate the \ direction) MOV R0, #1 \ Set shipPitch = 1 so the ship is very STR R0, [R11, #shipPitch] \ slightly pitched up for take-off MOV R0, #LAUNCHPAD_SIZE/2 \ Set the starting coordinates of the MOV R2, R0 \ player's ship as follows: ADD R3, R11, #xPlayer \ MOV R1, #LAUNCHPAD_Y \ xPlayer = LAUNCHPAD_SIZE / 2 STMIA R3!, {R0-R2} \ yPlayer = LAUNCHPAD_Y \ zPlayer = LAUNCHPAD_SIZE / 2 \ \ which is in the middle of the launchpad MOV R0, #0 \ Set the player's velocity to zero as MOV R1, #0 \ follows: MOV R2, #0 \ STMIA R3!, {R0-R2} \ xVelocity = 0 \ yVelocity = 0 \ zVelocity = 0 BL ResetMousePosition \ Reset the mouse position to (511, 511), \ ready for the game \ ****************************************************************************** \ \ Name: MainLoop \ Type: Subroutine \ Category: Main loop \ Summary: The main game loop \ Deep dive: The main game loop \ \ ****************************************************************************** .MainLoop MOV R0, #129 \ Call OS_Byte 129 to read the keyboard with MOV R1, #0 \ the time limit in R1 and R2 (so that's MOV R2, #0 \ with no time limit as R1 and R2 are zero), SWI OS_Byte \ returning the result in R2 TEQ R2, #&1B \ If R2 = &1B then an escape condition BEQ EndGame \ occurred during the keyboard scan (in \ other words, Escape was pressed), so jump \ to EndGame to acknowledge the escape \ condition and quit the game BL MoveAndDrawPlayer \ Move the player's ship and draw it into \ the graphics buffers \ We now set up the rotation matrix for the \ rocks, using the main loop counter to \ generate rotation angles that change along \ with the main loop (so the rocks spin at a \ nice steady speed) LDR R0, [R11, #mainLoopCount] \ Set R0 = mainLoopCount << 24 MOV R0, R0, LSL #24 MOV R1, R0, LSL #1 \ Set R1 = mainLoopCount << 25 BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ "angles" given in R0 and R1, which we can \ apply to any rocks we draw in the \ MoveAndDrawParticles routine (as rocks are \ only rotating 3D objects apart from the \ player, and the player calculates its own \ rotation matrix) BL DropRocksFromTheSky \ If the score is 800 or more, then randomly \ drop rocks from the sky BL MoveAndDrawParticles \ Move and draw all the particles, such as \ smoke clouds and bullets, into the \ graphics buffers BL DrawObjects \ Draw all the objects, such as trees and \ buildings, into the graphics buffers BL AddTerminatorsToBuffers \ Add terminators to the ends of the \ graphics buffers so we know when to stop \ drawing BL DrawLandscapeAndBuffers \ Draw the landscape and the contents of the \ graphics buffers BL PrintCurrentScore \ Print the number of remaining bullets at \ the left end of the score bar BL DrawFuelLevel \ Draw the fuel bar BL SwitchScreenBank \ Switch screen banks and clear the newly \ hidden screen bank to black LDR R14, [R11, #mainLoopCount] \ Increment the main loop counter ADD R14, R14, #1 STR R14, [R11, #mainLoopCount] B MainLoop \ Loop back to repeat the main loop \ ****************************************************************************** \ \ Name: EndGame \ Type: Subroutine \ Category: Main loop \ Summary: Finish the game \ \ ****************************************************************************** .EndGame MOV R0, #126 \ Call OS_Byte 126 to acknowledge the escape SWI OS_Byte \ condition caused by the player pressing \ Escape in the main loop MOV R0, #22 \ Change to screen mode 0 SWI OS_WriteC MOV R0, #0 SWI OS_WriteC MOV R0, #4 \ Call OS_Byte 4 to set the cursor keys to MOV R1, #0 \ move the cursor, so they work normally MOV R2, #0 \ again SWI OS_Byte \ ****************************************************************************** \ \ Name: ReturnToDesktop \ Type: Subroutine \ Category: Start and end \ Summary: Return to the desktop \ \ ****************************************************************************** .ReturnToDesktop LDMFD R13!, {R14} \ Restore the value from the stack and store \ it in R14, so R14 contains the same value \ that it had when the game was first run, \ and which we stored on the stack in the \ Entry routine \ \ So this sets R14 to the address we should \ return to when quitting the game LDR R13, stackPointerOnEntry \ Set R13 to the value that we stored in the \ Entry routine, so that the stack pointer \ is restored to the value that it had when \ the game was first run, and which we \ stored in stackPointerOnEntry in the Entry \ routine MOV PC, R14 \ Exit from the game by jumping to the \ address in R14, which will return us to \ the Desktop (or wherever the game was run \ from) \ ****************************************************************************** \ \ Name: PrintHexNumber \ Type: Subroutine \ Category: Score bar \ Summary: An unused routine that prints an 8-digit hexadecimal number on the \ second character row of the screen \ Deep dive: Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R1 The number to print \ \ ****************************************************************************** .PrintHexNumber 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 STMFD R13!, {R0-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R0, R1, LSR #28 \ Print the top nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #24 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #20 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #16 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #12 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #8 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #4 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1 \ Print the bottom nibble of the value in R1 BL PrintHexDigit MOV R0, #&A \ Print a line feed (ASCII &0A) and carriage SWI OS_WriteC \ return (ASCII &0D) to move the cursor down MOV R0, #&D \ to the start of the next line, ready to SWI OS_WriteC \ print further numbers if required LDMFD R13!, {R0-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name: PrintHexDigit \ Type: Subroutine \ Category: Score bar \ Summary: An unused routine that prints a single digit hexadecimal number in \ the score bar \ Deep dive: Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R0 The number to print (only the low nibble is \ printed, the rest of the number is ignored) \ \ ****************************************************************************** .PrintHexDigit AND R0, R0, #&F \ Extract the low nibble from the number in \ R0 CMP R0, #&A \ If the low nibble in R0 >= &A then the hex ADDHS R0, R0, #&37 \ digit is A to F, so add &37 to get the \ corresponding hex digit (so this converts \ &A into ASCII &37 + &A = &41, which gives \ us "A", the hex digit we want) ADDLO R0, R0, #&30 \ Otherwise the low nibble in R0 is 0 to 9, \ so add the ASCII value of "0" (ASCII &30) \ to get the corresponding hex digit SWI OS_WriteC \ Print the character in R0 MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name: objectRock \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for a rock \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectRock EQUD 6 \ Number of vertices EQUD 8 \ Number of faces EQUD objectRockFaces - objectRock EQUD %00000011 \ Flags: Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has a shadow .objectRockVertices \ xObject, yObject, zObject EQUD &00000000, &00000000, &00A00000 \ Vertex 0 EQUD &00A00000, &00A00000, &00000000 \ Vertex 1 EQUD &FF600000, &00A00000, &00000000 \ Vertex 2 EQUD &00A00000, &FF600000, &00000000 \ Vertex 3 EQUD &FF600000, &FF600000, &00000000 \ Vertex 4 EQUD &00000000, &00000000, &FF600000 \ Vertex 5 .objectRockFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &54DA5200, &54DA5200, 0, 1, 2, &444 \ 0 EQUD &54DA5200, &00000000, &54DA5200, 0, 3, 1, &444 \ 1 EQUD &00000000, &AB25AE00, &54DA5200, 0, 4, 3, &444 \ 2 EQUD &AB25AE00, &00000000, &54DA5200, 0, 2, 4, &444 \ 3 EQUD &00000000, &54DA5200, &AB25AE00, 5, 1, 2, &444 \ 4 EQUD &54DA5200, &00000000, &AB25AE00, 5, 3, 1, &444 \ 5 EQUD &00000000, &AB25AE00, &AB25AE00, 5, 4, 3, &444 \ 6 EQUD &AB25AE00, &00000000, &AB25AE00, 5, 2, 4, &444 \ 7 \ ****************************************************************************** \ \ Name: objectPyramid \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for a pyramid \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectPyramid EQUD 5 \ Number of vertices EQUD 6 \ Number of faces EQUD objectPyramidFaces - objectPyramid EQUD %00000001 \ Flags: Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has no shadow .objectPyramidVertices \ xObject, yObject, zObject EQUD &00000000, &01000000, &00000000 \ Vertex 0 EQUD &00C00000, &FF800000, &00C00000 \ Vertex 1 EQUD &FF400000, &FF800000, &00C00000 \ Vertex 2 EQUD &00C00000, &FF800000, &FF400000 \ Vertex 3 EQUD &FF400000, &FF800000, &FF400000 \ Vertex 4 .objectPyramidFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &35AA66D2, &6B54CDA5, 0, 1, 2, &800 \ 0 EQUD &6B54CDA5, &35AA66D2, &00000000, 0, 3, 1, &088 \ 1 EQUD &00000000, &35AA66D2, &94AB325B, 0, 4, 3, &880 \ 2 EQUD &94AB325B, &35AA66D2, &00000000, 0, 2, 4, &808 \ 3 EQUD &00000000, &88000000, &00000000, 1, 2, 3, &444 \ 4 EQUD &00000000, &88000000, &00000000, 2, 3, 4, &008 \ 5 \ ****************************************************************************** \ \ Name: objectPlayer \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the player's ship \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectPlayer EQUD 9 \ Number of vertices EQUD 9 \ Number of faces EQUD objectPlayerFaces - objectPlayer EQUD %00000011 \ Flags: Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has a shadow .objectPlayerVertices \ xObject, yObject, zObject EQUD &01000000, &00500000, &00800000 \ Vertex 0 EQUD &01000000, &00500000, &FF800000 \ Vertex 1 EQUD &00000000, &000A0000, &FECCCCCD \ Vertex 2 EQUD &FF19999A, &00500000, &00000000 \ Vertex 3 EQUD &00000000, &000A0000, &01333333 \ Vertex 4 EQUD &FFE66667, &FF880000, &00000000 \ Vertex 5 EQUD &00555555, &00500000, &00400000 \ Vertex 6 EQUD &00555555, &00500000, &FFC00000 \ Vertex 7 EQUD &FFCCCCCD, &00500000, &00000000 \ Vertex 8 .objectPlayerFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &457C441A, &9E2A1F4C, &00000000, 0, 1, 5, &080 \ 0 EQUD &35F5D83B, &9BC03EC1, &DA12D71D, 1, 2, 5, &040 \ 1 EQUD &35F5D83B, &9BC03EC1, &25ED28E3, 0, 5, 4, &040 \ 2 EQUD &B123D51C, &AF3F50EE, &D7417278, 2, 3, 5, &040 \ 3 EQUD &B123D51D, &AF3F50EE, &28BE8D88, 3, 4, 5, &040 \ 4 EQUD &F765D8CD, &73242236, &DF4FD176, 1, 2, 3, &088 \ 5 EQUD &F765D8CD, &73242236, &20B02E8A, 0, 3, 4, &088 \ 6 EQUD &00000000, &78000000, &00000000, 0, 1, 3, &044 \ 7 EQUD &00000000, &78000000, &00000000, 6, 7, 8, &C80 \ 8 \ ****************************************************************************** \ \ Name: objectSmallLeafyTree \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the small leafy tree \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectSmallLeafyTree EQUD 11 \ Number of vertices EQUD 5 \ Number of faces EQUD objectSmallLeafyTreeFaces - objectSmallLeafyTree EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectSmallLeafyTreeVertices \ xObject, yObject, zObject EQUD &00300000, &FE800000, &00300000 \ Vertex 0 EQUD &FFD9999A, &00000000, &00000000 \ Vertex 1 EQUD &00266666, &00000000, &00000000 \ Vertex 2 EQUD &00000000, &FEF33334, &FF400000 \ Vertex 3 EQUD &00800000, &FF400000, &FF800000 \ Vertex 4 EQUD &FF400000, &FECCCCCD, &FFD55556 \ Vertex 5 EQUD &FF800000, &FEA66667, &00400000 \ Vertex 6 EQUD &00800000, &FE59999A, &002AAAAA \ Vertex 7 EQUD &00C00000, &FEA66667, &FFC00000 \ Vertex 8 EQUD &FFA00000, &FECCCCCD, &00999999 \ Vertex 9 EQUD &00C00000, &FF400000, &00C00000 \ Vertex 10 .objectSmallLeafyTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &14A01873, &AF8F9F93, &56A0681E, 0, 9, 10, &040 \ 0 EQUD &00000000, &00000000, &00000000, 0, 1, 2, &400 \ 1 EQUD &499A254E, &B123FC2C, &CB6D5299, 0, 3, 4, &080 \ 2 EQUD &E4D2EEBE, &8DC82837, &E72FE5E9, 0, 5, 6, &080 \ 3 EQUD &D5710585, &B29EF364, &AEC07EB3, 0, 7, 8, &080 \ 4 \ ****************************************************************************** \ \ Name: objectTallLeafyTree \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the tall leafy tree \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectTallLeafyTree EQUD 14 \ Number of vertices EQUD 6 \ Number of faces EQUD objectTallLeafyTreeFaces - objectTallLeafyTree EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectTallLeafyTreeVertices \ xObject, yObject, zObject EQUD &0036DB6D, &FD733334, &00300000 \ Vertex 0 EQUD &FFD00000, &00000000, &00000000 \ Vertex 1 EQUD &00300000, &00000000, &00000000 \ Vertex 2 EQUD &00000000, &FE0CCCCD, &FF400000 \ Vertex 3 EQUD &00800000, &FE59999A, &FF800000 \ Vertex 4 EQUD &FF533334, &FE333334, &FFC92493 \ Vertex 5 EQUD &FF400000, &FEA66667, &00600000 \ Vertex 6 EQUD &00000000, &FF19999A, &FF666667 \ Vertex 7 EQUD &FF800000, &FF400000, &FFA00000 \ Vertex 8 EQUD &FFA00000, &FE800000, &00999999 \ Vertex 9 EQUD &00C00000, &FECCCCCD, &00C00000 \ Vertex 10 EQUD &FFB33334, &FF19999A, &00E66666 \ Vertex 11 EQUD &00800000, &FF400000, &00C00000 \ Vertex 12 EQUD &00300000, &FE59999A, &00300000 \ Vertex 13 .objectTallLeafyTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &FD3D01DD, &D2CB371E, &6F20024E, 0, 9, 10, &040 \ 0 EQUD &1E6F981A, &BB105ECE, &5D638B16, 13, 11, 12, &080 \ 1 EQUD &00000000, &00000000, &00000000, 0, 1, 2, &400 \ 2 EQUD &49D96509, &B8E72762, &C19E3A19, 0, 3, 4, &080 \ 3 EQUD &AD213B74, &B641CA5D, &2DC40650, 0, 5, 6, &040 \ 4 EQUD &C9102051, &AC846CAD, &BD92A8C1, 13, 7, 8, &040 \ 5 \ ****************************************************************************** \ \ Name: objectSmokingRemainsLeft \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the smoking remains that bend to the left \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectSmokingRemainsLeft EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectSmokingRemainsLeftFaces - objectSmokingRemainsLeft EQUD %00000000 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingRemainsLeftVertices \ xObject, yObject, zObject EQUD &FFD9999A, &00000000, &00000000 \ Vertex 0 EQUD &00266666, &00000000, &00000000 \ Vertex 1 EQUD &002B3333, &FFC00000, &00000000 \ Vertex 2 EQUD &00300000, &FF800000, &00000000 \ Vertex 3 EQUD &FFD55556, &FECCCCCD, &00000000 \ Vertex 4 .objectSmokingRemainsLeftFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &00000000, &00000000, 0, 1, 3, &000 \ 0 EQUD &00000000, &00000000, &00000000, 2, 3, 4, &000 \ 1 \ ****************************************************************************** \ \ Name: objectSmokingRemainsRight \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the smoking remains that bend to the right \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectSmokingRemainsRight EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectSmokingRemainsRightFaces - objectSmokingRemainsRight EQUD %00000000 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingRemainsRightVertices \ xObject, yObject, zObject EQUD &002AAAAA, &00000000, &00000000 \ Vertex 0 EQUD &FFD55556, &00000000, &00000000 \ Vertex 1 EQUD &FFD4CCCD, &FFD00000, &00000000 \ Vertex 2 EQUD &FFD00000, &FFA00000, &00000000 \ Vertex 3 EQUD &002AAAAA, &FEA66667, &00000000 \ Vertex 4 .objectSmokingRemainsRightFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &00000000, &00000000, 0, 1, 3, &000 \ 0 EQUD &00000000, &00000000, &00000000, 2, 3, 4, &000 \ 1 \ ****************************************************************************** \ \ Name: objectFirTree \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the fir tree \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectFirTree EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectFirTreeFaces - objectFirTree EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectFirTreeVertices \ xObject, yObject, zObject EQUD &FFA00000, &FFC92493, &FFC92493 \ Vertex 0 EQUD &00600000, &FFC92493, &FFC92493 \ Vertex 1 EQUD &00000000, &FE333334, &0036DB6D \ Vertex 2 EQUD &00266666, &00000000, &00000000 \ Vertex 3 EQUD &FFD9999A, &00000000, &00000000 \ Vertex 4 .objectFirTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &00000000, &00000000, 2, 3, 4, &400 \ 0 EQUD &00000000, &E0B0E050, &8C280943, 0, 1, 2, &040 \ 1 \ ****************************************************************************** \ \ Name: objectGazebo \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the gazebo \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectGazebo EQUD 13 \ Number of vertices EQUD 8 \ Number of faces EQUD objectGazeboFaces - objectGazebo EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectGazeboVertices \ xObject, yObject, zObject EQUD &00000000, &FF000000, &00000000 \ Vertex 0 EQUD &FF800000, &FF400000, &00800000 \ Vertex 1 EQUD &FF800000, &FF400000, &FF800000 \ Vertex 2 EQUD &00800000, &FF400000, &FF800000 \ Vertex 3 EQUD &00800000, &FF400000, &00800000 \ Vertex 4 EQUD &FF800000, &00000000, &00800000 \ Vertex 5 EQUD &FF800000, &00000000, &FF800000 \ Vertex 6 EQUD &00800000, &00000000, &FF800000 \ Vertex 7 EQUD &00800000, &00000000, &00800000 \ Vertex 8 EQUD &FF99999A, &FF400000, &00800000 \ Vertex 9 EQUD &FF99999A, &FF400000, &FF800000 \ Vertex 10 EQUD &00666666, &FF400000, &FF800000 \ Vertex 11 EQUD &00666666, &FF400000, &00800000 \ Vertex 12 .objectGazeboFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &00000000, &78000000, 1, 5, 9, &444 \ 0 EQUD &00000000, &00000000, &88000000, 2, 6, 10, &444 \ 1 EQUD &00000000, &94AB325B, &35AA66D2, 0, 1, 4, &400 \ 2 EQUD &00000000, &00000000, &88000000, 3, 7, 11, &444 \ 3 EQUD &00000000, &00000000, &78000000, 4, 8, 12, &444 \ 4 EQUD &CA55992E, &94AB325B, &00000000, 0, 1, 2, &840 \ 5 EQUD &35AA66D2, &94AB325B, &00000000, 0, 3, 4, &840 \ 6 EQUD &00000000, &94AB325B, &CA55992E, 0, 2, 3, &400 \ 7 \ ****************************************************************************** \ \ Name: objectBuilding \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the building \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectBuilding EQUD 16 \ Number of vertices EQUD 12 \ Number of faces EQUD objectBuildingFaces - objectBuilding EQUD %00000000 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectBuildingVertices \ xObject, yObject, zObject EQUD &FF19999A, &FF266667, &00000000 \ Vertex 0 EQUD &FF400000, &FF266667, &00000000 \ Vertex 1 EQUD &00C00000, &FF266667, &00000000 \ Vertex 2 EQUD &00E66666, &FF266667, &00000000 \ Vertex 3 EQUD &FF19999A, &FF8CCCCD, &00A66666 \ Vertex 4 EQUD &FF19999A, &FF8CCCCD, &FF59999A \ Vertex 5 EQUD &00E66666, &FF8CCCCD, &00A66666 \ Vertex 6 EQUD &00E66666, &FF8CCCCD, &FF59999A \ Vertex 7 EQUD &FF400000, &FF666667, &00800000 \ Vertex 8 EQUD &FF400000, &FF666667, &FF800000 \ Vertex 9 EQUD &00C00000, &FF666667, &00800000 \ Vertex 10 EQUD &00C00000, &FF666667, &FF800000 \ Vertex 11 EQUD &FF400000, &00000000, &00800000 \ Vertex 12 EQUD &FF400000, &00000000, &FF800000 \ Vertex 13 EQUD &00C00000, &00000000, &00800000 \ Vertex 14 EQUD &00C00000, &00000000, &FF800000 \ Vertex 15 .objectBuildingFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &99CD0E6D, &3EE445CC, 0, 4, 6, &400 \ 0 EQUD &00000000, &99CD0E6D, &3EE445CC, 0, 3, 6, &400 \ 1 EQUD &88000000, &00000000, &00000000, 1, 8, 9, &DDD \ 2 EQUD &78000000, &00000000, &00000000, 2, 10, 11, &555 \ 3 EQUD &88000000, &00000000, &00000000, 8, 12, 13, &FFF \ 4 EQUD &88000000, &00000000, &00000000, 8, 9, 13, &FFF \ 5 EQUD &78000000, &00000000, &00000000, 10, 14, 15, &777 \ 6 EQUD &78000000, &00000000, &00000000, 10, 11, 15, &777 \ 7 EQUD &00000000, &00000000, &88000000, 9, 13, 15, &BBB \ 8 EQUD &00000000, &00000000, &88000000, 9, 11, 15, &BBB \ 9 EQUD &00000000, &99CD0E6D, &C11BBA34, 0, 5, 7, &800 \ 10 EQUD &00000000, &99CD0E6D, &C11BBA34, 0, 3, 7, &800 \ 11 \ ****************************************************************************** \ \ Name: objectSmokingBuilding \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the smoking remains of a building \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectSmokingBuilding EQUD 6 \ Number of vertices EQUD 6 \ Number of faces EQUD objectSmokingBuildingFaces - objectSmokingBuilding EQUD %00000000 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingBuildingVertices \ xObject, yObject, zObject EQUD &FF400000, &00000001, &00800000 \ Vertex 0 EQUD &FF400000, &00000001, &FF800000 \ Vertex 1 EQUD &00C00000, &00000001, &00800000 \ Vertex 2 EQUD &00C00000, &00000001, &FF800000 \ Vertex 3 EQUD &FF400000, &FF99999A, &00800000 \ Vertex 4 EQUD &00C00000, &FFB33334, &FF800000 \ Vertex 5 .objectSmokingBuildingFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &78000000, &00000000, 0, 1, 2, &000 \ 0 EQUD &00000000, &78000000, &00000000, 1, 2, 3, &000 \ 1 EQUD &00000000, &00000000, &78000000, 0, 2, 4, &333 \ 2 EQUD &88000000, &00000000, &00000000, 0, 1, 4, &666 \ 3 EQUD &78000000, &00000000, &00000000, 2, 3, 5, &555 \ 4 EQUD &00000000, &00000000, &88000001, 1, 3, 5, &777 \ 5 \ ****************************************************************************** \ \ Name: objectSmokingGazebo \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the smoking remains of a gazebo \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectSmokingGazebo EQUD 6 \ Number of vertices EQUD 4 \ Number of faces EQUD objectSmokingGazeboFaces - objectSmokingGazebo EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectSmokingGazeboVertices \ xObject, yObject, zObject EQUD &00000000, &FF8CCCCD, &FFF00000 \ Vertex 0 EQUD &00199999, &FF8CCCCD, &FFF00000 \ Vertex 1 EQUD &00800000, &00000000, &00800000 \ Vertex 2 EQUD &FF800000, &00000000, &00800000 \ Vertex 3 EQUD &00800000, &00000000, &FF800000 \ Vertex 4 EQUD &FF800000, &00000000, &FF800000 \ Vertex 5 .objectSmokingGazeboFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &A24BB5BE, &4AF6A1AD, 0, 1, 2, &000 \ 0 EQUD &00000000, &A24BB5BE, &4AF6A1AD, 0, 1, 3, &333 \ 1 EQUD &00000000, &AC59C060, &A9F5EA98, 0, 1, 4, &444 \ 2 EQUD &00000000, &AC59C060, &A9F5EA98, 0, 1, 5, &000 \ 3 \ ****************************************************************************** \ \ Name: objectRocket \ Type: Variable \ Category: 3D objects \ Summary: Object blueprint for the rocket \ Deep dive: Object blueprints \ \ ****************************************************************************** .objectRocket EQUD 13 \ Number of vertices EQUD 8 \ Number of faces EQUD objectRocketFaces - objectRocket EQUD %00000010 \ Flags: Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectRocketVertices \ xObject, yObject, zObject EQUD &00000000, &FE400000, &00000000 \ Vertex 0 EQUD &FFC80000, &FFD745D2, &00380000 \ Vertex 1 EQUD &FFC80000, &FFD745D2, &FFC80000 \ Vertex 2 EQUD &00380000, &FFD745D2, &00380000 \ Vertex 3 EQUD &00380000, &FFD745D2, &FFC80000 \ Vertex 4 EQUD &FF900000, &00000000, &00700000 \ Vertex 5 EQUD &FF900000, &00000000, &FF900000 \ Vertex 6 EQUD &00700000, &00000000, &00700000 \ Vertex 7 EQUD &00700000, &00000000, &FF900000 \ Vertex 8 EQUD &FFE40000, &FF071C72, &001C0000 \ Vertex 9 EQUD &FFE40000, &FF071C72, &FFE40000 \ Vertex 10 EQUD &001C0000, &FF071C72, &001C0000 \ Vertex 11 EQUD &001C0000, &FF071C72, &FFE40000 \ Vertex 12 .objectRocketFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000, &00000000, &00000000, 9, 1, 5, &CC0 \ 0 EQUD &00000000, &00000000, &00000000, 11, 3, 7, &CC0 \ 1 EQUD &00000000, &EFA75F67, &76E1A76B, 0, 1, 3, &C00 \ 2 EQUD &891E5895, &EFA75F67, &00000000, 0, 1, 2, &800 \ 3 EQUD &76E1A76B, &EFA75F67, &00000000, 3, 0, 4, &800 \ 4 EQUD &00000000, &EFA75F67, &891E5895, 0, 2, 4, &C00 \ 5 EQUD &00000000, &00000000, &00000000, 10, 2, 6, &CC0 \ 6 EQUD &00000000, &00000000, &00000000, 12, 4, 8, &CC0 \ 7 \ ****************************************************************************** \ \ Name: sinTable \ Type: Variable \ Category: Maths (Geometry) \ Summary: Sine/cosine lookup table \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains: \ \ (2^31 - 1) * SIN(2 * PI * (n / 1024)) \ \ For n = 0 to 1023 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines: \ \ FOR I% = 0 TO 1023 \ [ \ OPT pass% \ EQUD (2^31 - 1) * SIN(2 * PI * (I% / 1024)) \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .sinTable EQUD &00000000, &00C90F87, &01921D1F, &025B26D7 EQUD &03242ABE, &03ED26E6, &04B6195D, &057F0034 EQUD &0647D97C, &0710A344, &07D95B9E, &08A2009A EQUD &096A9049, &0A3308BC, &0AFB6805, &0BC3AC35 EQUD &0C8BD35D, &0D53DB92, &0E1BC2E3, &0EE38765 EQUD &0FAB272B, &1072A047, &1139F0CE, &120116D4 EQUD &12C8106E, &138EDBB0, &145576B1, &151BDF85 EQUD &15E21444, &16A81304, &176DD9DE, &183366E8 EQUD &18F8B83C, &19BDCBF2, &1A82A025, &1B4732EE EQUD &1C0B826A, &1CCF8CB2, &1D934FE4, &1E56CA1D EQUD &1F19F97A, &1FDCDC1A, &209F701C, &2161B39F EQUD &2223A4C5, &22E541AE, &23A6887E, &24677757 EQUD &25280C5D, &25E845B5, &26A82185, &27679DF3 EQUD &2826B928, &28E5714A, &29A3C484, &2A61B101 EQUD &2B1F34EB, &2BDC4E6E, &2C98FBBA, &2D553AFB EQUD &2E110A61, &2ECC681D, &2F875262, &3041C760 EQUD &30FBC54C, &31B54A5D, &326E54C6, &3326E2C2 EQUD &33DEF286, &3496824F, &354D9056, &36041AD8 EQUD &36BA2013, &376F9E45, &382493AF, &38D8FE92 EQUD &398CDD31, &3A402DD1, &3AF2EEB6, &3BA51E28 EQUD &3C56BA6F, &3D07C1D5, &3DB832A5, &3E680B2C EQUD &3F1749B7, &3FC5EC97, &4073F21C, &4121589A EQUD &41CE1E63, &427A41D0, &4325C135, &43D09AEC EQUD &447ACD4F, &452456BC, &45CD358E, &46756827 EQUD &471CECE6, &47C3C22E, &4869E664, &490F57ED EQUD &49B41533, &4A581C9D, &4AFB6C97, &4B9E038E EQUD &4C3FDFF3, &4CE10033, &4D8162C3, &4E210616 EQUD &4EBFE8A4, &4F5E08E2, &4FFB654C, &5097FC5D EQUD &5133CC93, &51CED46D, &5269126D, &53028517 EQUD &539B2AEF, &5433027C, &54CA0A49, &556040E2 EQUD &55F5A4D1, &568A34A8, &571DEEF8, &57B0D255 EQUD &5842DD53, &58D40E8B, &59646497, &59F3DE11 EQUD &5A827999, &5B1035CE, &5B9D1153, &5C290ACB EQUD &5CB420DF, &5D3E5236, &5DC79D7B, &5E50015C EQUD &5ED77C89, &5F5E0DB2, &5FE3B38C, &60686CCD EQUD &60EC382E, &616F146A, &61F1003E, &6271FA68 EQUD &62F201AB, &637114CB, &63EF328E, &646C59BE EQUD &64E88925, &6563BF91, &65DDFBD2, &66573CBA EQUD &66CF811E, &6746C7D6, &67BD0FBB, &683257A9 EQUD &68A69E80, &6919E31F, &698C246B, &69FD6149 EQUD &6A6D98A3, &6ADCC963, &6B4AF277, &6BB812CF EQUD &6C24295F, &6C8F351A, &6CF934FB, &6D6227F9 EQUD &6DCA0D13, &6E30E349, &6E96A99C, &6EFB5F11 EQUD &6F5F02B1, &6FC19384, &70231098, &708378FE EQUD &70E2CBC5, &71410803, &719E2CD1, &71FA3947 EQUD &72552C84, &72AF05A6, &7307C3CF, &735F6625 EQUD &73B5EBD0, &740B53F9, &745F9DD0, &74B2C882 EQUD &7504D344, &7555BD4A, &75A585CE, &75F42C09 EQUD &7641AF3B, &768E0EA4, &76D94988, &77235F2C EQUD &776C4EDA, &77B417DE, &77FAB987, &78403327 EQUD &78848413, &78C7ABA0, &7909A92B, &794A7C10 EQUD &798A23B0, &79C89F6C, &7A05EEAC, &7A4210D7 EQUD &7A7D055A, &7AB6CBA2, &7AEF6322, &7B26CB4E EQUD &7B5D039C, &7B920B88, &7BC5E28E, &7BF8882F EQUD &7C29FBED, &7C5A3D4E, &7C894BDC, &7CB72723 EQUD &7CE3CEB0, &7D0F4217, &7D3980EB, &7D628AC5 EQUD &7D8A5F3F, &7DB0FDF6, &7DD6668D, &7DFA98A7 EQUD &7E1D93E8, &7E3F57FD, &7E5FE492, &7E7F3955 EQUD &7E9D55FB, &7EBA3A38, &7ED5E5C5, &7EF0585E EQUD &7F0991C2, &7F2191B3, &7F3857F5, &7F4DE44F EQUD &7F62368E, &7F754E7E, &7F872BF2, &7F97CEBC EQUD &7FA736B3, &7FB563B2, &7FC25595, &7FCE0C3D EQUD &7FD8878D, &7FE1C76A, &7FE9CBBF, &7FF09477 EQUD &7FF62181, &7FFA72D0, &7FFD8859, &7FFF6216 EQUD &7FFFFFFE, &7FFF6216, &7FFD8859, &7FFA72D0 EQUD &7FF62181, &7FF09477, &7FE9CBBF, &7FE1C76A EQUD &7FD8878D, &7FCE0C3D, &7FC25595, &7FB563B2 EQUD &7FA736B3, &7F97CEBC, &7F872BF2, &7F754E7E EQUD &7F62368E, &7F4DE44F, &7F3857F5, &7F2191B3 EQUD &7F0991C2, &7EF0585E, &7ED5E5C5, &7EBA3A38 EQUD &7E9D55FB, &7E7F3955, &7E5FE492, &7E3F57FD EQUD &7E1D93E8, &7DFA98A7, &7DD6668D, &7DB0FDF6 EQUD &7D8A5F3F, &7D628AC5, &7D3980EB, &7D0F4217 EQUD &7CE3CEB0, &7CB72723, &7C894BDC, &7C5A3D4E EQUD &7C29FBED, &7BF8882F, &7BC5E28E, &7B920B88 EQUD &7B5D039C, &7B26CB4E, &7AEF6322, &7AB6CBA2 EQUD &7A7D055A, &7A4210D7, &7A05EEAC, &79C89F6C EQUD &798A23B0, &794A7C10, &7909A92B, &78C7ABA0 EQUD &78848413, &78403327, &77FAB987, &77B417DE EQUD &776C4EDA, &77235F2C, &76D94988, &768E0EA4 EQUD &7641AF3B, &75F42C09, &75A585CE, &7555BD4A EQUD &7504D344, &74B2C882, &745F9DD0, &740B53FA EQUD &73B5EBD0, &735F6625, &7307C3CF, &72AF05A5 EQUD &72552C84, &71FA3948, &719E2CD1, &71410803 EQUD &70E2CBC5, &708378FD, &70231098, &6FC19384 EQUD &6F5F02B0, &6EFB5F11, &6E96A99C, &6E30E348 EQUD &6DCA0D13, &6D6227F9, &6CF934FB, &6C8F351A EQUD &6C24295F, &6BB812D0, &6B4AF277, &6ADCC963 EQUD &6A6D98A3, &69FD6149, &698C246B, &6919E320 EQUD &68A69E80, &683257AA, &67BD0FBC, &6746C7D6 EQUD &66CF811E, &66573CBA, &65DDFBD1, &6563BF91 EQUD &64E88925, &646C59BF, &63EF328E, &637114CB EQUD &62F201AC, &6271FA68, &61F1003E, &616F146B EQUD &60EC382E, &60686CCE, &5FE3B38D, &5F5E0DB2 EQUD &5ED77C89, &5E50015D, &5DC79D7B, &5D3E5236 EQUD &5CB420DF, &5C290ACB, &5B9D1153, &5B1035CF EQUD &5A82799A, &59F3DE11, &59646497, &58D40E8C EQUD &5842DD53, &57B0D256, &571DEEFA, &568A34A8 EQUD &55F5A4D2, &556040E2, &54CA0A49, &5433027D EQUD &539B2AEF, &53028516, &5269126E, &51CED46E EQUD &5133CC93, &5097FC5D, &4FFB654D, &4F5E08E2 EQUD &4EBFE8A4, &4E210617, &4D8162C4, &4CE10033 EQUD &4C3FDFF3, &4B9E0390, &4AFB6C97, &4A581C9D EQUD &49B41533, &490F57ED, &4869E664, &47C3C22F EQUD &471CECE6, &46756827, &45CD358F, &452456BC EQUD &447ACD50, &43D09AEC, &4325C134, &427A41D0 EQUD &41CE1E64, &4121589B, &4073F21C, &3FC5EC98 EQUD &3F1749B8, &3E680B2C, &3DB832A6, &3D07C1D6 EQUD &3C56BA6F, &3BA51E28, &3AF2EEB7, &3A402DD0 EQUD &398CDD32, &38D8FE93, &382493AF, &376F9E45 EQUD &36BA2013, &36041AD7, &354D9056, &3496824F EQUD &33DEF287, &3326E2C1, &326E54C7, &31B54A5E EQUD &30FBC54C, &3041C760, &2F875262, &2ECC681D EQUD &2E110A61, &2D553AFC, &2C98FBB9, &2BDC4E6F EQUD &2B1F34EB, &2A61B100, &29A3C484, &28E5714B EQUD &2826B927, &27679DF3, &26A82186, &25E845B6 EQUD &25280C5D, &24677757, &23A6887F, &22E541AE EQUD &2223A4C5, &2161B3A0, &209F701B, &1FDCDC1A EQUD &1F19F97B, &1E56CA1D, &1D934FE5, &1CCF8CB3 EQUD &1C0B8269, &1B4732EF, &1A82A026, &19BDCBF1 EQUD &18F8B83C, &183366E8, &176DD9DD, &16A81304 EQUD &15E21444, &151BDF86, &145576B0, &138EDBB1 EQUD &12C8106F, &120116D4, &1139F0CF, &1072A048 EQUD &0FAB272A, &0EE38765, &0E1BC2E4, &0D53DB91 EQUD &0C8BD35E, &0BC3AC35, &0AFB6804, &0A3308BC EQUD &096A9049, &08A20099, &07D95B9E, &0710A345 EQUD &0647D97D, &057F0034, &04B6195D, &03ED26E7 EQUD &03242ABE, &025B26D7, &01921D20, &00C90F87 EQUD &00000000, &FF36F079, &FE6DE2E0, &FDA4D929 EQUD &FCDBD542, &FC12D91A, &FB49E6A3, &FA80FFCD EQUD &F9B82684, &F8EF5CBC, &F826A463, &F75DFF67 EQUD &F6956FB7, &F5CCF745, &F50497FC, &F43C53CB EQUD &F3742CA3, &F2AC2470, &F1E43D1D, &F11C789B EQUD &F054D8D6, &EF8D5FB9, &EEC60F32, &EDFEE92D EQUD &ED37EF92, &EC712450, &EBAA8950, &EAE4207B EQUD &EA1DEBBC, &E957ECFC, &E8922624, &E7CC9918 EQUD &E70747C5, &E642340F, &E57D5FDB, &E4B8CD12 EQUD &E3F47D97, &E330734D, &E26CB01C, &E1A935E4 EQUD &E0E60685, &E02323E6, &DF608FE5, &DE9E4C61 EQUD &DDDC5B3C, &DD1ABE53, &DC597782, &DB9888A9 EQUD &DAD7F3A4, &DA17BA4A, &D957DE7B, &D898620D EQUD &D7D946DA, &D71A8EB6, &D65C3B7C, &D59E4F01 EQUD &D4E0CB15, &D423B192, &D3670447, &D2AAC505 EQUD &D1EEF59F, &D13397E3, &D078AD9E, &CFBE38A1 EQUD &CF043AB5, &CE4AB5A3, &CD91AB3A, &CCD91D3F EQUD &CC210D79, &CB697DB1, &CAB26FAB, &C9FBE529 EQUD &C945DFED, &C89061BC, &C7DB6C52, &C727016E EQUD &C67322CF, &C5BFD230, &C50D114A, &C45AE1D8 EQUD &C3A94592, &C2F83E2B, &C247CD5B, &C197F4D5 EQUD &C0E8B649, &C03A1369, &BF8C0DE5, &BEDEA766 EQUD &BE31E19D, &BD85BE31, &BCDA3ECD, &BC2F6514 EQUD &BB8532B1, &BADBA945, &BA32CA72, &B98A97DA EQUD &B8E3131B, &B83C3DD2, &B796199D, &B6F0A814 EQUD &B64BEACD, &B5A7E364, &B504936A, &B461FC71 EQUD &B3C0200E, &B31EFFCE, &B27E9D3D, &B1DEF9EA EQUD &B140175D, &B0A1F71F, &B0049AB4, &AF6803A3 EQUD &AECC336E, &AE312B93, &AD96ED93, &ACFD7AEA EQUD &AC64D511, &ABCCFD84, &AB35F5B7, &AA9FBF1E EQUD &AA0A5B2F, &A975CB58, &A8E21107, &A84F2DAB EQUD &A7BD22AD, &A72BF174, &A69B9B69, &A60C21EF EQUD &A57D8667, &A4EFCA32, &A462EEAE, &A3D6F536 EQUD &A34BDF21, &A2C1ADCB, &A2386286, &A1AFFEA4 EQUD &A1288378, &A0A1F24F, &A01C4C74, &9F979333 EQUD &9F13C7D2, &9E90EB96, &9E0EFFC4, &9D8E0599 EQUD &9D0DFE55, &9C8EEB37, &9C10CD72, &9B93A642 EQUD &9B1776DD, &9A9C4070, &9A22042E, &99A8C347 EQUD &99307EE2, &98B93829, &9842F046, &97CDA857 EQUD &97596180, &96E61CE2, &9673DB96, &96029EB6 EQUD &9592675E, &9523369D, &94B50D88, &9447ED31 EQUD &93DBD6A1, &9370CAE4, &9306CB06, &929DD807 EQUD &9235F2EC, &91CF1CB8, &91695664, &9104A0EE EQUD &90A0FD50, &903E6C7C, &8FDCEF67, &8F7C8703 EQUD &8F1D343B, &8EBEF7FC, &8E61D32F, &8E05C6B8 EQUD &8DAAD37D, &8D50FA5B, &8CF83C31, &8CA099DC EQUD &8C4A1430, &8BF4AC06, &8BA06231, &8B4D377E EQUD &8AFB2CBC, &8AAA42B6, &8A5A7A32, &8A0BD3F6 EQUD &89BE50C5, &8971F15C, &8926B678, &88DCA0D5 EQUD &8893B126, &884BE821, &88054679, &87BFCCD9 EQUD &877B7BED, &87385460, &86F656D5, &86B583EF EQUD &8675DC51, &86376094, &85FA1154, &85BDEF29 EQUD &8582FAA6, &8549345D, &85109CDE, &84D934B2 EQUD &84A2FC63, &846DF478, &843A1D72, &840777D1 EQUD &83D60413, &83A5C2B1, &8376B424, &8348D8DD EQUD &831C3150, &82F0BDEA, &82C67F15, &829D753B EQUD &8275A0C2, &824F020A, &82299973, &8205675A EQUD &81E26C18, &81C0A803, &81A01B6F, &8180C6AB EQUD &8162AA05, &8145C5C9, &812A1A3B, &810FA7A2 EQUD &80F66E3E, &80DE6E4D, &80C7A80B, &80B21BB1 EQUD &809DC972, &808AB181, &8078D40E, &80683144 EQUD &8058C94D, &804A9C4E, &803DAA6B, &8031F3C3 EQUD &80277873, &801E3896, &80163441, &800F6B89 EQUD &8009DE7F, &80058D30, &800277A7, &80009DEB EQUD &80000001, &80009DEB, &800277A7, &80058D30 EQUD &8009DE7F, &800F6B89, &80163441, &801E3895 EQUD &80277873, &8031F3C3, &803DAA6B, &804A9C4E EQUD &8058C94D, &80683145, &8078D40E, &808AB182 EQUD &809DC972, &80B21BB1, &80C7A80B, &80DE6E4D EQUD &80F66E3E, &810FA7A1, &812A1A3B, &8145C5C8 EQUD &8162AA05, &8180C6AA, &81A01B6E, &81C0A803 EQUD &81E26C18, &8205675A, &82299972, &824F0209 EQUD &8275A0C2, &829D753B, &82C67F15, &82F0BDEA EQUD &831C314F, &8348D8DD, &8376B424, &83A5C2B1 EQUD &83D60413, &840777D0, &843A1D71, &846DF478 EQUD &84A2FC63, &84D934B2, &85109CDE, &8549345D EQUD &8582FAA6, &85BDEF29, &85FA1153, &86376093 EQUD &8675DC50, &86B583EF, &86F656D4, &87385460 EQUD &877B7BED, &87BFCCD8, &88054679, &884BE821 EQUD &8893B126, &88DCA0D4, &8926B678, &8971F15B EQUD &89BE50C5, &8A0BD3F6, &8A5A7A32, &8AAA42B6 EQUD &8AFB2CBB, &8B4D377D, &8BA06231, &8BF4AC06 EQUD &8C4A1430, &8CA099DB, &8CF83C30, &8D50FA5A EQUD &8DAAD37D, &8E05C6B8, &8E61D32F, &8EBEF7FB EQUD &8F1D343A, &8F7C8702, &8FDCEF66, &903E6C7B EQUD &90A0FD4F, &9104A0EE, &91695664, &91CF1CB7 EQUD &9235F2EC, &929DD806, &9306CB05, &9370CAE4 EQUD &93DBD6A0, &9447ED31, &94B50D88, &9523369D EQUD &9592675E, &96029EB6, &9673DB95, &96E61CE2 EQUD &9759617F, &97CDA856, &9842F045, &98B93829 EQUD &99307EE2, &99A8C347, &9A22042D, &9A9C406F EQUD &9B1776DC, &9B93A641, &9C10CD72, &9C8EEB36 EQUD &9D0DFE54, &9D8E0598, &9E0EFFC3, &9E90EB95 EQUD &9F13C7D2, &9F979334, &A01C4C73, &A0A1F24E EQUD &A1288376, &A1AFFEA3, &A2386285, &A2C1ADC9 EQUD &A34BDF21, &A3D6F535, &A462EEAC, &A4EFCA31 EQUD &A57D8667, &A60C21ED, &A69B9B69, &A72BF175 EQUD &A7BD22AB, &A84F2DAA, &A8E21108, &A975CB56 EQUD &AA0A5B2E, &AA9FBF1F, &AB35F5B5, &ABCCFD83 EQUD &AC64D512, &ACFD7AE8, &AD96ED92, &AE312B93 EQUD &AECC336C, &AF6803A3, &B0049AB5, &B0A1F71D EQUD &B140175C, &B1DEF9EA, &B27E9D3C, &B31EFFCD EQUD &B3C0200F, &B461FC70, &B5049369, &B5A7E365 EQUD &B64BEACD, &B6F0A813, &B796199A, &B83C3DD1 EQUD &B8E3131A, &B98A97D7, &BA32CA71, &BADBA944 EQUD &BB8532AF, &BC2F6514, &BCDA3ECC, &BD85BE2F EQUD &BE31E19C, &BEDEA767, &BF8C0DE2, &C03A1368 EQUD &C0E8B649, &C197F4D3, &C247CD5A, &C2F83E2C EQUD &C3A9458F, &C45AE1D8, &C50D114B, &C5BFD22E EQUD &C67322CE, &C727016F, &C7DB6C4F, &C89061BB EQUD &C945DFEE, &C9FBE527, &CAB26FAA, &CB697DB2 EQUD &CC210D78, &CCD91D3E, &CD91AB3B, &CE4AB5A2 EQUD &CF043AB4, &CFBE38A2, &D078AD9D, &D13397E3 EQUD &D1EEF5A0, &D2AAC504, &D3670446, &D423B18F EQUD &D4E0CB14, &D59E4F00, &D65C3B7A, &D71A8EB5 EQUD &D7D946D9, &D898620B, &D957DE7A, &DA17BA4B EQUD &DAD7F3A1, &DB9888A8, &DC597783, &DD1ABE50 EQUD &DDDC5B3B, &DE9E4C62, &DF608FE2, &E02323E5 EQUD &E0E60686, &E1A935E1, &E26CB01B, &E330734F EQUD &E3F47D95, &E4B8CD11, &E57D5FDC, &E642340C EQUD &E70747C4, &E7CC9919, &E8922621, &E957ECFC EQUD &EA1DEBBD, &EAE4207A, &EBAA894F, &EC712451 EQUD &ED37EF91, &EDFEE92C, &EEC60F33, &EF8D5FB8 EQUD &F054D8D5, &F11C7898, &F1E43D1C, &F2AC246F EQUD &F3742CA0, &F43C53CA, &F50497FC, &F5CCF742 EQUD &F6956FB6, &F75DFF67, &F826A460, &F8EF5CBB EQUD &F9B82685, &FA80FFCA, &FB49E6A2, &FC12D91B EQUD &FCDBD540, &FDA4D928, &FE6DE2E1, &FF36F077 \ ****************************************************************************** \ \ Name: arctanTable \ Type: Variable \ Category: Maths (Geometry) \ Summary: Arctan lookup table \ Deep dive: Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains: \ \ ((2^31 - 1) / PI) * arctan(n / 128) \ \ For n = 0 to 127 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines: \ \ FOR I% = 0 TO 127 \ [ \ OPT pass% \ EQUD ((2^31 - 1) / PI) * ATN(I% / 128) \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .arctanTable EQUD &00000000, &00517C55, &00A2F61E, &00F46AD0 EQUD &0145D7E1, &01973AC7, &01E890FC, &0239D7FB EQUD &028B0D43, &02DC2E53, &032D38B3, &037E29EB EQUD &03CEFF89, &041FB721, &04704E4A, &04C0C2A4 EQUD &051111D4, &05613983, &05B13767, &06010937 EQUD &0650ACB6, &06A01FAE, &06EF5FF1, &073E6B5A EQUD &078D3FCE, &07DBDB3A, &082A3B94, &08785EDF EQUD &08C64325, &0913E67C, &09614703, &09AE62E6 EQUD &09FB385B, &0A47C5A2, &0A940907, &0AE000E1 EQUD &0B2BAB95, &0B77078F, &0BC2134B, &0C0CCD4E EQUD &0C57342A, &0CA1467C, &0CEB02EF, &0D346836 EQUD &0D7D7514, &0DC62856, &0E0E80D4, &0E567D73 EQUD &0E9E1D23, &0EE55EE2, &0F2C41B6, &0F72C4B3 EQUD &0FB8E6F9, &0FFEA7B0, &1044060F, &10890156 EQUD &10CD98D1, &1111CBD6, &115599C6, &1199020E EQUD &11DC0423, &121E9F86, &1260D3C1, &12A2A069 EQUD &12E4051D, &13250183, &1365954E, &13A5C038 EQUD &13E58203, &1424DA7D, &1463C97A, &14A24ED7 EQUD &14E06A7A, &151E1C50, &155B6450, &15984275 EQUD &15D4B6C4, &1610C149, &164C6216, &16879945 EQUD &16C266F6, &16FCCB50, &1736C67E, &177058B5 EQUD &17A9822C, &17E24322, &181A9BDA, &18528C9E EQUD &188A15BB, &18C13785, &18F7F252, &192E467F EQUD &1964346D, &1999BC80, &19CEDF21, &1A039CBD EQUD &1A37F5C4, &1A6BEAA9, &1A9F7BE4, &1AD2A9EF EQUD &1B057548, &1B37DE6E, &1B69E5E5, &1B9B8C33 EQUD &1BCCD1DF, &1BFDB775, &1C2E3D81, &1C5E6491 EQUD &1C8E2D38, &1CBD9807, &1CECA593, &1D1B5671 EQUD &1D49AB3A, &1D77A486, &1DA542F0, &1DD28713 EQUD &1DFF718C, &1E2C02F7, &1E583BF4, &1E841D21 EQUD &1EAFA71E, &1EDADA8D, &1F05B80D, &1F304042 EQUD &1F5A73CC, &1F84534E, &1FADDF6B, &1FD718C5 \ ****************************************************************************** \ \ Name: squareRootTable \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: Square root lookup table \ Deep dive: Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains: \ \ (2^31 - 1) * SQRT(n / 1024) \ \ For n = 0 to 1023 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines: \ \ FOR I% = 0 TO 1023 \ [ \ OPT pass% \ EQUD (2^31 - 1) * SQR(I% / 1024) \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .squareRootTable EQUD &00000000, &03FFFFFF, &05A82799, &06ED9EBA EQUD &07FFFFFF, &08F1BBCD, &09CC4709, &0A953FD4 EQUD &0B504F33, &0BFFFFFF, &0CA62C1D, &0D443949 EQUD &0DDB3D74, &0E6C15A2, &0EF77508, &0F7DEF58 EQUD &0FFFFFFF, &107E0F66, &10F876CC, &116F8334 EQUD &11E3779B, &12548EB9, &12C2FC59, &132EEE75 EQUD &13988E13, &13FFFFFF, &1465655F, &14C8DC2E EQUD &152A7FA9, &158A68A4, &15E8ADD2, &16456405 EQUD &16A09E66, &16FA6EA1, &1752E50D, &17AA10D1 EQUD &17FFFFFF, &1854BFB3, &18A85C24, &18FAE0C1 EQUD &194C583A, &199CCC99, &19EC4749, &1A3AD129 EQUD &1A887293, &1AD53369, &1B211B1C, &1B6C30B8 EQUD &1BB67AE8, &1BFFFFFF, &1C48C5FF, &1C90D29C EQUD &1CD82B44, &1D1ED520, &1D64D51D, &1DAA2FEF EQUD &1DEEEA11, &1E3307CC, &1E768D39, &1EB97E45 EQUD &1EFBDEB1, &1F3DB217, &1F7EFBEB, &1FBFBF7E EQUD &1FFFFFFF, &203FC07E, &207F03EC, &20BDCD1D EQUD &20FC1ECD, &2139FB9A, &2177660F, &21B4609B EQUD &21F0ED99, &222D0F51, &2268C7F4, &22A419A2 EQUD &22DF0668, &23199043, &2353B91E, &238D82D6 EQUD &23C6EF37, &23FFFFFF, &2438B6E1, &2471157F EQUD &24A91D72, &24E0D043, &25182F72, &254F3C74 EQUD &2585F8B2, &25BC658C, &25F28458, &26285661 EQUD &265DDCEA, &2693192F, &26C80C60, &26FCB7A7 EQUD &27311C27, &27653AFA, &27991533, &27CCABDD EQUD &27FFFFFF, &28331298, &2865E49F, &28987707 EQUD &28CACABE, &28FCE0A9, &292EB9AA, &2960569E EQUD &2991B85C, &29C2DFB5, &29F3CD77, &2A24826A EQUD &2A54FF53, &2A8544F1, &2AB55400, &2AE52D36 EQUD &2B14D149, &2B4440E6, &2B737CBA, &2BA2856D EQUD &2BD15BA4, &2BFFFFFF, &2C2E731E, &2C5CB599 EQUD &2C8AC80A, &2CB8AB04, &2CE65F19, &2D13E4D8 EQUD &2D413CCC, &2D6E677F, &2D9B6577, &2DC83737 EQUD &2DF4DD42, &2E215816, &2E4DA830, &2E79CE09 EQUD &2EA5CA1B, &2ED19CDA, &2EFD46BA, &2F28C82D EQUD &2F5421A3, &2F7F5388, &2FAA5E48, &2FD5424D EQUD &2FFFFFFF, &302A97C4, &30550A00, &307F5716 EQUD &30A97F66, &30D38350, &30FD6331, &31271F66 EQUD &3150B849, &317A2E33, &31A3817C, &31CCB27A EQUD &31F5C182, &321EAEE7, &32477AFB, &32702610 EQUD &3298B075, &32C11A78, &32E96466, &33118E8B EQUD &33399932, &336184A5, &3389512C, &33B0FF0F EQUD &33D88E93, &33FFFFFF, &34275396, &344E899C EQUD &3475A253, &349C9DFD, &34C37CD9, &34EA3F28 EQUD &3510E527, &35376F15, &355DDD2E, &35842FAF EQUD &35AA66D2, &35D082D2, &35F683E8, &361C6A4C EQUD &36423638, &3667E7E2, &368D7F80, &36B2FD48 EQUD &36D86170, &36FDAC2A, &3722DDAC, &3747F628 EQUD &376CF5D0, &3791DCD5, &37B6AB6A, &37DB61BD EQUD &37FFFFFF, &3824865F, &3848F50B, &386D4C31 EQUD &38918BFF, &38B5B4A2, &38D9C644, &38FDC113 EQUD &3921A539, &394572E2, &39692A36, &398CCB60 EQUD &39B05688, &39D3CBD7, &39F72B76, &3A1A758C EQUD &3A3DAA40, &3A60C9B9, &3A83D41C, &3AA6C991 EQUD &3AC9AA3B, &3AEC7641, &3B0F2DC6, &3B31D0EF EQUD &3B545FDE, &3B76DAB9, &3B9941A1, &3BBB94B8 EQUD &3BDDD422, &3BFFFFFF, &3C221871, &3C441D99 EQUD &3C660F98, &3C87EE8D, &3CA9BA99, &3CCB73DB EQUD &3CED1A72, &3D0EAE7E, &3D30301C, &3D519F6C EQUD &3D72FC8A, &3D944794, &3DB580A9, &3DD6A7E4 EQUD &3DF7BD62, &3E18C140, &3E39B399, &3E5A948A EQUD &3E7B642E, &3E9C22A0, &3EBCCFFB, &3EDD6C59 EQUD &3EFDF7D6, &3F1E728B, &3F3EDC92, &3F5F3605 EQUD &3F7F7EFD, &3F9FB792, &3FBFDFDF, &3FDFF7FB EQUD &3FFFFFFF, &401FF803, &403FE01F, &405FB86A EQUD &407F80FD, &409F39ED, &40BEE353, &40DE7D44 EQUD &40FE07D8, &411D8325, &413CEF40, &415C4C40 EQUD &417B9A3B, &419AD946, &41BA0976, &41D92AE0 EQUD &41F83D9A, &421741B8, &4236374E, &42551E71 EQUD &4273F735, &4292C1AF, &42B17DF1, &42D02C0F EQUD &42EECC1E, &430D5E30, &432BE257, &434A58A9 EQUD &4368C136, &43871C11, &43A5694E, &43C3A8FE EQUD &43E1DB33, &43FFFFFF, &441E1775, &443C21A5 EQUD &445A1EA2, &44780E7C, &4495F145, &44B3C70E EQUD &44D18FE8, &44EF4BE3, &450CFB11, &452A9D81 EQUD &45483344, &4565BC6A, &45833904, &45A0A921 EQUD &45BE0CD1, &45DB6423, &45F8AF28, &4615EDEF EQUD &46332087, &465046FE, &466D6165, &468A6FCB EQUD &46A7723D, &46C468CB, &46E15383, &46FE3274 EQUD &471B05AC, &4737CD39, &4754892A, &4771398C EQUD &478DDE6E, &47AA77DC, &47C705E6, &47E38898 EQUD &47FFFFFF, &481C6C2A, &4838CD26, &485522FF EQUD &48716DC3, &488DAD7E, &48A9E23E, &48C60C10 EQUD &48E22AFF, &48FE3F19, &491A486B, &493646FF EQUD &49523AE4, &496E2424, &498A02CC, &49A5D6E9 EQUD &49C1A086, &49DD5FAE, &49F9146E, &4A14BED2 EQUD &4A305EE4, &4A4BF4B1, &4A678044, &4A8301A8 EQUD &4A9E78E8, &4AB9E610, &4AD5492B, &4AF0A243 EQUD &4B0BF165, &4B273699, &4B4271ED, &4B5DA369 EQUD &4B78CB19, &4B93E907, &4BAEFD3E, &4BCA07C8 EQUD &4BE508B0, &4BFFFFFF, &4C1AEDC1, &4C35D1FE EQUD &4C50ACC2, &4C6B7E16, &4C864604, &4CA10496 EQUD &4CBBB9D5, &4CD665CC, &4CF10884, &4D0BA207 EQUD &4D26325E, &4D40B993, &4D5B37AE, &4D75ACBB EQUD &4D9018C0, &4DAA7BC9, &4DC4D5DE, &4DDF2708 EQUD &4DF96F4F, &4E13AEBF, &4E2DE55E, &4E481336 EQUD &4E62384F, &4E7C54B3, &4E96686B, &4EB0737E EQUD &4ECA75F5, &4EE46FD9, &4EFE6132, &4F184A09 EQUD &4F322A66, &4F4C0251, &4F65D1D3, &4F7F98F4 EQUD &4F9957BB, &4FB30E32, &4FCCBC5F, &4FE6624C EQUD &4FFFFFFF, &50199582, &503322DB, &504CA812 EQUD &50662530, &507F9A3B, &5099073D, &50B26C3B EQUD &50CBC93E, &50E51E4D, &50FE6B70, &5117B0AE EQUD &5130EE0F, &514A2399, &51635155, &517C7748 EQUD &5195957C, &51AEABF5, &51C7BABD, &51E0C1DA EQUD &51F9C152, &5212B92D, &522BA972, &52449228 EQUD &525D7355, &52764D01, &528F1F31, &52A7E9EE EQUD &52C0AD3D, &52D96926, &52F21DAE, &530ACADD EQUD &532370B8, &533C0F47, &5354A691, &536D369A EQUD &5385BF6A, &539E4108, &53B6BB79, &53CF2EC4 EQUD &53E79AEE, &53FFFFFF, &54185DFD, &5430B4EC EQUD &544904D5, &54614DBC, &54798FA8, &5491CA9F EQUD &54A9FEA7, &54C22BC5, &54DA5200, &54F2715D EQUD &550A89E3, &55229B96, &553AA67E, &5552AA9F EQUD &556AA800, &55829EA6, &559A8E96, &55B277D7 EQUD &55CA5A6D, &55E23660, &55FA0BB3, &5611DA6C EQUD &5629A292, &56416429, &56591F37, &5670D3C1 EQUD &568881CC, &56A0295F, &56B7CA7E, &56CF652E EQUD &56E6F975, &56FE8757, &57160EDB, &572D9005 EQUD &57450ADB, &575C7F61, &5773ED9C, &578B5592 EQUD &57A2B748, &57BA12C3, &57D16807, &57E8B719 EQUD &57FFFFFF, &581742BE, &582E7F59, &5845B5D7 EQUD &585CE63C, &5874108C, &588B34CD, &58A25303 EQUD &58B96B33, &58D07D62, &58E78994, &58FE8FCE EQUD &59159015, &592C8A6D, &59437EDA, &595A6D62 EQUD &59715609, &598838D3, &599F15C6, &59B5ECE4 EQUD &59CCBE33, &59E389B8, &59FA4F76, &5A110F72 EQUD &5A27C9B1, &5A3E7E36, &5A552D06, &5A6BD626 EQUD &5A827999, &5A991764, &5AAFAF8B, &5AC64213 EQUD &5ADCCEFF, &5AF35653, &5B09D814, &5B205447 EQUD &5B36CAEE, &5B4D3C0E, &5B63A7AC, &5B7A0DCB EQUD &5B906E6F, &5BA6C99D, &5BBD1F57, &5BD36FA4 EQUD &5BE9BA85, &5BFFFFFF, &5C164017, &5C2C7ACF EQUD &5C42B02D, &5C58E033, &5C6F0AE6, &5C853049 EQUD &5C9B5060, &5CB16B2F, &5CC780BB, &5CDD9105 EQUD &5CF39C13, &5D09A1E8, &5D1FA288, &5D359DF6 EQUD &5D4B9436, &5D61854C, &5D77713B, &5D8D5807 EQUD &5DA339B4, &5DB91645, &5DCEEDBE, &5DE4C022 EQUD &5DFA8D75, &5E1055BA, &5E2618F5, &5E3BD72A EQUD &5E51905B, &5E67448D, &5E7CF3C2, &5E929DFE EQUD &5EA84346, &5EBDE39B, &5ED37F01, &5EE9157C EQUD &5EFEA710, &5F1433BE, &5F29BB8C, &5F3F3E7B EQUD &5F54BC90, &5F6A35CE, &5F7FAA37, &5F9519D0 EQUD &5FAA849B, &5FBFEA9C, &5FD54BD5, &5FEAA84B EQUD &5FFFFFFF, &601552F6, &602AA132, &603FEAB8 EQUD &60552F89, &606A6FA9, &607FAB1A, &6094E1E1 EQUD &60AA1401, &60BF417B, &60D46A54, &60E98E8E EQUD &60FEAE2C, &6113C932, &6128DFA2, &613DF17F EQUD &6152FECC, &6168078D, &617D0BC4, &61920B74 EQUD &61A706A0, &61BBFD4B, &61D0EF78, &61E5DD2A EQUD &61FAC663, &620FAB27, &62248B78, &62396759 EQUD &624E3ECD, &626311D7, &6277E079, &628CAAB7 EQUD &62A17093, &62B6320F, &62CAEF30, &62DFA7F7 EQUD &62F45C67, &63090C83, &631DB84D, &63325FC9 EQUD &634702F9, &635BA1DF, &63703C7F, &6384D2DA EQUD &639964F5, &63ADF2D0, &63C27C6F, &63D701D5 EQUD &63EB8304, &63FFFFFF, &641478C8, &6428ED61 EQUD &643D5DCE, &6451CA11, &6466322D, &647A9623 EQUD &648EF5F7, &64A351AB, &64B7A942, &64CBFCBE EQUD &64E04C21, &64F4976E, &6508DEA8, &651D21D0 EQUD &653160EA, &65459BF8, &6559D2FC, &656E05F8 EQUD &658234F0, &65965FE5, &65AA86DA, &65BEA9D0 EQUD &65D2C8CC, &65E6E3CE, &65FAFADA, &660F0DF1 EQUD &66231D17, &6637284C, &664B2F94, &665F32F1 EQUD &66733265, &66872DF3, &669B259C, &66AF1963 EQUD &66C3094B, &66D6F555, &66EADD84, &66FEC1DA EQUD &6712A259, &67267F04, &673A57DC, &674E2CE4 EQUD &6761FE1E, &6775CB8D, &67899532, &679D5B0F EQUD &67B11D27, &67C4DB7C, &67D89611, &67EC4CE6 EQUD &67FFFFFF, &6813AF5D, &68275B03, &683B02F2 EQUD &684EA72D, &686247B6, &6875E48F, &68897DBA EQUD &689D1339, &68B0A50E, &68C4333C, &68D7BDC3 EQUD &68EB44A7, &68FEC7E9, &6912478C, &6925C391 EQUD &69393BFA, &694CB0CA, &69602202, &69738FA4 EQUD &6986F9B3, &699A6030, &69ADC31D, &69C1227C EQUD &69D47E50, &69E7D69A, &69FB2B5B, &6A0E7C97 EQUD &6A21CA4F, &6A351484, &6A485B39, &6A5B9E70 EQUD &6A6EDE2B, &6A821A6A, &6A955332, &6AA88882 EQUD &6ABBBA5D, &6ACEE8C5, &6AE213BD, &6AF53B44 EQUD &6B085F5E, &6B1B800D, &6B2E9D51, &6B41B72E EQUD &6B54CDA4, &6B67E0B6, &6B7AF066, &6B8DFCB4 EQUD &6BA105A4, &6BB40B36, &6BC70D6D, &6BDA0C4A EQUD &6BED07D0, &6BFFFFFF, &6C12F4DA, &6C25E662 EQUD &6C38D499, &6C4BBF81, &6C5EA71C, &6C718B6B EQUD &6C846C71, &6C974A2D, &6CAA24A4, &6CBCFBD5 EQUD &6CCFCFC4, &6CE2A071, &6CF56DDE, &6D08380D EQUD &6D1AFF00, &6D2DC2B8, &6D408337, &6D53407F EQUD &6D65FA91, &6D78B16F, &6D8B651A, &6D9E1594 EQUD &6DB0C2E0, &6DC36CFD, &6DD613EF, &6DE8B7B7 EQUD &6DFB5855, &6E0DF5CD, &6E20901F, &6E33274D EQUD &6E45BB59, &6E584C44, &6E6ADA10, &6E7D64BE EQUD &6E8FEC50, &6EA270C8, &6EB4F227, &6EC7706E EQUD &6ED9EBA0, &6EEC63BE, &6EFED8C8, &6F114AC2 EQUD &6F23B9AB, &6F362587, &6F488E56, &6F5AF41A EQUD &6F6D56D4, &6F7FB686, &6F921332, &6FA46CD8 EQUD &6FB6C37B, &6FC9171B, &6FDB67BB, &6FEDB55C EQUD &6FFFFFFF, &701247A5, &70248C51, &7036CE04 EQUD &70490CBE, &705B4883, &706D8152, &707FB72D EQUD &7091EA17, &70A41A0F, &70B64719, &70C87134 EQUD &70DA9863, &70ECBCA7, &70FEDE02, &7110FC74 EQUD &712317FF, &713530A5, &71474667, &71595946 EQUD &716B6944, &717D7661, &718F80A1, &71A18803 EQUD &71B38C89, &71C58E35, &71D78D08, &71E98902 EQUD &71FB8227, &720D7876, &721F6BF2, &72315C9B EQUD &72434A73, &7255357C, &72671DB6, &72790323 EQUD &728AE5C4, &729CC59A, &72AEA2A8, &72C07CED EQUD &72D2546C, &72E42926, &72F5FB1B, &7307CA4E EQUD &731996C0, &732B6071, &733D2763, &734EEB98 EQUD &7360AD10, &73726BCE, &738427D1, &7395E11C EQUD &73A797AF, &73B94B8D, &73CAFCB6, &73DCAB2B EQUD &73EE56ED, &73FFFFFF, &7411A660, &74234A13 EQUD &7434EB19, &74468972, &74582520, &7469BE25 EQUD &747B5481, &748CE835, &749E7943, &74B007AC EQUD &74C19372, &74D31C95, &74E4A316, &74F626F7 EQUD &7507A839, &751926DD, &752AA2E5, &753C1C50 EQUD &754D9322, &755F075A, &757078FA, &7581E804 EQUD &75935477, &75A4BE56, &75B625A1, &75C78A5A EQUD &75D8EC82, &75EA4C1A, &75FBA923, &760D039E EQUD &761E5B8C, &762FB0EF, &764103C8, &76525417 EQUD &7663A1DE, &7674ED1D, &768635D7, &76977C0C EQUD &76A8BFBD, &76BA00EC, &76CB3F99, &76DC7BC5 EQUD &76EDB572, &76FEECA1, &77102152, &77215388 EQUD &77328342, &7743B082, &7754DB49, &77660399 EQUD &77772971, &77884CD4, &77996DC2, &77AA8C3D EQUD &77BBA844, &77CCC1DB, &77DDD900, &77EEEDB7 EQUD &77FFFFFF, &78110FD9, &78221D48, &7833284A EQUD &784430E3, &78553712, &78663AD9, &78773C39 EQUD &78883B33, &789937C8, &78AA31F8, &78BB29C5 EQUD &78CC1F30, &78DD123A, &78EE02E4, &78FEF12E EQUD &790FDD1B, &7920C6AA, &7931ADDD, &794292B5 EQUD &79537532, &79645557, &79753323, &79860E98 EQUD &7996E7B6, &79A7BE80, &79B892F5, &79C96516 EQUD &79DA34E5, &79EB0263, &79FBCD90, &7A0C966D EQUD &7A1D5CFC, &7A2E213E, &7A3EE332, &7A4FA2DB EQUD &7A606039, &7A711B4D, &7A81D419, &7A928A9C EQUD &7AA33ED8, &7AB3F0CE, &7AC4A07F, &7AD54DEB EQUD &7AE5F914, &7AF6A1FB, &7B0748A0, &7B17ED05 EQUD &7B288F29, &7B392F0F, &7B49CCB8, &7B5A6823 EQUD &7B6B0152, &7B7B9846, &7B8C2D00, &7B9CBF80 EQUD &7BAD4FC8, &7BBDDDD8, &7BCE69B1, &7BDEF355 EQUD &7BEF7AC4, &7BFFFFFF, &7C108306, &7C2103DC EQUD &7C318280, &7C41FEF3, &7C527937, &7C62F14C EQUD &7C736732, &7C83DAEC, &7C944C7A, &7CA4BBDC EQUD &7CB52914, &7CC59423, &7CD5FD08, &7CE663C6 EQUD &7CF6C85C, &7D072ACC, &7D178B17, &7D27E93D EQUD &7D384540, &7D489F20, &7D58F6DD, &7D694C7A EQUD &7D799FF6, &7D89F153, &7D9A4090, &7DAA8DB0 EQUD &7DBAD8B3, &7DCB219A, &7DDB6865, &7DEBAD16 EQUD &7DFBEFAD, &7E0C302B, &7E1C6E91, &7E2CAADF EQUD &7E3CE517, &7E4D1D39, &7E5D5346, &7E6D873F EQUD &7E7DB925, &7E8DE8F8, &7E9E16BA, &7EAE426A EQUD &7EBE6C0A, &7ECE939B, &7EDEB91E, &7EEEDC92 EQUD &7EFEFDFA, &7F0F1D55, &7F1F3AA5, &7F2F55EA EQUD &7F3F6F25, &7F4F8657, &7F5F9B81, &7F6FAEA3 EQUD &7F7FBFBE, &7F8FCED4, &7F9FDBE4, &7FAFE6EF EQUD &7FBFEFF7, &7FCFF6FB, &7FDFFBFE, &7FEFFEFF \ ****************************************************************************** \ \ Name: divisionTable \ Type: Variable \ Category: Maths (Arithmetic) \ Summary: Division lookup tables \ \ ------------------------------------------------------------------------------ \ \ There are 64 tables, each one for a different denominator d (d = 0 to 63) \ \ In table d, byte n in the table contains: \ \ 65536 * n / d \ \ For n = 0 to 63 (the value is &FFFFFFFF when n = 0) \ \ The address of the table containing the values of n / d is: \ \ divisionTable + d * 256 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines: \ \ FOR I% = 0 TO 63 \ [ \ OPT pass% \ EQUD &FFFFFFFF \ ] \ FOR J% = 1 TO 63 \ [ \ OPT pass% \ EQUD 65536 * I% / J% \ ] \ NEXT \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .divisionTable EQUD &FFFFFFFF, &00000000, &00000000, &00000000 \ n / 0 (n = 0 to 63) EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &00000000, &00000000, &00000000, &00000000 EQUD &FFFFFFFF, &00010000, &00008000, &00005555 \ n / 1 (n = 0 to 63) EQUD &00004000, &00003333, &00002AAA, &00002492 EQUD &00002000, &00001C71, &00001999, &00001745 EQUD &00001555, &000013B1, &00001249, &00001111 EQUD &00001000, &00000F0F, &00000E38, &00000D79 EQUD &00000CCC, &00000C30, &00000BA2, &00000B21 EQUD &00000AAA, &00000A3D, &000009D8, &0000097B EQUD &00000924, &000008D3, &00000888, &00000842 EQUD &00000800, &000007C1, &00000787, &00000750 EQUD &0000071C, &000006EB, &000006BC, &00000690 EQUD &00000666, &0000063E, &00000618, &000005F4 EQUD &000005D1, &000005B0, &00000590, &00000572 EQUD &00000555, &00000539, &0000051E, &00000505 EQUD &000004EC, &000004D4, &000004BD, &000004A7 EQUD &00000492, &0000047D, &00000469, &00000456 EQUD &00000444, &00000432, &00000421, &00000410 EQUD &FFFFFFFF, &00020000, &00010000, &0000AAAA \ n / 2 (n = 0 to 63) EQUD &00008000, &00006666, &00005555, &00004924 EQUD &00004000, &000038E3, &00003333, &00002E8B EQUD &00002AAA, &00002762, &00002492, &00002222 EQUD &00002000, &00001E1E, &00001C71, &00001AF2 EQUD &00001999, &00001861, &00001745, &00001642 EQUD &00001555, &0000147A, &000013B1, &000012F6 EQUD &00001249, &000011A7, &00001111, &00001084 EQUD &00001000, &00000F83, &00000F0F, &00000EA0 EQUD &00000E38, &00000DD6, &00000D79, &00000D20 EQUD &00000CCC, &00000C7C, &00000C30, &00000BE8 EQUD &00000BA2, &00000B60, &00000B21, &00000AE4 EQUD &00000AAA, &00000A72, &00000A3D, &00000A0A EQUD &000009D8, &000009A9, &0000097B, &0000094F EQUD &00000924, &000008FB, &000008D3, &000008AD EQUD &00000888, &00000864, &00000842, &00000820 EQUD &FFFFFFFF, &00030000, &00018000, &00010000 \ n / 3 (n = 0 to 63) EQUD &0000C000, &00009999, &00008000, &00006DB6 EQUD &00006000, &00005555, &00004CCC, &000045D1 EQUD &00004000, &00003B13, &000036DB, &00003333 EQUD &00003000, &00002D2D, &00002AAA, &0000286B EQUD &00002666, &00002492, &000022E8, &00002164 EQUD &00002000, &00001EB8, &00001D89, &00001C71 EQUD &00001B6D, &00001A7B, &00001999, &000018C6 EQUD &00001800, &00001745, &00001696, &000015F1 EQUD &00001555, &000014C1, &00001435, &000013B1 EQUD &00001333, &000012BB, &00001249, &000011DC EQUD &00001174, &00001111, &000010B2, &00001057 EQUD &00001000, &00000FAC, &00000F5C, &00000F0F EQUD &00000EC4, &00000E7D, &00000E38, &00000DF6 EQUD &00000DB6, &00000D79, &00000D3D, &00000D04 EQUD &00000CCC, &00000C97, &00000C63, &00000C30 EQUD &FFFFFFFF, &00040000, &00020000, &00015555 \ n / 4 (n = 0 to 63) EQUD &00010000, &0000CCCC, &0000AAAA, &00009249 EQUD &00008000, &000071C7, &00006666, &00005D17 EQUD &00005555, &00004EC4, &00004924, &00004444 EQUD &00004000, &00003C3C, &000038E3, &000035E5 EQUD &00003333, &000030C3, &00002E8B, &00002C85 EQUD &00002AAA, &000028F5, &00002762, &000025ED EQUD &00002492, &0000234F, &00002222, &00002108 EQUD &00002000, &00001F07, &00001E1E, &00001D41 EQUD &00001C71, &00001BAC, &00001AF2, &00001A41 EQUD &00001999, &000018F9, &00001861, &000017D0 EQUD &00001745, &000016C1, &00001642, &000015C9 EQUD &00001555, &000014E5, &0000147A, &00001414 EQUD &000013B1, &00001352, &000012F6, &0000129E EQUD &00001249, &000011F7, &000011A7, &0000115B EQUD &00001111, &000010C9, &00001084, &00001041 EQUD &FFFFFFFF, &00050000, &00028000, &0001AAAA \ n / 5 (n = 0 to 63) EQUD &00014000, &00010000, &0000D555, &0000B6DB EQUD &0000A000, &00008E38, &00008000, &0000745D EQUD &00006AAA, &00006276, &00005B6D, &00005555 EQUD &00005000, &00004B4B, &0000471C, &0000435E EQUD &00004000, &00003CF3, &00003A2E, &000037A6 EQUD &00003555, &00003333, &0000313B, &00002F68 EQUD &00002DB6, &00002C23, &00002AAA, &0000294A EQUD &00002800, &000026C9, &000025A5, &00002492 EQUD &0000238E, &00002298, &000021AF, &000020D2 EQUD &00002000, &00001F38, &00001E79, &00001DC4 EQUD &00001D17, &00001C71, &00001BD3, &00001B3B EQUD &00001AAA, &00001A1F, &00001999, &00001919 EQUD &0000189D, &00001826, &000017B4, &00001745 EQUD &000016DB, &00001674, &00001611, &000015B1 EQUD &00001555, &000014FB, &000014A5, &00001451 EQUD &FFFFFFFF, &00060000, &00030000, &00020000 \ n / 6 (n = 0 to 63) EQUD &00018000, &00013333, &00010000, &0000DB6D EQUD &0000C000, &0000AAAA, &00009999, &00008BA2 EQUD &00008000, &00007627, &00006DB6, &00006666 EQUD &00006000, &00005A5A, &00005555, &000050D7 EQUD &00004CCC, &00004924, &000045D1, &000042C8 EQUD &00004000, &00003D70, &00003B13, &000038E3 EQUD &000036DB, &000034F7, &00003333, &0000318C EQUD &00003000, &00002E8B, &00002D2D, &00002BE2 EQUD &00002AAA, &00002983, &0000286B, &00002762 EQUD &00002666, &00002576, &00002492, &000023B8 EQUD &000022E8, &00002222, &00002164, &000020AE EQUD &00002000, &00001F58, &00001EB8, &00001E1E EQUD &00001D89, &00001CFB, &00001C71, &00001BED EQUD &00001B6D, &00001AF2, &00001A7B, &00001A08 EQUD &00001999, &0000192E, &000018C6, &00001861 EQUD &FFFFFFFF, &00070000, &00038000, &00025555 \ n / 7 (n = 0 to 63) EQUD &0001C000, &00016666, &00012AAA, &00010000 EQUD &0000E000, &0000C71C, &0000B333, &0000A2E8 EQUD &00009555, &000089D8, &00008000, &00007777 EQUD &00007000, &00006969, &0000638E, &00005E50 EQUD &00005999, &00005555, &00005174, &00004DE9 EQUD &00004AAA, &000047AE, &000044EC, &0000425E EQUD &00004000, &00003DCB, &00003BBB, &000039CE EQUD &00003800, &0000364D, &000034B4, &00003333 EQUD &000031C7, &0000306E, &00002F28, &00002DF2 EQUD &00002CCC, &00002BB5, &00002AAA, &000029AC EQUD &000028BA, &000027D2, &000026F4, &00002620 EQUD &00002555, &00002492, &000023D7, &00002323 EQUD &00002276, &000021CF, &0000212F, &00002094 EQUD &00002000, &00001F70, &00001EE5, &00001E5F EQUD &00001DDD, &00001D60, &00001CE7, &00001C71 EQUD &FFFFFFFF, &00080000, &00040000, &0002AAAA \ n / 8 (n = 0 to 63) EQUD &00020000, &00019999, &00015555, &00012492 EQUD &00010000, &0000E38E, &0000CCCC, &0000BA2E EQUD &0000AAAA, &00009D89, &00009249, &00008888 EQUD &00008000, &00007878, &000071C7, &00006BCA EQUD &00006666, &00006186, &00005D17, &0000590B EQUD &00005555, &000051EB, &00004EC4, &00004BDA EQUD &00004924, &0000469E, &00004444, &00004210 EQUD &00004000, &00003E0F, &00003C3C, &00003A83 EQUD &000038E3, &00003759, &000035E5, &00003483 EQUD &00003333, &000031F3, &000030C3, &00002FA0 EQUD &00002E8B, &00002D82, &00002C85, &00002B93 EQUD &00002AAA, &000029CB, &000028F5, &00002828 EQUD &00002762, &000026A4, &000025ED, &0000253C EQUD &00002492, &000023EE, &0000234F, &000022B6 EQUD &00002222, &00002192, &00002108, &00002082 EQUD &FFFFFFFF, &00090000, &00048000, &00030000 \ n / 9 (n = 0 to 63) EQUD &00024000, &0001CCCC, &00018000, &00014924 EQUD &00012000, &00010000, &0000E666, &0000D174 EQUD &0000C000, &0000B13B, &0000A492, &00009999 EQUD &00009000, &00008787, &00008000, &00007943 EQUD &00007333, &00006DB6, &000068BA, &0000642C EQUD &00006000, &00005C28, &0000589D, &00005555 EQUD &00005249, &00004F72, &00004CCC, &00004A52 EQUD &00004800, &000045D1, &000043C3, &000041D4 EQUD &00004000, &00003E45, &00003CA1, &00003B13 EQUD &00003999, &00003831, &000036DB, &00003594 EQUD &0000345D, &00003333, &00003216, &00003105 EQUD &00003000, &00002F05, &00002E14, &00002D2D EQUD &00002C4E, &00002B78, &00002AAA, &000029E4 EQUD &00002924, &0000286B, &000027B9, &0000270D EQUD &00002666, &000025C5, &00002529, &00002492 EQUD &FFFFFFFF, &000A0000, &00050000, &00035555 \ n / 10 (n = 0 to 63) EQUD &00028000, &00020000, &0001AAAA, &00016DB6 EQUD &00014000, &00011C71, &00010000, &0000E8BA EQUD &0000D555, &0000C4EC, &0000B6DB, &0000AAAA EQUD &0000A000, &00009696, &00008E38, &000086BC EQUD &00008000, &000079E7, &0000745D, &00006F4D EQUD &00006AAA, &00006666, &00006276, &00005ED0 EQUD &00005B6D, &00005846, &00005555, &00005294 EQUD &00005000, &00004D93, &00004B4B, &00004924 EQUD &0000471C, &00004530, &0000435E, &000041A4 EQUD &00004000, &00003E70, &00003CF3, &00003B88 EQUD &00003A2E, &000038E3, &000037A6, &00003677 EQUD &00003555, &0000343E, &00003333, &00003232 EQUD &0000313B, &0000304D, &00002F68, &00002E8B EQUD &00002DB6, &00002CE9, &00002C23, &00002B63 EQUD &00002AAA, &000029F7, &0000294A, &000028A2 EQUD &FFFFFFFF, &000B0000, &00058000, &0003AAAA \ n / 11 (n = 0 to 63) EQUD &0002C000, &00023333, &0001D555, &00019249 EQUD &00016000, &000138E3, &00011999, &00010000 EQUD &0000EAAA, &0000D89D, &0000C924, &0000BBBB EQUD &0000B000, &0000A5A5, &00009C71, &00009435 EQUD &00008CCC, &00008618, &00008000, &00007A6F EQUD &00007555, &000070A3, &00006C4E, &0000684B EQUD &00006492, &0000611A, &00005DDD, &00005AD6 EQUD &00005800, &00005555, &000052D2, &00005075 EQUD &00004E38, &00004C1B, &00004A1A, &00004834 EQUD &00004666, &000044AE, &0000430C, &0000417D EQUD &00004000, &00003E93, &00003D37, &00003BEA EQUD &00003AAA, &00003978, &00003851, &00003737 EQUD &00003627, &00003521, &00003425, &00003333 EQUD &00003249, &00003167, &0000308D, &00002FBA EQUD &00002EEE, &00002E29, &00002D6B, &00002CB2 EQUD &FFFFFFFF, &000C0000, &00060000, &00040000 \ n / 12 (n = 0 to 63) EQUD &00030000, &00026666, &00020000, &0001B6DB EQUD &00018000, &00015555, &00013333, &00011745 EQUD &00010000, &0000EC4E, &0000DB6D, &0000CCCC EQUD &0000C000, &0000B4B4, &0000AAAA, &0000A1AF EQUD &00009999, &00009249, &00008BA2, &00008590 EQUD &00008000, &00007AE1, &00007627, &000071C7 EQUD &00006DB6, &000069EE, &00006666, &00006318 EQUD &00006000, &00005D17, &00005A5A, &000057C5 EQUD &00005555, &00005306, &000050D7, &00004EC4 EQUD &00004CCC, &00004AED, &00004924, &00004771 EQUD &000045D1, &00004444, &000042C8, &0000415C EQUD &00004000, &00003EB1, &00003D70, &00003C3C EQUD &00003B13, &000039F6, &000038E3, &000037DA EQUD &000036DB, &000035E5, &000034F7, &00003411 EQUD &00003333, &0000325C, &0000318C, &000030C3 EQUD &FFFFFFFF, &000D0000, &00068000, &00045555 \ n / 13 (n = 0 to 63) EQUD &00034000, &00029999, &00022AAA, &0001DB6D EQUD &0001A000, &000171C7, &00014CCC, &00012E8B EQUD &00011555, &00010000, &0000EDB6, &0000DDDD EQUD &0000D000, &0000C3C3, &0000B8E3, &0000AF28 EQUD &0000A666, &00009E79, &00009745, &000090B2 EQUD &00008AAA, &0000851E, &00008000, &00007B42 EQUD &000076DB, &000072C2, &00006EEE, &00006B5A EQUD &00006800, &000064D9, &000061E1, &00005F15 EQUD &00005C71, &000059F2, &00005794, &00005555 EQUD &00005333, &0000512B, &00004F3C, &00004D65 EQUD &00004BA2, &000049F4, &00004859, &000046CE EQUD &00004555, &000043EB, &0000428F, &00004141 EQUD &00004000, &00003ECA, &00003DA1, &00003C82 EQUD &00003B6D, &00003A62, &00003961, &00003868 EQUD &00003777, &0000368E, &000035AD, &000034D3 EQUD &FFFFFFFF, &000E0000, &00070000, &0004AAAA \ n / 14 (n = 0 to 63) EQUD &00038000, &0002CCCC, &00025555, &00020000 EQUD &0001C000, &00018E38, &00016666, &000145D1 EQUD &00012AAA, &000113B1, &00010000, &0000EEEE EQUD &0000E000, &0000D2D2, &0000C71C, &0000BCA1 EQUD &0000B333, &0000AAAA, &0000A2E8, &00009BD3 EQUD &00009555, &00008F5C, &000089D8, &000084BD EQUD &00008000, &00007B96, &00007777, &0000739C EQUD &00007000, &00006C9B, &00006969, &00006666 EQUD &0000638E, &000060DD, &00005E50, &00005BE5 EQUD &00005999, &0000576A, &00005555, &00005359 EQUD &00005174, &00004FA4, &00004DE9, &00004C41 EQUD &00004AAA, &00004924, &000047AE, &00004646 EQUD &000044EC, &0000439F, &0000425E, &00004129 EQUD &00004000, &00003EE0, &00003DCB, &00003CBE EQUD &00003BBB, &00003AC1, &000039CE, &000038E3 EQUD &FFFFFFFF, &000F0000, &00078000, &00050000 \ n / 15 (n = 0 to 63) EQUD &0003C000, &00030000, &00028000, &00022492 EQUD &0001E000, &0001AAAA, &00018000, &00015D17 EQUD &00014000, &00012762, &00011249, &00010000 EQUD &0000F000, &0000E1E1, &0000D555, &0000CA1A EQUD &0000C000, &0000B6DB, &0000AE8B, &0000A6F4 EQUD &0000A000, &00009999, &000093B1, &00008E38 EQUD &00008924, &00008469, &00008000, &00007BDE EQUD &00007800, &0000745D, &000070F0, &00006DB6 EQUD &00006AAA, &000067C8, &0000650D, &00006276 EQUD &00006000, &00005DA8, &00005B6D, &0000594D EQUD &00005745, &00005555, &0000537A, &000051B3 EQUD &00005000, &00004E5E, &00004CCC, &00004B4B EQUD &000049D8, &00004873, &0000471C, &000045D1 EQUD &00004492, &0000435E, &00004234, &00004115 EQUD &00004000, &00003EF3, &00003DEF, &00003CF3 EQUD &FFFFFFFF, &00100000, &00080000, &00055555 \ n / 16 (n = 0 to 63) EQUD &00040000, &00033333, &0002AAAA, &00024924 EQUD &00020000, &0001C71C, &00019999, &0001745D EQUD &00015555, &00013B13, &00012492, &00011111 EQUD &00010000, &0000F0F0, &0000E38E, &0000D794 EQUD &0000CCCC, &0000C30C, &0000BA2E, &0000B216 EQUD &0000AAAA, &0000A3D7, &00009D89, &000097B4 EQUD &00009249, &00008D3D, &00008888, &00008421 EQUD &00008000, &00007C1F, &00007878, &00007507 EQUD &000071C7, &00006EB3, &00006BCA, &00006906 EQUD &00006666, &000063E7, &00006186, &00005F41 EQUD &00005D17, &00005B05, &0000590B, &00005726 EQUD &00005555, &00005397, &000051EB, &00005050 EQUD &00004EC4, &00004D48, &00004BDA, &00004A79 EQUD &00004924, &000047DC, &0000469E, &0000456C EQUD &00004444, &00004325, &00004210, &00004104 EQUD &FFFFFFFF, &00110000, &00088000, &0005AAAA \ n / 17 (n = 0 to 63) EQUD &00044000, &00036666, &0002D555, &00026DB6 EQUD &00022000, &0001E38E, &0001B333, &00018BA2 EQUD &00016AAA, &00014EC4, &000136DB, &00012222 EQUD &00011000, &00010000, &0000F1C7, &0000E50D EQUD &0000D999, &0000CF3C, &0000C5D1, &0000BD37 EQUD &0000B555, &0000AE14, &0000A762, &0000A12F EQUD &00009B6D, &00009611, &00009111, &00008C63 EQUD &00008800, &000083E0, &00008000, &00007C57 EQUD &000078E3, &0000759F, &00007286, &00006F96 EQUD &00006CCC, &00006A25, &0000679E, &00006535 EQUD &000062E8, &000060B6, &00005E9B, &00005C98 EQUD &00005AAA, &000058D0, &0000570A, &00005555 EQUD &000053B1, &0000521C, &00005097, &00004F20 EQUD &00004DB6, &00004C59, &00004B08, &000049C3 EQUD &00004888, &00004758, &00004631, &00004514 EQUD &FFFFFFFF, &00120000, &00090000, &00060000 \ n / 18 (n = 0 to 63) EQUD &00048000, &00039999, &00030000, &00029249 EQUD &00024000, &00020000, &0001CCCC, &0001A2E8 EQUD &00018000, &00016276, &00014924, &00013333 EQUD &00012000, &00010F0F, &00010000, &0000F286 EQUD &0000E666, &0000DB6D, &0000D174, &0000C859 EQUD &0000C000, &0000B851, &0000B13B, &0000AAAA EQUD &0000A492, &00009EE5, &00009999, &000094A5 EQUD &00009000, &00008BA2, &00008787, &000083A8 EQUD &00008000, &00007C8A, &00007943, &00007627 EQUD &00007333, &00007063, &00006DB6, &00006B29 EQUD &000068BA, &00006666, &0000642C, &0000620A EQUD &00006000, &00005E0A, &00005C28, &00005A5A EQUD &0000589D, &000056F1, &00005555, &000053C8 EQUD &00005249, &000050D7, &00004F72, &00004E1A EQUD &00004CCC, &00004B8A, &00004A52, &00004924 EQUD &FFFFFFFF, &00130000, &00098000, &00065555 \ n / 19 (n = 0 to 63) EQUD &0004C000, &0003CCCC, &00032AAA, &0002B6DB EQUD &00026000, &00021C71, &0001E666, &0001BA2E EQUD &00019555, &00017627, &00015B6D, &00014444 EQUD &00013000, &00011E1E, &00010E38, &00010000 EQUD &0000F333, &0000E79E, &0000DD17, &0000D37A EQUD &0000CAAA, &0000C28F, &0000BB13, &0000B425 EQUD &0000ADB6, &0000A7B9, &0000A222, &00009CE7 EQUD &00009800, &00009364, &00008F0F, &00008AF8 EQUD &0000871C, &00008375, &00008000, &00007CB7 EQUD &00007999, &000076A2, &000073CF, &0000711D EQUD &00006E8B, &00006C16, &000069BD, &0000677D EQUD &00006555, &00006343, &00006147, &00005F5F EQUD &00005D89, &00005BC6, &00005A12, &0000586F EQUD &000056DB, &00005555, &000053DC, &00005270 EQUD &00005111, &00004FBC, &00004E73, &00004D34 EQUD &FFFFFFFF, &00140000, &000A0000, &0006AAAA \ n / 20 (n = 0 to 63) EQUD &00050000, &00040000, &00035555, &0002DB6D EQUD &00028000, &000238E3, &00020000, &0001D174 EQUD &0001AAAA, &000189D8, &00016DB6, &00015555 EQUD &00014000, &00012D2D, &00011C71, &00010D79 EQUD &00010000, &0000F3CF, &0000E8BA, &0000DE9B EQUD &0000D555, &0000CCCC, &0000C4EC, &0000BDA1 EQUD &0000B6DB, &0000B08D, &0000AAAA, &0000A529 EQUD &0000A000, &00009B26, &00009696, &00009249 EQUD &00008E38, &00008A60, &000086BC, &00008348 EQUD &00008000, &00007CE0, &000079E7, &00007711 EQUD &0000745D, &000071C7, &00006F4D, &00006CEF EQUD &00006AAA, &0000687D, &00006666, &00006464 EQUD &00006276, &0000609A, &00005ED0, &00005D17 EQUD &00005B6D, &000059D3, &00005846, &000056C7 EQUD &00005555, &000053EF, &00005294, &00005145 EQUD &FFFFFFFF, &00150000, &000A8000, &00070000 \ n / 21 (n = 0 to 63) EQUD &00054000, &00043333, &00038000, &00030000 EQUD &0002A000, &00025555, &00021999, &0001E8BA EQUD &0001C000, &00019D89, &00018000, &00016666 EQUD &00015000, &00013C3C, &00012AAA, &00011AF2 EQUD &00010CCC, &00010000, &0000F45D, &0000E9BD EQUD &0000E000, &0000D70A, &0000CEC4, &0000C71C EQUD &0000C000, &0000B961, &0000B333, &0000AD6B EQUD &0000A800, &0000A2E8, &00009E1E, &00009999 EQUD &00009555, &0000914C, &00008D79, &000089D8 EQUD &00008666, &0000831F, &00008000, &00007D05 EQUD &00007A2E, &00007777, &000074DE, &00007262 EQUD &00007000, &00006DB6, &00006B85, &00006969 EQUD &00006762, &0000656F, &0000638E, &000061BE EQUD &00006000, &00005E50, &00005CB0, &00005B1E EQUD &00005999, &00005821, &000056B5, &00005555 EQUD &FFFFFFFF, &00160000, &000B0000, &00075555 \ n / 22 (n = 0 to 63) EQUD &00058000, &00046666, &0003AAAA, &00032492 EQUD &0002C000, &000271C7, &00023333, &00020000 EQUD &0001D555, &0001B13B, &00019249, &00017777 EQUD &00016000, &00014B4B, &000138E3, &0001286B EQUD &00011999, &00010C30, &00010000, &0000F4DE EQUD &0000EAAA, &0000E147, &0000D89D, &0000D097 EQUD &0000C924, &0000C234, &0000BBBB, &0000B5AD EQUD &0000B000, &0000AAAA, &0000A5A5, &0000A0EA EQUD &00009C71, &00009837, &00009435, &00009069 EQUD &00008CCC, &0000895D, &00008618, &000082FA EQUD &00008000, &00007D27, &00007A6F, &000077D4 EQUD &00007555, &000072F0, &000070A3, &00006E6E EQUD &00006C4E, &00006A43, &0000684B, &00006666 EQUD &00006492, &000062CE, &0000611A, &00005F75 EQUD &00005DDD, &00005C53, &00005AD6, &00005965 EQUD &FFFFFFFF, &00170000, &000B8000, &0007AAAA \ n / 23 (n = 0 to 63) EQUD &0005C000, &00049999, &0003D555, &00034924 EQUD &0002E000, &00028E38, &00024CCC, &00021745 EQUD &0001EAAA, &0001C4EC, &0001A492, &00018888 EQUD &00017000, &00015A5A, &0001471C, &000135E5 EQUD &00012666, &00011861, &00010BA2, &00010000 EQUD &0000F555, &0000EB85, &0000E276, &0000DA12 EQUD &0000D249, &0000CB08, &0000C444, &0000BDEF EQUD &0000B800, &0000B26C, &0000AD2D, &0000A83A EQUD &0000A38E, &00009F22, &00009AF2, &000096F9 EQUD &00009333, &00008F9C, &00008C30, &000088EE EQUD &000085D1, &000082D8, &00008000, &00007D46 EQUD &00007AAA, &00007829, &000075C2, &00007373 EQUD &0000713B, &00006F18, &00006D09, &00006B0D EQUD &00006924, &0000674C, &00006584, &000063CB EQUD &00006222, &00006086, &00005EF7, &00005D75 EQUD &FFFFFFFF, &00180000, &000C0000, &00080000 \ n / 24 (n = 0 to 63) EQUD &00060000, &0004CCCC, &00040000, &00036DB6 EQUD &00030000, &0002AAAA, &00026666, &00022E8B EQUD &00020000, &0001D89D, &0001B6DB, &00019999 EQUD &00018000, &00016969, &00015555, &0001435E EQUD &00013333, &00012492, &00011745, &00010B21 EQUD &00010000, &0000F5C2, &0000EC4E, &0000E38E EQUD &0000DB6D, &0000D3DC, &0000CCCC, &0000C631 EQUD &0000C000, &0000BA2E, &0000B4B4, &0000AF8A EQUD &0000AAAA, &0000A60D, &0000A1AF, &00009D89 EQUD &00009999, &000095DA, &00009249, &00008EE2 EQUD &00008BA2, &00008888, &00008590, &000082B9 EQUD &00008000, &00007D63, &00007AE1, &00007878 EQUD &00007627, &000073EC, &000071C7, &00006FB5 EQUD &00006DB6, &00006BCA, &000069EE, &00006822 EQUD &00006666, &000064B8, &00006318, &00006186 EQUD &FFFFFFFF, &00190000, &000C8000, &00085555 \ n / 25 (n = 0 to 63) EQUD &00064000, &00050000, &00042AAA, &00039249 EQUD &00032000, &0002C71C, &00028000, &000245D1 EQUD &00021555, &0001EC4E, &0001C924, &0001AAAA EQUD &00019000, &00017878, &0001638E, &000150D7 EQUD &00014000, &000130C3, &000122E8, &00011642 EQUD &00010AAA, &00010000, &0000F627, &0000ED09 EQUD &0000E492, &0000DCB0, &0000D555, &0000CE73 EQUD &0000C800, &0000C1F0, &0000BC3C, &0000B6DB EQUD &0000B1C7, &0000ACF9, &0000A86B, &0000A41A EQUD &0000A000, &00009C18, &00009861, &000094D6 EQUD &00009174, &00008E38, &00008B21, &0000882B EQUD &00008555, &0000829C, &00008000, &00007D7D EQUD &00007B13, &000078C1, &00007684, &0000745D EQUD &00007249, &00007047, &00006E58, &00006C79 EQUD &00006AAA, &000068EB, &00006739, &00006596 EQUD &FFFFFFFF, &001A0000, &000D0000, &0008AAAA \ n / 26 (n = 0 to 63) EQUD &00068000, &00053333, &00045555, &0003B6DB EQUD &00034000, &0002E38E, &00029999, &00025D17 EQUD &00022AAA, &00020000, &0001DB6D, &0001BBBB EQUD &0001A000, &00018787, &000171C7, &00015E50 EQUD &00014CCC, &00013CF3, &00012E8B, &00012164 EQUD &00011555, &00010A3D, &00010000, &0000F684 EQUD &0000EDB6, &0000E584, &0000DDDD, &0000D6B5 EQUD &0000D000, &0000C9B2, &0000C3C3, &0000BE2B EQUD &0000B8E3, &0000B3E4, &0000AF28, &0000AAAA EQUD &0000A666, &0000A257, &00009E79, &00009ACA EQUD &00009745, &000093E9, &000090B2, &00008D9D EQUD &00008AAA, &000087D6, &0000851E, &00008282 EQUD &00008000, &00007D95, &00007B42, &00007904 EQUD &000076DB, &000074C5, &000072C2, &000070D0 EQUD &00006EEE, &00006D1D, &00006B5A, &000069A6 EQUD &FFFFFFFF, &001B0000, &000D8000, &00090000 \ n / 27 (n = 0 to 63) EQUD &0006C000, &00056666, &00048000, &0003DB6D EQUD &00036000, &00030000, &0002B333, &0002745D EQUD &00024000, &000213B1, &0001EDB6, &0001CCCC EQUD &0001B000, &00019696, &00018000, &00016BCA EQUD &00015999, &00014924, &00013A2E, &00012C85 EQUD &00012000, &0001147A, &000109D8, &00010000 EQUD &0000F6DB, &0000EE58, &0000E666, &0000DEF7 EQUD &0000D800, &0000D174, &0000CB4B, &0000C57C EQUD &0000C000, &0000BACF, &0000B5E5, &0000B13B EQUD &0000ACCC, &0000A895, &0000A492, &0000A0BE EQUD &00009D17, &00009999, &00009642, &00009310 EQUD &00009000, &00008D0F, &00008A3D, &00008787 EQUD &000084EC, &0000826A, &00008000, &00007DAC EQUD &00007B6D, &00007943, &0000772C, &00007527 EQUD &00007333, &0000714F, &00006F7B, &00006DB6 EQUD &FFFFFFFF, &001C0000, &000E0000, &00095555 \ n / 28 (n = 0 to 63) EQUD &00070000, &00059999, &0004AAAA, &00040000 EQUD &00038000, &00031C71, &0002CCCC, &00028BA2 EQUD &00025555, &00022762, &00020000, &0001DDDD EQUD &0001C000, &0001A5A5, &00018E38, &00017943 EQUD &00016666, &00015555, &000145D1, &000137A6 EQUD &00012AAA, &00011EB8, &000113B1, &0001097B EQUD &00010000, &0000F72C, &0000EEEE, &0000E739 EQUD &0000E000, &0000D936, &0000D2D2, &0000CCCC EQUD &0000C71C, &0000C1BA, &0000BCA1, &0000B7CB EQUD &0000B333, &0000AED4, &0000AAAA, &0000A6B2 EQUD &0000A2E8, &00009F49, &00009BD3, &00009882 EQUD &00009555, &00009249, &00008F5C, &00008C8C EQUD &000089D8, &0000873E, &000084BD, &00008253 EQUD &00008000, &00007DC1, &00007B96, &0000797D EQUD &00007777, &00007582, &0000739C, &000071C7 EQUD &FFFFFFFF, &001D0000, &000E8000, &0009AAAA \ n / 29 (n = 0 to 63) EQUD &00074000, &0005CCCC, &0004D555, &00042492 EQUD &0003A000, &000338E3, &0002E666, &0002A2E8 EQUD &00026AAA, &00023B13, &00021249, &0001EEEE EQUD &0001D000, &0001B4B4, &00019C71, &000186BC EQUD &00017333, &00016186, &00015174, &000142C8 EQUD &00013555, &000128F5, &00011D89, &000112F6 EQUD &00010924, &00010000, &0000F777, &0000EF7B EQUD &0000E800, &0000E0F8, &0000DA5A, &0000D41D EQUD &0000CE38, &0000C8A6, &0000C35E, &0000BE5B EQUD &0000B999, &0000B512, &0000B0C3, &0000ACA6 EQUD &0000A8BA, &0000A4FA, &0000A164, &00009DF5 EQUD &00009AAA, &00009782, &0000947A, &00009191 EQUD &00008EC4, &00008C13, &0000897B, &000086FB EQUD &00008492, &0000823E, &00008000, &00007DD4 EQUD &00007BBB, &000079B4, &000077BD, &000075D7 EQUD &FFFFFFFF, &001E0000, &000F0000, &000A0000 \ n / 30 (n = 0 to 63) EQUD &00078000, &00060000, &00050000, &00044924 EQUD &0003C000, &00035555, &00030000, &0002BA2E EQUD &00028000, &00024EC4, &00022492, &00020000 EQUD &0001E000, &0001C3C3, &0001AAAA, &00019435 EQUD &00018000, &00016DB6, &00015D17, &00014DE9 EQUD &00014000, &00013333, &00012762, &00011C71 EQUD &00011249, &000108D3, &00010000, &0000F7BD EQUD &0000F000, &0000E8BA, &0000E1E1, &0000DB6D EQUD &0000D555, &0000CF91, &0000CA1A, &0000C4EC EQUD &0000C000, &0000BB51, &0000B6DB, &0000B29A EQUD &0000AE8B, &0000AAAA, &0000A6F4, &0000A367 EQUD &0000A000, &00009CBC, &00009999, &00009696 EQUD &000093B1, &000090E7, &00008E38, &00008BA2 EQUD &00008924, &000086BC, &00008469, &0000822B EQUD &00008000, &00007DE6, &00007BDE, &000079E7 EQUD &FFFFFFFF, &001F0000, &000F8000, &000A5555 \ n / 31 (n = 0 to 63) EQUD &0007C000, &00063333, &00052AAA, &00046DB6 EQUD &0003E000, &000371C7, &00031999, &0002D174 EQUD &00029555, &00026276, &000236DB, &00021111 EQUD &0001F000, &0001D2D2, &0001B8E3, &0001A1AF EQUD &00018CCC, &000179E7, &000168BA, &0001590B EQUD &00014AAA, &00013D70, &0001313B, &000125ED EQUD &00011B6D, &000111A7, &00010888, &00010000 EQUD &0000F800, &0000F07C, &0000E969, &0000E2BE EQUD &0000DC71, &0000D67C, &0000D0D7, &0000CB7C EQUD &0000C666, &0000C18F, &0000BCF3, &0000B88E EQUD &0000B45D, &0000B05B, &0000AC85, &0000A8D9 EQUD &0000A555, &0000A1F5, &00009EB8, &00009B9B EQUD &0000989D, &000095BC, &000092F6, &0000904A EQUD &00008DB6, &00008B3A, &000088D3, &00008682 EQUD &00008444, &00008219, &00008000, &00007DF7 EQUD &FFFFFFFF, &00200000, &00100000, &000AAAAA \ n / 32 (n = 0 to 63) EQUD &00080000, &00066666, &00055555, &00049249 EQUD &00040000, &00038E38, &00033333, &0002E8BA EQUD &0002AAAA, &00027627, &00024924, &00022222 EQUD &00020000, &0001E1E1, &0001C71C, &0001AF28 EQUD &00019999, &00018618, &0001745D, &0001642C EQUD &00015555, &000147AE, &00013B13, &00012F68 EQUD &00012492, &00011A7B, &00011111, &00010842 EQUD &00010000, &0000F83E, &0000F0F0, &0000EA0E EQUD &0000E38E, &0000DD67, &0000D794, &0000D20D EQUD &0000CCCC, &0000C7CE, &0000C30C, &0000BE82 EQUD &0000BA2E, &0000B60B, &0000B216, &0000AE4C EQUD &0000AAAA, &0000A72F, &0000A3D7, &0000A0A0 EQUD &00009D89, &00009A90, &000097B4, &000094F2 EQUD &00009249, &00008FB8, &00008D3D, &00008AD8 EQUD &00008888, &0000864B, &00008421, &00008208 EQUD &FFFFFFFF, &00210000, &00108000, &000B0000 \ n / 33 (n = 0 to 63) EQUD &00084000, &00069999, &00058000, &0004B6DB EQUD &00042000, &0003AAAA, &00034CCC, &00030000 EQUD &0002C000, &000289D8, &00025B6D, &00023333 EQUD &00021000, &0001F0F0, &0001D555, &0001BCA1 EQUD &0001A666, &00019249, &00018000, &00016F4D EQUD &00016000, &000151EB, &000144EC, &000138E3 EQUD &00012DB6, &0001234F, &00011999, &00011084 EQUD &00010800, &00010000, &0000F878, &0000F15F EQUD &0000EAAA, &0000E453, &0000DE50, &0000D89D EQUD &0000D333, &0000CE0C, &0000C924, &0000C477 EQUD &0000C000, &0000BBBB, &0000B7A6, &0000B3BE EQUD &0000B000, &0000AC68, &0000A8F5, &0000A5A5 EQUD &0000A276, &00009F65, &00009C71, &00009999 EQUD &000096DB, &00009435, &000091A7, &00008F2F EQUD &00008CCC, &00008A7D, &00008842, &00008618 EQUD &FFFFFFFF, &00220000, &00110000, &000B5555 \ n / 34 (n = 0 to 63) EQUD &00088000, &0006CCCC, &0005AAAA, &0004DB6D EQUD &00044000, &0003C71C, &00036666, &00031745 EQUD &0002D555, &00029D89, &00026DB6, &00024444 EQUD &00022000, &00020000, &0001E38E, &0001CA1A EQUD &0001B333, &00019E79, &00018BA2, &00017A6F EQUD &00016AAA, &00015C28, &00014EC4, &0001425E EQUD &000136DB, &00012C23, &00012222, &000118C6 EQUD &00011000, &000107C1, &00010000, &0000F8AF EQUD &0000F1C7, &0000EB3E, &0000E50D, &0000DF2D EQUD &0000D999, &0000D44A, &0000CF3C, &0000CA6B EQUD &0000C5D1, &0000C16C, &0000BD37, &0000B931 EQUD &0000B555, &0000B1A1, &0000AE14, &0000AAAA EQUD &0000A762, &0000A439, &0000A12F, &00009E41 EQUD &00009B6D, &000098B3, &00009611, &00009386 EQUD &00009111, &00008EB0, &00008C63, &00008A28 EQUD &FFFFFFFF, &00230000, &00118000, &000BAAAA \ n / 35 (n = 0 to 63) EQUD &0008C000, &00070000, &0005D555, &00050000 EQUD &00046000, &0003E38E, &00038000, &00032E8B EQUD &0002EAAA, &0002B13B, &00028000, &00025555 EQUD &00023000, &00020F0F, &0001F1C7, &0001D794 EQUD &0001C000, &0001AAAA, &00019745, &00018590 EQUD &00017555, &00016666, &0001589D, &00014BDA EQUD &00014000, &000134F7, &00012AAA, &00012108 EQUD &00011800, &00010F83, &00010787, &00010000 EQUD &0000F8E3, &0000F229, &0000EBCA, &0000E5BE EQUD &0000E000, &0000DA89, &0000D555, &0000D05F EQUD &0000CBA2, &0000C71C, &0000C2C8, &0000BEA3 EQUD &0000BAAA, &0000B6DB, &0000B333, &0000AFAF EQUD &0000AC4E, &0000A90E, &0000A5ED, &0000A2E8 EQUD &0000A000, &00009D31, &00009A7B, &000097DD EQUD &00009555, &000092E2, &00009084, &00008E38 EQUD &FFFFFFFF, &00240000, &00120000, &000C0000 \ n / 36 (n = 0 to 63) EQUD &00090000, &00073333, &00060000, &00052492 EQUD &00048000, &00040000, &00039999, &000345D1 EQUD &00030000, &0002C4EC, &00029249, &00026666 EQUD &00024000, &00021E1E, &00020000, &0001E50D EQUD &0001CCCC, &0001B6DB, &0001A2E8, &000190B2 EQUD &00018000, &000170A3, &00016276, &00015555 EQUD &00014924, &00013DCB, &00013333, &0001294A EQUD &00012000, &00011745, &00010F0F, &00010750 EQUD &00010000, &0000F914, &0000F286, &0000EC4E EQUD &0000E666, &0000E0C7, &0000DB6D, &0000D653 EQUD &0000D174, &0000CCCC, &0000C859, &0000C415 EQUD &0000C000, &0000BC14, &0000B851, &0000B4B4 EQUD &0000B13B, &0000ADE3, &0000AAAA, &0000A790 EQUD &0000A492, &0000A1AF, &00009EE5, &00009C34 EQUD &00009999, &00009714, &000094A5, &00009249 EQUD &FFFFFFFF, &00250000, &00128000, &000C5555 \ n / 37 (n = 0 to 63) EQUD &00094000, &00076666, &00062AAA, &00054924 EQUD &0004A000, &00041C71, &0003B333, &00035D17 EQUD &00031555, &0002D89D, &0002A492, &00027777 EQUD &00025000, &00022D2D, &00020E38, &0001F286 EQUD &0001D999, &0001C30C, &0001AE8B, &00019BD3 EQUD &00018AAA, &00017AE1, &00016C4E, &00015ED0 EQUD &00015249, &0001469E, &00013BBB, &0001318C EQUD &00012800, &00011F07, &00011696, &00010EA0 EQUD &0001071C, &00010000, &0000F943, &0000F2DF EQUD &0000ECCC, &0000E706, &0000E186, &0000DC47 EQUD &0000D745, &0000D27D, &0000CDE9, &0000C988 EQUD &0000C555, &0000C14E, &0000BD70, &0000B9B9 EQUD &0000B627, &0000B2B7, &0000AF68, &0000AC37 EQUD &0000A924, &0000A62C, &0000A34F, &0000A08A EQUD &00009DDD, &00009B47, &000098C6, &00009659 EQUD &FFFFFFFF, &00260000, &00130000, &000CAAAA \ n / 38 (n = 0 to 63) EQUD &00098000, &00079999, &00065555, &00056DB6 EQUD &0004C000, &000438E3, &0003CCCC, &0003745D EQUD &00032AAA, &0002EC4E, &0002B6DB, &00028888 EQUD &00026000, &00023C3C, &00021C71, &00020000 EQUD &0001E666, &0001CF3C, &0001BA2E, &0001A6F4 EQUD &00019555, &0001851E, &00017627, &0001684B EQUD &00015B6D, &00014F72, &00014444, &000139CE EQUD &00013000, &000126C9, &00011E1E, &000115F1 EQUD &00010E38, &000106EB, &00010000, &0000F96F EQUD &0000F333, &0000ED44, &0000E79E, &0000E23B EQUD &0000DD17, &0000D82D, &0000D37A, &0000CEFA EQUD &0000CAAA, &0000C687, &0000C28F, &0000BEBE EQUD &0000BB13, &0000B78C, &0000B425, &0000B0DF EQUD &0000ADB6, &0000AAAA, &0000A7B9, &0000A4E1 EQUD &0000A222, &00009F79, &00009CE7, &00009A69 EQUD &FFFFFFFF, &00270000, &00138000, &000D0000 \ n / 39 (n = 0 to 63) EQUD &0009C000, &0007CCCC, &00068000, &00059249 EQUD &0004E000, &00045555, &0003E666, &00038BA2 EQUD &00034000, &00030000, &0002C924, &00029999 EQUD &00027000, &00024B4B, &00022AAA, &00020D79 EQUD &0001F333, &0001DB6D, &0001C5D1, &0001B216 EQUD &0001A000, &00018F5C, &00018000, &000171C7 EQUD &00016492, &00015846, &00014CCC, &00014210 EQUD &00013800, &00012E8B, &000125A5, &00011D41 EQUD &00011555, &00010DD6, &000106BC, &00010000 EQUD &0000F999, &0000F383, &0000EDB6, &0000E82F EQUD &0000E2E8, &0000DDDD, &0000D90B, &0000D46C EQUD &0000D000, &0000CBC1, &0000C7AE, &0000C3C3 EQUD &0000C000, &0000BC60, &0000B8E3, &0000B586 EQUD &0000B249, &0000AF28, &0000AC23, &0000A938 EQUD &0000A666, &0000A3AC, &0000A108, &00009E79 EQUD &FFFFFFFF, &00280000, &00140000, &000D5555 \ n / 40 (n = 0 to 63) EQUD &000A0000, &00080000, &0006AAAA, &0005B6DB EQUD &00050000, &000471C7, &00040000, &0003A2E8 EQUD &00035555, &000313B1, &0002DB6D, &0002AAAA EQUD &00028000, &00025A5A, &000238E3, &00021AF2 EQUD &00020000, &0001E79E, &0001D174, &0001BD37 EQUD &0001AAAA, &00019999, &000189D8, &00017B42 EQUD &00016DB6, &0001611A, &00015555, &00014A52 EQUD &00014000, &0001364D, &00012D2D, &00012492 EQUD &00011C71, &000114C1, &00010D79, &00010690 EQUD &00010000, &0000F9C1, &0000F3CF, &0000EE23 EQUD &0000E8BA, &0000E38E, &0000DE9B, &0000D9DF EQUD &0000D555, &0000D0FA, &0000CCCC, &0000C8C8 EQUD &0000C4EC, &0000C135, &0000BDA1, &0000BA2E EQUD &0000B6DB, &0000B3A6, &0000B08D, &0000AD8F EQUD &0000AAAA, &0000A7DE, &0000A529, &0000A28A EQUD &FFFFFFFF, &00290000, &00148000, &000DAAAA \ n / 41 (n = 0 to 63) EQUD &000A4000, &00083333, &0006D555, &0005DB6D EQUD &00052000, &00048E38, &00041999, &0003BA2E EQUD &00036AAA, &00032762, &0002EDB6, &0002BBBB EQUD &00029000, &00026969, &0002471C, &0002286B EQUD &00020CCC, &0001F3CF, &0001DD17, &0001C859 EQUD &0001B555, &0001A3D7, &000193B1, &000184BD EQUD &000176DB, &000169EE, &00015DDD, &00015294 EQUD &00014800, &00013E0F, &000134B4, &00012BE2 EQUD &0001238E, &00011BAC, &00011435, &00010D20 EQUD &00010666, &00010000, &0000F9E7, &0000F417 EQUD &0000EE8B, &0000E93E, &0000E42C, &0000DF51 EQUD &0000DAAA, &0000D634, &0000D1EB, &0000CDCD EQUD &0000C9D8, &0000C609, &0000C25E, &0000BED6 EQUD &0000BB6D, &0000B823, &0000B4F7, &0000B1E5 EQUD &0000AEEE, &0000AC10, &0000A94A, &0000A69A EQUD &FFFFFFFF, &002A0000, &00150000, &000E0000 \ n / 42 (n = 0 to 63) EQUD &000A8000, &00086666, &00070000, &00060000 EQUD &00054000, &0004AAAA, &00043333, &0003D174 EQUD &00038000, &00033B13, &00030000, &0002CCCC EQUD &0002A000, &00027878, &00025555, &000235E5 EQUD &00021999, &00020000, &0001E8BA, &0001D37A EQUD &0001C000, &0001AE14, &00019D89, &00018E38 EQUD &00018000, &000172C2, &00016666, &00015AD6 EQUD &00015000, &000145D1, &00013C3C, &00013333 EQUD &00012AAA, &00012298, &00011AF2, &000113B1 EQUD &00010CCC, &0001063E, &00010000, &0000FA0B EQUD &0000F45D, &0000EEEE, &0000E9BD, &0000E4C4 EQUD &0000E000, &0000DB6D, &0000D70A, &0000D2D2 EQUD &0000CEC4, &0000CADE, &0000C71C, &0000C37D EQUD &0000C000, &0000BCA1, &0000B961, &0000B63C EQUD &0000B333, &0000B043, &0000AD6B, &0000AAAA EQUD &FFFFFFFF, &002B0000, &00158000, &000E5555 \ n / 43 (n = 0 to 63) EQUD &000AC000, &00089999, &00072AAA, &00062492 EQUD &00056000, &0004C71C, &00044CCC, &0003E8BA EQUD &00039555, &00034EC4, &00031249, &0002DDDD EQUD &0002B000, &00028787, &0002638E, &0002435E EQUD &00022666, &00020C30, &0001F45D, &0001DE9B EQUD &0001CAAA, &0001B851, &0001A762, &000197B4 EQUD &00018924, &00017B96, &00016EEE, &00016318 EQUD &00015800, &00014D93, &000143C3, &00013A83 EQUD &000131C7, &00012983, &000121AF, &00011A41 EQUD &00011333, &00010C7C, &00010618, &00010000 EQUD &0000FA2E, &0000F49F, &0000EF4D, &0000EA36 EQUD &0000E555, &0000E0A7, &0000DC28, &0000D7D7 EQUD &0000D3B1, &0000CFB2, &0000CBDA, &0000C825 EQUD &0000C492, &0000C11F, &0000BDCB, &0000BA93 EQUD &0000B777, &0000B475, &0000B18C, &0000AEBA EQUD &FFFFFFFF, &002C0000, &00160000, &000EAAAA \ n / 44 (n = 0 to 63) EQUD &000B0000, &0008CCCC, &00075555, &00064924 EQUD &00058000, &0004E38E, &00046666, &00040000 EQUD &0003AAAA, &00036276, &00032492, &0002EEEE EQUD &0002C000, &00029696, &000271C7, &000250D7 EQUD &00023333, &00021861, &00020000, &0001E9BD EQUD &0001D555, &0001C28F, &0001B13B, &0001A12F EQUD &00019249, &00018469, &00017777, &00016B5A EQUD &00016000, &00015555, &00014B4B, &000141D4 EQUD &000138E3, &0001306E, &0001286B, &000120D2 EQUD &00011999, &000112BB, &00010C30, &000105F4 EQUD &00010000, &0000FA4F, &0000F4DE, &0000EFA8 EQUD &0000EAAA, &0000E5E0, &0000E147, &0000DCDC EQUD &0000D89D, &0000D487, &0000D097, &0000CCCC EQUD &0000C924, &0000C59D, &0000C234, &0000BEEA EQUD &0000BBBB, &0000B8A7, &0000B5AD, &0000B2CB EQUD &FFFFFFFF, &002D0000, &00168000, &000F0000 \ n / 45 (n = 0 to 63) EQUD &000B4000, &00090000, &00078000, &00066DB6 EQUD &0005A000, &00050000, &00048000, &00041745 EQUD &0003C000, &00037627, &000336DB, &00030000 EQUD &0002D000, &0002A5A5, &00028000, &00025E50 EQUD &00024000, &00022492, &00020BA2, &0001F4DE EQUD &0001E000, &0001CCCC, &0001BB13, &0001AAAA EQUD &00019B6D, &00018D3D, &00018000, &0001739C EQUD &00016800, &00015D17, &000152D2, &00014924 EQUD &00014000, &00013759, &00012F28, &00012762 EQUD &00012000, &000118F9, &00011249, &00010BE8 EQUD &000105D1, &00010000, &0000FA6F, &0000F51B EQUD &0000F000, &0000EB1A, &0000E666, &0000E1E1 EQUD &0000DD89, &0000D95B, &0000D555, &0000D174 EQUD &0000CDB6, &0000CA1A, &0000C69E, &0000C341 EQUD &0000C000, &0000BCDA, &0000B9CE, &0000B6DB EQUD &FFFFFFFF, &002E0000, &00170000, &000F5555 \ n / 46 (n = 0 to 63) EQUD &000B8000, &00093333, &0007AAAA, &00069249 EQUD &0005C000, &00051C71, &00049999, &00042E8B EQUD &0003D555, &000389D8, &00034924, &00031111 EQUD &0002E000, &0002B4B4, &00028E38, &00026BCA EQUD &00024CCC, &000230C3, &00021745, &00020000 EQUD &0001EAAA, &0001D70A, &0001C4EC, &0001B425 EQUD &0001A492, &00019611, &00018888, &00017BDE EQUD &00017000, &000164D9, &00015A5A, &00015075 EQUD &0001471C, &00013E45, &000135E5, &00012DF2 EQUD &00012666, &00011F38, &00011861, &000111DC EQUD &00010BA2, &000105B0, &00010000, &0000FA8D EQUD &0000F555, &0000F053, &0000EB85, &0000E6E6 EQUD &0000E276, &0000DE30, &0000DA12, &0000D61B EQUD &0000D249, &0000CE98, &0000CB08, &0000C797 EQUD &0000C444, &0000C10C, &0000BDEF, &0000BAEB EQUD &FFFFFFFF, &002F0000, &00178000, &000FAAAA \ n / 47 (n = 0 to 63) EQUD &000BC000, &00096666, &0007D555, &0006B6DB EQUD &0005E000, &000538E3, &0004B333, &000445D1 EQUD &0003EAAA, &00039D89, &00035B6D, &00032222 EQUD &0002F000, &0002C3C3, &00029C71, &00027943 EQUD &00025999, &00023CF3, &000222E8, &00020B21 EQUD &0001F555, &0001E147, &0001CEC4, &0001BDA1 EQUD &0001ADB6, &00019EE5, &00019111, &00018421 EQUD &00017800, &00016C9B, &000161E1, &000157C5 EQUD &00014E38, &00014530, &00013CA1, &00013483 EQUD &00012CCC, &00012576, &00011E79, &000117D0 EQUD &00011174, &00010B60, &00010590, &00010000 EQUD &0000FAAA, &0000F58D, &0000F0A3, &0000EBEB EQUD &0000E762, &0000E304, &0000DED0, &0000DAC3 EQUD &0000D6DB, &0000D316, &0000CF72, &0000CBEE EQUD &0000C888, &0000C53E, &0000C210, &0000BEFB EQUD &FFFFFFFF, &00300000, &00180000, &00100000 \ n / 48 (n = 0 to 63) EQUD &000C0000, &00099999, &00080000, &0006DB6D EQUD &00060000, &00055555, &0004CCCC, &00045D17 EQUD &00040000, &0003B13B, &00036DB6, &00033333 EQUD &00030000, &0002D2D2, &0002AAAA, &000286BC EQUD &00026666, &00024924, &00022E8B, &00021642 EQUD &00020000, &0001EB85, &0001D89D, &0001C71C EQUD &0001B6DB, &0001A7B9, &00019999, &00018C63 EQUD &00018000, &0001745D, &00016969, &00015F15 EQUD &00015555, &00014C1B, &0001435E, &00013B13 EQUD &00013333, &00012BB5, &00012492, &00011DC4 EQUD &00011745, &00011111, &00010B21, &00010572 EQUD &00010000, &0000FAC6, &0000F5C2, &0000F0F0 EQUD &0000EC4E, &0000E7D9, &0000E38E, &0000DF6B EQUD &0000DB6D, &0000D794, &0000D3DC, &0000D045 EQUD &0000CCCC, &0000C971, &0000C631, &0000C30C EQUD &FFFFFFFF, &00310000, &00188000, &00105555 \ n / 49 (n = 0 to 63) EQUD &000C4000, &0009CCCC, &00082AAA, &00070000 EQUD &00062000, &000571C7, &0004E666, &0004745D EQUD &00041555, &0003C4EC, &00038000, &00034444 EQUD &00031000, &0002E1E1, &0002B8E3, &00029435 EQUD &00027333, &00025555, &00023A2E, &00022164 EQUD &00020AAA, &0001F5C2, &0001E276, &0001D097 EQUD &0001C000, &0001B08D, &0001A222, &000194A5 EQUD &00018800, &00017C1F, &000170F0, &00016666 EQUD &00015C71, &00015306, &00014A1A, &000141A4 EQUD &00013999, &000131F3, &00012AAA, &000123B8 EQUD &00011D17, &000116C1, &000110B2, &00010AE4 EQUD &00010555, &00010000, &0000FAE1, &0000F5F5 EQUD &0000F13B, &0000ECAD, &0000E84B, &0000E412 EQUD &0000E000, &0000DC11, &0000D846, &0000D49C EQUD &0000D111, &0000CDA3, &0000CA52, &0000C71C EQUD &FFFFFFFF, &00320000, &00190000, &0010AAAA \ n / 50 (n = 0 to 63) EQUD &000C8000, &000A0000, &00085555, &00072492 EQUD &00064000, &00058E38, &00050000, &00048BA2 EQUD &00042AAA, &0003D89D, &00039249, &00035555 EQUD &00032000, &0002F0F0, &0002C71C, &0002A1AF EQUD &00028000, &00026186, &000245D1, &00022C85 EQUD &00021555, &00020000, &0001EC4E, &0001DA12 EQUD &0001C924, &0001B961, &0001AAAA, &00019CE7 EQUD &00019000, &000183E0, &00017878, &00016DB6 EQUD &0001638E, &000159F2, &000150D7, &00014834 EQUD &00014000, &00013831, &000130C3, &000129AC EQUD &000122E8, &00011C71, &00011642, &00011057 EQUD &00010AAA, &00010539, &00010000, &0000FAFA EQUD &0000F627, &0000F182, &0000ED09, &0000E8BA EQUD &0000E492, &0000E08F, &0000DCB0, &0000D8F2 EQUD &0000D555, &0000D1D6, &0000CE73, &0000CB2C EQUD &FFFFFFFF, &00330000, &00198000, &00110000 \ n / 51 (n = 0 to 63) EQUD &000CC000, &000A3333, &00088000, &00074924 EQUD &00066000, &0005AAAA, &00051999, &0004A2E8 EQUD &00044000, &0003EC4E, &0003A492, &00036666 EQUD &00033000, &00030000, &0002D555, &0002AF28 EQUD &00028CCC, &00026DB6, &00025174, &000237A6 EQUD &00022000, &00020A3D, &0001F627, &0001E38E EQUD &0001D249, &0001C234, &0001B333, &0001A529 EQUD &00019800, &00018BA2, &00018000, &00017507 EQUD &00016AAA, &000160DD, &00015794, &00014EC4 EQUD &00014666, &00013E70, &000136DB, &00012FA0 EQUD &000128BA, &00012222, &00011BD3, &000115C9 EQUD &00011000, &00010A72, &0001051E, &00010000 EQUD &0000FB13, &0000F656, &0000F1C7, &0000ED61 EQUD &0000E924, &0000E50D, &0000E11A, &0000DD49 EQUD &0000D999, &0000D608, &0000D294, &0000CF3C EQUD &FFFFFFFF, &00340000, &001A0000, &00115555 \ n / 52 (n = 0 to 63) EQUD &000D0000, &000A6666, &0008AAAA, &00076DB6 EQUD &00068000, &0005C71C, &00053333, &0004BA2E EQUD &00045555, &00040000, &0003B6DB, &00037777 EQUD &00034000, &00030F0F, &0002E38E, &0002BCA1 EQUD &00029999, &000279E7, &00025D17, &000242C8 EQUD &00022AAA, &0002147A, &00020000, &0001ED09 EQUD &0001DB6D, &0001CB08, &0001BBBB, &0001AD6B EQUD &0001A000, &00019364, &00018787, &00017C57 EQUD &000171C7, &000167C8, &00015E50, &00015555 EQUD &00014CCC, &000144AE, &00013CF3, &00013594 EQUD &00012E8B, &000127D2, &00012164, &00011B3B EQUD &00011555, &00010FAC, &00010A3D, &00010505 EQUD &00010000, &0000FB2B, &0000F684, &0000F209 EQUD &0000EDB6, &0000E98B, &0000E584, &0000E1A0 EQUD &0000DDDD, &0000DA3A, &0000D6B5, &0000D34D EQUD &FFFFFFFF, &00350000, &001A8000, &0011AAAA \ n / 53 (n = 0 to 63) EQUD &000D4000, &000A9999, &0008D555, &00079249 EQUD &0006A000, &0005E38E, &00054CCC, &0004D174 EQUD &00046AAA, &000413B1, &0003C924, &00038888 EQUD &00035000, &00031E1E, &0002F1C7, &0002CA1A EQUD &0002A666, &00028618, &000268BA, &00024DE9 EQUD &00023555, &00021EB8, &000209D8, &0001F684 EQUD &0001E492, &0001D3DC, &0001C444, &0001B5AD EQUD &0001A800, &00019B26, &00018F0F, &000183A8 EQUD &000178E3, &00016EB3, &0001650D, &00015BE5 EQUD &00015333, &00014AED, &0001430C, &00013B88 EQUD &0001345D, &00012D82, &000126F4, &000120AE EQUD &00011AAA, &000114E5, &00010F5C, &00010A0A EQUD &000104EC, &00010000, &0000FB42, &0000F6B0 EQUD &0000F249, &0000EE08, &0000E9EE, &0000E5F7 EQUD &0000E222, &0000DE6D, &0000DAD6, &0000D75D EQUD &FFFFFFFF, &00360000, &001B0000, &00120000 \ n / 54 (n = 0 to 63) EQUD &000D8000, &000ACCCC, &00090000, &0007B6DB EQUD &0006C000, &00060000, &00056666, &0004E8BA EQUD &00048000, &00042762, &0003DB6D, &00039999 EQUD &00036000, &00032D2D, &00030000, &0002D794 EQUD &0002B333, &00029249, &0002745D, &0002590B EQUD &00024000, &000228F5, &000213B1, &00020000 EQUD &0001EDB6, &0001DCB0, &0001CCCC, &0001BDEF EQUD &0001B000, &0001A2E8, &00019696, &00018AF8 EQUD &00018000, &0001759F, &00016BCA, &00016276 EQUD &00015999, &0001512B, &00014924, &0001417D EQUD &00013A2E, &00013333, &00012C85, &00012620 EQUD &00012000, &00011A1F, &0001147A, &00010F0F EQUD &000109D8, &000104D4, &00010000, &0000FB58 EQUD &0000F6DB, &0000F286, &0000EE58, &0000EA4E EQUD &0000E666, &0000E29F, &0000DEF7, &0000DB6D EQUD &FFFFFFFF, &00370000, &001B8000, &00125555 \ n / 55 (n = 0 to 63) EQUD &000DC000, &000B0000, &00092AAA, &0007DB6D EQUD &0006E000, &00061C71, &00058000, &00050000 EQUD &00049555, &00043B13, &0003EDB6, &0003AAAA EQUD &00037000, &00033C3C, &00030E38, &0002E50D EQUD &0002C000, &00029E79, &00028000, &0002642C EQUD &00024AAA, &00023333, &00021D89, &0002097B EQUD &0001F6DB, &0001E584, &0001D555, &0001C631 EQUD &0001B800, &0001AAAA, &00019E1E, &00019249 EQUD &0001871C, &00017C8A, &00017286, &00016906 EQUD &00016000, &0001576A, &00014F3C, &00014771 EQUD &00014000, &000138E3, &00013216, &00012B93 EQUD &00012555, &00011F58, &00011999, &00011414 EQUD &00010EC4, &000109A9, &000104BD, &00010000 EQUD &0000FB6D, &0000F704, &0000F2C2, &0000EEA4 EQUD &0000EAAA, &0000E6D1, &0000E318, &0000DF7D EQUD &FFFFFFFF, &00380000, &001C0000, &0012AAAA \ n / 56 (n = 0 to 63) EQUD &000E0000, &000B3333, &00095555, &00080000 EQUD &00070000, &000638E3, &00059999, &00051745 EQUD &0004AAAA, &00044EC4, &00040000, &0003BBBB EQUD &00038000, &00034B4B, &00031C71, &0002F286 EQUD &0002CCCC, &0002AAAA, &00028BA2, &00026F4D EQUD &00025555, &00023D70, &00022762, &000212F6 EQUD &00020000, &0001EE58, &0001DDDD, &0001CE73 EQUD &0001C000, &0001B26C, &0001A5A5, &00019999 EQUD &00018E38, &00018375, &00017943, &00016F96 EQUD &00016666, &00015DA8, &00015555, &00014D65 EQUD &000145D1, &00013E93, &000137A6, &00013105 EQUD &00012AAA, &00012492, &00011EB8, &00011919 EQUD &000113B1, &00010E7D, &0001097B, &000104A7 EQUD &00010000, &0000FB82, &0000F72C, &0000F2FB EQUD &0000EEEE, &0000EB04, &0000E739, &0000E38E EQUD &FFFFFFFF, &00390000, &001C8000, &00130000 \ n / 57 (n = 0 to 63) EQUD &000E4000, &000B6666, &00098000, &00082492 EQUD &00072000, &00065555, &0005B333, &00052E8B EQUD &0004C000, &00046276, &00041249, &0003CCCC EQUD &00039000, &00035A5A, &00032AAA, &00030000 EQUD &0002D999, &0002B6DB, &00029745, &00027A6F EQUD &00026000, &000247AE, &0002313B, &00021C71 EQUD &00020924, &0001F72C, &0001E666, &0001D6B5 EQUD &0001C800, &0001BA2E, &0001AD2D, &0001A0EA EQUD &00019555, &00018A60, &00018000, &00017627 EQUD &00016CCC, &000163E7, &00015B6D, &00015359 EQUD &00014BA2, &00014444, &00013D37, &00013677 EQUD &00013000, &000129CB, &000123D7, &00011E1E EQUD &0001189D, &00011352, &00010E38, &0001094F EQUD &00010492, &00010000, &0000FB96, &0000F752 EQUD &0000F333, &0000EF36, &0000EB5A, &0000E79E EQUD &FFFFFFFF, &003A0000, &001D0000, &00135555 \ n / 58 (n = 0 to 63) EQUD &000E8000, &000B9999, &0009AAAA, &00084924 EQUD &00074000, &000671C7, &0005CCCC, &000545D1 EQUD &0004D555, &00047627, &00042492, &0003DDDD EQUD &0003A000, &00036969, &000338E3, &00030D79 EQUD &0002E666, &0002C30C, &0002A2E8, &00028590 EQUD &00026AAA, &000251EB, &00023B13, &000225ED EQUD &00021249, &00020000, &0001EEEE, &0001DEF7 EQUD &0001D000, &0001C1F0, &0001B4B4, &0001A83A EQUD &00019C71, &0001914C, &000186BC, &00017CB7 EQUD &00017333, &00016A25, &00016186, &0001594D EQUD &00015174, &000149F4, &000142C8, &00013BEA EQUD &00013555, &00012F05, &000128F5, &00012323 EQUD &00011D89, &00011826, &000112F6, &00010DF6 EQUD &00010924, &0001047D, &00010000, &0000FBA9 EQUD &0000F777, &0000F368, &0000EF7B, &0000EBAE EQUD &FFFFFFFF, &003B0000, &001D8000, &0013AAAA \ n / 59 (n = 0 to 63) EQUD &000EC000, &000BCCCC, &0009D555, &00086DB6 EQUD &00076000, &00068E38, &0005E666, &00055D17 EQUD &0004EAAA, &000489D8, &000436DB, &0003EEEE EQUD &0003B000, &00037878, &0003471C, &00031AF2 EQUD &0002F333, &0002CF3C, &0002AE8B, &000290B2 EQUD &00027555, &00025C28, &000244EC, &00022F68 EQUD &00021B6D, &000208D3, &0001F777, &0001E739 EQUD &0001D800, &0001C9B2, &0001BC3C, &0001AF8A EQUD &0001A38E, &00019837, &00018D79, &00018348 EQUD &00017999, &00017063, &0001679E, &00015F41 EQUD &00015745, &00014FA4, &00014859, &0001415C EQUD &00013AAA, &0001343E, &00012E14, &00012828 EQUD &00012276, &00011CFB, &000117B4, &0001129E EQUD &00010DB6, &000108FB, &00010469, &00010000 EQUD &0000FBBB, &0000F79B, &0000F39C, &0000EFBE EQUD &FFFFFFFF, &003C0000, &001E0000, &00140000 \ n / 60 (n = 0 to 63) EQUD &000F0000, &000C0000, &000A0000, &00089249 EQUD &00078000, &0006AAAA, &00060000, &0005745D EQUD &00050000, &00049D89, &00044924, &00040000 EQUD &0003C000, &00038787, &00035555, &0003286B EQUD &00030000, &0002DB6D, &0002BA2E, &00029BD3 EQUD &00028000, &00026666, &00024EC4, &000238E3 EQUD &00022492, &000211A7, &00020000, &0001EF7B EQUD &0001E000, &0001D174, &0001C3C3, &0001B6DB EQUD &0001AAAA, &00019F22, &00019435, &000189D8 EQUD &00018000, &000176A2, &00016DB6, &00016535 EQUD &00015D17, &00015555, &00014DE9, &000146CE EQUD &00014000, &00013978, &00013333, &00012D2D EQUD &00012762, &000121CF, &00011C71, &00011745 EQUD &00011249, &00010D79, &000108D3, &00010456 EQUD &00010000, &0000FBCD, &0000F7BD, &0000F3CF EQUD &FFFFFFFF, &003D0000, &001E8000, &00145555 \ n / 61 (n = 0 to 63) EQUD &000F4000, &000C3333, &000A2AAA, &0008B6DB EQUD &0007A000, &0006C71C, &00061999, &00058BA2 EQUD &00051555, &0004B13B, &00045B6D, &00041111 EQUD &0003D000, &00039696, &0003638E, &000335E5 EQUD &00030CCC, &0002E79E, &0002C5D1, &0002A6F4 EQUD &00028AAA, &000270A3, &0002589D, &0002425E EQUD &00022DB6, &00021A7B, &00020888, &0001F7BD EQUD &0001E800, &0001D936, &0001CB4B, &0001BE2B EQUD &0001B1C7, &0001A60D, &00019AF2, &00019069 EQUD &00018666, &00017CE0, &000173CF, &00016B29 EQUD &000162E8, &00015B05, &0001537A, &00014C41 EQUD &00014555, &00013EB1, &00013851, &00013232 EQUD &00012C4E, &000126A4, &0001212F, &00011BED EQUD &000116DB, &000111F7, &00010D3D, &000108AD EQUD &00010444, &00010000, &0000FBDE, &0000F7DF EQUD &FFFFFFFF, &003E0000, &001F0000, &0014AAAA \ n / 62 (n = 0 to 63) EQUD &000F8000, &000C6666, &000A5555, &0008DB6D EQUD &0007C000, &0006E38E, &00063333, &0005A2E8 EQUD &00052AAA, &0004C4EC, &00046DB6, &00042222 EQUD &0003E000, &0003A5A5, &000371C7, &0003435E EQUD &00031999, &0002F3CF, &0002D174, &0002B216 EQUD &00029555, &00027AE1, &00026276, &00024BDA EQUD &000236DB, &0002234F, &00021111, &00020000 EQUD &0001F000, &0001E0F8, &0001D2D2, &0001C57C EQUD &0001B8E3, &0001ACF9, &0001A1AF, &000196F9 EQUD &00018CCC, &0001831F, &000179E7, &0001711D EQUD &000168BA, &000160B6, &0001590B, &000151B3 EQUD &00014AAA, &000143EB, &00013D70, &00013737 EQUD &0001313B, &00012B78, &000125ED, &00012094 EQUD &00011B6D, &00011674, &000111A7, &00010D04 EQUD &00010888, &00010432, &00010000, &0000FBEF EQUD &FFFFFFFF, &003F0000, &001F8000, &00150000 \ n / 63 (n = 0 to 63) EQUD &000FC000, &000C9999, &000A8000, &00090000 EQUD &0007E000, &00070000, &00064CCC, &0005BA2E EQUD &00054000, &0004D89D, &00048000, &00043333 EQUD &0003F000, &0003B4B4, &00038000, &000350D7 EQUD &00032666, &00030000, &0002DD17, &0002BD37 EQUD &0002A000, &0002851E, &00026C4E, &00025555 EQUD &00024000, &00022C23, &00021999, &00020842 EQUD &0001F800, &0001E8BA, &0001DA5A, &0001CCCC EQUD &0001C000, &0001B3E4, &0001A86B, &00019D89 EQUD &00019333, &0001895D, &00018000, &00017711 EQUD &00016E8B, &00016666, &00015E9B, &00015726 EQUD &00015000, &00014924, &0001428F, &00013C3C EQUD &00013627, &0001304D, &00012AAA, &0001253C EQUD &00012000, &00011AF2, &00011611, &0001115B EQUD &00010CCC, &00010864, &00010421, &00010000 \ ****************************************************************************** \ \ Two-pass assembly loop \ \ ****************************************************************************** .endCode ] NEXT pass% \ Loop back for the second pass \ ****************************************************************************** \ \ Save GameCode.bin \ \ ****************************************************************************** OSCLI "SAVE GameCode "+STR$~CODE%+" "+STR$~O%+" "+STR$~Entry+" "+STR$~CODE