Skip to navigation

Revs on the BBC Micro

3D objects: GetObjPitchAngle

Name: GetObjPitchAngle [Show more] Type: Subroutine Category: 3D objects Summary: Calculate an object's pitch angle Deep dive: Pitch and yaw angles
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * BuildRoadSign calls GetObjPitchAngle * GetObjectAngles calls entry point GetObjPitchAngle-2 * GetSectionAngles (Part 3 of 3) calls entry point GetObjPitchAngle-2 * GetSegmentAngles (Part 1 of 3) calls entry point GetObjPitchAngle-2 * GetSegmentAngles (Part 2 of 3) calls entry point GetObjPitchAngle-2

Arguments: X The offset of the variable to use for the object's 3D coordinates * &F4 = yHelmetCoord * &FA = yCoord1 * &FD = yCoord2 Y The offset of the second variable to use: * 0 = yPlayerCoord * 6 = yRoadSignCoord (L K) The result from GetObjectDistance, which is called between GetObjYawAngle and GetObjPitchAngle Returns: LL The pitch angle of the object A The pitch angle of the object (same as LL) scaleUp The scale up factor for the object scaleDown The scale down factor for the object C flag Is the object visible on-screen: * Clear if the object is on-screen * Set if it isn't on-screen N flag Set according to the y-coordinate, so a BPL following the call will branch if the y-coordinate is positive Other entry points: GetObjPitchAngle-2 Use yPlayerCoord (Y = 0)
LDY #0 \ Use xPlayerCoord for the second variable when calling \ the routine via GetObjPitchAngle-2 .GetObjPitchAngle \ The vectors used in this routine are configured by the \ values of X and Y, but for the purposes of simplicity, \ the comments will assume the following: \ \ * X = &FD, yCoord2 \ \ * Y = 0, yPlayerCoord LDA ySegmentCoordILo,X \ Set (WW QQ) = yCoord2 - yPlayerCoord SEC \ SBC yPlayerCoordHi,Y \ starting with the low bytes STA QQ LDA ySegmentCoordIHi,X \ And then the high bytes SBC yPlayerCoordTop,Y STA WW \ Let's call this difference in y-coordinates y-delta, \ so: \ \ (WW QQ) = (A QQ) = y-delta BPL pang1 \ If (A QQ) is positive, jump to pang1 to skip the \ following LDA #0 \ Set (A QQ) = 0 - (WW QQ) SEC \ SBC QQ \ starting with the low bytes STA QQ LDA #0 \ And then the high bytes SBC WW \ So (A QQ) is now positive, in other words: \ \ (A QQ) = |y-delta| .pang1 LSR A \ Set (A QQ) = (A QQ) >> 3 ROR QQ \ = |y-delta| / 8 LSR A ROR QQ LSR A ROR QQ STA TT \ Set (TT QQ) = (A QQ) \ = |y-delta| / 8 \ We now compare the two 16-bit values in (A QQ) and \ (L K) CMP L \ If A < L, then (A QQ) < (L K), so jump to pang3 BCC pang3 BNE pang2 \ If A <> L, i.e. A > L, then (A QQ) > (L K), so jump \ to pang2 to return from the subroutine with the C flag \ set \ The high bytes are equal, so now we compare the low \ bytes LDA QQ \ If QQ < K, then (A QQ) < (L K), so jump to pang3 CMP K BCC pang3 .pang2 \ If we get here then (A QQ) >= (L K), so: \ \ |y-delta| / 8 >= (L K) SEC \ Set the C flag RTS \ Return from the subroutine .pang3 LDY #0 \ Set Y = 0, which we use to count the number of shifts \ in the following calculation LDA L \ Set (A K) = (L K) JMP pang5 \ Jump to pang5 .pang4 \ This part is called from below, if we want to scale \ the division ASL QQ \ Set (TT QQ) = (TT QQ) << 1 ROL TT INY \ Increment Y .pang5 \ If we get here, then: \ \ * (TT QQ) = |y-delta| / 8 \ \ * WW is the high byte of y-delta \ \ * (A K) = |x-delta| \ \ * |x-delta| > |y-delta| / 8 \ \ * Y = 0 \ \ We now do the following division so we can use \ trigonometry to calculate the pitch angle: \ \ (|y-delta| / 8) / |x-delta| \ \ To get started, we shift both 16-bit values to the \ left as far as possible, which we can do without \ affecting the result as we are going to divide the two \ values, so any mutual shifts will cancel each other \ out in the division \ \ We count the number of shifts we do in Y \ \ Once that's done, we can drop the low bytes and just \ divide the high bytes, which retains as much accuracy \ as possible while avoiding the need for full 16-bit \ division \ \ So we keep shifting left until we get a 1 in bit 7 of \ (A K), as that's the larger of the two values ASL K \ Set (A K) = (A K) << 1 ROL A BCC pang4 \ If we just shifted a 0 out of the high byte of (A K), \ then we can keep shifting, so loop back to rotn6 to \ keep shifting both values ROR A \ We just shifted a 1 out of bit 7 of A, so reverse the \ shift so A contains the correct high byte (we don't \ care about the low byte any more) \ So by this point, (A K) and (TT QQ) have both been \ scaled by the same number of shifts STA V \ Set V = A, the high byte of the scaled |x-delta|, \ which we know is at least 128 (as bit 7 is set) STY scaleDown \ Set scaleDown to the number of shifts in Y TAY \ Set scaleUp = arctanP(A) LDA arctanP-128,Y \ = arctanP(|x-delta|) STA scaleUp LDA QQ \ Set T = QQ, the low byte of the scaled |y-delta|, to STA T \ use for rounding the result in Divide8x8 LDA TT \ Set A = TT, the high byte of the scaled |y-delta| JSR Divide8x8 \ Set T = 256 * A / V \ = 256 * (|y-delta| / 8) / |x-delta| \ \ using the lower byte of the |y-delta| numerator for \ rounding LDA T \ If T >= 128, jump to pang8 to return from the CMP #128 \ subroutine with the C flag set BCS pang8 BIT WW \ If y-delta is positive, jump to pang6 to skip the BPL pang6 \ following and add 60 to T LDA #60 \ Set A = 60 - T SEC SBC T JMP pang7 \ Jump to pang7 .pang6 CLC \ Set A = T + 60 ADC #60 .pang7 SEC \ Set LL = A - playerPitchAngle SBC playerPitchAngle STA LL CLC \ Clear the C flag to indicate success .pang8 RTS \ Return from the subroutine