Skip to navigation


Aviator B source

Name: EraseCanopyLines [Show more] Type: Subroutine Category: Drawing lines Summary: Draw all the lines from a line buffer to erase them Deep dive: Line buffers
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCanopyView calls EraseCanopyLines

We call this routine from DrawCanopyView, after drawing all the new lines and just before we flip colours. See the DrawCanopyView routine for more details of the colour-cycling system that this forms part of. This is what the EraseCanopyLines routine does: If colourCycle = %00001111, then colour 1 is white and colour 2 is black: * Draw the lines in line buffer 1, using AND logic and bit pattern %00001111 * Colour 2 on-screen is %11110000, so AND'ing with %00001111 gives 0 * So this erases all the lines in buffer 1, which are on-screen in colour 2 i.e. we erase all the lines in line buffer 1 from the hidden screen If colourCycle = %11110000, then colour 1 is black and colour 2 is white: * Draw the lines in line buffer 2, using AND logic and bit pattern %11110000 * Colour 1 is %00001111, so AND'ing with %11110000 gives 0 * So this erases all the lines in buffer 2, which are on-screen in colour 1 i.e. we erase all the lines in line buffer 2 from the hidden screen
.EraseCanopyLines LDA #%00000000 \ Set colourLogic = %00000000 so when we draw a line, it STA colourLogic \ erases it JSR ModifyDrawRoutine \ Modify the drawing routines to use AND logic and the \ bit patterns that match colourCycle .ecal1 LDA colourCycle \ If bit 7 of colourCycle is clear, i.e. %00001111, jump BPL ecal3 \ down to ecal3 \ If we get here, colourCycle is %11110000 LDX lineBuffer2Count \ If lineBuffer2Count <> 47, line buffer 2 is not CPX #47 \ empty, so jump down to ecal2 to draw the next line BNE ecal2 \ from the buffer RTS \ Return from the subroutine .ecal2 DEC lineBuffer2Count \ Decrement the value in lineBuffer2Count as we are \ about to draw the next line from buffer 2 JMP ecal5 \ Jump down to ecal5 to draw the next line from buffer 2 .ecal3 LDX lineBuffer1Count \ If lineBuffer1Count <> -1, line buffer 1 is not CPX #255 \ empty, so jump down to ecal4 to draw the next line BNE ecal4 \ from the buffer RTS \ Return from the subroutine .ecal4 DEC lineBuffer1Count \ Decrement the value in lineBuffer1Count as we are \ about to draw the next line from buffer 1 .ecal5 \ We now fetch the next line from the line buffer LDA lineBufferR,X \ Set R to the start x-coordinate from lineBufferR STA R STA xTemp1Lo \ Set the x-coordinate of (R, S) = (xTemp1Lo, yTemp1Lo) \ so we can access it in the DrawCanopyLine routine LDA lineBufferW,X \ Set W to the max/min x-coordinate from lineBufferW STA W LDA lineBufferS,X \ Set S to the start y-coordinate from lineBufferS STA S STA yTemp1Lo \ Set the y-coordinate of (R, S) = (xTemp1Lo, yTemp1Lo) \ so we can access it in the DrawCanopyLine routine LDA lineBufferG,X \ Set G to max/min y-coordinate from lineBufferG STA G LDA lineBufferT,X \ Set T to the |x-delta| from lineBufferT STA T LDA lineBufferU,X \ Set U to the |y-delta| from lineBufferU STA U LDA lineBufferV,X \ Set V to the direction from lineBufferV STA V JSR DrawCanopyLine \ Draw the line (to erase it) JMP ecal1 \ Loop back to ecal1 to erase the next line EQUB &17 \ This byte appears to be unused
Name: Multiply16x16Mix [Show more] Type: Subroutine Category: Maths Summary: Multiply two 16-bit numbers with different sign bits (one in bit 7 of the high byte, the other in bit 0 of the low byte)
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyAerodynamics (Part 3 of 3) calls Multiply16x16Mix * SetPointCoords calls Multiply16x16Mix

This routine multiplies two 16-bit numbers with different sign bits, where one argument (J I) has the sign in bit 7 of the high byte, and the other argument (S R) has the sign in bit 0 of the low byte. It calculates: (H G) = (J I) * (S R) >> 16 The result in (H G) has the sign in bit 7 of the high byte. The fractional part of the result is in W, so this is the full result, though in practice W is ignored: (H G W) = (J I) * (S R)
Arguments: (J I) A signed 16-bit number, with the sign in bit 7 of J (S R) A signed 16-bit number, with the sign in bit 0 of R K Optionally negate (J I): * 0 = calculate (J I) * (S R) >> 16 * 128 = calculate -(J I) * (S R) >> 16 In practice, this routine is always called with K = 0
Returns: (H G) The result of the multiplication, with the sign in bit 7 of H K The sign of the result (in bit 7)
.Multiply16x16Mix LDA J \ If J is positive, jump to mmix1 as (J I) is already a BPL mmix1 \ positive 16-bit number LDA #0 \ Negate (J I) so (J I) is now a positive 16-bit number SEC SBC I STA I LDA #0 SBC J STA J LDA K \ Flip bit 7 of K, so when K = 0 we negate the result EOR #%10000000 \ below to give the result the correct sign STA K .mmix1 LDA R \ If bit 0 of R is 0, jump to mmix2 as (S R) is positive AND #1 BEQ mmix2 LDA K \ Otherwise (S R) is negative, so flip bit 7 of K, so EOR #%10000000 \ when K = 0 we negate the result below to give the STA K \ result the correct sign .mmix2 JSR Multiply16x16 \ Calculate (H A) = (S R) * (J I) >> 16 \ \ and set the C flag if we need to increment H STA G \ Set (H G) = (H A) \ = (S R) * (J I) >> 16 BCC mmix3 \ Increment the top byte in H if required INC H .mmix3 LDA K \ If K is positive, jump to mmix4 to return from the BPL mmix4 \ subroutine as the sign of the result is already \ correct SEC \ Negate (H G) so the result has the correct sign LDA #0 SBC G STA G LDA #0 SBC H STA H .mmix4 RTS \ Return from the subroutine
Name: Multiply8x8 [Show more] Type: Subroutine Category: Maths Summary: Multiply two unsigned 8-bit numbers Deep dive: Times tables and nibble arithmetic
Context: See this subroutine on its own page References: This subroutine is called as follows: * DivideScaled calls Multiply8x8 * Multiply16x16 calls Multiply8x8 * Multiply8x16 calls Multiply8x8 * Sine16Bit calls Multiply8x8

This routine multiplies two unsigned 8-bit numbers, as follows: (A V) = X * Y It uses the following algorithm: X * Y = %XXXXxxxx * %YYYYyyyy = (%XXXX0000 + %0000xxxx) * (%YYYY0000 + %0000yyyy) = (%XXXX0000 * %YYYY0000) + (%XXXX0000 * %0000yyyy) + (%0000xxxx * %YYYY0000) + (%0000xxxx * %0000yyyy) = (%YYYY * %XXXX) << 8 + (%XXXX * %yyyy << 4) + (%xxxx * %YYYY << 4) + (%xxxx * %yyyy) = (%YYYY * %XXXX) << 8 + ((%XXXX * %yyyy) + (%xxxx * %YYYY)) << 4 + (%xxxx * %yyyy)
Arguments: X An unsigned 8-bit value (0 to 255) Y An unsigned 8-bit value (0 to 255)
Returns: (A V) X * Y
.Multiply8x8 \ We start with X = %XXXXxxxx and Y = %YYYYyyyy LDA lowNibble,X \ Set T = (X AND %00001111) OR (Y AND %11110000) ORA highNibble,Y \ = %0000xxxx OR %YYYY0000 STA T \ = %YYYYxxxx AND #%11110000 \ Set U = (A AND %11110000) OR (X >> 4) ORA shift4Right,X \ = %YYYY0000 OR %0000XXXX STA U \ = %YYYYXXXX AND #%00001111 \ Set Y = (A AND %00001111) OR (Y << 4) ORA shift4Left,Y \ = %0000XXXX OR %yyyy0000 TAY \ = %yyyyXXXX AND #%11110000 \ Set X = (A AND %11110000) OR (X AND %00001111) ORA lowNibble,X \ = %yyyy0000 OR %0000xxxx TAX \ = %yyyyxxxx LDA timesTable,X \ Set V = %yyyy * %xxxx STA V LDX T \ Set X = T = %YYYYxxxx LDA timesTable,X \ Set A = (%YYYY * %xxxx) + (%yyyy * XXXX) CLC \ ADC timesTable,Y \ Call this %AAAAaaaa with carry C ROR A \ Set T = A rotated right by 4 places ROR A \ = %aaaCAAAA ROR A \ ROR A \ and set the C flag to bit 3 of A STA T ROR A \ Set A = %aaaaCAAA AND #%11110000 \ Set A = %aaaa0000 CLC \ Set V = V + A ADC V \ = V + %aaaa0000 STA V \ = (%yyyy * %xxxx) + %aaaa0000 LDA T \ Set A = T AND %00011111 AND #%00011111 \ = %aaaCAAAA AND %00011111 \ = %000CAAAA LDX U \ Set A = A + %YYYY * %XXXX ADC timesTable,X \ = %000CAAAA + (%YYYY * %XXXX) \ So we now have: \ \ (A V) = A << 8 + V \ \ = (%000CAAAA + (%YYYY * %XXXX)) << 8 \ + (%yyyy * %xxxx) + %aaaa0000 \ \ = %000CAAAA << 8 \ + (%YYYY * %XXXX) << 8 \ + (%yyyy * %xxxx) \ + %aaaa0000 \ \ = (%YYYY * %XXXX) << 8 \ + %000CAAAAaaaa0000 \ + (%yyyy * %xxxx) \ \ We also have the following: \ \ %CAAAAaaaa = (%YYYY * %xxxx) + (%yyyy * XXXX) \ \ and: \ \ %000CAAAAaaaa0000 = %CAAAAaaaa << 4 \ \ so combining them all, we get: \ \ (A V) = (%YYYY * %XXXX) << 8 \ + %CAAAAaaaa << 4 \ + (%yyyy * %xxxx) \ \ = (%YYYY * %XXXX) << 8 \ ((%YYYY * %xxxx) + (%yyyy * XXXX)) << 4 \ + (%yyyy * %xxxx) \ \ which is the result we want RTS \ Return from the subroutine
Name: ProjectAxisAngle [Show more] Type: Subroutine Category: 3D geometry Summary: Convert the rotation angles of the plane into coordinates Deep dive: Rotation matrices
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateFlightModel (Part 4 of 4) calls ProjectAxisAngle

This routine converts the plane's rotation angles into a set of coordinate values that we can use in the SetMatrices routine to populate the four rotation matrices. There are six of these values, each of which is a 16-bit number with the sign in bit 0: * (mx1Hi mx1Lo), which we can refer to as mx1 * (mx2Hi mx2Lo), which we can refer to as mx2 * (my1Hi my1Lo), which we can refer to as my1 * (my2Hi my2Lo), which we can refer to as my2 * (mz1Hi mz1Lo), which we can refer to as mz1 * (mz2Hi mz2Lo), which we can refer to as mz2 The calculation is done for one axis at a time, so the same routine not only sets mx1 and mx2, but also my1 and my2, and mz1 and mz2. If we take the x-axis as an example, and assume for simplicity that the angle in xRotation is less than 90 degrees, the routine will calculate the following: * mx1 = -cos(xRotation) * mx2 = sin(xRotation) Considering the x-axis calculation, the routine sets mx1 and mx2 according to the current rotation of the plane around the x-axis, which is stored in (xRotationHi xRotationLo), which we can refer to as xRotation. The routine converts the angle in xRotation into a pair of Cartesian coordinates, by projecting a line rotated by that amount onto the Cartesian axes; in a sense, we are converting from Polar coordinates into Cartesian, so we can use those coordinates to populate the rotation matrices in SetMatrices.
Arguments: matrixAxis The axis to be processed: * 0 = set mx1 and mx2 from xRotation * 1 = set my1 and my2 from yRotation * 2 = set mz1 and mz2 from zRotation Y Same value as matrixAxis
.ProjectAxisAngle \ The routine processes the axis defined in Y, but the \ comments below are for the x-axis, where we set the \ values of mx1 and mx2 depending on the value of \ xRotation LDA xRotationHi,Y \ Set (G A) = (xRotationHi xRotationLo) for axis Y STA G \ \ starting with the high byte STA K \ Set K = xRotationHi, so we can access the high byte \ when writing the results below LDA xRotationLo,Y \ Set the low byte of (G A) \ \ so (G A) = xRotation ASL A \ Set (G W) = (G A) << 2 ROL G \ = xRotation << 2 ASL A \ ROL G \ so (G W) is the angle, reduced into a quarter circle STA W \ by removing the top four bits \ \ The original angle is stored as the portion of a whole \ circle, where xRotation is in the range 0 to 65535 and \ 65535 represents a full circle, so shifting it to the \ left and dropping the top two bits out reduces the \ range of the angle to a quarter circle, while leaving \ the range of the top byte as 0 to 255, which is what \ we need for looking up the reduced angle in the sine \ table LDX G \ Set (X W) = (G W) to pass the rotation angle to \ Sine16Bit JSR Sine16Bit \ Set (A Y) = sin(X W) \ = sin(xRotation) STA Q \ Set (Q P) = (A Y) STY P LDA G \ Set (X W) = ~(G W) EOR #&FF \ = ~xRotation << 2 TAX \ LDA W \ This sets (X W) to the original xRotation, reduced to EOR #&FF \ the quarter circle as before, and then inverted within STA W \ that quarter - or, to put it another way, if (G W) is \ the angle within the quarter (0 to 90 degrees), then \ (X W) is now 90 - (G W) \ \ Because sin(90 - X) = -cos(X), this means the call to \ Sine16Bit returns the cosine instead of the sine JSR Sine16Bit \ Set (A Y) = sin(X W) \ = sin(~xRotation) \ = -cos(xRotation) STA S \ Set (S R) = (A Y) STY R \ We now copy the results into mx1 and mx2, setting the \ sign in bit 0 as we go (the sign is in bit 0 as this \ is a matrix number) \ \ We do this based on the value in K, which contains the \ high byte of (xRotationHi xRotationLo) for this axis \ \ * 0 to 63 (bit 6 clear, bit 7 clear) \ * 64 to 127 (bit 6 set, bit 7 clear) \ * 128 to 191 (bit 6 clear, bit 7 set) \ * 192 to 255 (bit 6 set, bit 7 set) \ \ These four ranges correspond to the four quadrants of \ the rotation angle. For example, for the z-axis, which \ points into the screen, we can visualise the quadrants \ by imagining the plane doing a full 360-degree roll to \ the right; for the first quarter the plane is still \ upright, for the second and third quarters it is \ upside down, and then for the final quarter it is \ upright again \ \ The following sets the m1 and m2 values according to \ which quadrant this particular axis is rotated into LDY matrixAxis \ Set Y to the axis once again BIT K \ If bit 6 of K is set, jump to axis2 BVS axis2 BMI axis1 \ If bit 7 of K is set, jump to axis1 \ If we get here then K has: \ \ * Bit 6 clear \ * Bit 7 clear \ \ so this is the first quadrant of the axis' rotation LDA Q \ Set (mx2Hi mx2Lo) = (Q P) STA mx2Hi,Y \ = sin(xRotation) LDA P \ AND #%11111110 \ with bit 0 clear (positive) STA mx2Lo,Y LDA S \ Set (mx1Hi mx1Lo) = (S R) STA mx1Hi,Y \ = -cos(xRotation) LDA R \ AND #%11111110 \ with bit 0 clear (positive) STA mx1Lo,Y JMP axis4 \ Jump to axis4 to return from the subroutine .axis1 \ If we get here then K has: \ \ * Bit 6 clear \ * Bit 7 set \ \ so this is the third quadrant of the axis' rotation LDA Q \ Set (mx2Hi mx2Lo) = -(Q P) STA mx2Hi,Y \ LDA P \ with bit 0 set (negative) ORA #1 STA mx2Lo,Y LDA S \ Set (mx1Hi mx1Lo) = -(S R) STA mx1Hi,Y \ LDA R \ ORA #1 \ with bit 0 set (negative) STA mx1Lo,Y BNE axis4 \ Jump to axis4 to return from the subroutine (this BNE \ is effectively a JMP as A is never zero) .axis2 \ If we get here then K has bit 6 set BMI axis3 \ If bit 7 of K is set, jump to axis3 \ If we get here then K has: \ \ * Bit 6 set \ * Bit 7 clear \ \ so this is the second quadrant of the axis' rotation LDA S \ Set (mx2Hi mx2Lo) = (S R) STA mx2Hi,Y \ LDA R \ with bit 0 clear (positive) AND #%11111110 STA mx2Lo,Y LDA Q \ Set (mx1Hi mx1Lo) = -(Q P) STA mx1Hi,Y \ LDA P \ with bit 0 set (negative) ORA #1 STA mx1Lo,Y BNE axis4 \ Jump to axis4 to return from the subroutine (this BNE \ is effectively a JMP as A is never zero) .axis3 \ If we get here then K has: \ \ * Bit 6 set \ * Bit 7 set \ \ so this is the fourth quadrant of the axis' rotation LDA S \ Set (mx2Hi mx2Lo) = -(S R) STA mx2Hi,Y \ LDA R \ with bit 0 set (negative) ORA #1 STA mx2Lo,Y LDA Q \ Set (mx1Hi mx1Lo) = (Q P) STA mx1Hi,Y \ LDA P \ with bit 0 clear (positive) AND #%11111110 STA mx1Lo,Y .axis4 RTS \ Return from the subroutine
Name: Sine16Bit [Show more] Type: Subroutine Category: Maths Summary: Calculate the sine of a 16-bit number Deep dive: Trigonometry
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProjectAxisAngle calls Sine16Bit

