| 1 | // This file is part of SmallBASIC |
| 2 | // |
| 3 | // PolyLineFill |
| 4 | // See: "Zen of graphics programming", Chapter 24. L24-1.C. |
| 5 | // |
| 6 | // This program is distributed under the terms of the GPL v2.0 or later |
| 7 | // Download the GNU Public License (GPL) from www.gnu.org |
| 8 | // |
| 9 | // Copyright(C) 2000 Nicholas Christopoulos |
| 10 | |
| 11 | #include "common/sys.h" |
| 12 | #include "common/device.h" |
| 13 | |
| 14 | struct EdgeState { |
| 15 | struct EdgeState *NextEdge; |
| 16 | int X, StartY; |
| 17 | int WholePixelXMove; |
| 18 | int XDirection; |
| 19 | int ErrorTerm; |
| 20 | int ErrorTermAdjUp; |
| 21 | int ErrorTermAdjDown; |
| 22 | int Count; |
| 23 | }; |
| 24 | // 18bytes |
| 25 | |
| 26 | void pf_build_GET(ipt_t *, int, struct EdgeState *); |
| 27 | void pf_move_xsorted_AET(int); |
| 28 | void pf_scan_out_AET(int); |
| 29 | void pf_advance_AET(void); |
| 30 | void pf_xsort_AET(void); |
| 31 | |
| 32 | /* |
| 33 | * Pointers to global edge table (GET) and active edge table (AET) |
| 34 | */ |
| 35 | static struct EdgeState *GETPtr; |
| 36 | static struct EdgeState *AETPtr; |
| 37 | |
| 38 | /* |
| 39 | * FillPoly |
| 40 | * |
| 41 | * XOffset, YOffset Coordinates to draw the polygon. |
| 42 | * *VertexList The array of the points. |
| 43 | * ptNum The number of points. |
| 44 | * Color The color. |
| 45 | */ |
| 46 | void dev_pfill(ipt_t * pts, int ptNum) { |
| 47 | struct EdgeState *EdgeTableBuffer; |
| 48 | int CurrentY; |
| 49 | |
| 50 | /* |
| 51 | * It takes a minimum of 3 vertices to cause any pixels to be |
| 52 | * drawn; reject polygons that are guaranteed to be invisible |
| 53 | */ |
| 54 | if (ptNum < 3) |
| 55 | return; |
| 56 | |
| 57 | EdgeTableBuffer = (struct EdgeState *) malloc(sizeof(struct EdgeState) * (ptNum + 1)); |
| 58 | |
| 59 | /* |
| 60 | * Build the global edge table. |
| 61 | */ |
| 62 | |
| 63 | pf_build_GET(pts, ptNum, EdgeTableBuffer); |
| 64 | |
| 65 | /* |
| 66 | * Scan down through the polygon edges, one scan line at a time, |
| 67 | * so long as at least one edge remains in either the GET or AET |
| 68 | */ |
| 69 | AETPtr = NULL; /* initialize the active edge table to empty */ |
| 70 | if (GETPtr != NULL) { |
| 71 | CurrentY = GETPtr->StartY; /* start at the top polygon vertex */ |
| 72 | } else { |
| 73 | CurrentY = 0; |
| 74 | } |
| 75 | while ((GETPtr != NULL) || (AETPtr != NULL)) { |
| 76 | pf_move_xsorted_AET(CurrentY); /* update AET for this scan line */ |
| 77 | pf_scan_out_AET(CurrentY); /* draw this scan line from AET */ |
| 78 | pf_advance_AET(); /* advance AET edges 1 scan line */ |
| 79 | pf_xsort_AET(); /* resort on X */ |
| 80 | CurrentY++; /* advance to the next scan line */ |
| 81 | } |
| 82 | |
| 83 | free(EdgeTableBuffer); |
| 84 | } |
| 85 | |
| 86 | /* |
| 87 | * Creates a GET in the buffer pointed to by NextFreeEdgeStruc from |
| 88 | * the vertex list. Edge endpoints are flipped, if necessary, to |
| 89 | * guarantee all edges go top to bottom. The GET is sorted primarily |
| 90 | * by ascending Y start coordinate, and secondarily by ascending X |
| 91 | * start coordinate within edges with common Y coordinates |
| 92 | */ |
| 93 | void pf_build_GET(ipt_t * VertexPtr, int ptNum, struct EdgeState *NextFreeEdgeStruc) { |
| 94 | int i, StartX, StartY, EndX, EndY, DeltaY, DeltaX, Width, temp; |
| 95 | struct EdgeState *NewEdgePtr; |
| 96 | struct EdgeState *FollowingEdge; |
| 97 | struct EdgeState **FollowingEdgeLink; |
| 98 | |
| 99 | /* |
| 100 | * Scan through the vertex list and put all non-0-height edges into |
| 101 | * the GET, sorted by increasing Y start coordinate |
| 102 | */ |
| 103 | GETPtr = NULL; /* initialize the global edge table to empty */ |
| 104 | for (i = 0; i < ptNum; i++) { |
| 105 | /* |
| 106 | * Calculate the edge height and width |
| 107 | */ |
| 108 | StartX = VertexPtr[i].x; |
| 109 | StartY = VertexPtr[i].y; |
| 110 | /* |
| 111 | * The edge runs from the current point to the previous one |
| 112 | */ |
| 113 | if (i == 0) { |
| 114 | /* |
| 115 | * Wrap back around to the end of the list |
| 116 | */ |
| 117 | EndX = VertexPtr[ptNum - 1].x; |
| 118 | EndY = VertexPtr[ptNum - 1].y; |
| 119 | } else { |
| 120 | EndX = VertexPtr[i - 1].x; |
| 121 | EndY = VertexPtr[i - 1].y; |
| 122 | } |
| 123 | /* |
| 124 | * Make sure the edge runs top to bottom |
| 125 | */ |
| 126 | if (StartY > EndY) { |
| 127 | SWAP(StartX, EndX, temp); |
| 128 | SWAP(StartY, EndY, temp); |
| 129 | } |
| 130 | /* |
| 131 | * Skip if this can't ever be an active edge (has 0 height) |
| 132 | */ |
| 133 | if ((DeltaY = EndY - StartY) != 0) { |
| 134 | /* |
| 135 | * Allocate space for this edge's info, and fill in the structure |
| 136 | */ |
| 137 | NewEdgePtr = NextFreeEdgeStruc++; |
| 138 | NewEdgePtr->XDirection = /* direction in which X moves */ |
| 139 | ((DeltaX = EndX - StartX) > 0) ? 1 : -1; |
| 140 | Width = abs(DeltaX); |
| 141 | NewEdgePtr->X = StartX; |
| 142 | NewEdgePtr->StartY = StartY; |
| 143 | NewEdgePtr->Count = DeltaY; |
| 144 | NewEdgePtr->ErrorTermAdjDown = DeltaY; |
| 145 | if (DeltaX >= 0) /* initial error term going L->R */ |
| 146 | NewEdgePtr->ErrorTerm = 0; |
| 147 | else |
| 148 | /* initial error term going R->L */ |
| 149 | NewEdgePtr->ErrorTerm = -DeltaY + 1; |
| 150 | if (DeltaY >= Width) { /* Y-major edge */ |
| 151 | NewEdgePtr->WholePixelXMove = 0; |
| 152 | NewEdgePtr->ErrorTermAdjUp = Width; |
| 153 | } else { /* X-major edge */ |
| 154 | NewEdgePtr->WholePixelXMove = (Width / DeltaY) * NewEdgePtr->XDirection; |
| 155 | NewEdgePtr->ErrorTermAdjUp = Width % DeltaY; |
| 156 | } |
| 157 | /* |
| 158 | * Link the new edge into the GET so that the edge list is still sorted |
| 159 | * by Y coordinate, and by X coordinate for all edges with the same Y |
| 160 | * coordinate |
| 161 | */ |
| 162 | FollowingEdgeLink = &GETPtr; |
| 163 | for (;;) { |
| 164 | FollowingEdge = *FollowingEdgeLink; |
| 165 | if ((FollowingEdge == NULL) || (FollowingEdge->StartY > StartY) |
| 166 | || ((FollowingEdge->StartY == StartY) && (FollowingEdge->X >= StartX))) { |
| 167 | NewEdgePtr->NextEdge = FollowingEdge; |
| 168 | *FollowingEdgeLink = NewEdgePtr; |
| 169 | break; |
| 170 | } |
| 171 | FollowingEdgeLink = &FollowingEdge->NextEdge; |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /* |
| 178 | * Sorts all edges currently in the active edge table into ascending |
| 179 | * order of current X coordinates |
| 180 | */ |
| 181 | void pf_xsort_AET() { |
| 182 | struct EdgeState *CurrentEdge; |
| 183 | struct EdgeState **CurrentEdgePtr; |
| 184 | struct EdgeState *TempEdge; |
| 185 | int SwapOccurred; |
| 186 | |
| 187 | /* |
| 188 | * Scan through the AET and swap any adjacent edges for which the * second |
| 189 | * edge is at a lower current X coord than the first edge. Repeat until no |
| 190 | * further swapping is needed |
| 191 | */ |
| 192 | if (AETPtr != NULL) { |
| 193 | do { |
| 194 | SwapOccurred = 0; |
| 195 | CurrentEdgePtr = &AETPtr; |
| 196 | while ((CurrentEdge = *CurrentEdgePtr)->NextEdge != NULL) { |
| 197 | if (CurrentEdge->X > CurrentEdge->NextEdge->X) { |
| 198 | /* |
| 199 | * The second edge has a lower X than the first; swap them in the AET |
| 200 | */ |
| 201 | TempEdge = CurrentEdge->NextEdge->NextEdge; |
| 202 | *CurrentEdgePtr = CurrentEdge->NextEdge; |
| 203 | CurrentEdge->NextEdge->NextEdge = CurrentEdge; |
| 204 | CurrentEdge->NextEdge = TempEdge; |
| 205 | SwapOccurred = 1; |
| 206 | } |
| 207 | CurrentEdgePtr = &(*CurrentEdgePtr)->NextEdge; |
| 208 | } |
| 209 | } while (SwapOccurred != 0); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | /* |
| 214 | * Advances each edge in the AET by one scan line. |
| 215 | * Removes edges that have been fully scanned. |
| 216 | */ |
| 217 | void pf_advance_AET() { |
| 218 | struct EdgeState *CurrentEdge; |
| 219 | struct EdgeState **CurrentEdgePtr; |
| 220 | |
| 221 | /* |
| 222 | * Count down and remove or advance each edge in the AET |
| 223 | */ |
| 224 | CurrentEdgePtr = &AETPtr; |
| 225 | while ((CurrentEdge = *CurrentEdgePtr) != NULL) { |
| 226 | /* |
| 227 | * Count off one scan line for this edge |
| 228 | */ |
| 229 | if ((--(CurrentEdge->Count)) == 0) { |
| 230 | /* |
| 231 | * This edge is finished, so remove it from the AET |
| 232 | */ |
| 233 | *CurrentEdgePtr = CurrentEdge->NextEdge; |
| 234 | } else { |
| 235 | /* |
| 236 | * Advance the edge's X coordinate by minimum move |
| 237 | */ |
| 238 | CurrentEdge->X += CurrentEdge->WholePixelXMove; |
| 239 | /* |
| 240 | * Determine whether it's time for X to advance one extra |
| 241 | */ |
| 242 | if ((CurrentEdge->ErrorTerm += CurrentEdge->ErrorTermAdjUp) > 0) { |
| 243 | CurrentEdge->X += CurrentEdge->XDirection; |
| 244 | CurrentEdge->ErrorTerm -= CurrentEdge->ErrorTermAdjDown; |
| 245 | } |
| 246 | CurrentEdgePtr = &CurrentEdge->NextEdge; |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | /* |
| 252 | * Moves all edges that start at the specified Y coordinate from the |
| 253 | * GET to the AET, maintaining the X sorting of the AET. |
| 254 | */ |
| 255 | void pf_move_xsorted_AET(int YToMove) { |
| 256 | struct EdgeState *AETEdge; |
| 257 | struct EdgeState **AETEdgePtr; |
| 258 | struct EdgeState *TempEdge; |
| 259 | int CurrentX; |
| 260 | |
| 261 | /* |
| 262 | * The GET is Y sorted. Any edges that start at the desired Y |
| 263 | * coordinate will be first in the GET, so we'll move edges from |
| 264 | * the GET to AET until the first edge left in the GET is no longer |
| 265 | * at the desired Y coordinate. Also, the GET is X sorted within |
| 266 | * each Y coordinate, so each successive edge we add to the AET is |
| 267 | * guaranteed to belong later in the AET than the one just added |
| 268 | */ |
| 269 | AETEdgePtr = &AETPtr; |
| 270 | while ((GETPtr != NULL) && (GETPtr->StartY == YToMove)) { |
| 271 | CurrentX = GETPtr->X; |
| 272 | /* |
| 273 | * Link the new edge into the AET so that the AET is still sorted by X |
| 274 | * coordinate |
| 275 | */ |
| 276 | while (1) { |
| 277 | AETEdge = *AETEdgePtr; |
| 278 | if ((AETEdge == NULL) || (AETEdge->X >= CurrentX)) { |
| 279 | TempEdge = GETPtr->NextEdge; |
| 280 | *AETEdgePtr = GETPtr; /* link the edge into the AET */ |
| 281 | GETPtr->NextEdge = AETEdge; |
| 282 | AETEdgePtr = &GETPtr->NextEdge; |
| 283 | GETPtr = TempEdge; /* unlink the edge from the GET */ |
| 284 | break; |
| 285 | } else { |
| 286 | AETEdgePtr = &AETEdge->NextEdge; |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | /* |
| 293 | * Fills the scan line described by the current AET at the specified Y |
| 294 | * coordinate in the specified color, using the odd/even fill rule. |
| 295 | */ |
| 296 | void pf_scan_out_AET(int YToScan) { |
| 297 | int LeftX; |
| 298 | struct EdgeState *CurrentEdge; |
| 299 | |
| 300 | /* |
| 301 | * Scan through the AET, drawing line segments as each pair of edge |
| 302 | * crossings is encountered. The nearest pixel on or to the right |
| 303 | * of left edges is drawn, and the nearest pixel to the left of but |
| 304 | * not on right edges is drawn |
| 305 | */ |
| 306 | CurrentEdge = AETPtr; |
| 307 | while (CurrentEdge != NULL) { |
| 308 | LeftX = CurrentEdge->X; |
| 309 | CurrentEdge = CurrentEdge->NextEdge; |
| 310 | /* |
| 311 | * 8/5/95, NDC: Zen's bug |
| 312 | */ |
| 313 | if (CurrentEdge->X) |
| 314 | dev_line(LeftX, YToScan, CurrentEdge->X - 1, YToScan); |
| 315 | CurrentEdge = CurrentEdge->NextEdge; |
| 316 | } |
| 317 | } |
| 318 | |