Skip to navigation

Elite on the BBC Micro and NES

Version analysis of LOIN (Part 3 of 7)

This code appears in the following versions (click to see it in the source code):

Code variations between these versions are shown below.

Name: LOIN (Part 3 of 7) Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and up or left and down Deep dive: Bresenham's line algorithm
This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going right and up (no swap) or left and down (swap)

Code variation 1 of 12A variation in the comments only

Tap on a block to expand it, and tap it again to revert.

* X1 < X2 and Y1-1 > Y2
* X1 < X2 and Y1 > Y2
* Draw from (X1, Y1) at bottom left to (X2, Y2) at top right, omitting the first pixel

Code variation 2 of 12A variation in the comments only

This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.

This routine looks complex, but that's because the loop that's used in the cassette and disc versions has been unrolled to speed it up. The algorithm is unchanged, it's just a lot longer.

Code variation 3 of 12Related to the screen mode

This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.

LDA #%10001000 \ Modify the value in the LDA instruction at LI100 below AND COL \ to contain a pixel mask for the first pixel in the STA LI100+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%01000100 \ Modify the value in the LDA instruction at LI110 below AND COL \ to contain a pixel mask for the second pixel in the STA LI110+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%00100010 \ Modify the value in the LDA instruction at LI120 below AND COL \ to contain a pixel mask for the third pixel in the STA LI120+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%00010001 \ Modify the value in the LDA instruction at LI130 below AND COL \ to contain a pixel mask for the fourth pixel in the STA LI130+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour

Code variation 4 of 12Other (e.g. bug fix, optimisation)

The original versions contain a bug where the last pixel is skipped instead of the first pixel, but only when drawing lines that go right and up or left and down. This leads to a messy line join between this kind of line and lines with different slopes. This bug was fixed in the advanced versions.

Tap on a block to expand it, and tap it again to revert.

LDA SWAP \ If SWAP > 0 then we swapped the coordinates above, so BNE LI6 \ jump down to LI6 to skip plotting the first pixel \ \ This appears to be a bug that omits the last pixel \ of this type of shallow line, rather than the first \ pixel, which makes the treatment of this kind of line \ different to the other kinds of slope (they all have a \ BEQ instruction at this point, rather than a BNE) \ \ The result is a rather messy line join when a shallow \ line that goes right and up or left and down joins a \ line with any of the other three types of slope \ \ This bug was fixed in the advanced versions of Elite, \ where the BNE is replaced by a BEQ to bring it in line \ with the other three slopes
\ We now work our way along the line from left to right, \ using X as a decreasing counter, and at each count we \ plot a single pixel using the pixel mask in R LDA SWAP \ If SWAP = 0 then we didn't swap the coordinates above, BEQ LI190 \ so jump down to LI190 to plot the first pixel

Code variation 5 of 12Related to the screen mode

This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.

\ If we get here then we want to omit the first pixel LDA R \ Fetch the pixel byte from R, which we set in part 2 to \ the horizontal pixel number within the character block \ where the line starts (so it's 0, 1, 2 or 3) BEQ LI100+6 \ If R = 0, jump to LI100+6 to start plotting from the \ second pixel in this byte (LI100+6 points to the DEX \ instruction after the EOR/STA instructions, so the \ pixel doesn't get plotted but we join at the right \ point to decrement X correctly to plot the next three) CMP #2 \ If R < 2 (i.e. R = 1), jump to LI110+6 to skip the BCC LI110+6 \ first two pixels but plot the next two CLC \ Clear the C flag so it doesn't affect the additions \ below BEQ LI120+6 \ If R = 2, jump to LI120+6 to skip the first three \ pixels but plot the last one BNE LI130+6 \ If we get here then R must be 3, so jump to LI130+6 to \ skip plotting any of the pixels, but making sure we \ join the routine just after the plotting instructions .LI190

Code variation 6 of 12Related to the screen mode

Tap on a block to expand it, and tap it again to revert.

DEX \ Decrement the counter in X because we're about to plot \ the first pixel .LIL2 \ We now loop along the line from left to right, using X \ as a decreasing counter, and at each count we plot a \ single pixel using the pixel mask in R LDA R \ Fetch the pixel byte from R
DEX \ Decrement the counter in X because we're about to plot \ the first pixel LDA R \ Fetch the pixel byte from R, which we set in part 2 to \ the horizontal pixel number within the character block \ where the line starts (so it's 0, 1, 2 or 3)

Code variation 7 of 12Related to the screen mode

This variation is blank in the 6502 Second Processor and Master versions.

EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI6 LSR R \ Shift the single pixel in R to the right to step along \ the x-axis, so the next pixel we plot will be at the \ next x-coordinate along BCC LI7 \ If the pixel didn't fall out of the right end of R \ into the C flag, then jump to LI7 ROR R \ Otherwise we need to move over to the next character \ block, so first rotate R right so the set C flag goes \ back into the left end, giving %10000000 LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC

