Filled primitives - 489 bytes (2.9%)


§1. Plot a filled parallelogram and finish PLOT.

para.png

§2. plotParallelogramFilled.

.plotParallelogramFilled = $8e5b
    JSR .plotParallelogramInternal                      
    JMP .setGraphicsCursorPositionAndFinishPLOT         

§3. Plot a filled triangle and finish PLOT.

 Triangles are reimplemented here to add the fill pattern ability. It also fixes some questionable
 rasterisation of the original OS routine. In particular, the following triangle draws a better
 base edge with GXR enabled than without:

     10 MODE 2
     20 MOVE 119*8,72*4
     30 MOVE 40*8,71*4
     40 PLOT 85,80*8,111*4

 Note that the left vertex is one pixel lower than the right vertex. This example is from the roof
 of the Witch's House from the game Citadel (See http://www.bbcmicro.co.uk/game.php?id=290 ).

 Firstly the regular MOS triangle routine:

witch_mos.png


 Secondly the GXR triangle routine:

witch_gxr.png

.plotTriangleFilled = $8e61
    JSR .plotTriangleInternal                           
    JMP .setGraphicsCursorPositionAndFinishPLOT         

§4. Plots a filled parallelogram.

   Specified by three vertices of the parallelogram:
       - old graphics cursor position
       - current graphics cursor position
       - plot parameter point
   The fourth vertex is calculated from these three.
   The four vertices are sorted from the bottom up.

   Drawn from the bottom up in three parts:

       1. Bottom triangle      (with flat top edge)
       2. Centre parallelogram (with flat top and bottom edges)
       3. Top triangle         (with flat bottom edge)

 e.g.
                                                                    #####  <--- workspaceABCD
                                                                ####    #
                                                            ####        #
                                                        ####           #
                                                    ####               #
                                                ####                  #
                                            ####                      #
                                        ####                         #
                                    ####                3.           #
                                ####                                #
                            ####                                    #
     old graphics --->  ####---------------------------------------#
           cursor      #                                           #
                       #                                          #
                      #                     2.                    #
                      #                                          #
                     #---------------------------------------####  <--- PLOT parameter
                     #                                   ####
                    #                                ####
                    #                            ####
                   #            1.           ####
                   #                     ####
                  #                  ####
                  #              ####
                 #           ####
                 #       ####
                #    ####
 graphics --->  #####
   cursor
.plotParallelogramInternal = $8e67
    LDX #1                                              loop counter
    LDY #0                                              

    This calculates the fourth vertex of the parallelogram from the other three:
    workspaceABCD = PLOT parameter - graphics cursor + old graphics cursor
    
    Loops twice, first with Y=0 (X coordinate) and then with Y=2 (Y coordinate)
