Skip to navigation


Maths (Geometry): ProjectVertexOntoScreen

Name: ProjectVertexOntoScreen [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Project a vertex coordinate from a 3D object onto the screen Deep dive: Drawing 3D objects Drawing the landscape Projecting onto the screen
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * DrawLandscapeAndBuffers (Part 2 of 4) calls ProjectVertexOntoScreen * DrawObject (Part 2 of 5) calls ProjectVertexOntoScreen

This routine projects a 3D coordinate (x, y, z) into screen coordinates, as follows: screen x-coordinate = 160 + x / z screen y-coordinate = 64 + y / z The vertex is deemed visible if its z-coordinate is less than &80000000, and is not projected if it is further away than this. This routine differs from the particle projection routine in that it projects vertices irrespective of their viewing angle.
Arguments: (R0, R1, R2) Particle coordinates in (x, y, z)
Returns: (R0, R1) Projected screen coordinates (x, y) C flag C flag is set if the vertex is too far away to draw, clear if it is close enough
.ProjectVertexOntoScreen LDMIA R0, {R0-R2} \ Fetch the coordinates we want to project \ from (R0, R1, R2), which we'll call \ (x, y, z) in the following CMN R2, #&80000000 \ If R2 + &80000000 produces a carry, then MOVCS PC, R14 \ the vertex is too far away to be visible, \ so return from the subroutine with the \ C flag set STMFD R13!, {R5-R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOVS R3, R0 \ Set R3 = |R0| RSBMI R3, R3, #0 \ = |x| MVNMI R5, R3 \ If R0 is negative, set R5 = ~R3 MOVPL R5, R3 \ If R0 is positive, set R5 = R3 MOVS R4, R1 \ Set R4 = |R1| RSBMI R4, R4, #0 \ = |y| MVNMI R6, R4 \ If R1 is negative, set R6 = ~R4 MOVPL R6, R4 \ If R1 is positive, set R6 = R4 ORR R5, R5, R6 \ Set R5 = R5 OR R6 OR R2 ORR R5, R5, R2 \ = |R0| OR |R1| OR R2 ORR R5, R5, #1 \ = |x| OR |y| OR z \ \ And round it up so it is non-zero \ \ So this sets R5 to a number that is \ greater than |x|, |y| and z, so R5 is \ therefore an upper bound on the values of \ all three coordinates \ We now work out how many times we can \ scale up R5 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R6, #0 \ Set R6 = 0 to use as the scale factor in \ the following loop .pver1 MOVS R5, R5, LSL #1 \ Shift R5 to the left until the top bit is ADDPL R6, R6, #1 \ set, incrementing R6 for each shift so R6 BPL pver1 \ contains the scale factor we have applied \ to R5 \ \ So this scales R5 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R6 MOV R2, R2, LSL R6 \ We now scale up the updated coordinate MOV R3, R3, LSL R6 \ (|x|, |y|, z) in (R3, R4, R2) by the scale MOV R4, R4, LSL R6 \ factor in R6, as we know the result will \ stay within 32-bit words CMP R2, R3 \ If R2 < R3 or R2 < R4, then at least one CMPHS R2, R4 \ of these is true: BLO pver4 \ \ |x| > z or |y| > z \ \ so jump to pver4 to deal with this special \ case, when the vertex has a greater \ viewing angle then 45 degrees in either \ direction MOV R7, R2 \ Set R7 = R2 \ = z \ \ where the value of z is scaled up \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&100 \ Set bit 8 in R14 to act as our shifted \ division bit, so we populate nine bits of \ the result .pver2 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver2 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #23 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ (as the result contains nine bits), so \ now we have: \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the \ result \ \ This is the first part of the algorithm .pver3 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have: SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver3 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor MOV R5, R5, LSR #24 \ Shift both division results down so we MOV R6, R6, LSR #24 \ only keep the top byte of each result TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set: RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set: \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates: \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set: RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set: \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates: \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen (this CMN instruction \ is a hacky way of clearing the C flag) LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine .pver4 \ If we get here then: \ \ |x| > z or |y| > z \ \ So the vertex has a greater viewing angle \ then 45 degrees in either direction \ \ The following registers are also set: \ \ (R3, R4, R2) = (|x|, |y|, z) MOV R5, #24 \ Set R5 = 24 CMP R3, R4 \ Set R6 = R3 / 2 MOVHS R6, R3, LSR #1 \ = |x| / 2 MOVLO R6, R3, LSR #1 \ \ The comparison seems to be unnecessary as \ both conditions do the same thing (if the \ shifts used the C flag then this would \ make more sense, but LSR overwrites the C \ flag rather than using it) .pver5 CMP R2, R6 \ Shift R6 to the right and increment R5 ADDLO R5, R5, #1 \ until R2 >= R6, so this scales R6 down, MOV R6, R6, LSR #1 \ counting the scale factor in R5 BLO pver5 \ \ Specifically, this scales |x| / 2 down \ until it is smaller than z MOV R2, R2, LSR #23 \ We now scale down the updated coordinate MOV R3, R3, LSR R5 \ (|x|, |y|, z) in (R3, R4, R2) by the scale MOV R4, R4, LSR R5 \ factor in R5 (for x and y) and by 23 \ places (for z) MOV R7, R2 \ Set R7 = R2 STMFD R13!, {R5} \ Store the scale factor in R5 on the stack \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm, scaling both \ the numerator and denominator up by 24 \ places before doing the division MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&80 \ Set bit 7 in R14 to act as our shifted \ division bit, so we populate eight bits of \ the result MOV R4, R4, LSL #24 \ Scale the numerator and denominator up by MOV R7, R7, LSL #24 \ 24 places to ensure more accuracy .pver6 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver6 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #24 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ (as the result contains eight bits), so \ now we have: \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the MOV R3, R3, LSL #22 \ result MOV R2, R2, LSL #22 \ \ This is the first part of the algorithm .pver7 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have: SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver7 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor LDMFD R13!, {R14} \ Fetch the scale factor that we stored on \ the stack into R14 SUB R14, R14, #23 \ I have no idea what this does, as it RSB R14, R14, #23 \ appears to have no effect at all MOV R5, R5, LSR R14 \ Apply the scale factor in R14 to R5 MOV R6, R6, LSR R14 \ Apply the scale factor in R14+1 to R6 MOV R6, R6, LSR #1 TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set: RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set: \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates: \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set: RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set: \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates: \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen (this CMN instruction \ is a hacky way of clearing the C flag) LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine