Aviator on the BBC Micro

# 3D geometry: ProjectAxisAngle

```       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 in context in the source code
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

* (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

.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
```