-
    SEC                                                 }
    LDA .vdu25ParameterXLow,Y                           }
    SBC .vduGraphicsCursorPixelsXLow,Y                  }
    STA .vduWorkspaceA,Y                                } workspaceAB = PLOT param -
                                                        } graphics cursor
    LDA .vdu25ParameterXHigh,Y                          }
    SBC .vduGraphicsCursorPixelsXHigh,Y                 }
    STA .vduWorkspaceB,Y                                }

    CLC                                                 }
    LDA .vduWorkspaceA,Y                                }
    ADC .vduOldGraphicsCursorPixelsXLow,Y               }
    STA .vduWorkspaceA,Y                                } workspaceAB += old graphics cursor
    LDA .vduWorkspaceB,Y                                }
    ADC .vduOldGraphicsCursorPixelsXHigh,Y              }
    STA .vduWorkspaceB,Y                                }
    LDY #2                                              
    DEX                                                 
    BPL -                                               

    The next code sorts the four points of the parallelogram vertically:
    
    XY = SORT(old, current)
    .gxrTemp1 = X                  .gxrTemp1 is the index to the smaller Y coordinate of
                                    old and current, Yreg is the larger
    XY = SORT(param, Y)
    .gxrTemp2 = X                  .gxrTemp2 is the index to the smaller Y coordinate of
                                    param and Yreg, Yreg is the larger
    XY = SORT(workspaceA, Y)
    .gxrTemp4 = Y                  .gxrTemp4 is the index to the larger Y coordinate of
                                    workspaceA and Yreg
    XY = SORT(gxrTemp2, X)
    .gxrTemp3 = Y                  .gxrTemp3 is the index to the larger Y coordinate of
                                   .gxrTemp2 and Xreg
    Y = .gxrTemp1
    then calls .sortThreePointsAndPlotFlatEdgedTriangle which completes the process using two
    more SORT calls.
    
    [NOTE: For fewer comparisons (i.e. using 5 SORT calls total instead of 6),
           see
    https://stackoverflow.com/questions/6145364/sort-4-number-with-few-comparisons ]

    LDY #.vduOldGraphicsCursorPixelsXLow - .vduVariablesStart   } sort positions X and Y
    LDX #.vduGraphicsCursorPixelsXLow    - .vduVariablesStart   } so that X is the offset to
    JSR .gxrSortPointsXY                                        } the point with the
    STX .gxrTemp1                                       } smallest Y coordinate

    LDX #.vdu25ParameterXLow - .vduVariablesStart       }
    JSR .gxrSortPointsXY                                }
    STX .gxrTemp2                                       }

    LDX #.vduWorkspaceA - .vduVariablesStart            }
    JSR .gxrSortPointsXY                                }
    STY .gxrTemp4                                       }

    LDY .gxrTemp2                                       }
    JSR .gxrSortPointsXY                                }
    STY .gxrTemp3                                       }

    Step 1. Draw bottom triangle (with flat top edge)
    LDY .gxrTemp1                                       }
    JSR .sortThreePointsAndPlotFlatEdgedTriangle        

    Step 2. Draw centre parallelogram (with flat top and bottom edges)
    LDA .gxrTemp4                                       
    STA .gxrScratchspace1                               
    LDX #.line2StartPointX - .vduVariablesStart         
    JSR .lineInitialisation                             

    The line initialisation sets up line 2 variables:
       .line2StartPointX: initial and current point X [2 bytes]
       .line2StartPointY: initial and current point Y [2 bytes]
       .line2AbsDeltaX  : abs(deltaX)                 [2 bytes]
       .line2AbsDeltaY  : abs(deltaY)                 [2 bytes]
       .line2ErrorTerm  : Bresenham error term        [2 bytes]
       .line2Signs      : signs of deltaY (top bit) and deltaX (bit 6) [1 byte]

    LDY .gxrScratchspace2                               middle point
    JSR .drawFlatParallelogram                          

    Step 3. Draw top triangle (with flat bottom edge)
    LDY .gxrTemp3                                       
    LDA .gxrTemp4                                       
    STA .gxrScratchspace2                               
    LDX #.line3StartPointX - .vduVariablesStart         
    JSR .drawFlatBasedTriangleInternal                  

    Draw final row
    JMP .fillTriangleRowFromLeftToRight                 

§5. Plot a filled triangle.

 Triangles are drawn in two halves. First from the bottom to the middle point with a flat
 edge. Then from the flat middle edge up to the peak at the top.


                                            -- <- top point
                                          ---
                                        --  -
                       minor line ->  --   -
                                    --     -
                                  --second-
                                --   half -
                              --         -
             middle point -> #############
                              #         #
                              #  first  #   <- main line ('line 3')
                               #  half #    goes all the way to the top point
                               #       #
                                #     #
                 minor line ->  #     #
                                 #   #
                                 #   #
                                  # #
                                  # #
                                   #
                                   #  <- bottom point
.plotTriangleInternal = $8ee1
    LDY #.vduOldGraphicsCursorPixelsXLow - .vduVariablesStart   }
    LDX #.vduGraphicsCursorPixelsXLow    - .vduVariablesStart   } sort positions by Y then X
    JSR .gxrSortPointsXY                                        

    STY .gxrTemp3                                               
    LDY #.vdu25ParameterXLow - .vduVariablesStart               draw bottom half from lowest
    JSR .sortThreePointsAndPlotFlatEdgedTriangle                point up to the flat base

    LDA .gxrTemp3                                               top point
    JSR .drawFlatBasedTriangle                                  draw top half from the flat
                                                                base to top point
    fall through... (to draw final row)

§6. fillTriangleRowFromLeftToRight.

.fillTriangleRowFromLeftToRight = $8ef6
    LDY #.triangleRowLeftPointX - .vduVariablesStart    
    LDX #.triangleRowRightPointX - .vduVariablesStart   
    JMP .setMasksAndFillRow                             