Code variation 8 of 12Related to the screen mode

This variation is blank in the Cassette, Disc (flight), Disc (docked), 6502 Second Processor and Master versions.

BCC LI7 \ If the addition of the low bytes of SC overflowed, INC SC+1 \ increment the high byte

Code variation 9 of 12Related to the screen mode

This variation is blank in the 6502 Second Processor and Master versions.

.LI7 LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LIC2 \ If the addition didn't overflow, jump to LIC2 DEY \ Otherwise we just overflowed, so decrement Y to move \ to the pixel line above BPL LIC2 \ If Y is positive we are still within the same \ character block, so skip to LIC2

Code variation 10 of 12Related to the screen mode

This variation is blank in the 6502 Second Processor and Master versions.

Tap on a block to expand it, and tap it again to revert.

DEC SCH \ Otherwise we need to move up into the character block LDY #7 \ above, so decrement the high byte of the screen \ address and set the pixel line to the last line in \ that character block
\ We now need to move up into the character block above, \ and each character row in screen memory takes up &140 \ bytes (&100 for the visible part and &20 for each of \ the blank borders on the side of the screen), so \ that's what we need to subtract from SC(1 0) LDA SC \ Set SC(1 0) = SC(1 0) - &140 SBC #&40 \ STA SC \ Starting with the low bytes LDA SC+1 \ And then subtracting the high bytes SBC #&01 STA SC+1 LDY #7 \ Set the pixel line to the last line in that character \ block

Code variation 11 of 12Related to the screen mode

This variation is blank in the 6502 Second Processor and Master versions.

.LIC2 DEX \ Decrement the counter in X BNE LIL2 \ If we haven't yet reached the right end of the line, \ loop back to LIL2 to plot the next pixel along LDY YSAV \ Restore Y from YSAV, so that it's preserved

Code variation 12 of 12Related to the screen mode

This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.

BEQ LI100 \ If R = 0, jump to LI100 to start plotting from the \ first pixel in this byte CMP #2 \ If R < 2 (i.e. R = 1), jump to LI110 to start plotting BCC LI110 \ from the second pixel in this byte CLC \ Clear the C flag so it doesn't affect the additions \ below BEQ LI120 \ If R = 2, jump to LI120 to start plotting from the \ third pixel in this byte JMP LI130 \ If we get here then R must be 3, so jump to LI130 to \ start plotting from the fourth pixel in this byte .LI100 LDA #%10001000 \ Set a mask in A to the first pixel in the 4-pixel byte \ (note that this value is modified by the code at the \ start of this section to be a bit mask for the colour \ in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X .LIEXS BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI110 \ If the addition didn't overflow, jump to LI110 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI101 \ If Y is negative we need to move up into the character \ block above, so jump to LI101 to decrement the screen \ address accordingly (jumping back to LI110 afterwards) .LI110 LDA #%01000100 \ Set a mask in A to the second pixel in the 4-pixel \ byte (note that this value is modified by the code at \ the start of this section to be a bit mask for the \ colour in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI120 \ If the addition didn't overflow, jump to LI120 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI111 \ If Y is negative we need to move up into the character \ block above, so jump to LI111 to decrement the screen \ address accordingly (jumping back to LI120 afterwards) .LI120 LDA #%00100010 \ Set a mask in A to the third pixel in the 4-pixel byte \ (note that this value is modified by the code at the \ start of this section to be a bit mask for the colour \ in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI130 \ If the addition didn't overflow, jump to LI130 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI121 \ If Y is negative we need to move up into the character \ block above, so jump to LI121 to decrement the screen \ address accordingly (jumping back to LI130 afterwards) .LI130 LDA #%00010001 \ Set a mask in A to the fourth pixel in the 4-pixel \ byte (note that this value is modified by the code at \ the start of this section to be a bit mask for the \ colour in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI140 \ If the addition didn't overflow, jump to LI140 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI131 \ If Y is negative we need to move up into the character \ block above, so jump to LI131 to decrement the screen \ address accordingly (jumping back to LI140 afterwards) .LI140 DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC BCC LI100 \ If the addition didn't overflow, jump back to LI100 \ to plot the next pixel INC SC+1 \ Otherwise the low byte of SC(1 0) just overflowed, so \ increment the high byte SC+1 as we just crossed over \ into the right half of the screen CLC \ Clear the C flag to avoid breaking any arithmetic BCC LI100 \ Jump back to LI100 to plot the next pixel .LI101 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI110 \ Jump back to the instruction after the BMI that called \ this routine .LI111 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI120 \ Jump back to the instruction after the BMI that called \ this routine .LI121 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI130 \ Jump back to the instruction after the BMI that called \ this routine .LI131 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI140 \ Jump back to the instruction after the BMI that called \ this routine .LIEX
 RTS                    \ Return from the subroutine