Includes dash pattern - 1147 bytes (7%)
- §1. Line initialisation
- §2. Copy and subtract coordinates
- §3. Negate vdu[X+4,X+5]
- §4. Move to next pixel along a line
- §5. incrementCoordinateX
- §6. Move to a new column, updating the X coordinate and the error term
- §7. decrementCoordinateX
- §8. clipLineCoordinateX
- §9. Swap the X and Y coordinates of the current point on a line and invert the error term
- §10. Clip line to graphics window in Y
- §11. Plot line
- §12. Plot a line
- §13. Unused
- §14. Unused
- §15. Move down one cell
- §16. Copy dot-dash line state and pattern to page C
- §17. Copy dot-dash line state back to private workspace
Used when drawing a line, triangle, circle segment or circle sector.
Used to initialise variables to track the rasterisation of a straight line. e.g. Takes the difference of the coordinates to find the delta X and delta Y of the line. Initialises the error term of Bresenham's algorithm. On Entry: A: vdu index of line end point Y: vdu index of line start point X: vdu index, where to store results in practice X is one of .line1StartPointX .line2StartPointX .line3StartPointX On Exit: vdu[X+0,X+1]: start point X vdu[X+2,X+3]: start point Y vdu[X+4,X+5]: abs(deltaX) vdu[X+6,X+7]: abs(deltaY) vdu[X+8,X+9]: initial error term vdu[X+10]: bit 7 is sgn(deltaY) and bit 6 is sgn(deltaX) Preserves X
.lineInitialisation = $9872 PHA } STA .gxrScratchspace3 } TYA } remember A,Y PHA } LDA .gxrScratchspace3 } vdu[X+0,X+1] = vdu[Y+0,Y+1] (start point X) vdu[X+2,X+3] = vdu[Y+2,Y+3] (start point Y) vdu[X+4,X+5] = vdu[A+0,A+1] - vdu[Y+0,Y+1] (deltaX) vdu[X+6,X+7] = vdu[A+2,A+3] - vdu[Y+2,Y+3] (deltaY) JSR .copyAndSubtractCoordinates PLA } TAY } recall A,Y PLA } Move on by two to do the same for the Y coordinates INY INY Y += 2 CLC ADC #2 A += 2 INX INX X += 2 JSR .copyAndSubtractCoordinates DEX X -= 2 DEX The rest of this routine takes two 16 bit signed numbers: a = vdu[X+4,X+5] = deltaX b = vdu[X+6,X+7] = deltaY and performs the following calculation: isALarger = (a > b) ; remember which is larger vdu[X+10] = 128*sgn(b) + 64*sgn(a) ; remember the sign bits a = abs(a) ; take absolute value b = abs(b) ; take absolute value temp = max(a, b) if isALarger then temp -= 1 vdu[X+8,X+9] (error term) = temp/2 - b Work out which is larger: vdu[X+6,X+7] or vdu[X+4,X+5] LDA .vduVariablesStart + 6,X CMP .vduVariablesStart + 4,X LDA .vduVariablesStart + 7,X SBC .vduVariablesStart + 5,X PHP carry set if vdu[X+6,X+7] >= vdu[X+4,X+5] Remember signs of vdu[X+4,X+5] and vdu[X+6,X+7] in top two bits of vdu[X+10] LDA .vduVariablesStart + 5,X ASL shift top bit of vdu[X+5] into vdu[X+10] ROR .vduVariablesStart + 10,X LDA .vduVariablesStart + 7,X ASL shift top bit of vdu[X+7] into vdu[X+10] ROR .vduVariablesStart + 10,X BPL .doneAbsY Negate vdu[X+6,X+7] to get the absolute value INX INX JSR .negateVDUX45 DEX DEX .doneAbsY = $98b1 LDA .vduVariablesStart + 10,X get sign of vdu[X+4,X+5] ROL BPL + Negate vdu[X+4,X+5] to get the absolute value JSR .negateVDUX45 + Work out which is larger: vdu[X+6,X+7] or vdu[X+4,X+5] LDA .vduVariablesStart + 6,X CMP .vduVariablesStart + 4,X LDA .vduVariablesStart + 7,X SBC .vduVariablesStart + 5,X BMI .fourFiveIsLarger AY = vdu[X+6,X+7] LDA .vduVariablesStart + 7,X LDY .vduVariablesStart + 6,X JMP .gotMax .fourFiveIsLarger = $98d1 AY = vdu[X+4,X+5] LDA .vduVariablesStart + 5,X LDY .vduVariablesStart + 4,X .gotMax = $98d7 PLP BMI .skipSubtractOne AY -= 1 INY } [NOTE: use 'CPY #0' for two fewer } cycles] DEY } BNE + SEC SBC #1 A -= 1 + DEY Y -= 1 .skipSubtractOne = $98e2 vdu[X+8,X+9] = AY/2 - vdu[X+6,X+7] LSR PHA TYA ROR SEC SBC .vduVariablesStart + 6,X STA .vduVariablesStart + 8,X PLA SBC .vduVariablesStart + 7,X STA .vduVariablesStart + 9,X RTS
§2. Copy and subtract coordinates.
vdu[X,X+1] = vdu[Y,Y+1] vdu[X+4,X+5] = vdu[A,A+1] - vdu[X,X+1]
.copyAndSubtractCoordinates = $98f5 STA .gxrScratchspace3 LDA .vduVariablesStart,Y STA .vduVariablesStart,X LDA .vduVariablesStart+1,Y STA .vduVariablesStart+1,X LDY .gxrScratchspace3 SEC LDA .vduVariablesStart,Y SBC .vduVariablesStart,X STA .vduVariablesStart+4,X LDA .vduVariablesStart+1,Y SBC .vduVariablesStart+1,X STA .vduVariablesStart+5,X RTS
On Entry: X: vdu offset On Exit: A,Y: low byte of negated value
.negateVDUX45 = $991b SEC LDA #0 SBC .vduVariablesStart + 4,X TAY LDA #0 SBC .vduVariablesStart + 5,X STA .vduVariablesStart + 5,X TYA STA .vduVariablesStart + 4,X RTS
§4. Move to next pixel along a line.
Update the current point to move to the next pixel along the line. Also updates the error term. Notes: if the error term is negative, then jump to a routine to handle that, then return else: add abs(deltaX) to error term if the error term is now negative, then call a routine to handle that increment or decrement current X coordinate (based on sign of deltaY) On Entry: the straight line variables are initialised: vdu[X+4,X+5]: abs(deltaX) vdu[X+6,X+7]: abs(deltaY) vdu[X+8,X+9]: error term vdu[X+10]: sign bits of deltaX and deltaY On Exit: error term updated X: incremented by 2
.lineMoveToNextPixel = $992f LDA .vduVariablesStart + 9,X get error term (high byte) BPL .errorTermMinusEqualsAbsDeltaY if (error term < 0) then branch .errorTermPlusEqualsAbsDeltaX = $9934 CLC } LDA .vduVariablesStart + 8,X } ADC .vduVariablesStart + 4,X } STA .vduVariablesStart + 8,X } error term += abs(deltaX) LDA .vduVariablesStart + 9,X } ADC .vduVariablesStart + 5,X } STA .vduVariablesStart + 9,X } BMI + if (error term < 0) then branch JSR .errorTermMinusEqualsAbsDeltaY + INX INX LDA .vduVariablesStart + 8,X BMI .decrementCoordinateX increment or decrement Y coordinate fall through...
.incrementCoordinateX = $9953 INC .vduVariablesStart,X BNE + INC .vduVariablesStart+1,X + RTS
§6. Move to a new column, updating the X coordinate and the error term.
error term -= abs(deltaY) currentPointX += 1 (or -= 1 depending on the sign of deltaX)
.errorTermMinusEqualsAbsDeltaY = $995c SEC } LDA .vduVariablesStart + 8,X } SBC .vduVariablesStart + 6,X } STA .vduVariablesStart + 8,X } error term -= abs(deltaY) LDA .vduVariablesStart + 9,X } SBC .vduVariablesStart + 7,X } STA .vduVariablesStart + 9,X } LDA .vduVariablesStart + 10,X sign of deltaX ASL BPL .incrementCoordinateX increment or decrement X coordinate fall through...
.decrementCoordinateX = $9975 LDA .vduVariablesStart,X BNE + DEC .vduVariablesStart+1,X + DEC .vduVariablesStart,X RTS
.clipLineCoordinateX = $9981 JSR .swapXAndYCoordinatesOfPointXAndInvertErrorTerm LDA .vduVariablesStart + 10,X signs of the two lines ASL carry = sgn(deltaY) (ignored) ASL carry = sgn(deltaX) LDA .vduVariablesStart + 10,X ROR put sgn(deltaX) back into the top bit in the sgn(deltaY) position. STA .vduTempStoreDA clearing the sgn(deltaX) position. CLC having done the swap BPL .distanceFromleftEdge if (sgn(deltaY) >= 0) then branch LDA .vduVariablesStart + 2,X } SBC .vduGraphicsWindowPixelsRightLow } TAY } YA = Y coordinate - graphics } window right edge LDA .vduVariablesStart + 3,X } SBC .vduGraphicsWindowPixelsRightHigh } JMP .clipYCoordinateAndSwapBack } .distanceFromleftEdge = $99a2 LDA .vduGraphicsWindowPixelsLeftLow } SBC .vduVariablesStart + 2,X } YA = graphics window left edge - Y } coordinate TAY } LDA .vduGraphicsWindowPixelsLeftHigh } SBC .vduVariablesStart + 3,X } .clipYCoordinateAndSwapBack = $99af JSR .clipYCoordinate JSR .swapXAndYCoordinatesOfPointXAndInvertErrorTerm restore original sense of X and Y coordinates JMP .errorTermMinusEqualsAbsDeltaY
§9. Swap the X and Y coordinates of the current point on a line and invert the error term.
On Entry: vdu[X+0,X+1] = x1 coordinate vdu[X+2,X+3] = y1 coordinate vdu[X+4,X+5] = x2 coordinate vdu[X+6,X+7] = y2 coordinate On Exit: vdu[X+0,X+1] = y1 coordinate vdu[X+2,X+3] = x1 coordinate vdu[X+4,X+5] = y2 coordinate vdu[X+6,X+7] = x2 coordinate vdu[X+8,X+9] ^= $FFFF (i.e. inverted)
.swapXAndYCoordinatesOfPointXAndInvertErrorTerm = $99b8 INX } TXA } push X+1 PHA } Swap X coordinate with Y coordinate INX TXA A = X+2 DEX DEX return to original X TAY Y = X+2 [NOTE: the above can be optimised to save 2 bytes: .swapXAndYCoordinatesOfPointXAndInvertErrorTerm INX ; } TXA ; } A=X+1 DEX ; } PHA ; push X+1 TAY ; Y=X+1 INY ; Y=X+2 ] JSR .exchangeTwoVDUBytes this also increments X, Y by two Exchange Y coordinates INX INX X+=2 INY INY Y+=2 JSR .exchangeTwoVDUBytes X+=2, Y+=2 Invert error term STA .gxrScratchspace3 [NOTE: Redundant] PLA } TAX } recall X+1 from above LDA .gxrScratchspace3 [NOTE: Redundant] Invert error term JSR .invertVDUX8 invert high byte DEX decrement X and invert low byte .invertVDUX8 = $99d6 LDA .vduVariablesStart + 8,X error term (high or low) EOR #$FF STA .vduVariablesStart + 8,X error term (high or low) RTS
§10. Clip line to graphics window in Y.
Use the results of the line initialisation to clip the line to the graphics window. Note: The line initialisation leaves us with vduVariables: offset variable name meaning ------------------------------------------ +0,+1 .line1StartPointX start point X +2,+3 .line1StartPointY start point Y +4,+5 .line1AbsDeltaX abs(deltaX) +6,+7 .line1AbsDeltaY abs(deltaY) +8,+9 .line1ErrorTerm error term +10 .line1Signs bit 7 is sgn(deltaY) and bit 6 is sgn(deltaX)
.clipLineCoordinateY = $99df CLC LDA .vduVariablesStart + 10,X get the signs of the line's deltas STA .vduTempStoreDA store signs of deltas BPL .offsetFromBottomOfGraphicsWindow if (deltaY >= 0) then branch LDA .vduVariablesStart + 2,X } SBC .vduGraphicsWindowPixelsTopLow } TAY } YA = line1StartPointY - graphics } window top LDA .vduVariablesStart + 3,X } SBC .vduGraphicsWindowPixelsTopHigh } JMP .gotHowMuchToClipInY .offsetFromBottomOfGraphicsWindow = $99f7 LDA .vduGraphicsWindowPixelsBottomLow } SBC .vduVariablesStart + 2,X } TAY } YA = graphics window bottom - } line1StartPointY LDA .vduGraphicsWindowPixelsBottomHigh } SBC .vduVariablesStart + 3,X } .gotHowMuchToClipInY = $9a04 JSR .clipYCoordinate JMP .errorTermPlusEqualsAbsDeltaX .clipYCoordinate = $9a0a STY .vduTempStoreDE } store how much to clip in Y STA .vduTempStoreDF } LDA .vduVariablesStart + 2,X AY = line1StartPointY LDY .vduVariablesStart + 3,X ASL .vduTempStoreDA shift signs of the deltas BCS + if (deltaY < 0) then branch line1StartPointY += amount to clip in Y ADC .vduTempStoreDE STA .vduVariablesStart + 2,X TYA ADC .vduTempStoreDF JMP ++ + line1StartPointY -= amount to clip in Y SBC .vduTempStoreDE STA .vduVariablesStart + 2,X TYA SBC .vduTempStoreDF ++ STA .vduVariablesStart + 3,X LDA .vduVariablesStart + 9,X line1ErrorTerm+1 (high byte) If top bit of A set, then A=255 else A=0 PHP LDA #0 PLP BPL + SEC } [NOTE: could be LDA #255 for one } byte saving] SBC #1 } + [NOTE: Alternatively this saves 3 bytes and a few cycles: LDA #0 LDY .vduVariablesStart + 9,X BPL + LDA #255 ] STA .vduTempStoreDC LSR STA .vduTempStoreDD At this point DC/DD is either $0000, or $7FFF, which is added to result of the following 16 bit multiply This is to add rounding, with the result in the top two bytes. We multiply the error term by the amount we are clipping by in Y which to give details is (line1ErrorTerm + fractional part given in .vduTempStoreDC/D) * .vduTempStoreDE/F this will give us the new error term 16 bit x 16 bit = 32 bit multiply (but only top 16 bits used) (similar to https://github.com/TobyLobster/multiply_test/blob/main/tests/mult43.a but for 16 bit) LDY #16 loop counter .multiply16loop = $9a41 Rotate the four bytes one bit at a time LDA .vduTempStoreDD ASL carry = top bit of DD ROL .vduVariablesStart + 8,X } ROL .vduVariablesStart + 9,X } ROL .vduTempStoreDC } shift result ROL .vduTempStoreDD } ASL .vduTempStoreDE shift multiplier ROL .vduTempStoreDF BCC + Add multiplicand to result CLC } LDA .vduTempStoreDC } ADC .vduVariablesStart + 4,X } STA .vduTempStoreDC } DC/D += vdu[X+4,X+5] LDA .vduTempStoreDD } ADC .vduVariablesStart + 5,X } STA .vduTempStoreDD } BCC + Add one to result INC .vduVariablesStart + 8,X BNE + INC .vduVariablesStart + 9,X + DEY BNE .multiply16loop loop back until done 16 times (end of multiply) STA .gxrScratchspace3 LDA .vduVariablesStart + 9,X get .line1ErrorTerm high byte ASL PHP } LDA .gxrScratchspace3 } [NOTE: redundant instructions, } just use BCC .clipDivide instead?] PLP } BPL .clipDivide } Store new error term LDA .vduTempStoreDC STA .vduVariablesStart + 8,X store new error term LDA .vduTempStoreDD STA .vduVariablesStart + 9,X store new error term RTS .clipDivide = $9a89 16 bit divide: error term / line1AbsDeltaY LDY #16 loop counter .divideLoop16 = $9a8b ROL .vduTempStoreDC dividend ROL .vduTempStoreDD dividend ROL .vduVariablesStart + 8,X remainder ROL .vduVariablesStart + 9,X remainder SEC } LDA .vduVariablesStart + 8,X } temp = remainder - divisor SBC .vduVariablesStart + 6,X } STA .vduTempStoreDE } LDA .vduVariablesStart + 9,X } SBC .vduVariablesStart + 7,X } BCC .skip if (temp < 0) then branch STA .vduVariablesStart + 9,X LDA .vduTempStoreDE remainder = temp STA .vduVariablesStart + 8,X .skip = $9aae DEY BNE .divideLoop16 loop back until done all 16 bits Last iteration of divide dividend *= 2 ROL .vduTempStoreDC ROL .vduTempStoreDD (end of divide) textLeft[X] -= divisor SEC LDA .vduVariablesStart + 8,X SBC .vduVariablesStart + 6,X STA .vduVariablesStart + 8,X LDA .vduVariablesStart + 9,X SBC .vduVariablesStart + 7,X STA .vduVariablesStart + 9,X LDA .vduVariablesStart,X AY = .line1StartPointX LDY .vduVariablesStart+1,X ASL .vduTempStoreDA BCS .subtractDCDDPlusOne if (deltaX < 0) then branch Add SEC } ADC .vduTempStoreDC } STA .vduVariablesStart,X } .line1StartPointX += 1 + DC/DD TYA } ADC .vduTempStoreDD } JMP .storeAndReturn } .subtractDCDDPlusOne = $9ade Subtract CLC } SBC .vduTempStoreDC } STA .vduVariablesStart,X } .line1StartPointX -= 1 + DC/DD TYA } SBC .vduTempStoreDD } .storeAndReturn = $9ae7 STA .vduVariablesStart+1,X } .return16 = $9aea RTS
.plotLine = $9aeb JSR .copyDashPatternAndStateIntoPageC LDA .vdu25ParameterPlotType JSR .plotLineInternal JSR .copyDashStateBackIntoWorkspace JMP .setGraphicsCursorPositionAndFinishPLOT
This overrides the OS line plotting, to add the dot-dash pattern and fill pattern features. Note that dot-dash and fill patterns can be mixed: 10 MODE 1 20 GCOL 16,0 30 VDU 23,6,&F0;0;0;0; 40 FOR A=1 TO 200 50 MOVE 640,512 60 PLOT 21,RND(1280),RND(1024) 70 NEXT (See https://stardot.org.uk/forums/viewtopic.php?p=341226#p341226 ) On Entry: A: plot type (one of the line plot types) | | | Plot | Plot | | | AND $c0: | First | Last | PLOT Codes | x4 | EOR $40 | Point | Point | Pattern --------------------+------------------------------------------------- $00 - $07 ( 0 - 7) | %00000000 | %01000000 | Y | Y | none $08 - $0F ( 8 - 15) | %00100000 | %01000000 | Y | N | none $10 - $17 (16 - 23) | %01000000 | %00000000 | Y | Y | restarts on each new line $18 - $1F (24 - 31) | %01100000 | %00000000 | Y | N | restarts on each new line $20 - $27 (32 - 39) | %10000000 | %11000000 | N | Y | none $28 - $2F (40 - 47) | %10100000 | %11000000 | N | N | none $30 - $37 (48 - 55) | %11000000 | %10000000 | N | Y | continues on each new line $38 - $3F (56 - 63) | %11100000 | %10000000 | N | N | continues on each new line
.plotLineInternal = $9afa ASL } ASL } STA .vduTempStoreDB } x 4 AND #$C0 } EOR #$40 } see table in comment above to see } how plot codes change BNE .skipPatternReset } Reset pattern LDA #$80 STA .currentDotDashPatternBitMask mask = top bit LDA #0 STA .currentDotDashPatternByte current dot-dash byte = 0 LDA .currentDotDashPatternBitLength STA .currentDotDashPatternNumBitsRemaining remaining = length .skipPatternReset = $9b14 Check if the initial point is within the graphics window LDX #.vduGraphicsCursorPixelsXLow - .vduVariablesStart graphics cursor = initial point of line JSR .checkPointXIsWithinGraphicsWindow STA .vduTempStoreDC store result of graphics windows boundary check .vduTempStoreDC is the result of the initial point boundary check: %0000 success (point is within window) %0001 graphics cursor X is left of the left edge of the graphics window %0010 graphics cursor X is right of the right edge of the graphics window %0100 graphics cursor Y is below the bottom edge of the graphics window %1000 graphics cursor Y is above the top edge of the graphics window BEQ + if (in graphics window) then branch Initial point is outside graphics window, so change the plot code to not draw the initial point Remove top bit from PLOT type [NOTE: the logic is the wrong way round. It should OR in (set) bit 5. The effect is benign though.] LDA #%01111111 AND .vduTempStoreDB STA .vduTempStoreDB + Check if the final point is within the graphics window LDX #.vdu25ParameterXLow - .vduVariablesStart PLOT parameter = final point of line JSR .checkPointXIsWithinGraphicsWindow STA .gxrScratchspace1 store result of graphics windows bounds check .gxrScratchspace1 is the result of the final point boundary check: %0000 success (point is within window) %0001 graphics cursor X is left of the left edge of the graphics window %0010 graphics cursor X is right of the right edge of the graphics window %0100 graphics cursor Y is below the bottom edge of the graphics window %1000 graphics cursor Y is above the top edge of the graphics window BEQ .pointIsInsideGraphicsWindow if (point is within graphics window) then branch Final point is outside the graphics window, so change the plot code to not draw the final point [NOTE: the logic is the wrong way round. It should OR in (set) bit 5. The effect is benign though.] TAX Remove bit 5 LDA #%11011111 AND .vduTempStoreDB STA .vduTempStoreDB TXA BIT .vduTempStoreDC bit test (like doing an AND) the two graphics bounds checks together .ifNotEqualReturn = $9b37 BNE .return16 if (both points are in the same offscreen region) then branch (return, since the line is entirely offscreen) .pointIsInsideGraphicsWindow = $9b39 LDY #.vduGraphicsCursorPixelsXLow - .vduVariablesStart start point LDA #.vdu25ParameterXLow - .vduVariablesStart LDX #.line1StartPointX - .vduVariablesStart JSR .lineInitialisation The line initialisation sets up line 1 variables: .line1StartPointX: initial and current point X [2 bytes] .line1StartPointY: initial and current point Y [2 bytes] .line1AbsDeltaX : abs(deltaX) [2 bytes] .line1AbsDeltaY : abs(deltaY) [2 bytes] .line1ErrorTerm : Bresenham error term [2 bytes] .line1Signs : signs of deltaY (top bit) and deltaX (bit 6) [1 byte] .vduTempStoreDC holds the result of the initial point boundary check: %0000 success (point is within window) %0001 graphics cursor X is left of the left edge of the graphics window %0010 graphics cursor X is right of the right edge of the graphics window %0100 graphics cursor Y is below the bottom edge of the graphics window %1000 graphics cursor Y is above the top edge of the graphics window LDA .vduTempStoreDC initial point graphics boundary check result AND #$0C get the Y result PHP remember if zero or not LDA .vduTempStoreDC PLP recall BEQ .initialPointIsWithinGraphicsWindowY if (the Y coordinate is inside the graphics window) then branch Clip Y coordinate LDX #.line1StartPointX - .vduVariablesStart JSR .clipLineCoordinateY Check the point is in the graphics window now it has been clipped LDX #.line1StartPointX - .vduVariablesStart JSR .checkPointXIsWithinGraphicsWindow BIT .gxrScratchspace1 check previous result BNE .ifNotEqualReturn if (the two ends are now in the same offscreen region) then return (nothing to draw) .initialPointIsWithinGraphicsWindowY = $9b5b STA .gxrScratchspace3 result of graphics window boundary check for the clipped initial point AND #3 get just the X result PHP remember if X is in the graphics window or not LDA .gxrScratchspace3 PLP recall if X is in the graphics window or not BEQ .initialPointIsWithinGraphicsWindowX if (the X coordinate is inside the graphics window) then branch LDX #.line1StartPointX - .vduVariablesStart JSR .clipLineCoordinateX Check the point is in the graphics window now it has been clipped LDX #.line1StartPointX - .vduVariablesStart JSR .checkPointXIsWithinGraphicsWindow .initialPointIsWithinGraphicsWindowX = $9b71 TAY } BNE .ifNotEqualReturn } if (clipped line is still off } screen) then return Get X and Y as offsets to the final point, taking into account clipping LDY #.vdu25ParameterXLow - .vduVariablesStart } use final point by default LDX #.vdu25ParameterYLow - .vduVariablesStart } (note reversed Y and X in } this section) LDA .gxrScratchspace1 result of final point graphics windows bounds check BEQ .skipFinalPointCheck if (final point within graphics window) then branch LDY #.vduGraphicsWindowPixelsRightLow - .vduVariablesStart use top right of graphics window LDX #.vduGraphicsWindowPixelsTopLow - .vduVariablesStart BIT .line1Signs BPL + if (deltaY >= 0) then branch LDX #.vduGraphicsWindowPixelsBottomLow - .vduVariablesStart use bottom of graphics window + BVC .skipFinalPointCheck if (deltaX >= 0) then branch LDY #.vduGraphicsWindowPixelsLeftLow - .vduVariablesStart use left of graphics window .skipFinalPointCheck = $9b8c Y and X registers are the indices to the X,Y coordinates for the final point First get abs(deltaY) CLC LDA .vduVariablesStart,X SBC .line1StartPointY start point Y BCC + Negate A to get absolute value ADC #0 EOR #$FF + STA .vduTempStoreDC store abs(deltaY) Get -abs(deltaX) CLC LDA .vduVariablesStart,Y final clipped X point (low) SBC .line1StartPointX start point X (low) TAX LDA .vduVariablesStart+1,Y final clipped X point (high) SBC .line1StartPointX+1 start point X (high) BMI ++ if negative then branch (no need to negate result) Negate XA by adding one then inverting the bits INX BNE + CLC ADC #1 + Invert A EOR #$FF Invert X TAY remember A TXA } EOR #$FF } invert X TAX } TYA recall A ++ STA .vduTempStoreDD } store -abs(deltaX) STX .gxrScratchspace1 } this counts upwards towards zero, } used as a loop counter LDX #.line1StartPointX - .vduVariablesStart JSR .gxrSetScreenAddressAndSetGraphicsColourMask get screen address of start point | Plot | Plot | | First | Last | PLOT Codes | vduTempStoreDB | Point | Point | Pattern -------------------------+------------------------------------------ $00 - $07 ( 0 - 7) | %00000000 | Y | Y | none $08 - $0F ( 8 - 15) | %00100000 | Y | N | none $10 - $17 (16 - 23) | %01000000 | Y | Y | restarts on each new line $18 - $1F (24 - 31) | %01100000 | Y | N | restarts on each new line $20 - $27 (32 - 39) | %10000000 | N | Y | none $28 - $2F (40 - 47) | %10100000 | N | N | none $30 - $37 (48 - 55) | %11000000 | N | Y | continues on each new line $38 - $3F (56 - 63) | %11100000 | N | N | continues on each new line ASL .vduTempStoreDB BCS .skipLinePlotPixel skip if initial point is omitted .plotLineLoop = $9bc7 | Plot | Plot | | First | Last | PLOT Codes | vduTempStoreDB | Point | Point | Pattern -------------------------+------------------------------------------ $00 - $07 ( 0 - 7) | %00000000 | Y | Y | none $08 - $0F ( 8 - 15) | %01000000 | Y | N | none $10 - $17 (16 - 23) | %10000000 | Y | Y | restarts on each new line $18 - $1F (24 - 31) | %11000000 | Y | N | restarts on each new line $20 - $27 (32 - 39) | %00000000 | N | Y | none $28 - $2F (40 - 47) | %01000000 | N | N | none $30 - $37 (48 - 55) | %10000000 | N | Y | continues on each new line $38 - $3F (56 - 63) | %11000000 | N | N | continues on each new line BIT .vduTempStoreDB test plot type BVC .finalPointIncluded if (final point included) then branch Final point omitted Check if we are about to plot the final point LDA .gxrScratchspace1 top byte of -abs(deltaX) AND .vduTempStoreDC AND .vduTempStoreDD low byte of -abs(deltaX) CLC ADC #1 add one BEQ .return17 if (zero) then branch (finished) BIT .vduTempStoreDB test plot type (see table above) .finalPointIncluded = $9bd9 BPL .linePlotPixel if (first point is included) then branch (plot pixel) STX .gxrScratchspace3 index of line1StartPointX Move dot-dash line pattern on by one bit, restart when we reach the pattern end LDX .currentDotDashPatternByte LDA .currentDotDashPattern,X AND .currentDotDashPatternBitMask and with mask to see if bit is set PHP LSR .currentDotDashPatternBitMask shift the current mask one place to look at next bit BCC + if (mask byte not exhausted) branch ROR .currentDotDashPatternBitMask sets the mask back to $80 for the next byte INC .currentDotDashPatternByte move to next byte + DEC .currentDotDashPatternNumBitsRemaining BNE .skipResetPattern Restart pattern LDA .currentDotDashPatternBitLength STA .currentDotDashPatternNumBitsRemaining remaining = length LDA #$80 STA .currentDotDashPatternBitMask set mask to top bit set (the first bit of the pattern) LDA #0 STA .currentDotDashPatternByte start at first byte of pattern .skipResetPattern = $9c08 LDX .gxrScratchspace3 recall index to line1StartPointX [NOTE: could be LDX #line1StartPointX - vduStart] PLP get flag to see if the dot pattern pixel is set BEQ .skipLinePlotPixel if (no pixel required) then branch .linePlotPixel = $9c0e !if (MACHINE = BBC_B) | (MACHINE = ELECTRON) { LDA .vduCurrentPlotByteMask current pixel AND .vduGraphicsColourByteOR and to get colour ORA (.vduScreenAddressOfGraphicsCursorCellLow),Y OR in byte from screen STA .vduTempStoreDA store LDA .vduCurrentPlotByteMask } AND .vduGraphicsColourByteEOR } plot mode EOR .vduTempStoreDA } STA (.vduScreenAddressOfGraphicsCursorCellLow),Y store byte to screen } else if MACHINE = BBC_B_PLUS { JSR .plotPointWithinBoundsAtY } else { +unknown_machine } .skipLinePlotPixel = $9c1e LDA .line1ErrorTerm+1 error term (high byte) BPL .moveToNextColumn INC .vduTempStoreDC increment deltaY until it reaches zero BEQ .return17 if (deltaY == 0) then return BIT .line1Signs sign of deltaY BMI .moveDownARow if (deltaY < 0) then branch DEY move up one row BPL .moveUpOrDownDone if (not at top of character cell) then branch JSR .moveGraphicsCursorAddressUpOneCharacterCell JMP .moveUpOrDownDone .return17 = $9c35 RTS .moveDownARow = $9c36 INY CPY #8 BNE .moveUpOrDownDone if (not at bottom of character cell) then branch JSR .plotSpriteMoveDownOneCell move to next cell .moveUpOrDownDone = $9c3e JSR .gxrSetGraphicsColourMasks Add to error term CLC } LDA .line1ErrorTerm } ADC .line1AbsDeltaX } STA .line1ErrorTerm } line1ErrorTerm += line1AbsDeltaX LDA .line1ErrorTerm+1 } ADC .line1AbsDeltaX+1 } STA .line1ErrorTerm+1 } BPL .moveToNextColumn JMP .plotLineLoop .moveToNextColumn = $9c59 Add one to X coordinate stored in (.gxrScratchspace1, .vduTempStoreDD) INC .gxrScratchspace1 BNE + INC .vduTempStoreDD BEQ .return17 if (last column) then branch (return) + BIT .line1Signs sign of original deltaX BVS .movePixelToTheLeft if (deltaX < 0) then branch Move a pixel to the right LSR .vduCurrentPlotByteMask BCC .afterShiftingLeftOrRight JSR .moveGraphicsCursorAddressTotheRightAndUpdateMask JMP .afterShiftingLeftOrRight .movePixelToTheLeft = $9c71 Move a pixel to the left ASL .vduCurrentPlotByteMask BCC .afterShiftingLeftOrRight JSR .moveGraphicsCursorAddressTotheLeftAndUpdateMask .afterShiftingLeftOrRight = $9c78 Subtract from the error term: line1ErrorTerm -= line1AbsDeltaY SEC LDA .line1ErrorTerm SBC .line1AbsDeltaY STA .line1ErrorTerm LDA .line1ErrorTerm+1 } SBC .line1AbsDeltaY+1 } high byte STA .line1ErrorTerm+1 } JMP .plotLineLoop
Increment .vduWorkspaceAB,X or decrement it, based on negative flag on entry
.incOrDecWorkspaceAB_X = $9c8e BMI .dec INC .vduWorkspaceA,X BNE ++ INC .vduWorkspaceB,X RTS .dec = $9c99 LDA .vduWorkspaceA,X BNE + DEC .vduWorkspaceB,X + DEC .vduWorkspaceA,X ++ RTS
Check if .vduWorkspaceABCD == .vduWorkspaceEFGH
.checkIfABCDEqualsEFGH = $9ca5 LDY #4 loop counter .checkLoop = $9ca7 LDA .vduWorkspaceA-1,Y CMP .vduWorkspaceE-1,Y BNE .checkIsDone DEY BNE .checkLoop .checkIsDone = $9cb2 RTS
.plotSpriteMoveDownOneCell = $9cb3 CLC LDA .vduScreenAddressOfGraphicsCursorCellLow ADC .vduBytesPerCharacterRowLow STA .vduScreenAddressOfGraphicsCursorCellLow LDA .vduScreenAddressOfGraphicsCursorCellHigh ADC .vduBytesPerCharacterRowHigh BPL + SEC SBC .vduScreenSizeHighByte loop from bottom of screen to top + STA .vduScreenAddressOfGraphicsCursorCellHigh LDY #0 start at the top of the next cell RTS
§16. Copy dot-dash line state and pattern to page C.
When plotting a line, copy the dot-dash line pattern data into page C for ease of access
.copyDashPatternAndStateIntoPageC = $9ccb JSR .getPrivateWorkspaceAddress LDY #.workspaceOffsetDotDashPatternByte LDX #3 + 8 loop counter (3 bytes of dot-dash state + 8 bytes of dot-dash pattern) - LDA (.privateWorkspaceLow),Y STA .currentDotDashPattern,X copy from workspace to current pattern DEY DEX BPL - RTS
§17. Copy dot-dash line state back to private workspace.
When we are done plotting a line, copy the latest dot-dash state back into the private workspace area. This allows the dot-dash state to continue on the next line, if needed.
.copyDashStateBackIntoWorkspace = $9cdc JSR .getPrivateWorkspaceAddress LDY #.workspaceOffsetDotDashPatternByte LDX #2 loop counter - LDA .currentDotDashPatternNumBitsRemaining,X } STA (.privateWorkspaceLow),Y } workspace[pattern byte] = } .currentDotDashPatternByte DEY } workspace[dot-dash bit] = } .currentDotDashPatternBitMask DEX } workspace[bits remaining] = } .currentDotDashPatternNumBitsRemaining BPL - } RTS