§7. Sorts three points, then plots a triangle with a flat top or bottom edge.

 On Entry:
   Xreg and Yreg are workspace offsets to two coordinates
   .gxrTemp3 is the offset to a third coordinate, which must be >= (Yreg offset coordinate)
 On Exit:
   Y: index to middle point
.sortThreePointsAndPlotFlatEdgedTriangle = $8efd
    Sort three points
    JSR .gxrSortPointsXY                                

    STX .gxrTemp1                                       .gxrTemp1 = lower point, Yreg =
                                                        higher point
    LDX .gxrTemp3                                       compare Yreg with third coordinate
    JSR .gxrSortPointsXY                                

    STY .gxrTemp3                                       
    STX .gxrTemp2                                       

    The three values are sorted now:
    
      .gxrTemp3 is the highest  point of original Xreg,Yreg and .gxrTemp3
      .gxrTemp2 is the middle   point of original Xreg,Yreg and .gxrTemp3
      .gxrTemp1 is the lowest   point of original Xreg,Yreg and .gxrTemp3

    Copy the lowest point to .triangleRowLeftPointX/Y and .triangleRowRightPointX/Y.
    These will track the left and right edge points for the current row.
    LDY .gxrTemp1                                       
    LDX #$FC                                            loop counter ($FC,$FD,$FE,$FF)
-
    LDA .vduVariablesStart,Y                            }
    STA .triangleRowLeftPointX - $FC,X                  } store point in
    STA .triangleRowRightPointX - $FC,X                 } .triangleRowLeftPointX/Y and
    INY                                                 } .triangleRowRightPointX/Y
    INX                                                 }
    BNE -                                               }

    Initialise main line ('line 3') variables (see diagram below)
    LDY .gxrTemp1                                       bottom point
    LDA .gxrTemp3                                       top point
    STA .gxrScratchspace2                               
    LDX #.line3StartPointX - .vduVariablesStart         
    JSR .lineInitialisation                             

    The line initialisation sets up line 3 variables:
       .line3StartPointX: initial and current point X [2 bytes]
       .line3StartPointY: initial and current point Y [2 bytes]
       .line3AbsDeltaX  : abs(deltaX)                 [2 bytes]
       .line3AbsDeltaY  : abs(deltaY)                 [2 bytes]
       .line3ErrorTerm  : Bresenham error term        [2 bytes]
       .line3Signs      : signs of deltaY (top bit) and deltaX (bit 6) [1 byte]

    LDY .gxrTemp1                                       bottom point
    LDA .gxrTemp2                                       middle point
    JSR .drawFlatBasedTriangle                          draw bottom half of triangle

    LDY .gxrTemp2                                       middle point, ready to draw top half
    RTS                                                 

§8. Draw a flat based (or flat topped) triangle.

 Coordinates have been pre-sorted into top, middle and bottom points of the original triangle.

 On Entry:
   A: index of vduVariable for middle point
   Y: index of vduVariable for bottom (start) point
   .line3*: line variables hold the details for the main line

                                                               -- <- top point
                                                             ---
                                                           --  -
                                          minor line ->  --   -
                                                       --     -
                                                     --      -
                                                   --        -
                                                 --         -
 'A' register, the index of the middle point -> #############
                                                 #         #
                                                 #         #   <- main line (aka 'line 3')
                                                  #       #       goes all the way to the top point
                                                  #       #
                                                   #     #
                                    minor line ->  #     #
                                                    #   #
                                                    #   #
                                                     # #
                                                     # #
                                                      #
                                                      #  <- 'Y' register, the index of the current
                                                             point, starting at the bottom
.drawFlatBasedTriangle = $8f3c
    STA .gxrScratchspace1                               store end of minor line index
    LDX #.line2StartPointX - .vduVariablesStart         where the minor line initialisation
                                                        results will go

.drawFlatBasedTriangleInternal = $8f41
    JSR .lineInitialisation                             
    The line initialisation sets up line variables for the minor line, (either line 2 or 3,
    as specified by the X register).
    For regular filled triangle plotting, this is line2,
    For top half of a parallelogram this is line3.
    
    Assuming line 2, these variables are set up:
    
       .line2StartPointX: initial and current point X [2 bytes]
       .line2StartPointY: initial and current point Y [2 bytes]
       .line2AbsDeltaX  : abs(deltaX)                 [2 bytes]
       .line2AbsDeltaY  : abs(deltaY)                 [2 bytes]
       .line2ErrorTerm  : Bresenham error term        [2 bytes]
       .line2Signs      : signs of deltaY (top bit) and deltaX (bit 6) [1 byte]

    LDY .gxrScratchspace1                               get middle point index

    [NOTE: cheeky little entry point for drawing a parallelogram with flat top and bottom
           edges using the same algorithm as drawing the triangle with a single flat edge.
           This code can actually draw a trapezium with flat top and bottom edges, but in
           practice is only used for a triangle or a parallelogram.]