Sets (A Y) = sin(X W) where (X W) is a 16-bit angle, with 0 to 65535 representing a quarter circle. The sine lookup table contains 256 entries that represent a quarter circle, so we work out the result by first looking up the sine for the high byte X, and then interpolating W/256 of the way between the results for X and X + 1. It might help to think of X being an integer (0 to 255) and W being a fraction (0 to 255) and we're trying to map X.W onto the sine table by finding the entry for X and doing linear interpolation between X and X + 1 for the fractional amount.
Arguments: (X W) The 16-bit angle, representing a quarter-circle in the range 0 to 255
Returns: (A Y) sin(X W) (X W) Unchanged
.Sine16Bit STX H \ Store X in H for later SEC \ Set (A I) = sin(X + 1) - sin(X) LDA sinLo+1,X \ SBC sinLo,X \ starting with the low bytes STA I LDA sinHi+1,X \ And then the high bytes SBC sinHi,X \ Let's call this dX, the difference between sin(X + 1) \ sin(X) and, so (A I) = dX LSR A \ The maximum differential value we can have is 402, so ROR I \ we halve (A I) to fit the result into one byte, like LDX I \ this: \ \ (A I) = dX / 2 \ \ and then setting X to the low byte in I, so: \ \ X = dX / 2 LDY W \ Set Y = W JSR Multiply8x8 \ Set (A V) = X * Y \ = (dX / 2) * W LDX H \ Set X to the original argument we stored above ASL V \ Set (A V) = (A V) * 2 ROL A \ = (dX / 2) * W * 2 \ = dX * W \ \ and put bit 7 of (A V) into the C flag, so in effect \ we have: \ \ (C A V) = dX * W PHP \ Store the C flag on the stack CLC \ Set (A Y) = (C A) + sin(X) ADC sinLo,X \ = (dX * W / 256) + sin(X) TAY \ \ starting with the low bytes LDA #0 \ And then adding the carry to the high byte, so now we ADC sinHi,X \ have the interim result for (0 A) + sin(X) PLP \ And finally adding C (which we retrieve from the ADC #0 \ stack) to give the final result for (C A) + sin(X) \ So at this point we have: \ \ (A Y) = sin(X) + (dX * W / 256) \ \ which is the result we want, as it takes sin(X) and \ adds on W/256 of the difference between sin(X) and \ sin(X + 1) RTS \ Return from the subroutine
Name: Multiply4x16 [Show more] Type: Subroutine Category: Maths Summary: Multiply a 4-bit and a 16-bit number Deep dive: Times tables and nibble arithmetic
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetObjPointCoords (Part 1 of 2) calls Multiply4x16

This routine multiplies a 4-bit number by a 16-bit number, using the lookup table at timesTable for fast results. It does the following calculation: (G W) = V * (S R) >> 8 If bit 7 of K is set, the routine returns double this amount.
Arguments: (S R) A 16-bit number V A 4-bit number (0 to 15), can be positive or negative (i.e. bit 7 contains the sign, bits 0-3 the magnitude) K Bit 7 determines whether to multiply the result by 2: * If bit 7 of K = 0, calculate (G W) = V * (S R) >> 8 * If bit 7 of K = 1, calculate (G W) = V * (S R) >> 7
.Multiply4x16 LDX S \ Set X = S = %SSSSssss LDY V \ Set Y = V = %VVVVvvvv LDA shift4Left,Y \ Set A = V << 4 \ = %vvvv0000 ORA shift4Right,X \ Set Y = A OR (S >> 4) TAY \ = %vvvv0000 OR %0000SSSS \ = %vvvvSSSS AND #%11110000 \ Set U = (A AND %11110000) OR (X AND %00001111) ORA lowNibble,X \ = (%vvvvSSSS AND %11110000) OR (X AND %00001111) STA U \ = %vvvv0000 OR (%SSSSssss AND %00001111) \ = %vvvvssss LDX R \ Set X = R = %RRRRrrrr AND #%11110000 \ Set X = (A AND %1111000) OR (X >> 4) ORA shift4Right,X \ = (%vvvvssss AND %11110000) OR (%RRRRrrrr >> 4) TAX \ = %vvvv0000 OR %0000RRRR \ = %vvvvRRRR LDA timesTable,X \ Set X = %vvvv * %RRRR TAX \ \ Call this %XXXXxxxx STX T \ Set T = %XXXXxxxx LDA timesTable,Y \ Set Y = %vvvv * %SSSS TAY \ \ Call this %YYYYyyyy LDA shift4Right,X \ Set A = (X >> 4) OR (Y << 4) ORA shift4Left,Y \ = %0000XXXX OR %yyyy0000 \ = %yyyyXXXX CLC \ Set W = A + (%vvvv * %ssss) LDX U \ = %yyyyXXXX + (%vvvv * %ssss) ADC timesTable,X STA W LDA shift4Right,Y \ Set G = (Y >> 4) + carry ADC #0 \ = %0000YYYY + carry STA G \ So (G W) = %YYYYyyyyXXXX + (%vvvv * %ssss) \ = %YYYYyyyy0000 + %XXXX + (%vvvv * %ssss) \ = %vvvv * %SSSS << 4 \ + %vvvv * %RRRR >> 4 \ + %vvvv * %ssss \ = %vvvv * (%SSSS << 4 + %ssss + %RRRR >> 4) \ = %vvvv * (%SSSSssss + %RRRR >> 4) \ = %vvvv * (%SSSSssss + %RRRRrrrr >> 8) \ = %vvvv * (S R) >> 8 \ = V * (S R) >> 8 BIT K \ If bit 7 of K is clear, jump to mulp2 to skip the BPL mulp2 \ following and apply the correct sign to the result LDX R \ Set X = R = %RRRRrrrr LDA V \ Set Y = (V AND %00001111) OR (X << 3) AND #%00001111 \ = %0000vvvv OR %rrrr0000 ORA shift4Left,X \ = %rrrrvvvv TAY LDX T \ Set X = T = %XXXXxxxx LDA shift4Left,X \ Set A = (X << 4) + (%rrrr * %vvvv) CLC \ = %xxxx0000 + (%rrrr * %vvvv) ADC timesTable,Y \ = %vvvv * %RRRR << 4 + %rrrr * %vvvv \ = %vvvv * (%RRRR << 4 + %rrrr) \ = %vvvv * %RRRRrrrr \ = V * R BCC mulp1 \ If the addition didn't overflow, i.e. V * R < 256, \ jump to mulp1 to skip the following INC W \ Set (G W) = (G W) + 1 BNE mulp1 \ INC G \ to round the result up, as the low bytes of the \ multiplication produced a carry .mulp1 ASL A \ Set (G W A) = (G W A) * 2 ROL W \ ROL G \ so the result is doubled when bit 7 of K is set .mulp2 LDA V \ If V is positive, skip the following BPL mulp3 LDA #0 \ V is negative, so we now negate (G W) by subtracting SEC \ it from 0, first the low byte and then the high byte SBC W STA W LDA #0 SBC G STA G .mulp3 RTS \ Return from the subroutine
Name: SetObjPointCoords (Part 1 of 2) [Show more] Type: Subroutine Category: 3D geometry Summary: Calculate the coordinate for a point within an object Deep dive: 3D objects Rotating and translating points in 3D space
Context: See this subroutine on its own page References: This subroutine is called as follows: * FireGuns calls SetObjPointCoords * ProcessLine (Part 6 of 7) calls SetObjPointCoords * ProcessRunwayLine (Part 2 of 5) calls SetObjPointCoords

