Skip to navigation

Maths (Geometry): GetSineAndCosine

Name: GetSineAndCosine [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the sine and the cosine of an angle
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * GetObjPointAngles calls GetSineAndCosine

Arguments: (A T) The angle, representing a full circle with the range A = 0 to 255, and T representing the fractional part (though only bit 7 of T is used)
Returns: sinA The value of |sin(A)| cosA The value of |cos(A)| H The sign of each of the results: * Bit 7 is the sign of sin(A), with 0 = positive and 1 = negative * Bit 6 is the sign of cos(A), with 0 = positive and 1 = negative
.GetSineAndCosine BPL scos1 \ If A is in the range 128 to 255, flip bit 6 EOR #%01000000 \ \ The degree system in the Sentinel looks like this: \ \ 0 \ -32 | +32 Overhead view of player \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ \ / | \ \ -96 | +96 \ 128 \ \ Bit 7 is set in the left half, so this operation only \ affects angles in that half \ \ In the left half, bit 6 is as follows: \ \ * -1 to -64 (%11111111 to %11000000) \ \ * -65 to -128 (%10111111 to %10000000) \ \ so flipping bit 6 effectively swaps the two quarters \ from this: \ \ 0 \ -32 | \ \ | \ \ | \ \| \ -64 -----+ \ /| \ / | \ / | \ -96 | \ 128 \ \ into this: \ \ -64 \ -96 | \ \ | \ \ | \ \| \ 128 -----+ \ \ and this: \ \ 0 -----+ \ /| \ / | \ / | \ -32 | \ -64 \ \ by doing these conversions: \ \ * -1 to -64 (%11111111 to %11000000) \ -> -65 to -128 (%10111111 to %10000000) \ \ * -65 to -128 (%10111111 to %10000000) \ * -1 to -64 (%11111111 to %11000000) \ \ This is the same as rotating the angle through 90 \ degrees, which is the same as adding PI/2 to the angle \ \ It's a trigonometric identity that: \ \ cos(x) = sin(x + PI/2) \ \ so this shifts the angle so that when we fetch from \ sine lookup table, we are actually getting the cosine \ \ We use this fact below to determine which result to \ return in sinA and cosA after we have done the lookups \ from the sine table .scos1 STA H \ Store the updated angle argument in H so we can fetch \ bits 6 and 7 from the angle below ASL T \ Set (A T) = (A T) << 1 ROL A \ \ The original argument represents a full circle in the \ range A = 0 to 255, so this reduces that range to a \ half circle with A = 0 to 255, as bit 7 is shifted out \ of the top \ \ This effectively drops the left half of the angle \ circle, to leave us with an angle in this range: \ \ 0 \ | +64 \ | / \ | / \ |/ \ +----- +128 \ |\ \ | \ \ | \ \ | +192 \ 256 AND #%01111111 \ Clear bit 6 of the result, so A represents a quarter \ circle in the range 0 to 127 \ \ This effectively drops the bottom-right quarter of the \ angle circle, to leave us with an angle in this range: \ \ 0 \ | +64 \ | / \ | / \ |/ \ +----- +127 \ \ So by this point we have discarded bits 6 and 7 of the \ original angle and scaled the angle to be in the range \ 0 to 127, so we can use the result as an index into \ the sine table (which contains 128 values) \ \ The sine table only covers angles in the first quarter \ of the circle, which means the result of looking up a \ value from the sine table will always be positive, so \ this will return the absolute value, i.e. |sin(x)| TAX \ Copy A into X so we can use it as an index to fetch \ the sine of our angle EOR #%01111111 \ Negate A using two's complement, leaving bit 7 clear CLC \ ADC #1 \ Because A was in the range 0 to 127 before being \ negated, this is effectively the same as subtracting \ A from 127, like this: \ \ A = 127 - X BPL scos2 \ If A > 127 then set A = 127, so A is capped to the LDA #127 \ range 0 to 127 and is suitable for looking up the \ result from the sine table .scos2 TAY \ Copy A into Y so we can use it as an index into the \ sine table, so we have the following: \ \ Y = 127 - X \ Because our angle is in the first quadrant where 127 \ represents a quarter circle of 90 degrees or PI/2 \ radians, we can now look up the sine and cosine as \ follows: LDA sin,X \ Set A = sin(X) LDX sin,Y \ Set X = sin(Y) \ = sin(127 - X) \ = sin(PI/2 - X) \ \ And because X is in the range 0 to PI/2, we have: \ \ X = sin(PI/2 - X) \ = cos(X) \ Because the sine table only contains positive values \ from the first quadrant, this means we have the \ following: \ \ A = |sin(X)| \ \ X = |cos(X)| \ \ We now need to analyse the original angle argument A \ (via bits 6 and 7 of the angle in H) to make sure we \ return the correct result \ \ Here's the angle system again: \ \ 0 \ -32 | +32 Overhead view of player \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ \ / | \ \ -96 | +96 \ 128 \ \ If our original angle argument A is the top-right \ quadrant then the result above will be correct \ \ If our original angle argument A is the bottom-right \ quadrant then we can apply the following trigonometric \ identities: \ \ sin(A) = sin(X + PI/2) \ = cos(X) \ \ cos(A) = cos(X + PI/2) \ = -sin(X) \ \ So for the original argument A, we have: \ \ |sin(A)| = |cos(X)| \ \ |cos(A)| = |-sin(X)| \ = |sin(X)| \ \ So it follows that for this angle range, we need to \ return the current value of X for the sine result and \ the current value of A for the cosine result \ \ If our original angle argument A is the bottom-left \ quadrant then we can apply the following trigonometric \ identities: \ \ sin(A) = sin(X + PI) \ = -sin(X) \ \ cos(A) = cos(X + PI) \ = -cos(X) \ \ And given that we are returning the absolute value, \ this means for this quadrant, the return values are \ correct \ \ If our original angle argument A is the top-left \ quadrant then we can apply the following trigonometric \ identities: \ \ sin(A) = sin(X - PI/2) \ = -cos(X) \ \ cos(A) = cos(X - PI/2) \ = sin(X) \ \ So again we need to return the current value of X for \ the sine result and the current value of A for the \ cosine result \ \ In terms of the original angle A, then, we have the \ following: \ \ * Top-right quadrant = results are correct \ \ * Bottom-right quadrant = swap sin and cos \ \ * Bottom-left quadrant = results are correct \ \ * Top-left quadrant = swap sin and cos \ \ In terms of bits 6 and 7 of H, we swapped the left two \ quadrants, so this means: \ \ * Top-right quadrant = both clear \ \ * Bottom-right quadrant = bit 7 clear, bit 6 set \ \ * Bottom-left quadrant = both set \ \ * Top-left quadrant = bit 7 set, bit 6 clear \ \ So the return values are correct when bits 6 and 7 are \ either both clear or both set, and we need to swap the \ sine and cosine results if one is set and the other is \ clear \ \ Also, this means that the signs of the results are \ captured in H as follows, as the quadrant defines the \ sign of sin(A) and cos(A): \ \ * Top-right quadrant = both clear \ sin(A) is positive \ cos(A) is positive \ \ * Bottom-right quadrant = bit 7 clear, bit 6 set \ sin(A) is positive \ cos(A) is negative \ \ * Bottom-left quadrant = both set \ sin(A) is negative \ cos(A) is negative \ \ * Top-left quadrant = bit 7 set, bit 6 clear \ sin(A) is negative \ cos(A) is positive \ \ We now return the sign in H along with the absolute \ values in sinA and cosA, making sure that they are the \ correct way around BIT H \ If bit 7 of H is set, jump to scos4 BMI scos4 \ If we get here then bit 7 of H is clear BVS scos5 \ If bit 6 of H is set, jump to scos5 .scos3 \ If we get here then one of these is true: \ \ * Bit 7 of H is clear and bit 6 of H is clear \ \ * Bit 7 of H is set and bit 6 of H is set \ \ So the results we calculated above are correct: \ \ A = |sin(X)| \ \ X = |cos(X)| \ \ and they will be the same for |sin(A)| and |cos(A)| \ for the original argument A: STA sinA \ Store A in sinA to return |sin(A)| STX cosA \ Store X in cosA to return |cos(A)| RTS \ Return from the subroutine .scos4 \ If we get here then bit 7 of H is set BVS scos3 \ If bit 6 of H is set, jump to scos3 .scos5 \ If we get here then one of these is true: \ \ * Bit 7 of H is clear and bit 6 of H is set \ \ * Bit 7 of H is set and bit 6 of H is clear \ \ So the results we calculated above: \ \ A = |sin(X)| \ \ X = |cos(X)| \ \ need to be swapped around to be correct for |sin(A)| \ and |cos(A)| for the original argument A: \ \ A = |cos(A)| \ \ X = |sin(A)| STA cosA \ Store A in cosA to return |cos(A)| STX sinA \ Store X in sinA to return |sin(A)| RTS \ Return from the subroutine