.drawFlatParallelogram = $8f47
    STY .vduTempStoreDB                                 remember current point index
.drawFlatTriangleLoop = $8f49
    Check to see if we are done
    LDA .vduVariablesStart + 2,Y                        }
    CMP .line1AbsDeltaY                                 }
    BNE +                                               } if (minor line current Y == middle
                                                        } point Y) then branch (finished
                                                        } drawing)
    LDA .vduVariablesStart + 3,Y                        }
    CMP .line1AbsDeltaY+1                               }
    BEQ .finishedDrawingTriangle                        }

+
    LDX #.line2StartPointX - .vduVariablesStart         given the current point for the
                                                        minor line, update it until about to
                                                        move to a new row
    JSR .updatePointOnLineXUntilReadyToMoveToNextRow    and update the current left or right
                                                        point for the row

    LDX #.line3StartPointX - .vduVariablesStart         given the current point for the main
                                                        line, update it until about to move
                                                        to a new row
    JSR .updatePointOnLineXUntilReadyToMoveToNextRow    and update the current left or right
                                                        point for the row

    JSR .fillTriangleRowFromLeftToRight                 fill current row from left point to
                                                        right

    Move to the first point on the next row for each line (having got ready for that before
    the fill above)
    LDX #.line2StartPointX - .vduVariablesStart         
    JSR .lineMoveToNextPixel                            move to next point on line 2 (which
                                                        moves to a new row)

    LDX #.line3StartPointX - .vduVariablesStart         
    JSR .lineMoveToNextPixel                            move to next point on line 3 (which
                                                        moves to a new row)

    LDX #.line2StartPointX - .vduVariablesStart         
    LDY #.line3StartPointX - .vduVariablesStart         
    JSR .sortCoordinatesXYByColumn                      sort to make X the left point, Y the
                                                        right point

    Store the new row's left and right points in triangleRow[Left/Right]Point

    Remember X (the left point) for later
    STA .gxrScratchspace3                               [NOTE: redundant]
    TXA                                                 
    PHA                                                 remember X
    LDA .gxrScratchspace3                               [NOTE: redundant]

    First store the right point (indexed by Y)
    LDX #$FC                                            loop counter ($FC,$FD,$FE,$FF)
-
    LDA .vduVariablesStart,Y                            } copy right point
    STA .triangleRowRightPointX - $FC,X                 } store in .triangleRowRightPointX/Y
    INY                                                 }
    INX                                                 }
    BNE -                                               }

    Recall X (the left point)
    STA .gxrScratchspace3                               [NOTE: redundant]
    PLA                                                 
    TAX                                                 restore X
    LDA .gxrScratchspace3                               [NOTE: redundant]

    Lastly store the left point (indexed by X)
    LDY #$FC                                            loop counter
-
    LDA .vduVariablesStart,X                            }
    STA .triangleRowLeftPointX - $FC,Y                  } copy left point
    INX                                                 } store in .triangleRowLeftPointX/Y
    INY                                                 }
    BNE -                                               }

    LDY .vduTempStoreDB                                 recall current point index
    JMP .drawFlatTriangleLoop                           

.finishedDrawingTriangle = $8fa4
    We continue to update the line 2 point to get the row span for the final row
    LDA #.line2StartPointX - .vduVariablesStart         
    LDX .gxrScratchspace1                               
    JSR .updateToGetRowSpan                             

    We continue to update the line 3 point to get the row span for the final row
    LDY .vduTempStoreDB                                 
    LDA #.line3StartPointX - .vduVariablesStart         
    LDX .gxrScratchspace2                               