Arguments: GG The ID of the point to process and update matrixNumber The matrix to use in the calculation: * 0 = matrix 1 * 9 = matrix 2 * 18 = matrix 3 * 27 = matrix 4 objectAnchorPoint Point ID of the anchor point to which we add the final result to
Returns: xPointHi etc. Point GG is set to the point's coordinate in 3D space showLine If the calculation overflows, then the coordinate does not fit within the boundaries of Aviator's 3D world, so bit 6 is set to indicate that the line containing this point should not be shown xTemp1Hi etc. The rotated vector from the anchor point to the object point
.SetObjPointCoords LDX GG \ Set X to the point ID whose coordinates we want to \ calculate LDY xObjectPoint,X \ Set PP to the point's anchor-relative x-coordinate STY PP \ from xObjectPoint LDY yObjectPoint,X \ Set QQ to the point's anchor-relative y-coordinate STY QQ \ from yObjectPoint LDY zObjectPoint,X \ Set RR (and Y) to the point's anchor-relative STY RR \ z-coordinate from zObjectPoint, which also contains \ the scale factor in bits 4 to 7 LDA shift4Right,Y \ Set UU (and A) to Y >> 4, to extract the scale factor STA UU \ from bits 4 to 7 of the zObjectPoint entry CMP #9 \ If the scale factor in A >= 9, set bit 7 of K so the ROR K \ result of the call to Multiply4x16 below is doubled, \ i.e. (G W) is doubled. This gives us an extra factor \ of 2 on top of the maximum 2^8 factor we would get by \ left-shifting the result (see part 2 for the scaling \ code) LDX #5 \ We now zero (xTemp1, yTemp1, zTemp1), which is stored \ in six bytes to give us three 16-bit coordinate values \ (from xTemp1Lo to zTemp1Hi), so set a counter in X to \ count the bytes LDA #0 \ Set A = 0 to use as our zero .objp1 STA xTemp1Lo,X \ Zero the X-th byte of the six-byte coordinate block \ between xTemp1Lo and zTemp1Hi DEX \ Decrement the loop counter BPL objp1 \ Loop back until we have zeroed all six bytes \ We now do the matrix multiplication: \ \ [ xTemp1 ] [ m0 m1 m2 ] [ xObjectPoint ] \ [ yTemp1 ] = [ m3 m4 m5 ] x [ yObjectPoint ] \ [ zTemp1 ] [ m6 m7 m8 ] [ zObjectPoint ] \ \ We do this in three loops of three, using an outer and \ inner loop. We set two loop counters, VV and X, for \ the outer and inner loops respectively. They both \ iterate through 2, 1 and 0, with VV iterating through \ zTemp1, yTemp1 and xTemp1, and X iterating through \ zObjectPoint, yObjectPoint and xObjectPoint \ \ We also iterate a counter in P for the matrix entries, \ which counts down from m8 to m0, decreasing by 1 on \ each iteration \ \ All the iterators count backwards, so the calculations \ in order are: \ \ * zTemp1 += zObjectPoint * m8 \ * zTemp1 += yObjectPoint * m7 \ * zTemp1 += xObjectPoint * m6 \ \ * yTemp1 += zObjectPoint * m5 \ * yTemp1 += yObjectPoint * m4 \ * yTemp1 += xObjectPoint * m3 \ \ * xTemp1 += zObjectPoint * m2 \ * xTemp1 += yObjectPoint * m1 \ * xTemp1 += xObjectPoint * m0 \ \ Or, to switch it around the other way and plug in the \ initial value of (xTemp1, yTemp1, zTemp1) = (0, 0, 0), \ we get: \ \ * xTemp1 = m0 * xObjectPoint + m1 * yObjectPoint \ + m2 * zObjectPoint \ \ * yTemp1 = m3 * xObjectPoint + m4 * yObjectPoint \ + m5 * zObjectPoint \ \ * zTemp1 = m6 * xObjectPoint + m7 * yObjectPoint \ + m8 * zObjectPoint \ \ which gives us the matrix multiplication that we want \ to calculate LDA matrixNumber \ Set P = matrixNumber + 8 CLC \ ADC #8 \ In the following loop, P counts down from m8 to m0, STA P \ which we implement as an index that counts down from \ matrixNumber + 8 to matrixNumber, so that it iterates \ through the correct matrix table \ \ This works because matrixNumber contains 0 for matrix \ 1, 9 for matrix 2 and so on, and each matrix table, \ such as matrix1Lo, contains 9 entries LDA #2 \ Set VV = 2, to act as our outer loop counter that STA VV \ iterates through zTemp1, yTemp1 and xTemp1 .objp2 LDX #2 \ Set X = 2, to act as our inner loop counter that \ iterates through zObjectPoint, yObjectPoint and \ xObjectPoint (i.e. RR, QQ and PP) .objp3 LDY P \ Set Y = P, which is the number of the matrix element \ to multiply next LDA PP,X \ Set A = PP, QQ or RR (when X = 0, 1 or 2), which is \ the correct zObjectPoint, yObjectPoint or xObjectPoint \ to multiply next STA I \ Store A in I (this doesn't appear to be used) AND #%00001111 \ Set V = bits 0-3 of A, which removes the scale factor STA V \ in the case of zObjectPoint (the other points always \ fit into bits 0 to 3) BEQ objp5 \ If V = 0, jump to objp5 to move on to the next loop, \ as we already know the result of V * (S R) will be \ zero LDA matrix1Hi,Y \ Set S = P-th entry of matrix1Hi STA S LDA matrix1Lo,Y \ Set R = P-th entry of matrix1Lo STA R \ \ so now (S R) is the 16-bit matrix element that we want \ to multiply AND #1 \ If bit 0 of R is clear, then the matrix entry is BEQ objp4 \ positive, skip the following three instructions LDA V \ Bit 0 of R is set, which means this matrix entry is EOR #%10000000 \ negative, so set bit 7 of V to make it negative, to STA V \ give the result of the multiplication below the \ correct sign .objp4 STX Q \ Store the loop counter in X, so we can retrieve it \ after the call to Multiply4x16 JSR Multiply4x16 \ Call Multiply4x16 to calculate: \ \ (G W) = V * (S R) >> 8 if bit 7 of K = 0 \ \ (G W) = V * (S R) >> 7 if bit 7 of K = 1 \ \ K is only set to 1 if the scale factor is 9, in which \ case we effectively do one of the factors of 2 at this \ point (and the other 8 in part 2) LDX Q \ Restore the value of X LDY VV \ Fetch the outer loop counter from VV, which points to \ the relevant xTemp1, yTemp1 or zTemp1 coordinate LDA W \ Add (G W) to the xTemp1 coordinate, starting with the CLC \ low bytes ADC xTemp1Lo,Y STA xTemp1Lo,Y LDA G \ And then the high bytes, so we have the following (if ADC xTemp1Hi,Y \ we are working with xTemp1 and m0, for example): STA xTemp1Hi,Y \ \ xTemp1 += (G W) \ += V * (S R) \ += xObjectPoint * m0 \ \ which is the result we want for this element of the \ matrix multiplication .objp5 LDA P \ If P = matrixNumber then we have done all nine CMP matrixNumber \ calculations, so jump down to objp6 to apply the BEQ objp6 \ correct scale factor DEC P \ Otherwise we have more calculations to do, so \ decrement P to point to the next matrix entry DEX \ Decrement the inner loop counter to work our way \ through zObjectPoint, yObjectPoint and xObjectPoint BPL objp3 \ Loop back until we have worked through all three \ anchor-relative points DEC VV \ Decrement the outer loop counter to work our way \ through zTemp1, yTemp1 and xTemp1 JMP objp2 \ Jump back to objp2 to do the next calculation
Name: SetObjPointCoords (Part 2 of 2) [Show more] Type: Subroutine Category: 3D geometry Summary: Apply the correct scale factor to the matrix multiplication Deep dive: 3D objects Rotating and translating points in 3D space
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.objp6 \ We now want to apply the scale factor that we \ extracted into UU above. The scale factor is given as \ a power of 2, so a scale factor of n means we scale \ the result by 2^n, which we can do by shifting left \ by n places (where n is in the range 0 to 8) \ \ Note that n can also be 9, in which case we already \ doubled the result in part 1, so by this point we only \ need to shift a maximum of 8 places \ \ Note also that we are scaling up 16-bit numbers, so \ in order to capture these scaled-up values, we add a \ third byte to the significant end, giving us a 24-bit \ number to shift, and we take the top two bytes as our \ final result, discarding the least significant byte \ \ This means the result we end up with is divided by \ another 256 but fits into two bytes LDX #2 \ Set X = 2 to act as a loop counter, to iterate through \ 2, 1 and 0, which we use to work through zTemp1, \ yTemp1 and xTemp1, scaling each one separately \ \ The comments below refer to xTemp1, for simplicity .objp7 LDY UU \ Set Y = UU = %0000ZZZZ, which is our scale factor BEQ objp10 \ If UU = 0, then the scale factor is 2^0, or 1, so \ there is no scaling to do, so jump to objp10 to drop \ the least significant byte without any shifting CPY #8 \ If Y >= 8 then we can skip doing 8 shifts as the BCS objp9 \ result is already correct. To see this, consider the \ process below, which creates (0 xTemp1Hi xTemp1Lo) and \ shifts it left by the number of places, which would be \ this if Y = 8: \ \ (0 xTemp1Hi xTemp1Lo) << 8 = (xTemp1Hi xTemp1Lo 0) \ \ from which we would then drop the least significant \ byte to give (xTemp1Hi xTemp1Lo)... which is what we \ already have, so when Y >= 8 we can simply jump to \ objp9 to move on to the next axis \ \ (As noted above, if Y = 9 then we already doubled the \ result in part 1, so the above approach works for both \ Y = 8 and 9) LDA xTemp1Lo,X \ Set P to the low byte of xTemp1 STA P LDA #0 \ Set R = 0 to act as the new highest byte in our 24-bit STA R \ number LDA xTemp1Hi,X \ Set A to the high byte of xTemp1, so we now have the \ following 24-bit number: \ \ (R A P) = (0 xTemp1Hi xTemp1Lo) BPL objp8 \ If the high byte of xTemp1 is positive, skip the \ following instruction DEC R \ xTemp1Hi is negative, so decrement R to &FF so (R A P) \ is a correctly signed 24-bit negative number \ We now shift (R A P) left by Y places (where Y is in \ the range 1 to 7) .objp8 ASL P \ Set (R A P) = (R A P) << 1 ROL A ROL R DEY \ Decrement the shift counter in Y BNE objp8 \ Loop back to objp8 until we have shifted (R A P) left \ by Y places STA xTemp1Lo,X \ Set xTemp1 = (R A) LDA R \ STA xTemp1Hi,X \ so we drop the least significant byte, as discussed \ above .objp9 DEX \ Decrement the counter in X so we work through zTemp1, \ yTemp1 and xTemp1 BPL objp7 \ Loop back to objp7 until we have processed all three \ axes BMI objp12 \ Jump to objp12 to finish off (this BMI is effectively \ a JMP as X is now negative) .objp10 \ If we get here then the scale factor is 0, so we can \ simply drop the least significant byte without any \ shifting, as discussed above LDA #0 \ Set (R A) = (0 xTemp1Hi) STA R LDA xTemp1Hi,X BPL objp11 \ If xTemp1Hi is positive, skip the next instruction DEC R \ xTemp1Hi is negative, so decrement R to &FF so (R A) \ is a 16-bit negative number .objp11 STA xTemp1Lo,X \ Set xTemp1 = (R A) LDA R STA xTemp1Hi,X JMP objp9 \ Jump back to objp9 to move on to the next axis (i.e. \ yTemp1 or zTemp1) .objp12 \ Our vector in (xTemp1, yTemp1, zTemp1) is now scaled \ properly, so it's time for the addition: \ \ [ xPoint ] [ xAnchor ] [ xTemp1 ] \ [ yPoint ] = [ yAnchor ] + [ yTemp1 ] \ [ zPoint ] [ zAnchor ] [ zTemp1 ] LDX GG \ Set X to the point ID whose coordinates we want to \ calculate, so the original point is updated with the \ final result LDY objectAnchorPoint \ Set Y to the point ID of the object's anchor point \ Fall through into AddTempToPoint to add the anchor \ point to the (xTemp1, yTemp1, zTemp1) vector and store \ the result in (xPoint, yPoint, zPoint)
Name: AddTempToPoint (Part 1 of 2) [Show more] Type: Subroutine Category: 3D geometry Summary: Add the xTemp1 vector to a point, store the result in another point, and set the result to hidden if it overflows
Context: See this subroutine on its own page References: This subroutine is called as follows: * FireGuns calls AddTempToPoint * ProcessRunwayLine (Part 2 of 5) calls AddTempToPoint * ProcessRunwayLine (Part 4 of 5) calls AddTempToPoint * ProcessRunwayLine (Part 5 of 5) calls AddTempToPoint

Set point X to the sum of the xTemp1 vector and point Y. In other words, add the following: (xTemp1 yTemp1 zTemp1) + point Y's coordinate in (xPoint, yPoint, zPoint) and store the results in point X's coordinate in (xPoint, yPoint, zPoint). We also set showLine to "hidden" if the addition overflows in any of the axes.
Arguments: X The ID of the point in which to store the result Y The ID of the point to add to the xTemp1 vector
Returns: showLine If the calculation overflows, then the coordinate does not fit within the boundaries of Aviator's 3D world, so bit 6 is set to indicate that the line containing this point should not be shown
.AddTempToPoint LDA xTemp1Lo \ Set point X's x-coordinate to the following: CLC \ ADC xPointLo,Y \ (xTemp1Hi xTemp1Lo) + (xPointHi+Y xPointLo+Y) STA xPointLo,X \ LDA xTemp1Hi \ i.e. we add xTemp1 and point Y's x-coordinate ADC xPointHi,Y STA xPointHi,X PHP \ Store the flags for the x-axis addition on the stack, \ so we can check them in part 2 CLC \ Set point X's y-coordinate to the following: LDA yTemp1Lo \ ADC yPointLo,Y \ (yTemp1Hi yTemp1Lo) + (yPointHi+Y yPointLo+Y) STA yPointLo,X \ LDA yTemp1Hi \ i.e. we add yTemp1 and point Y's y-coordinate ADC yPointHi,Y STA yPointHi,X PHP \ Store the flags for the y-axis addition on the stack, \ so we can check them in part 2 CLC \ Set point X's z-coordinate to the following: LDA zTemp1Lo \ ADC zPointLo,Y \ (zTemp1Hi zTemp1Lo) + (zPointHi+Y zPointLo+Y) STA zPointLo,X \ LDA zTemp1Hi \ i.e. we add zTemp1 and point Y's z-coordinate ADC zPointHi,Y STA zPointHi,X JMP addv1 \ Jump to part 2 to set showLine according to the \ overflow flag from each of the results (so if any axes \ overflow, we hide the line containing the point) NOP \ This instruction is not used; perhaps at one point the \ JMP above was a JSR instruction, and this was an RTS?
Name: Add16x16Bit0 [Show more] Type: Subroutine Category: Maths Summary: Add two 16-bit numbers that have their signs in bit 0
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetMatrices calls Add16x16Bit0

This routine adds two 16-bit numbers, both of which have their signs in bit 0 of the low byte. It calculates: (S R) = (H G) + (J I) The result in (S R) has the sign in bit 0 of the low byte.
Arguments: (H G) A signed 16-bit number, with the sign in bit 0 of G (J I) A signed 16-bit number, with the sign in bit 0 of I
Returns: (S R) The result of the multiplication, with the sign in bit 0 of R K The sign of the result (in bit 0)
.Add16x16Bit0 LDA G \ Set K to the sign of G (from bit 0) AND #1 STA K EOR I \ If G and I have different sign bits, then this will AND #1 \ set A to 1, in which case jump to abit2 BNE abit2 \ If we get here then the two arguments (H G) and (J I) \ have the same sign, which is stored in K LDA G \ Set (S R) = (H G) + (J I) CLC \ ADC I \ starting with the low bytes, setting bit 0 of the AND #%11111110 \ result to the sign in K (as the result of adding two ORA K \ numbers with sign K will have sign K) STA R LDA H \ Then adding the high bytes ADC J STA S BCC abit1 \ If the addition didn't overflow, jump to abit1 to \ return from the subroutine, as the result is correct LDA #%11111111 \ The addition overflowed, so we return the maximum STA S \ value in (R S), which has every bit set except for the LDA #%11111110 \ sign bit, which is set to K ORA K STA R .abit1 RTS \ Return from the subroutine .abit2 \ If we get here then the two arguments (H G) and (J I) \ have different signs, with the sign of (H G) stored in \ K LDA G \ Set (S R) = (H G) - (J I) SEC \ SBC I \ starting with the low bytes STA R LDA H \ And then the high bytes SBC J STA S BCC abit3 \ If the subtraction underflowed, jump to abit3 \ Otherwise the result is correct apart from the sign, \ and the sign of the result should be that of (H G), as \ (H G) is the larger value (i.e. it has the highest \ magnitude), as otherwise the subtraction would \ underflow LDA R \ Set the sign bit of (S R) to K, as that contains the AND #%11111110 \ sign of (H G) ORA K STA R RTS \ Return from the subroutine .abit3 \ If we get here then the (H G) - (J I) subtraction \ underflowed, so (J I) is the larger value (i.e. it has \ the highest magnitude) \ \ We therefore need to negate the subtraction result to \ get (J I) - (H G), and the sign of the final result \ should be given the sign of (J I), as that's the \ dominant part... and as K contains the sign of (H G) \ and we know (H G) and (J I) have opposite signs, we \ should therefore give the result the opposite sign to \ the sign in K LDA #0 \ Negate the (S R) to get the correct result, SEC \ SBC R \ (S R) = 0 - (S R) AND #%11111110 \ ORA K \ while also setting the sign in bit 0 to the opposite EOR #1 \ of K STA R LDA #0 SBC S STA S RTS \ Return from the subroutine
Name: SetMatrices [Show more] Type: Subroutine Category: 3D geometry Summary: Set up matrices 1 to 4 Deep dive: Rotation matrices
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateFlightModel (Part 4 of 4) calls SetMatrices

This routine populates the four matrix tables with 16-bit values. Matrix 1 is the most complex matrix, and contains the rotation matrix for rotating a point around the origin by the plane's roll, then pitch, then yaw. It is constructed from the coordinates produced by the ProjectAxisAngle routine as follows: [ (mx2 * my2 * mz2) -mx1 * mz2 (mx2 * my1 * mz2) ] [ + (my1 * mz1) - (my2 * mz1) ] [ ] [ -(mx2 * my2 * mz1) mx1 * mz1 -(mx2 * my1 * mz1) ] [ + (my1 * mz2) - (my2 * mz2) ] [ ] [ mx1 * my2 mx2 mx1 * my1 ] Matrix 2 is the transpose of matrix 1: [ m0 m3 m6 ] [ m1 m4 m7 ] [ m2 m5 m8 ] and therefore represents the inverse transformation of matrix 1. Matrix 3 is as follows: [ mz1 -mz2 0 ] [ mz2 mz1 0 ] [ 0 0 1 ] This is a rotation matrix that rotates through the plane's roll angle. The zeroes and 1 are hardcoded in memory, with the latter being stored as &FFFE = %11111111 11111110, with the sign in bit 0. Matrix 4 contains an interim step from the calculation of matrix 1, and contains the rotation matrix for the roll-and-pitch rotation (i.e. without any yaw). It looks like this: [ mz1 -mx1 * mz2 mx2 * mz2 ] [ mz2 mx1 * mz1 -mx2 * mz1 ] [ 0 mx2 mx1 ] The m6 entry is not set by any code, but it contains a zero in the source code and stays that way.
Arguments: matrixNumber The routine is only ever called with matrixNumber = 0, so it only ever writes to matrices 1 to 4 - presumably the need for this argument was dropped at some point matrixAxis The routine is only ever called with matrixAxis = 0, so it only ever reads from mx1 and mx2 - presumably the need for this argument was dropped at some point
.SetMatrices LDX matrixAxis \ Set X to the matrix axis (this routine is only ever \ called with a matrixAxis of 0, so this sets X = 0 \ We first set up a load of temporary variables that we \ can use to populate the various matrices, storing them \ in the matrix 1 variables (we will populate matrix 1 \ properly after we have populated matrices 2, 3 and 4) LDA mx1Lo,X \ Set (J I) = -(mx1Hi mx1Lo) EOR #1 \ = -mx1 STA I \ LDA mx1Hi,X \ by flipping the sign in bit 0 STA J LDX #5 \ Set m1 = (J I) * mz2 / 256 LDY #1 \ = -mx1 * mz2 / 256 JSR SetMatrixEntry \ \ Also set (S R) = mz2 LDX #1 \ Set m3 = (S R) * my1 / 256 LDY #3 \ = mz2 * my1 / 256 JSR SetMatrixEntry2 LDX #3 \ Set m2 = (S R) * mx2 / 256 LDY #2 \ = mz2 * mx2 / 256 JSR SetMatrixEntry2 LDX #4 \ Set m0 = (S R) * my2 / 256 LDY #0 \ = mz2 * my2 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = my2 LDX #0 \ Set m6 = (J I) * mx1 / 256 LDY #6 \ = my2 * mx1 / 256 JSR SetMatrixEntry \ \ Also set (S R) = mx1 LDX #1 \ Set m8 = (S R) * my1 / 256 LDY #8 \ = mx1 * my1 / 256 JSR SetMatrixEntry2 LDX #2 \ Set m4 = (S R) * mz1 / 256 LDY #4 \ = mx1 * mz1 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = mz1 LDX #3 \ Set m5 = (J I) * -mx2 / 256 LDY #5 \ = mz1 * -mx2 / 256 JSR SetMatrixEntry3 \ \ Also set (H G) = m5 \ So we now have the following set of values (stripping \ out the division by 256 and reordering the \ multiplications for clarity): \ \ m0 = my2 * mz2 \ m1 = -mx1 * mz2 \ m2 = mx2 * mz2 \ m3 = my1 * mz2 \ m4 = mx1 * mz1 \ m5 = -mx2 * mz1 \ m6 = mx1 * my2 \ m8 = mx1 * my1 \ \ Note that we didn't set m7 in the above, and also note \ that in the following, we update some of these values, \ so, for example, m0 and m2 change part way through the \ calculation process (which I have pointed out) \ \ We now work our way through the matrices as follows: \ \ * Populate matrix 3 (which is hardcoded apart from \ the 2x2 matrix in the top-left corner) \ \ * Populate matrix 4 (except m6, which is hardcoded) \ \ * Populate matrix 1 (by overwriting some of the \ m0-m8 variables we just set, but keeping others) \ \ * Set matrix 2 to the transpose of matrix 1 LDX matrixAxis \ Set X = 0 LDY matrixNumber \ Set Y = 0 LDA mz1Lo,X \ Set m0 in matrix 3 = mz1 STA matrix3Lo,Y \ m4 in matrix 3 = mz1 STA matrix3Lo+4,Y \ m0 in matrix 4 = mz1 STA matrix4Lo,Y LDA mz1Hi,X STA matrix3Hi,Y STA matrix3Hi+4,Y STA matrix4Hi,Y LDA mz2Lo,X \ Set m3 in matrix 3 = mz2 STA matrix3Lo+3,Y \ m3 in matrix 4 = mz2 STA matrix4Lo+3,Y \ m1 in matrix 3 = -mz2 EOR #1 STA matrix3Lo+1,Y LDA mz2Hi,X STA matrix3Hi+1,Y STA matrix3Hi+3,Y STA matrix4Hi+3,Y LDA mx1Lo,X \ Set m8 in matrix 4 = mx1 STA matrix4Lo+8,Y LDA mx1Hi,X STA matrix4Hi+8,Y LDA mx2Lo,X \ Set m7 in matrix 4 = mx2 STA matrix4Lo+7,Y LDA mx2Hi,X STA matrix4Hi+7,Y \ The following loop does the following: \ \ * Set m1 in matrix 4 = m1 in matrix 1 = -mx1 * mz2 \ * Set m2 in matrix 4 = m2 in matrix 1 = mx2 * mz2 \ * Set m4 in matrix 4 = m4 in matrix 1 = mx1 * mz1 \ * Set m5 in matrix 4 = m5 in matrix 1 = -mx2 * mz1 LDY #5 \ Set a loop counter in Y to count through 5, 4, 2, 1 .smat1 CPY #3 \ If Y = 3 then jump to smat2 to skip this iteration BEQ smat2 LDA matrix1Lo,Y \ Set mY in matrix 4 = mY in matrix 1 STA matrix4Lo,Y LDA matrix1Hi,Y STA matrix4Hi,Y .smat2 DEY \ Decrement the loop counter BNE smat1 \ Loop back until we have copied all four entries from \ matrix 1 to matrix 4 LDA G \ Set (J I) = (H G) STA I \ = m5 LDA H \ = -mx2 * mz1 STA J LDX matrixAxis \ Set X = 0 LDA my2Lo,X \ Set (S R) = my2 STA R LDA my2Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = -(mx2 * mz1 * my2) >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+3,Y \ Set (J I) = m3 STA I \ = my1 * mz2 LDA matrix1Hi+3,Y STA J JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = -(mx2 * mz1 * my2) + (my1 * mz2) LDA R \ Set m3 = (S R) STA matrix1Lo+3,Y LDA S STA matrix1Hi+3,Y LDA matrix1Lo+5,Y \ Set (J I) = m5 STA I \ = -mx2 * mz1 LDA matrix1Hi+5,Y STA J LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (S R) = my1 STA R LDA my1Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = -(mx2 * mz1 * my1) >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo,Y \ Set (J I) = -m0 EOR #1 \ = -my2 * mz2 STA I LDA matrix1Hi,Y STA J JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = -(mx2 * mz1 * my1) - (my2 * mz2) LDA R \ Set m5 = (S R) STA matrix1Lo+5,Y LDA S STA matrix1Hi+5,Y LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (S R) = my1 STA R LDA my1Hi,X STA S LDX #2 \ Set m0 = (S R) * mz1 / 256 LDY #0 \ = my1 * mz1 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = mz1 LDX matrixAxis \ Set X = 0 LDA my2Lo,X \ Set (S R) = -my2 EOR #1 STA R LDA my2Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = mz1 * -my2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+2,Y \ Set (J I) = m2 STA I \ = mx2 * mz2 STA xTemp1Lo \ LDA matrix1Hi+2,Y \ Also set xTemp1 = m2 STA J \ = mx2 * mz2 STA xTemp1Hi LDA G \ Set m2 = (H G) STA matrix1Lo+2,Y \ = -my2 * mz1 LDA H STA matrix1Hi+2,Y LDA R \ Set (S R) = -(S R) EOR #1 \ = my2 STA R JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = mx2 * mz2 * my2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo,Y \ Set (J I) = m0 STA I \ = my1 * mz1 LDA matrix1Hi,Y \ STA J \ (note that we updated m0 above, so this is different \ to the first value we gave m0) JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = (mx2 * mz2 * my2) + (my1 * mz1) LDA R \ Set m0 = (S R) STA matrix1Lo,Y LDA S STA matrix1Hi,Y LDA xTemp1Lo \ Set (S R) = xTemp1 STA R \ = mx2 * mz2 LDA xTemp1Hi STA S LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (J I) = my1 STA I LDA my1Hi,X STA J JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = my1 * mx2 * mz2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+2,Y \ Set (J I) = m2 STA I \ = -my2 * mz1 LDA matrix1Hi+2,Y \ STA J \ (note that we updated m2 above, so this is different \ to the first value we gave m2) JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = (my1 * mx2 * mz2) - (my2 * mz1) LDA R \ Set m2 = (S R) STA matrix1Lo+2,Y \ = (my1 * mx2 * mz2) - (my2 * mz1) LDA S STA matrix1Hi+2,Y LDX matrixAxis \ Set X = 0 LDA mx2Lo,X \ Set m7 = mx2 STA matrix1Lo+7,Y LDA mx2Hi,X STA matrix1Hi+7,X \ We now set matrix 2 to the transpose of matrix 1, so \ if matrix 1 looks like this: \ \ [ m0 m1 m2 ] \ [ m3 m4 m5 ] \ [ m6 m7 m8 ] \ \ then matrix 2 looks like this: \ \ [ m0 m3 m6 ] \ [ m1 m4 m7 ] \ [ m2 m5 m8 ] \ \ This sets matrix 2 to the inverse of matrix 1 LDA #2 \ We transpose the matrix by looping through each column STA T \ in matrix 1 and copy it into the corresponding column \ in matrix 2, so set a loop counter in T for each row LDY matrixNumber \ Set Y = 0 to loop through the start of each row in \ matrix 1, so it iterates through 0, 3 and 6 LDX matrixNumber \ Set X = 0 to loop through the top of each column in \ matrix 2, so it iterates through 0, 1 and 2 .smat3 \ The following blocks iterate through the nine copies \ (three in each loop) as follows: LDA matrix1Lo,Y \ Set m0 in matrix 2 = m0 in matrix 1 STA matrix2Lo,X \ m1 = m3 LDA matrix1Hi,Y \ m2 = m6 STA matrix2Hi,X LDA matrix1Lo+1,Y \ Set m3 in matrix 2 = m1 in matrix 1 STA matrix2Lo+3,X \ m4 = m4 LDA matrix1Hi+1,Y \ m5 = m7 STA matrix2Hi+3,X LDA matrix1Lo+2,Y \ Set m6 in matrix 2 = m2 in matrix 1 STA matrix2Lo+6,X \ m7 = m5 LDA matrix1Hi+2,Y \ m8 = m8 STA matrix2Hi+6,X INY \ Set Y = Y + 3 INY \ INY \ to point to the start of the next row in matrix 1 INX \ Set X = X + 1 \ \ to point to the top of the next column in matrix 2 DEC T \ Decrement the loop counter BPL smat3 \ Loop back until we have copied all nine entries from \ matrix 1 to matrix 2 RTS \ Return from the subroutine
Name: SetMatrixEntry [Show more] Type: Subroutine Category: 3D geometry Summary: Calculate a matrix entry
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetMatrices calls SetMatrixEntry * SetMatrices calls via SetMatrixEntry2 * SetMatrices calls via SetMatrixEntry3

This routine reads one of the projected coordinate values that we calculated in ProjectAxisAngle (in mx1 to mz2), performs a multiplication, and writes the result to the specified entry in matrix 1. If mRead is the matrix entry in X (from mx1 to mz2) and mWrite is the matrix entry in Y (from m0 to m8), then it calculates the following: mWrite = (J I) * mRead >> 16
Arguments: X The matrix entry to read (mRead): * 0 = mx1 * 1 = my1 * 2 = mz1 * 3 = mx2 * 4 = my2 * 5 = mz2 Y The matrix entry to write (mWrite) in matrix 1: * 0 = m0 * 1 = m1 * 2 = m2 ... * 7 = m7 * 8 = m8 matrixNumber The routine is only ever called with matrixNumber = 0, so it only ever writes to matrix 1, but calling the routine with a different value would allow us to write to a different matrix matrixAxis The routine is only ever called with matrixAxis = 0, which has no effect on the calculation, but calling the routine with a non-zero value of matrixAxis would allow different mRead values to be used, i.e. matrixAxis+X
Returns: mWrite Set to (J I) * mRead >> 16 (S R) The value of mRead (H G) The value written to mWrite
Other entry points: SetMatrixEntry2 Set mWrite = (S R) * mRead >> 16 and (J I) = mRead SetMatrixEntry3 Set mWrite = (J I) * -mRead >> 16 and (S R) = -mRead
.SetMatrixEntry TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set R = the low byte from the matrix entry to read STA R .smen1 LDA mx1Hi,X \ Set (S R) = the matrix entry to read STA S JMP smen2 \ Jump down to smen2 to do the calculation .SetMatrixEntry3 TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set R = the low byte from the matrix entry to read, EOR #1 \ with the sign in bit 0 flipped STA R JMP smen1 \ Jump up to smen1 to set the high byte .SetMatrixEntry2 TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set (J I) = the matrix entry to read STA I LDA mx1Hi,X STA J .smen2 TYA \ Set N = Y + matrixNumber CLC \ ADC matrixNumber \ This sets N = Y when matrixNumber = 0, which is the STA N \ only value that this routine is called with JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 LDY N \ Fetch the index of the matrix entry to write that we \ stored in N above LDA G \ Store (H G) in the matrix entry to write STA matrix1Lo,Y LDA H STA matrix1Hi,Y RTS \ Return from the subroutine
Name: Multiply16x16 [Show more] Type: Subroutine Category: Maths Summary: Multiply two unsigned 16-bit numbers Deep dive: Times tables and nibble arithmetic
Context: See this subroutine on its own page References: This subroutine is called as follows: * Multiply16x16Bit0 calls Multiply16x16 * Multiply16x16Mix calls Multiply16x16

This routine multiplies two unsigned 16-bit numbers: (H A) = (J I) * (S R) >> 16 The fractional part of the result is in W, so this is the full result, though in practice W is ignored: (H A W) = (J I) * (S R) >> 8 The fractional part is rounded up or down, to the nearest integer, by adding 128 at mult1. This part of the code is modified by the ApplyAerodynamics routine to toggle this rounding behaviour. If the C flag is set on return, then the result in H needs to be incremented, so technically the result is: (H+C A) = (J I) * (S R) >> 16 The multiplication is done using the following algorithm: (J I) * (S R) = (J << 8 + I) * (S << 8 + R) = (J << 8 * S << 8) + (J << 8 * R) + (I * S << 8) + (I * R) = (J * S) << 16 + (J * R) << 8 + (I * S) << 8 + (I * R) This returns a fractional result with the lowest byte containing the fraction. The routine doesn't care about the very lowest byte, so it actually calculates the following, effectively shifting the above to the right by 8 places and dropping the (I * R) part: (J I) * (S R) = (J * S) << 8 + (J * R) + (I * S) before adding 128 to round the result up or down.
Arguments: (S R) An unsigned 16-bit number (J I) An unsigned 16-bit number
Returns: (H A) The result of the multiplication C flag If set, H should be incremented by the caller to get the correct result
.Multiply16x16 LDX J \ Set (A V) = X * Y LDY R \ = J * R JSR Multiply8x8 STA G \ Set (G A) = (A V) LDA V \ = J * R CLC \ Clear the C flag for the following addition .mult1 ADC #128 \ Set (G W) = (G A) + 128 STA W \ \ starting with the low bytes \ \ The ADC #128 instruction gets modified by the \ ApplyAerodynamics routine as follows: \ \ * ADC #0 disables the rounding \ \ * ADC #128 enables the rounding BCC mult2 \ And then the high bytes, so we now have: INC G \ \ (G W) = (J * R) + 128 .mult2 LDX S \ Set (A V) = X * Y LDY J \ = S * J JSR Multiply8x8 STA H \ Set (H A) = (A V) LDA V \ = S * J \ We now do the following addition: \ \ (H G W) = (H A) << 8 + (G W) \ = (H A 0) + (G W) \ \ We don't need to do the lowest byte, as W + 0 is just \ W again, so we can just do the following: \ \ (H G) = (H A) + G \ \ and then keep W as the lowest significant byte of the \ result CLC \ Set (H G) = (H A) + G ADC G \ STA G \ starting with the low bytes BCC mult3 \ And then the high bytes, so we now have: INC H \ \ (H G) = (H A) + G \ \ which is the same as: \ \ (H G W) = (H A) << 8 + (G W) \ = (S * J) << 8 + (J * R) + 128 .mult3 LDX S \ Set (A V) = X * Y LDY I \ = S * I JSR Multiply8x8 STA T \ Set (T A) = (A V) LDA V \ = S * I \ We now do the following addition: \ \ (H A W) = (T A) + (H G W) \ \ though we don't actually do the highest byte, but \ instead return the C flag depending on whether the \ addition of the middle bytes overflowed (in which case \ the highest byte in H needs incrementing, a task we \ leave to the caller) CLC \ Set (A W) = (T A) + (G W) ADC W \ STA W \ starting with the low bytes LDA T \ And then the high bytes, so we now have: ADC G \ \ (H A W) = (T A) + (H G W) \ = (S * I) + (S * J) << 8 + (J * R) + 128 \ \ which is the result we need. The C flag is set if the \ last addition overflowed, in which case H needs to be \ incremented by the caller to get the final result RTS \ Return from the subroutine
Name: Multiply16x16Bit0 [Show more] Type: Subroutine Category: Maths Summary: Multiply two 16-bit numbers that have their signs in bit 0
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetMatrices calls Multiply16x16Bit0 * SetMatrixEntry calls Multiply16x16Bit0

This routine multiplies two 16-bit numbers, both of which have their signs in bit 0 of the low byte. It calculates: (H G) = (J I) * (S R) >> 16 The result in (H G) has the sign in bit 0 of the low byte.
Arguments: (J I) A signed 16-bit number, with the sign in bit 0 of I (S R) A signed 16-bit number, with the sign in bit 0 of R
Returns: (H G) The result of the multiplication, with the sign in bit 0 of G K The sign of the result (in bit 0)
.Multiply16x16Bit0 LDA R \ Extract the sign of (J I) * (S R) from bit 0 of I and EOR I \ bit 0 of R, and store the result in K AND #%00000001 STA K JSR Multiply16x16 \ Calculate (H A) = (S R) * (J I) >> 16 \ \ and set the C flag if we need to increment H AND #%11111110 \ Set bit 0 of A to the sign we stored in K above, so ORA K \ (H A) has the correct sign of the multiplication STA G \ Set (H G) = (H A) \ = (S R) * (J I) >> 16 BCC mbit1 \ Increment the top byte in H if required INC H .mbit1 RTS \ Return from the subroutine
Name: SetPointCoords [Show more] Type: Subroutine Category: 3D geometry Summary: Calculate the coordinates for a point Deep dive: Lines and points Rotating and translating points in 3D space
Arguments: GG The ID of the point to process matrixNumber The matrix to use in the calculation: * 0 = matrix 1 * 9 = matrix 2 * 18 = matrix 3 * 27 = matrix 4 xPointHi etc. The point's coordinates before rotation
Returns: xPointHi etc. The point's coordinates after rotation xTemp1Hi etc. The point's coordinates after rotation
.SetPointCoords LDX GG \ Set X to the point ID whose coordinates we want to \ calculate LDA xPointLo,X \ Set (SS PP) to the point's x-coordinate STA PP LDA xPointHi,X STA SS LDA yPointLo,X \ Set (TT QQ) to the point's y-coordinate STA QQ LDA yPointHi,X STA TT LDA zPointLo,X \ Set (UU RR) to the point's z-coordinate STA RR LDA zPointHi,X STA UU LDX #5 \ We now zero (xTemp1, yTemp1, zTemp1), which is stored \ in six bytes to give us three 16-bit coordinate values \ (from xTemp1Lo to zTemp1Hi), so set a counter in X to \ count the bytes LDA #0 \ Set A = 0 to use as our zero .pcrd1 STA xTemp1Lo,X \ Zero the X-th byte of the six-byte coordinate block \ between xTemp1Lo and zTemp1Hi DEX \ Decrement the loop counter BPL pcrd1 \ Loop back until we have zeroed all six bytes \ We now do the matrix multiplication: \ \ [ xTemp1 ] [ m0 m1 m2 ] [ xPoint ] \ [ yTemp1 ] = [ m3 m4 m5 ] x [ yPoint ] \ [ zTemp1 ] [ m6 m7 m8 ] [ zPoint ] \ \ We do this in three loops of three, using an outer and \ inner loop. We set two loop counters, VV and X, for \ the outer and inner loops respectively. They both \ iterate through 2, 1 and 0, with VV iterating through \ zTemp1, yTemp1 and xTemp1, and X iterating through \ zPoint, yPoint and xPoint \ \ We also iterate a counter in P for the matrix entries, \ which counts down from m8 to m0, decreasing by 1 on \ each iteration \ \ All the iterators count backwards, so the calculations \ in order are: \ \ * zTemp1 += zPoint * m8 \ * zTemp1 += yPoint * m7 \ * zTemp1 += xPoint * m6 \ \ * yTemp1 += zPoint * m5 \ * yTemp1 += yPoint * m4 \ * yTemp1 += xPoint * m3 \ \ * xTemp1 += zPoint * m2 \ * xTemp1 += yPoint * m1 \ * xTemp1 += xPoint * m0 \ \ Or, to switch it around the other way and plug in the \ initial value of (xTemp1, yTemp1, zTemp1) = (0, 0, 0), \ we get: \ \ * xTemp1 = m0 * xPoint + m1 * yPoint + m2 * zPoint \ \ * yTemp1 = m3 * xPoint + m4 * yPoint + m5 * zPoint \ \ * zTemp1 = m6 * xPoint + m7 * yPoint + m8 * zPoint \ \ which gives us the matrix multiplication that we want \ to calculate LDA matrixNumber \ Set P = matrixNumber + 8 CLC \ ADC #8 \ In the following loop, P counts down from m8 to m0, STA P \ which we implement as an index that counts down from \ matrixNumber + 8 to matrixNumber, so that it iterates \ through the correct matrix table \ \ This works because matrixNumber contains 0 for matrix \ 1, 9 for matrix 2 and so on, and each matrix table, \ such as matrix1Lo, contains 9 entries LDA #2 \ Set VV = 2, to act as our outer loop counter that STA VV \ iterates through zTemp1, yTemp1 and xTemp1 .pcrd2 LDX #2 \ Set X = 2, to act as our inner loop counter that \ iterates through zPoint, yPoint and xPoint .pcrd3 LDY P \ Set Y = P, which is the number of the matrix element \ to multiply next LDA matrix1Hi,Y \ Set S = P-th entry of matrix1Hi STA S BNE pcrd4 LDA matrix1Lo,Y \ If the P-th entry of matrix1Lo is < 5, jump to pcrd5 CMP #5 \ to move on to the next loop BCC pcrd5 .pcrd4 LDA matrix1Lo,Y \ Set R = P-th entry of matrix1Lo STA R \ \ so now (S R) is the 16-bit matrix element that we want \ to multiply LDA PP,X \ Set I = PP, QQ or RR (when X = 0, 1 or 2), which is STA I \ the correct zPointLo, yPointLo or xPointLo to multiply \ next LDA SS,X \ Set J = SS, TT or UU (when X = 0, 1 or 2), which is STA J \ the correct zPointHi, yPointHi or xPointHi to multiply \ next so now (J I) is the 16-bit point coordinate that \ we want to multiply LDA #0 \ Set K = 0, so Multiply16x16Mix doesn't negate the STA K \ result STX Q \ Store the loop counter in X, so we can retrieve it \ after the call to Multiply16x16Mix JSR Multiply16x16Mix \ Call Multiply16x16Mix to calculate: \ \ (H G) = (J I) * (S R) >> 16 LDX Q \ Restore the value of X LDY VV \ Fetch the outer loop counter from VV, which points to \ the relevant xTemp1, yTemp1 or zTemp1 coordinate LDA G \ Add (H G) to the xTemp1 coordinate, starting with the CLC \ low bytes ADC xTemp1Lo,Y STA xTemp1Lo,Y LDA H \ And then the high bytes, so we have the following (if ADC xTemp1Hi,Y \ we are working with xTemp1 and m0, for example): STA xTemp1Hi,Y \ \ xTemp1 += (H G) \ += (J I) * (S R) \ += xPoint * m0 \ \ which is the result we want for this element of the \ matrix multiplication BVC pcrd5 LDA #%01000000 \ The addition overflowed for this axis, so set bit 6 of STA showLine \ showLine so the line containing this point is marked \ as being hidden .pcrd5 LDY P \ If P = matrixNumber then we have done all nine CPY matrixNumber \ calculations, so jump down to objp6 to update the BEQ pcrd6 \ point's coordinates with the result DEC P \ Otherwise we have more calculations to do, so \ decrement P to point to the next matrix entry DEX \ Decrement the inner loop counter to work our way \ through zPoint, yPoint and xPoint BPL pcrd3 \ Loop back until we have worked through all three \ anchor-relative points DEC VV \ Decrement the outer loop counter to work our way \ through zTemp1, yTemp1 and xTemp1 JMP pcrd2 \ Jump back to objp2 to do the next calculation .pcrd6 LDX GG \ Set X to the point ID whose coordinates we want to \ calculate, so the original point is updated with the \ final result \ Fall through into CopyTempToPoint to copy the result \ from (xTemp1, yTemp1, zTemp1) to (xPoint, yPoint, \ zPoint)
Name: CopyTempToPoint [Show more] Type: Subroutine Category: Utility routines Summary: Set a specified point to (xTemp1, yTemp1, zTemp1)
Context: See this subroutine on its own page References: This subroutine is called as follows: * FireGuns calls CopyTempToPoint

Arguments: X The ID of the point to set to (xTemp1, yTemp1, zTemp1)
.CopyTempToPoint LDA xTemp1Lo \ Set point X's x-coordinate to (xTemp1Hi xTemp1Lo) STA xPointLo,X LDA xTemp1Hi STA xPointHi,X LDA yTemp1Lo \ Set point X's y-coordinate to (yTemp1Hi yTemp1Lo) STA yPointLo,X LDA yTemp1Hi STA yPointHi,X LDA zTemp1Lo \ Set point X's z-coordinate to (zTemp1Hi zTemp1Lo) STA zPointLo,X LDA zTemp1Hi STA zPointHi,X RTS \ Return from the subroutine EQUB &00, &49, &60 \ These bytes appear to be unused, and simply repeat the \ last three bytes from above (i.e. the last two bytes \ of STA zPointHi,X and the RTS instruction)
Name: UpdateIndicator (Part 1 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Update a single indicator on the dashboard
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls UpdateIndicator * UpdateDashboard calls UpdateIndicator * UpdateFlightModel (Part 2 of 4) calls UpdateIndicator

Arguments: X Indicator number: * 0 = Compass * 1 = Airspeed indicator * 2 = Altimeter Small "hour" hand, whole dial = 10,000 feet * 3 = Altimeter Large "minute" hand, whole dial = 1,000 feet * 4 = Vertical speed indicator * 5 = Turn indicator Bottom part of the slip-and-turn indicator * 6 = Slip indicator Top part of the slip-and-turn indicator * 7 = Artificial horizon * 8 or 10 = Joystick position display * 9 = Rudder indicator * 11 = Thrust indicator
.UpdateIndicator STX WW \ Set WW to the value in X, so we can refer to it later \ if we overwrite the value in X CPX #0 \ If X = 0, jump down to uind1 to update indicator 0 BEQ uind1 CPX #2 \ If X < 2 (i.e. X = 1), jump down to uind2 to update BCC uind2 \ indicator 1 BEQ uind4 \ If X = 2, jump down to uind4 to update indicator 2 JMP uind7 \ X > 2, so jump down to uind7 to check for more values \ of X
Name: UpdateIndicator (Part 2 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the compass (indicator 0) Deep dive: Hard-coded division in the dashboard routines
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the compass heading from yRotationHi and reduces it to the range 0 to 73, before passing it to the DrawIndicatorHand to update the on-screen compass.
.uind1 \ If we get here then the indicator number in X is 0 LDA yRotationHi \ Set T = yRotationHi STA T \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication \ We don't need an LDA T instruction as A already \ contains the same value as T LSR A \ Bit 0 of n is 0 LSR A \ Bit 1 of n is 0 CLC \ Bit 2 of n is 1 ADC T ROR A LSR A \ Bit 3 of n is 0 LSR A \ Bit 4 of n is 0 CLC \ Bit 5 of n is 1 ADC T ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ So by now, A is in the range 0 to 73 - here's why: \ \ From the above, n = %00100100 (36), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 36 / 256) << 1 \ = T * 72 / 256 \ \ which takes the compass heading in the range 0 to 255 \ and reduces it to the range 0 to 73 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 3 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the airspeed indicator (indicator 1)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the forward airspeed from (zVelocityPHi zVelocityPLo) and reduces it to match the scale of the indicator, which is represented by a value of 9 (for 50 mph) to 74 (for 400 mph). The airspeed value can be outside these limits, but these examples show the scale factor. It then passes this value to the DrawIndicatorHand to update the on-screen airspeed indicator.
.uind2 \ If we get here then the indicator number in X is 1 LDA zVelocityPHi \ If the high byte of the forward airspeed in zVelocityP BPL uind3 \ is positive, jump down to uind3 to skip the following LDA #0 \ The airspeed is negative, so set A to 0 and jump to JMP DrawIndicatorHand \ DrawIndicatorHand to zero the indicator on-screen, \ returning from the subroutine using a tail call .uind3 LDA zVelocityPLo \ Set A = (zVelocityPHi zVelocityPLo) * 2 / 256 ASL A LDA zVelocityPHi ROL A \ So by now, A matches the range of the airspeed \ indicator - here's why: \ \ The indicatorBase for this indicator is 48 and the \ indicatorMin/Max range shown on the dial is 57 to 122, \ which represents 50 to 400 mph (according to the game \ manual) \ \ So after this scaling, a result in A of 9 represents \ 50 mph (as 48 + 9 = 57), and a result in A of 74 \ represents 400 mph (as 48 + 74 = 122), for example \ \ These values correspond to the following 16-bit values \ of (zVelocityPHi zVelocityPLo): \ \ * If A = 9, then airspeed = (00000100 10100000), \ which is 1184, or 50 mph \ \ * If A = 74, then airspeed = (00100101 00000000), \ which is 9472, or 400 mph \ \ The plane can go faster or slower than in these \ examples, but the dial only shows speeds between 50mph \ and 400 mph, so higher or lower speeds will be clipped JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 4 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the altimeter's small "hour" hand (indicator 2) Deep dive: Hard-coded division in the dashboard routines
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the altitude from (yPlaneHi yPlaneLo) and reduces it to the range 0 to 254, before passing it to the DrawIndicatorHand to update the small hand of the on-screen altimeter. It also sets altitudeMinutes to the low byte of the altitude, reduced to the range 0 to 104, so it can be used in part 5 to update the large hand of the on-screen altimeter.
.uind4 \ If we get here then the indicator number in X is 2 LDA yPlaneLo \ Set (A R) = (yPlaneHi yPlaneLo) STA R LDA yPlaneHi LSR A \ Set (S R) = (A R) / 4 ROR R \ = (yPlaneHi yPlaneLo) / 4 LSR A \ = altitude ROR R \ STA S \ so (S R) is the altitude in feet, as the value stored \ in (yPlaneHi yPlaneLo) is the actual altitude x 4 LDA #0 \ Set T = 0 STA T \ We now calculate A = R * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication LDA R \ Set A = R LSR A \ Bit 0 of n is 0 LSR A \ Bit 1 of n is 0 CLC \ Bit 2 of n is 1 ADC R ROR A LSR A \ Bit 3 of n is 0 CLC \ Bit 4 of n is 1 ADC R ROR A CLC \ Bit 5 of n is 1 ADC R ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %000110100 (52), so we just \ calculated: \ \ A = (R * n / 256) << 1 \ = (R * 52 / 256) << 1 \ = R * 104 / 256 \ \ which is the low byte of the altitude in (S R), \ reduced to a range of 0 to 104 to represent the whole \ dial's range of 0 to 1,000 feet STA altitudeMinutes \ Store the result in altitudeMinutes, so we can draw \ the altimeter's minute hand in indicator 3 \ We now calculate A = S * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication and \ keeping the overspill from the result LDA S \ Set A = S LSR A \ Bit 0 of n is 0 ROR T LSR A \ Bit 1 of n is 0 ROR T CLC \ Bit 2 of n is 1 ADC S ROR A ROR T LSR A \ Bit 3 of n is 0 ROR T CLC \ Bit 4 of n is 1 ADC S ROR A ROR T CLC \ Bit 5 of n is 1 ADC S ROR A ROR T LSR A \ Bit 6 of n is 0 ROR T \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00110100 (52), so we just \ calculated: \ \ A = (S * n / 256) << 1 \ = (S * 52 / 256) << 1 \ = S * 104 / 256 \ \ and T contains the overspill from the result STA U \ Set U = S * 104 / 256 LDA T \ Set (U A) = (U altitudeMinutes) + (0 T) CLC \ ADC altitudeMinutes \ by adding the low bytes BCC uind5 \ And, if the addition overflowed, incrementing the high INC U \ byte in U \ So we have just calculated: \ \ (U A) = (U altitudeMinutes) + (0 T) \ \ and we already know that: \ \ U = S * 104 / 256 \ \ altitudeMinutes = R * 104 / 256 \ \ so plugging these into the above, we get: \ \ (U A) = (U altitudeMinutes) + (0 T) \ = (S*104 R*104) / 256 + (0 T) \ = (S R) * 104 / 256 .uind5 LSR U \ Set (U A) = (U A) / 16 ROR A \ = ((S R) * 104 / 256) / 16 LSR U \ = (S R) * 6.5 / 256 ROR A \ LSR U ROR A LSR U ROR A \ So by now, A is in the range 0 to 254 - here's why: \ \ The maximum altitude that the altimeter can show is \ 10,000 feet (after which it just wraps around), so the \ final result of all these calculations is that the \ altitude in (S R) has been reduced from a range of 0 \ to 10,000 to a range of 0 to 254 in (U A), as: \ \ 10000 * 6.5 / 256 = 254 \ \ and that value is in A alone, as (U A) < 255, so U = 0 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 5 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the altimeter's "minute" hand (indicator 3)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.uind6 \ If we get here then the indicator number in X is 3 LDA altitudeMinutes \ Fetch the value of the altimeter's minute hand that we \ calculated in part 4 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 6 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Logic for checking which indicator to update
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.uind7 \ If we get here then X > 2 CPX #4 \ If X < 4 (i.e. X = 3), jump up to uind6 to update BCC uind6 \ indicator 3 BEQ uind8 \ If X = 4, jump down to uind8 to update indicator 4 JMP uind13 \ X > 4, so jump down to uind13 to check for more values \ of X
Name: UpdateIndicator (Part 7 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the vertical speed indicator (indicator 4) Deep dive: Hard-coded division in the dashboard routines
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the vertical speed from (yVelocityTop yVelocityHi) and reduces it to the range -40 to +40, before passing it to the DrawIndicatorHand routine to update the on-screen vertical speed indicator.
.uind8 \ If we get here then the indicator number in X is 4 LDA yVelocityHi \ Set (A T) = (yVelocityTop yVelocityHi) STA T \ = yVelocity LDA yVelocityTop BPL uind9 \ If the vertical speed is positive, jump down to uind9 LDA #0 \ The vertical speed is negative, so we make it positive SEC \ by calculating: SBC T \ STA T \ (A T) = (0 0) - (A T) \ \ starting with the low bytes LDA #0 \ And then the high bytes SBC yVelocityTop .uind9 \ By this point, (A T) = |yVelocity| LSR A \ Set (A T) = (A T) / 8 ROR T \ = |yVelocity| / 8 LSR A ROR T LSR A ROR T CMP #0 \ If A = 0, so (A T) = (0 T) = T, so jump to uind10 to BEQ uind10 \ skip the following as T contains the correct value of \ |yVelocity| / 8 LDA #255 \ A is non-zero, which means that (A T) > 255, so set STA T \ T = 255 so that T has a maximum value of 255 .uind10 \ At this point, T contains |yVelocity| / 8, capped to a \ maximum value of 255 \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication LDA T \ Set A = T LSR A \ Bit 0 of n is 0 CLC \ Bit 1 of n is 1 ADC T ROR A LSR A \ Bit 2 of n is 0 LSR A \ Bit 3 of n is 0 LSR A \ Bit 4 of n is 0 CLC \ Bit 5 of n is 1 ADC T ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00100010 (34), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 34 / 256) << 1 \ = T * 68 / 256 \ \ which takes |yVelocity| / 8 in the range 0 to 255 \ and reduces it to the range 0 to 68 CMP #40 \ If A < 40, jump to uind11 to skip the following BCC uind11 \ instruction LDA #40 \ Set A = 40, so A has a maximum of 40 and is now in the \ range 0 to 40 .uind11 BIT yVelocityTop \ If the top byte in yVelocityTop is positive (and BPL uind12 \ therefore so is the vertical speed), jump to uind12 to \ skip the following STA T \ Negate the value in A by calculating: LDA #0 \ SEC \ A = 0 - A SBC T \ So by now, A is in the range -40 to +40 \ \ In terms of the original value of yVelocity, this \ means that: \ \ yVelocity / 8 * (68 / 256) = A \ \ so: \ \ yVelocity = A * (256 / 68) * 8 \ = A * 2048 / 68 \ \ If A is 40 then this shows as a vertical speed of \ 4000 feet per minute on the indicator, so if v is the \ vertical speed in feet per minute, A = v / 100, and: \ \ yVelocity = A * 2048 / 68 \ = (v / 100) * 2048 / 68 \ = v * 2048 / 6800 \ = v * 128 / 425 \ \ so yVelocity is stored as 128 / 425 * the vertical \ speed in feet per minute .uind12 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 8 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Logic for checking which indicator to update
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.uind13 \ If we get here then X > 4 CPX #6 \ If X < 6 (i.e. X = 5), jump down to uind14 to update BCC uind14 \ indicator 5 BEQ uind19 \ If X = 6, jump down to uind19 to update indicator 6 JMP uind23 \ X > 6, so jump down to uind23 to check for more values \ of X
Name: UpdateIndicator (Part 9 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the turn indicator (indicator 5), the bottom part of the slip-and-turn indicator Deep dive: Hard-coded division in the dashboard routines
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the turn rate around the y-axis (i.e. the yaw rate) from (yTurnTop yTurnHi) and reduces it to the range -19 to +19, before passing it to the DrawIndicatorHand to update the bottom part of the slip-and-turn indicator.
.uind14 \ If we get here then the indicator number in X is 5 LDA yTurnHi \ Set (A T) = (yTurnTop yTurnHi) STA T \ = yTurn LDA yTurnTop BPL uind15 \ If the turn rate is positive, jump down to uind9 LDA #0 \ The turn rate is negative, so we make it positive SEC \ by calculating: SBC T \ STA T \ (A T) = (0 0) - (A T) \ \ starting with the low bytes LDA #0 \ And then the high bytes SBC yTurnTop .uind15 \ By this point, (A T) = |yTurn| BNE uind16 \ If the high byte in A is non-zero, this means that \ (A T) > 255, so skip the following three instructions \ to set A to the maximum value of 140 LDA T \ A is 0, so set A = T, so A now contains the correct \ value of |yTurn| CMP #140 \ If T < 140, jump to uind17 to skip the following two BCC uind17 \ instructions .uind16 LDA #140 \ Set T = 140, so T is always a maximum value of 140 STA T .uind17 \ At this point, T contains |yTurn|, capped to a maximum \ value of 140 \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication \ We don't need an LDA T instruction as A already \ contains the same value as T LSR A \ Bit 0 of n is 0 CLC \ Bit 1 of n is 1 ADC T ROR A LSR A \ Bit 2 of n is 0 LSR A \ Bit 3 of n is 0 CLC \ Bit 4 of n is 1 ADC T ROR A LSR A \ Bit 5 of n is 0 LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00010010 (18), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 18 / 256) << 1 \ = T * 36 / 256 \ \ which takes |yTurn| in the range 0 to 140 and reduces \ it to the range 0 to 19 BIT yTurnTop \ If the top byte in yTurnTop is negative (and therefore BMI uind18 \ so is the turn rate), jump to uind18 to skip the \ following STA T \ Negate the value in A by calculating: LDA #0 \ SEC \ A = 0 - A SBC T .uind18 \ So by now, A is in the range -19 to +19 \ \ The maximum turn rate shown on the indicator is \ 4 x 180 degrees per minute, which is shown when \ T = 140, so yTurn is stored as around 35 * the turn \ rate, as 140 / 4 = 35 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 10 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the slip indicator (indicator 6), the top part of the slip-and-turn indicator
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.uind19 \ If we get here then the indicator number in X is 6 LDA slipRate \ Set A = slipRate JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call
Name: UpdateIndicator (Part 11 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the artificial horizon (indicator 7)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Calculations for the artificial horizon's line vector are all performed in the ArtificialHorizon routine, which calculates the starting coordinates and deltas for the vector line. This routine simply takes those results, sets the direction bits for the line, and moves the starting point so the line is centred on the artificial horizon indicator.
.uind20 \ If we get here then the indicator number in X is 7 LDY #0 \ Set Y = 0, to use as an argument to ArtificialHorizon STY K \ Set K = 0, to use as an argument to ArtificialHorizon STY R \ Set R = 0, to use as the first guess for the direction \ of the horizon line (we will change it below if \ required) JSR ArtificialHorizon \ Call ArtificialHorizon with K = 0, Y = 0 to calculate \ the x-coordinate of the line's starting point in A CLC \ Clear the C flag (this appears to have no effect) STA S \ Set H = S, so this sets the x-coordinate of the line's \ starting point LDY #3 \ Set Y = 3 JSR ArtificialHorizon \ Call ArtificialHorizon with K = 0, Y = 3 to calculate \ the y-coordinate of the line's starting point in A STA H \ Set H = A, so this sets the y-coordinate of the line's \ starting point LDY #0 \ Set Y = 0, to use as an argument to ArtificialHorizon LDA #1 \ Set K = 1, to use as an argument to ArtificialHorizon STA K JSR ArtificialHorizon \ Call ArtificialHorizon with K = 1, Y = 0 to calculate \ the x-delta of the line SEC \ Set A = A - S SBC S \ = A - x-coordinate of start BPL uind21 \ If A is positive, the sign of the x-delta is correct, \ so jump to uind21 STA T \ The returned x-delta is negative, so store it in T so \ we can negate it below LDA #%10000000 \ Set bit 7 of R to indicate that the x-delta for the STA R \ line is negative and the y-delta is positive LDA #0 \ Set A = 0 - T SEC \ SBC T \ so the x-delta in A is now positive .uind21 CLC \ Set W = A + 1 ADC #1 \ STA W \ so this sets the line's x-delta, making sure the line \ is at least one pixel wide LDY #3 \ Set Y = 3, to use as an argument to ArtificialHorizon JSR ArtificialHorizon \ Call ArtificialHorizon with K = 1, Y = 3 to calculate \ the y-delta of the line SEC \ Set A = A - H SBC H \ = A - y-coordinate of start BPL uind22 \ If A is positive, the sign of the y-delta is correct, \ so jump to uind22 STA T \ The returned y-delta is negative, so store it in T so \ we can negate it below LDA #%01000000 \ Set bit 6 of R to indicate that the y-delta is ORA R \ negative, making sure to leave bit 7 as it is STA R LDA #0 \ Set A = A - T SEC \ SBC T \ so the y-delta in A is now positive .uind22 CLC \ Set G = A + 1 ADC #1 \ STA G \ so this sets the line's y-delta, making sure the line \ is at least 1 pixel tall LDA S \ Set S = S + 53 CLC \ ADC #53 \ so the start x-coordinate is moved to be relative to STA S \ the centre of the artificial horizon indicator, which \ is at (53, 160 + 29) LDA H \ Set H = H - 29 CLC \ ADC #227 \ so the start y-coordinate is moved to be relative to STA H \ the centre of the artificial horizon indicator, which \ is at (53, 160 + 29) JMP DrawIndicatorLine \ Draw the new artificial horizon, returning from the \ subroutine using a tail call
Name: UpdateIndicator (Part 12 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Logic for checking which indicator to update
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.uind23 \ If we get here then X > 6 CPX #7 \ If X = 7, jump up to uind20 to update indicator 7 BEQ uind20 CPX #9 \ If X < 9 (i.e. X = 8), jump down to uind25 to update BCC uind25 \ indicator 8 BEQ uind24 \ If X = 9, jump down to uind24 to update indicator 9 CPX #11 \ If X < 11 (i.e. X = 10), jump down to uind25 to update BCC uind25 \ indicator 10 BEQ uind26 \ If X = 11, jump down to uind26 to update indicator 11 \ If we get here then X > 11, which should not happen, \ but if it does, we simply do nothing RTS \ Return from the subroutine
Name: UpdateIndicator (Part 13 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the rudder indicator (indicator 9)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the rudder position from rudderPosition and reduces it to the range -8 to +8, before passing it to DrawIndicatorBar to update the rudder indicator.
.uind24 \ If we get here then the indicator number in X is 9 LDX #1 \ Set X = 1 so the current value of the indicator gets \ stored in yJoyCoord+1 in DrawIndicatorBar LDA #128 \ Set S = 128, to denote that when we fall through into STA S \ DrawIndicatorBar below, joyCoord is the y-coordinate, \ so we draw the indicator's vertical bar from point \ (H + W, joyCoord) LDA #80 \ Set W = 80 to use as the centre y-coordinate for the STA W \ rudder indicator (i.e. the centre bar) LDA rudderPosition \ Set A = rudderPosition SEC \ Set the C flag so the following call to ScaleSigned \ divides the rudder value by 16 JSR ScaleSigned \ Scale the value in A down by a factor of 16, retaining \ the sign and being sensitive to small values STA H \ Store the scaled value in H, which has now been \ reduced from the range -128 to 127 down to -8 to +8, \ to use as the x-coordinate offset from the centre of \ the indicator in DrawIndicatorBar LDY #163 \ Set Y = 163 to use as the y-coordinate of the top of \ the vertical bar LDA #11 \ Set A = 11 to set the height of the vertical bar at 11 \ pixels BNE DrawIndicatorBar \ Jump to DrawIndicatorBar to update indicator 9 by \ drawing a vertical bar of height 11 pixels with \ the top at (80 + H, 163)
Name: UpdateIndicator (Part 14 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the joystick position display (indicator 8 or 10)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the joystick position from aileronPosition and elevatorPosition and reduces it to -8 to +8 (for the x-position in aileronPosition) and to -32 to +32 (for the y-position in elevatorPosition), before passing it to the DrawJoystickCross to update the cross in the joystick position display. The x-position is reduced more because the joystick position display is taller than it is wide. Note that this indicator has two IDs, 8 and 10, so that it gets updated twice as often by UpdateDashboard.
.uind25 \ If we get here then the indicator number in X is 8 or \ 10 LDA #128 \ Redraw the existing cross on the joystick position JSR DrawJoystickCross \ display using EOR logic, which removes it LDA #%00100010 \ Redraw the joystick position display's x-axis, in case STA row24_char18_7 \ the old cross was overwriting the axis STA row24_char21_7 LDA #%01000100 STA row24_char19_7 LDA #%10011001 \ Redraw the joystick position display's y-axis, in case STA row24_char20_7 \ the old cross was overwriting the axis LDA #%10001000 STA row21_char20_7 STA row22_char20_7 STA row23_char20_7 STA row25_char20_7 STA row26_char20_7 STA row27_char20_7 LDA aileronPosition \ Set A = aileronPosition, the x-position of the \ joystick SEC \ Set the C flag so the following call to ScaleSigned \ divides the joystick x-position by 16 JSR ScaleSigned \ Scale the value in A down by a factor of 16, retaining \ the sign and being sensitive to small values STA xJoyCoord \ Store the scaled value of A in xJoyCoord, so we can \ pass it to DrawJoystickCross below to draw the new \ cross, but also so we can erase this cross when we \ need to update the indicator in the future LDA elevatorPosition \ Set A = elevatorPosition, the y-position of the \ joystick CLC \ Clear the C flag so the following call to ScaleSigned \ divides the joystick y-position by 4 JSR ScaleSigned \ Scale the value in A down by a factor of 4, retaining \ the sign and being sensitive to small values EOR #&FF \ Set A = -A using two's complement CLC \ ADC #1 \ This flips the sign of the y-position, because the \ joystick value will be high when we pull up, but this \ corresponds to moving the stick down, which should be \ shown lower down the indicator STA yJoyCoord \ Store the scaled value of A in yJoyCoord, so we can \ pass it to DrawJoystickCross below to draw the new \ cross, but also so we can erase this cross when we \ need to update the indicator in the future LDA #0 \ Draw a new cross on the joystick position display JSR DrawJoystickCross RTS \ Return from the subroutine
Name: UpdateIndicator (Part 15 of 15) [Show more] Type: Subroutine Category: Dashboard Summary: Calculations for the thrust indicator (indicator 11)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This section takes the thrust from (thrustHi thrustLo) and reduces it to the range 0 to 60, before falling through into DrawIndicatorBar to update the rudder indicator.
.uind26 \ If we get here then the indicator number in X is 11 LDA #128 \ Set S = 128, to denote that when we fall through into STA S \ DrawIndicatorBar below, joyCoord is the y-coordinate, \ so we draw the indicator's vertical bar from point \ (H + W, joyCoord) LDA #125 \ Set W = 125 to use as the centre y-coordinate for the STA W \ thrust indicator (i.e. the left end of the bar, as we \ are only showing positive thrust values) LDA thrustHi \ Set (R A) = (thrustHi thrustLo) STA R \ = Thrust LDA thrustLo LDX #3 \ Set X = 3 to act as a shift counter in the following \ loop, where we right shift (R A) four times .uind27 LSR R \ Set (R A) = (R A) / 2 ROR A \ = Thrust / 2 DEX \ Decrement the shift counter BPL uind27 \ Loop back until we have shifted right by 4 places, so \ we now have: \ \ (R A) = (R A) / 8 \ = Thrust / 8 \ \ We now ignore the high byte in R, so presumably it is \ zero STA R \ Set A = A + A / 2 LSR A \ = 1.5 * A ADC R \ = 1.5 * (Thrust / 8), rounded up \ = Thrust * 3 / 16 LSR A \ Set A = A / 4 LSR A \ = Thrust * 3 / 64 STA H \ Store the scaled value in H, which has now been \ reduced from the range 0 to 1280 down to 0 to 60, \ to use as the x-coordinate offset from the left end of \ the indicator in DrawIndicatorBar LDX #3 \ Set X = 3 so the current value of the indicator gets \ stored in yJoyCoord+3 in DrawIndicatorBar LDY #243 \ Set Y = 243 to use as the y-coordinate of the top of \ the vertical bar LDA #7 \ Set A = 7 to set the height of the vertical bar at 7 \ pixels \ Fall through into DrawIndicatorBar to update indicator \ 11 by drawing a vertical bar of height 7 pixels with \ the top at (125 + H, 243)
Name: DrawIndicatorBar [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a vertical bar on indicator 9 (rudder) or 11 (thrust)
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateIndicator (Part 13 of 15) calls DrawIndicatorBar

Arguments: A The height of the vertical bar in pixels S Defines the starting coordinate for the line: * 0 = (joyCoord, H + W) * 128 = (H + W, joyCoord) As all the vertical bar indicators are horizontal in Aviator, only the second option is used X The offset from yJoyCoord where we store the indicator value, so it can be erased when the bar needs to move: * 1 = rudder indicator (indicator 9) * 3 = thrust indicator (indicator 11) W The x-coordinate of the centre point of the indicator Y The y-coordinate of the top of the bar
.DrawIndicatorBar STA U \ Set U = A, so the line is A pixels tall LDA #1 \ Set T = 1, so the line is 1 pixel wide STA T STY joyCoord \ Set joyCoord = Y, the y-coordinate of the top of the \ bar LDA yJoyCoord,X \ Set G = the X-th byte of yJoyCoord, which contains the STA G \ x-coordinate of the current bar, so we can erase it LDA H \ Store H in the X-th byte of yJoyCoord, so the next STA yJoyCoord,X \ time we call this routine, we can use this to erase \ the bar we are about to draw JSR EraseOrthoLine \ Erase the current vertical bar at x-coordinate G LDA #0 \ Set N = 0 to switch the drawing mode to OR logic, so STA N \ the bar gets drawn on-screen JSR DrawOrthoLine \ Draw a new vertical bar at x-coordinate H RTS \ Return from the subroutine
Name: DrawIndicatorHand [Show more] Type: Subroutine Category: Drawing lines Summary: Apply min and max limits to an indicator value and draw a hand on the indicator
Arguments: A The value to show on the indicator X The indicator number (0-6)
.DrawIndicatorHand \ When we get here, the values of A are set as follows: \ \ * X = 0: Compass A = 0 to 73 \ * X = 1: Airspeed A = 9 to 74 \ * X = 2: Altimeter small hand A = 0 to 254 \ * X = 3: Altimeter large hand A = 0 to 104 \ * X = 4: Vertical speed A = -40 to 40 \ * X = 5: Turn A = -19 to 19 \ * X = 6: Slip A = ??? to ??? CLC \ Set A = A + the X-th byte of indicatorBase ADC indicatorBase,X \ \ This adds the relevant indicator's base value from the \ indicatorBase table to give the following: \ \ * Compass +0 A = 0 to 73 \ * Airspeed +48 A = 57 to 122 \ * Altimeter small hand +0 A = 0 to 254 \ * Altimeter large hand +0 A = 0 to 104 \ * Vertical speed +67 A = 27 to 107 \ * Turn +53 A = 34 to 72 \ * Slip +106 A = ??? to ??? CMP indicatorMin,X \ If A >= the X-th byte of indicatorMin, jump to dinh1 BCS dinh1 \ to skip the following LDA indicatorMin,X \ Set A to the X-th byte of indicatorMin, so A is at \ least the relevant value in indicatorMin BCC dinh2 \ Jump to dinh2 to skip the following, as we don't need \ to check the maximum limit (this BCC is effectively a \ JMP as we passed through the BCS above) .dinh1 \ If we get here then A >= the X-th byte of indicatorMin CMP indicatorMax,X \ If A >= the X-th byte of indicatorMax, jump to dinh2 BCC dinh2 \ to skip the following LDA indicatorMax,X \ Set A to the X-th byte of indicatorMax, so A is no \ more than the relevant value in indicatorMax .dinh2 \ A is now clipped to this indicator's range as given in \ the indicatorMin and indicatorMax tables, so the final \ ranges are: \ \ * Compass A = 0 to 73 \ * Airspeed A = 57 to 122 \ * Altimeter small hand A = 0 to 254 \ * Altimeter large hand A = 0 to 104 \ * Vertical speed A = 30 to 104 \ * Turn A = 33 to 72 \ * Slip A = 91 to 120 STA H \ Store the clipped indicator value in H JSR GetHandVector \ Calculate the vector for drawing the new dial hand \ with value A, returning the result in W, G and R \ Fall through into DrawIndicatorLine to draw the new \ hand on the indicator
Name: DrawIndicatorLine [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a line on indicators 0 to 7, i.e. a dial hand (0-6) or an artificial horizon (7) Deep dive: Line buffers
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateIndicator (Part 11 of 15) calls DrawIndicatorLine

Arguments: S Start point x-coordinate (artificial horizon only) H Start point y-coordinate (artificial horizon only) W Magnitude of x-coordinate of line's vector |x-delta| G Magnitude of y-coordinate of line's vector |y-delta| R Direction of vector (T, U): * Bit 7 is the direction of the x-delta * Bit 6 is the direction of the y-delta Direction is like a clock, so positive (clear) is up and right X The indicator number (0-7) WW The indicator number (0-7)
.DrawIndicatorLine LDA indicatorLineI,X \ Set I = x-coordinate of the starting point of the STA I \ current line LDA indicatorLineJ,X \ Set J = y-coordinate of the starting point of the STA J \ current line LDA indicatorLineT,X \ Set T = x-delta of the current line STA T LDA indicatorLineU,X \ Set U = y-delta of the current line STA U LDA indicatorLineV,X \ Set V = direction of the current line STA V LDA #128 \ Set N = 128 so the call to DrawVectorLine erases the STA N \ current line JSR DrawVectorLine \ Erase a line from (I, J) as a vector (T, U) with \ direction V LDX WW \ If this is not indicator 7, jump to dinl2 CPX #7 BNE dinl2 \ If we get here then this is the artificial horizon \ (indicator 7) LDA #%11111111 \ Set A to the pixel byte for four white pixels, to use \ for the bottom row of pixels in the centre block of \ the artificial horizon's centre line LDY #2 \ We want to redraw the three pixel rows in the centre \ block of the artificial horizon's centre line, which \ from bottom to top contain 3 pixels, 1 pixel and 1 \ pixel, so set a counter in Y for 3 bytes .dinl1 STA row23_char13_2,Y \ Redraw the Y-th pixel row in the centre block LDA #%01000100 \ The top two pixel rows of the centre block contain the \ vertical mark at the centre of the indicator, so set A \ to the appropriate single-pixel byte DEY \ Decrement the counter to move up to the next pixel row BPL dinl1 \ Loop back until we have redrawn all three pixel rows \ in the centre block LDA #%00110011 \ Redraw the two-pixels at the left end of the STA row23_char12_4 \ artificial horizon's centre line LDA #%10001000 \ Redraw the single pixel at the right end of the STA row23_char14_4 \ artificial horizon's centre line LDA S \ Fetch the x-coordinate of the starting point of the \ new line from S STA I \ Set I = the x-coordinate of the starting point of the \ new line to draw STA indicatorLineI,X \ Store the x-coordinate in indicatorLineI, so we can \ use it to erase the line later LDA H \ Set A = the y-coordinate of the starting point of the \ new line to draw STA indicatorLineJ,X \ Store the y-coordinate in indicatorLineJ, so we can \ use it to erase the line later BNE dinl3 \ Jump to dinl3 to draw the new line (this BNE is \ effectively a JMP as A is never zero) .dinl2 \ If we get here then this is indicator 0-6, so it's a \ hand-based dial LDA indicatorLineI,X \ Set I = x-coordinate of starting point of hand, which STA I \ is a fixed value for hand-based dials LDA indicatorLineJ,X \ Set J = y-coordinate of starting point of hand, which \ is a fixed value for hand-based dials .dinl3 STA J \ Store A in J as the y-coordinate of the starting \ point of the new line to draw LDA W \ Fetch the x-delta of the new line from W STA T \ Set T = the x-delta of the new line STA indicatorLineT,X \ Store the x-delta in indicatorLineT, so we can use it \ to erase the line later LDA G \ Fetch the y-delta of the new line from G STA U \ Set U = the y-delta of the new line STA indicatorLineU,X \ Store the y-delta in indicatorLineU, so we can use it \ to erase the line later LDA R \ Fetch the direction of the new line from R STA V \ Set V = the direction of the new line STA indicatorLineV,X \ Store the direction in indicatorLineV, so we can use \ it to erase the line later LDA #0 \ Set N = 0 so the call to DrawVectorLine draws the new STA N \ line JSR DrawVectorLine \ Draw a line from (I, J) as a vector (T, U) with \ direction V RTS \ Return from the subroutine
Name: DrawJoystickCross [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a cross in the joystick position display (indicator 8 or 10)
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateIndicator (Part 14 of 15) calls DrawJoystickCross

This routine draws a cross, relative to a centre point of (80, 216), and with a 3-pixel horizontal bar and a 5-pixel vertical bar.
Arguments: A Drawing mode: * 0 = Draw (using OR logic) * 128 = Erase (using EOR logic) xJoyCoord The current joystick x-coordinate yJoyCoord The current joystick y-coordinate
.DrawJoystickCross STA N \ Store the drawing mode in N \ First we draw the 3-pixel horizontal line right from \ (79 + x, 216 + y) LDA yJoyCoord \ Set H = yJoyCoord STA H LDA xJoyCoord \ Set joyCoord = xJoyCoord + 79 CLC \ ADC #79 \ to get the x-coordinate of the left end of the STA joyCoord \ horizontal line at 79 + x LDA #216 \ Set W = 216, the y-coordinate of the centre point, so STA W \ we draw the line from a y-coordinate of 216 + y LDA #0 \ Set S = 0, to denote that joyCoord is the STA S \ x-coordinate, so we draw the line from point \ (joyCoord, H + W) LDA #3 \ Set T = 3, so we draw a horizontal 3-pixel line STA T LDA #1 \ Set U = 1, so the line is 1 pixel high STA U JSR DrawOrthoLine \ Draw the horizontal part of the cross, starting from \ (79 + x, 216 + y) and drawing to the right for three \ pixels \ Now we draw the 5-pixel vertical line down from \ (80 + x, 214 + y) LDA xJoyCoord \ Set H = xJoyCoord STA H LDA yJoyCoord \ Set joyCoord = yJoyCoord + 214 CLC \ ADC #214 \ to get the y-coordinate of the top end of the vertical STA joyCoord \ line at 214 + y LDA #80 \ Set W = 80, the x-coordinate of the centre point, so STA W \ we draw the line from an x-coordinate of 80 + x LDA #128 \ Set S = 128, to denote that joyCoord is the STA S \ y-coordinate, so we draw the line from point \ (H + W, joyCoord) LDA #1 \ Set T = 1, so the line is 1 pixel wide STA T LDA #5 \ Set U = 5, so we draw a vertical 5-pixel line STA U JSR DrawOrthoLine \ Draw the vertical part of the cross, starting from \ (80 + x, 214 + y) and drawing down for five pixels RTS \ Return from the subroutine
Name: GetHandVector [Show more] Type: Subroutine Category: Dashboard Summary: Vector line calculation for a hand on indicators 0 to 6 Deep dive: Clock hands and dial indicators
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawIndicatorHand calls GetHandVector

Arguments: A The value to show as a hand on the dial indicator
Returns: W Magnitude of x-coordinate of line's vector |x-delta| G Magnitude of y-coordinate of line's vector |y-delta| R Direction of vector (T, U): * Bit 7 is the direction of the x-delta * Bit 6 is the direction of the y-delta Direction is like a clock, so positive (clear) is up and right, so this means the following: * x-delta +ve, y-delta +ve: 12 to 3 o'clock * x-delta +ve, y-delta -ve: 3 to 6 o'clock * x-delta -ve, y-delta -ve: 6 to 9 o'clock * x-delta -ve, y-delta +ve: 9 to 12 o'clock
.GetHandVector LDY #0 \ Set R = 0, to use as the first guess for the direction STY R \ of the hand on the dial (we will change it below if \ required) LDY dialQuadrant,X \ Set Y = the dialQuadrant value for this indicator, \ which is the size of a quarter of the dial in terms of \ the value in A INY \ Set K = Y + 1 STY K \ = dialQuadrant + 1 \ \ Doing this enables us to subtract this value below, \ leaving the result in a suitable state for negating \ using two's complement (see dhvc2) SEC \ Set the C flag for the subtraction we are about to do .dhvc1 SBC K \ Set A = A - K \ = A - (dialQuadrant + 1) \ \ so we have subtracted a quadrant's worth of value from \ the value we want to show in A BCS dhvc2 \ If the subtraction didn't underflow, jump to dhvc2 to \ skip the following two instructions \ The subtraction underflowed, so we know that the hand \ is in the first quadrant, i.e. 3 to 6 o'clock ADC K \ Reverse the subtraction by adding K to A, so A is now \ back to its original value JMP dhvc6 \ Jump down to dhvc5 to calculate the hand's vector, \ with R set to 0 to indicate that both the x-delta and \ y-delta for the line are positive .dhvc2 SBC dialQuadrant,X \ Subtract a second quadrant's worth, so: \ \ A = A - (dialQuadrant + 1) - dialQuadrant \ = A - 2 * dialQuadrant - 1 \ \ If we want to negate this value below, then we can do \ this using two's complement by simply inverting all \ the bits, as we have already subtracted 1, and we can \ negate by either inverting-and-adding-1, or by \ subtracting-1-and-inverting (as they are equivalent) BCS dhvc3 \ If the subtraction didn't underflow, jump to dhvc3 to \ skip the following three instructions \ The subtraction underflowed, so we know that the hand \ is in the second quadrant, i.e. 6 to 9 o'clock LDY #%01000000 \ Set bit 6 of R to indicate that the x-delta for the STY R \ line is positive and the y-delta is negative BNE dhvc5 \ Jump down to dhvc5 to negate A before calculating the \ hand's vector (this BNE is effectively a JMP as A is \ never zero) .dhvc3 SBC K \ Subtract a third quadrant's worth BCS dhvc4 \ If the subtraction didn't underflow, jump to dhvc4 to \ skip the following four instructions \ The subtraction underflowed, so we know that the hand \ is in the third quadrant on the dial ADC K \ Reverse the subtraction by adding K to A, so A is now \ back to its original value LDY #%11000000 \ Set bits 7 and 6 of R to indicate that both the STY R \ x-delta and y-delta for the line are negative BNE dhvc6 \ Jump down to dhvc5 to calculate the hand's vector \ (this BNE is effectively a JMP as A is never zero) .dhvc4 SBC dialQuadrant,X \ Subtract a fourth quadrant's worth BCS dhvc1 \ If the subtraction didn't underflow, jump to dhvc1 to \ start the subtraction process again, as we have now \ subtracted a whole dial's worth and need to keep going \ The subtraction underflowed, so we know that the hand \ is in the fourth quadrant on the dial LDY #%10000000 \ Set bit 7 of R to indicate that the x-delta for the STY R \ line is negative and the y-delta is positive .dhvc5 \ If we get here then the hand is either in the second \ or fourth quadrant on the dial EOR #&FF \ Invert the value of A (i.e. negate it) using two's \ complement, which works because we can negate a number \ by subtracting 1 and then inverting, and we \ effectively subtracted 1 in the above by using K .dhvc6 \ By the time we get here, the direction of the new \ dial hand is in R, the value of A has been reduced \ into negative territory by repeated subtraction, and \ it has been switched to be positive if the quadrant is \ bottom-right or top-left, i.e. 3 to 6 o'clock or \ 9 to 12 o'clock) \ \ We now use this value of A as our value of x in the \ above calculation STA S \ Store the reduced value of A in S \ We now calculate y-delta, where y = w - x or y = x - w \ depending on the quadrant. We already set the sign of \ x correctly with the above EOR, so if we calculate \ y = w - x, it will be correct LDA dialQuadrant,X \ Set A = the dialQuadrant value for this indicator, \ which is the size of a quarter of the dial in terms of \ the value in A. This is what we use for w in the above SEC \ Set A = A - S SBC S \ = w - x \ \ so A contains our y-delta \ We now cap the y-delta, which has the effect of \ blunting the points of our diamond CMP yDeltaMax,X \ If A < yDeltaMax for this indicator, jump to dhvc7 to BCC dhvc7 \ skip the following instruction LDA yDeltaMax,X \ Set A = yDeltaMax, so A is never greater than the \ yDeltaMax value for this indicator .dhvc7 CLC \ Set G = A + 1 ADC #1 \ = y-delta + 1 STA G \ \ so G contains the y-delta, plus 1 to ensure it is \ non-zero LDA S \ Fetch the reduced value of A that we stored in S we \ above, which we know is our x-delta CLC \ Set A = (A + 1) / 2 ADC #1 \ LSR A \ because mode 5 pixels are twice as wide as they are \ high, so this scales the x-delta to be a pixel value CMP xDeltaMax,X \ If A < xDeltaMax for this indicator, jump to dhvc8 to BCC dhvc8 \ skip the following instruction LDA xDeltaMax,X \ Set A = xDeltaMax, so A is never greater than the \ xDeltaMax value for this indicator .dhvc8 CLC \ Set W = A + 1 ADC #1 \ = x-delta + 1 STA W \ \ so W contains the y-delta, plus 1 to ensure it is \ non-zero RTS \ Return from the subroutine
Name: DrawVectorLine (Part 1 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a line: set up pixel bytes and slope variables Deep dive: Line buffers
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawFuelPixel calls DrawVectorLine * DrawIndicatorLine calls DrawVectorLine * DrawOrthoLine calls DrawVectorLine * DrawRadarBlip calls DrawVectorLine

Draw/erase a line from (I, J) as a vector (T, U) with direction V. This routine uses Bresenham's algorithm to draw the line, by working along the longer axis of the line vector, one pixel at a time, plotting a pixel after each step. All the while we keep a cumulative tally of fractional pixel counts along the shorter axis (known as the "slope error"), and move one pixel along the shorter axis when the tally reaches a multiple of the axis length.
Arguments: I Start point x-coordinate J Start point y-coordinate, relative to the bottom of the canopy/top of the dashboard. Specifically: * J = 0 to 151 are all within the bounds of the canopy * The bottom pixel inside the canopy is 0, while the top pixel just below the top canopy edge is 151 * J = -1 to -96 are all on the dashboard * The white horizontal edge at the bottom of the canopy/top of the dashboard is -1 * The bottom row of pixels on-screen is -96 N Drawing mode: * Bit 7 clear = draw * Bit 7 set = erase T Magnitude of x-coordinate of line's vector |x-delta| Horizontal width/length of line when V = 0 U Magnitude of y-coordinate of line's vector |y-delta| Vertical width/length of line when V = 0 V Direction of vector (T, U): * Bit 7 is the direction of the x-delta * Bit 6 is the direction of the y-delta Direction is like a clock, so positive (clear) is up and right
.DrawVectorLine LDY #3 \ Set Y = 3 to use as a byte counter in the following \ loop, so it writes four bytes LDA #%00010001 \ Set A = %00010001, the pixel pattern for pixel 0 in \ white .dvec1 STA RR,Y \ Set the Y-th byte of RR to A ASL A \ Set A = A * 2 DEY \ Decrement the byte counter BPL dvec1 \ Loop back until we have updated RR to RR+3 as \ follows: \ \ RR = %10001000 = pixel 3 in white \ RR+1 = %01000100 = pixel 2 in white \ RR+2 = %00100010 = pixel 1 in white \ RR+3 = %00010001 = pixel 0 in white LDA #0 \ Set QQ = 0 STA QQ STA PP \ Set PP = 0 LDA T \ If T < U, jump down to dvec2 to skip the following CMP U \ two instructions BCC dvec2 \ If we get here then T >= U, so the line is a shallow \ horizontal slope STA VV \ Set VV = T, the length of the longer axis BCS dvec11 \ Jump down to dvec11 to start drawing the line (this \ BCS is effectively a JMP as we just passed through a \ BCC) .dvec2 \ If we get here then T < U, so the line is a steep \ vertical slope LDA U \ Set VV = U, the length of the longer axis STA VV STA PP \ Set PP = U BCC dvec11 \ Jump down to dvec11 to start drawing the line (this \ BCC is effectively a JMP as we got here by taking a \ BCC)
Name: DrawVectorLine (Part 2 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Calculate the coordinates of the next pixel as we step along the line by one pixel
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.dvec3 \ If we get here then we need to step along the x-axis LDA QQ \ Set A = QQ + U CLC \ ADC U \ so A contains the cumulative step along the y-axis \ (the shorter axis) CMP T \ If A < T, then we haven't yet reached a full step of BCC dvec5 \ length T along the y-axis, so we don't change the \ y-coordinate and instead jump to dvec5 to do the step \ along the x-axis by one pixel \ We now need to step along the y-axis by one pixel as \ the cumulative step has just crossed over into a new \ multiple of T SBC T \ Set A = A - T \ \ so we keep the cumulative step within the bounds of a \ single byte (as we are only interested in when it \ crosses the boundary into a new multiple of T) \ We now move one pixel along the y-axis in the \ direction given in V BIT V \ If bit 6 of V is clear, jump to dvec4 to step along BVC dvec4 \ the y-axis in a positive direction DEC J \ Bit 6 of V is set, so decrement the y-coordinate in \ J so we move along the y-axis in a negative direction BVS dvec5 \ Jump to dvec5 to do the step along the x-axis by one \ pixel (this BVS is effectively a JMP as we know the V \ flag is set) .dvec4 INC J \ Bit 6 of V is clear, so increment the y-coordinate in \ J so we move along the y-axis in a positive direction .dvec5 STA QQ \ Store the updated fractional value in QQ \ We now move one pixel along the x-axis in the \ direction given in V BIT V \ If bit 7 of V is clear, jump to dvec6 to step along BPL dvec6 \ the x-axis in a positive direction DEC I \ Bit 7 of V is set, so decrement the x-coordinate in \ I so we move along the x-axis in a negative direction JMP dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel .dvec6 INC I \ Bit 7 of V is clear, so increment the x-coordinate in \ I so we move along the x-axis in a positive direction JMP dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel .dvec7 \ We jump here when we need to calculate the coordinates \ of the next pixel in the line when stepping along the \ longer delta axis one pixel at a time LDA PP \ If PP = 0 then this is a shallow horizontal slope, so BEQ dvec3 \ jump up to dvec3 step along the x-axis \ If we get here then this is a steep vertical line, so \ we need to step along the y-axis LDA QQ \ Set A = QQ + T CLC \ ADC T \ so A contains the cumulative step along the x-axis \ (the shorter axis) CMP U \ If A < U, then we haven't yet reached a full step of BCC dvec9 \ length U along the x-axis, so we don't change the \ x-coordinate and instead jump to dvec9 to do the step \ along the y-axis by one pixel \ We now need to step along the x-axis by one pixel as \ the cumulative step has just crossed over into a new \ multiple of U SBC U \ Set A = A - U \ \ so we keep the cumulative step within the bounds of a \ single byte (as we are only interested in when it \ crosses the boundary into a new multiple of U) \ We now move one pixel along the x-axis in the \ direction given in V BIT V \ If bit 7 of V is clear, jump to dvec8 to step along BPL dvec8 \ the x-axis in a positive direction DEC I \ Bit 7 of V is set, so decrement the x-coordinate in \ I so we move along the x-axis in a negative direction, \ i.e. to the left JMP dvec9 \ Jump to dvec9 to do the step along the y-axis by one \ pixel .dvec8 INC I \ Bit 7 of V is clear, so increment the x-coordinate in \ I so we move along the x-axis in a positive direction, \ i.e. to the right .dvec9 STA QQ \ Store the updated fractional value in QQ \ We now move one pixel along the y-axis in the \ direction given in V BIT V \ If bit 6 of V is clear, jump to dvec10 to step along BVC dvec10 \ the y-axis in a positive direction, i.e. up the screen DEC J \ Bit 6 of V is set, so decrement the y-coordinate in \ J so we move along the y-axis in a negative direction, \ i.e. down the screen BVS dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel (this BVS \ is effectively a JMP as we know the V flag is set) .dvec10 INC J \ Bit 6 of V is clear, so increment the y-coordinate in \ J so we move along the y-axis in a positive direction \ Now that we have moved (I, J) to the next pixel in the \ line, we fall through into part 3 to plot that pixel
Name: DrawVectorLine (Part 3 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Plot a pixel at (I, J)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.dvec11 \ When we first arrive here: \ \ * QQ = 0, where we will tally up the fractional part \ of the move along the shortest axis \ * RR = pixel byte table \ * VV = longest side of delta triangle \ * PP = 0 (shallow horizontal) \ VV (steep vertical) \ \ so we now draw a line from (I, J), moving one pixel \ at a time along the longest side of the delta triangle LDA I \ Set X = I / 4 LSR A \ LSR A \ so X is the number of the character block containing TAX \ pixel (I, J), as each character block is 4 pixels wide LDA J \ Set Y = J / 8 LSR A \ LSR A \ so Y is the number of the character row containing LSR A \ pixel (I, J), as each character row is 8 pixels high TAY LDA yLookupLo,Y \ Set P = Y-th byte of yLookupLo CLC \ + X-th byte of xLookupLo ADC xLookupLo,X \ = LO(screen address) + LO(X * 8) STA P LDA yLookupHi,Y \ Set Q = Y-th byte of yLookupHi ADC xLookupHi,X \ + X-th byte of xLookupHi STA Q \ = HI(screen address) + HI(X * 8) \ So (Q P) is the screen address of the pixel row \ containing pixel (I, J), out by 8 bytes for each row \ above or below the top of the dashboard LDA #159 \ Set Y = 159 - J SEC \ SBC J \ so Y is the number of pixels that (I, J) is above TAY \ (+ve) or below (-ve) the top of the dashboard, where a \ value of 0 is the bottom pixel inside the canopy, and \ a value of -1 is the white horizontal edge at the \ bottom of the canopy LDA I \ Set X = bits 0 and 1 of I AND #%00000011 \ = I mod 4 TAX \ = pixel number within the 4-pixel byte BIT N \ If bit 7 of N is set, jump to dvec12 to erase the line BMI dvec12 \ with EOR logic instead of drawing it with OR logic LDA RR,X \ Fetch the X-th byte of RR, which is a pixel byte with \ the X-th pixel set to white ORA (P),Y \ OR it with (Q P) + Y, which is the screen address of \ the pixel row containing (I, J) \ \ This will keep all pixels the same except the X-th \ pixel, which is set to white, so this will plot a \ pixel at (I, J) when stored in screen memory JMP dvec13 \ Jump to dvec13 to skip the following three \ instructions .dvec12 LDA RR,X \ Fetch the X-th byte of RR, which is a pixel byte with \ the X-th pixel set to white EOR #%11111111 \ Invert all the bits, so A is now a pixel byte that is \ all white except for the X-th pixel, which is black AND (P),Y \ AND it with (Q P) + Y, which is the screen address of \ pixel (I, J) \ \ This will keep all pixels the same except the X-th \ pixel, which is set to black, so this will erase the \ pixel at (I, J) when stored in screen memory .dvec13 STA (P),Y \ Store the byte in A in screen memory at (Q P) + Y, \ which sets all four pixels to the pixel pattern in A, \ which either draws or erases the pixel at (I, J) DEC VV \ Decrement VV to step one pixel along the longer axis BNE dvec7 \ If VV is non-zero, jump up to dvec7 to calculate the \ coordinate of the next pixel in the line RTS \ Return from the subroutine