.updateToGetRowSpan = $8fb3
    STA .vduTempStoreDE                                 
    LDA .vduVariablesStart + 2,X                        
    CMP .vduVariablesStart + 2,Y                        
    BNE +                                               if (Y coordinates don't match) then
                                                        branch

    LDA .vduVariablesStart + 3,X                        
    CMP .vduVariablesStart + 3,Y                        
    BEQ .updateLeftAndRightTriangleRowPoints            if (Y coordinates match) then branch

+
    LDX .vduTempStoreDE                                 
    JMP .updatePointOnLineXUntilReadyToMoveToNextRow    

§9. makeErrorTermNegative.

.makeErrorTermNegative = $8fca
    Push X (preserving A)
    STA .gxrScratchspace3                               }
    TXA                                                 } push X leaving A untouched
    PHA                                                 } [NOTE: wasteful. No need to
                                                        } preserve A!]
    LDA .gxrScratchspace3                               }

    JSR .lineMoveToNextPixel                            move to next point on line X

    Pop X (preserving A)
    STA .gxrScratchspace3                               }
    PLA                                                 } pop X leaving A untouched
    TAX                                                 } [NOTE: wasteful. No need to
                                                        } preserve A!]
    LDA .gxrScratchspace3                               }
    fall through...

§10. Update the error term then update the left and right extents for the row.

 On Exit:
   Preserves X
.updatePointOnLineXUntilReadyToMoveToNextRow = $8fdd
    LDA .vduVariablesStart + 9,X                        check error term
    BPL .makeErrorTermNegative                          if (error term >= 0) then branch
                                                        which updates the error term until
                                                        it becomes negative
    fall through...

§11. Update the left and right extents of the current row based on the current point on line X.

 if (lineCurrentX < triangleRowLeftPoint) {
     triangleRowLeftPointX = lineCurrentX
     return
 }
 if (lineCurrentX > triangleRowRightPointX) {
     triangleRowRightPointX = lineCurrentX
 }
.updateLeftAndRightTriangleRowPoints = $8fe2
    LDA .vduVariablesStart,X                            
    CMP .triangleRowLeftPointX                          
    LDA .vduVariablesStart + 1,X                        
    SBC .triangleRowLeftPointX+1                        
    BPL .testRightPoint                                 if (lineCurrentX >=
                                                        triangleRowLeftPointX) then branch

    LDA .vduVariablesStart,X                            
    STA .triangleRowLeftPointX                          
    LDA .vduVariablesStart + 1,X                        
    STA .triangleRowLeftPointX+1                        triangleRowLeftPointX = lineCurrentX
    RTS                                                 

.testRightPoint = $8ffd
    LDA .triangleRowRightPointX                         
    CMP .vduVariablesStart,X                            
    LDA .triangleRowRightPointX+1                       
    SBC .vduVariablesStart + 1,X                        
    BPL .return18                                       if (triangleRowRightPointX >=
                                                        lineCurrentX) then branch (return)

    LDA .vduVariablesStart,X                            
    STA .triangleRowRightPointX                         
    LDA .vduVariablesStart + 1,X                        
    STA .triangleRowRightPointX+1                       triangleRowRightPointX = lineCurrentX
.return18 = $9017
    RTS                                                 

§12. Sort two points, sorting by row first then column.

 Given two points (one indexed by register X the other by register Y), sort them by row (lowest
 first) then by column (leftmost first)

 i.e. if (point X is higher OR (level and right of point Y)) then swap points in place

 On Exit:
   X is the offset to the lower point
   Y is the offset to the higher point
.gxrSortPointsXY = $9018
    SEC                                                 
    LDA .vduVariablesStart + 2,Y                        
    SBC .vduVariablesStart + 2,X                        
    STA .vduTempStoreDE                                 tempStoreDE is diffLow
    LDA .vduVariablesStart + 3,Y                        
    SBC .vduVariablesStart + 3,X                        
    BMI .swapXandY                                      if (vdu[Y,Y+1] < vdu[X,X+1]) then
                                                        branch (swap)

    ORA .vduTempStoreDE                                 
    BNE .return5                                        if (coordinates are not equal) then
                                                        branch (return)

    Sort so that X = leftmost point, Y = rightmost
.sortCoordinatesXYByColumn = $902d
    LDA .vduVariablesStart,Y                            
    CMP .vduVariablesStart,X                            
    LDA .vduVariablesStart + 1,Y                        
    SBC .vduVariablesStart + 1,X                        
    BPL .return5                                        if (vdu[Y,Y+1] >= vdu[X,X+1]) then
                                                        branch (return)

.swapXandY = $903b
    TXA                                                 }
    STY .gxrScratchspace3                               }
    LDX .gxrScratchspace3                               } swap X and Y registers
    TAY                                                 }
.return5 = $9043
    RTS