github.com/kintar/etxt@v0.0.9/emask/helper_buffer_quad.go (about) 1 package emask 2 3 import "math" 4 5 // This file contains methods that implement a "fill quadrilateral" 6 // operation used with the outliner rasterizer. This is a CPU-heavy 7 // process, fiddly, slow and annoying. It could be done with an 8 // uint8 buffer, it could be much more optimized and many other 9 // things. Whatever. 10 11 // Fill a convex quadrilateral polygon whose bounds are defined by 12 // the points given as parameters. The points don't need to follow 13 // any particular order, but must define a convex quadrilateral 14 // (triangles and lines also work as they are like quadrilaterals 15 // with one or two sides collapsed, which the algorithm handles ok). 16 // 17 // Values outside the buffer's bounds will be clipped. (TODO: UNIMPLEMENTED) 18 // 19 // TODO: this could also be done on the GPU and the algorithms would be easier. 20 func (self *buffer) FillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy float64) { 21 // the first part is all about clipping 22 // TODO: clip directly at the outliner stage..? clip on both places..? 23 24 // clip polygon 25 if ay < 0 { 26 panic("clipping unimplemented") 27 // TODO: recursive calls after splitting... 28 //self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy) 29 //self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy) 30 } else if by > float64(self.Height) { 31 // two calls 32 panic("clipping unimplemented") 33 } else if ax < 0 { 34 panic("clipping unimplemented") 35 } else if bx < 0 { 36 panic("clipping unimplemented") 37 } else if cx < 0 { 38 panic("clipping unimplemented") 39 } else if dx < 0 { 40 panic("clipping unimplemented") 41 } else if ax > float64(self.Width) { 42 panic("clipping unimplemented") 43 } else if bx > float64(self.Width) { 44 panic("clipping unimplemented") 45 } else if cx > float64(self.Width) { 46 panic("clipping unimplemented") 47 } else if dx > float64(self.Width) { 48 panic("clipping unimplemented") 49 } else { // no clipping required, nice 50 self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy) 51 } 52 } 53 54 // Precondition: vertices must be all inside the working area. The call 55 // 56 // will panic otherwise. 57 func (self *buffer) uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy float64) { 58 if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) { 59 panic("nan") 60 } 61 62 // sort points so they go from smallest y to biggest y 63 type point struct{ x, y float64 } 64 pts := [4]point{point{ax, ay}, point{bx, by}, point{cx, cy}, point{dx, dy}} 65 if pts[0].y > pts[3].y { 66 pts[0], pts[3] = pts[3], pts[0] 67 } 68 if pts[0].y > pts[1].y { 69 pts[0], pts[1] = pts[1], pts[0] 70 } 71 if pts[2].y > pts[3].y { 72 pts[2], pts[3] = pts[3], pts[2] 73 } 74 if pts[1].y > pts[2].y { 75 pts[1], pts[2] = pts[2], pts[1] 76 if pts[0].y > pts[1].y { 77 pts[0], pts[1] = pts[1], pts[0] 78 } 79 if pts[2].y > pts[3].y { 80 pts[2], pts[3] = pts[3], pts[2] 81 } 82 } 83 84 // define some local helper functions 85 sort2f64 := func(a, b float64) (float64, float64) { 86 if a <= b { 87 return a, b 88 } else { 89 return b, a 90 } 91 } 92 leftVertId := func(a, b int) int { 93 if pts[a].x <= pts[b].x { 94 return a 95 } else { 96 return b 97 } 98 } 99 100 // since the quadrilateral is convex, we know that points 0 and 1 are 101 // connected and that points 2 and 3 are also connected. What we don't 102 // know is whether point 0 also connects to 2 or 3, and same for 1. 103 // find it out as this is necessary later in most cases. 104 pt0Conn := leftVertId(2, 3) // set pt0Pair to the bottom left vert id 105 if pts[0].x < pts[1].x { // if 0 on the left, 0 connects to bottom left 106 // bottom left was correct 107 } else if pts[0].x > pts[1].x { // if 0 on the right, 0 connects to bottom right 108 pt0Conn = 5 - pt0Conn // if pair was 2, set to 3, if it was 3, set to 2 109 } else if pts[0].x < pts[2].x { // 0.x == 1.x, both bottom verts are on one side 110 pt0Conn = 5 - pt0Conn // if pair was 2, set to 3, if it was 3, set to 2 111 } // else { /* bottom left was correct */ } 112 113 // subdivide the polygon in one, two or three parts 114 // (the code may seem both confusing and repetitive. don't get 115 // too hung up, try to understand each case one by one and follow 116 // what's happening geometrically... vertex indices are tricky) 117 flatTop := (pts[0].y == pts[1].y) 118 flatBottom := (pts[2].y == pts[3].y) 119 if flatTop && flatBottom { // quad can be drawn as a single trapeze 120 tlx, trx := sort2f64(pts[0].x, pts[1].x) 121 blx, brx := sort2f64(pts[2].x, pts[3].x) 122 self.FillAlignedQuad(pts[0].y, pts[3].y, tlx, trx, blx, brx) 123 } else if flatTop { // quad can be drawn with a trapeze and a triangle 124 tlx, trx := sort2f64(pts[0].x, pts[1].x) 125 126 // to get the first trapeze we need to intersect lines 0-3 or 127 // 1-3 (whichever would form a triangle with vert 2) with an 128 // horizontal line going through pts[2].y 129 vertIdOpp2 := 3 - pt0Conn // vertex id opposite to vert 2 130 ia, ib, ic := toLinearFormABC(pts[vertIdOpp2].x, pts[vertIdOpp2].y, pts[3].x, pts[3].y) 131 ix := -(ic + ib*pts[2].y) / ia // ax + by + c = 0, then x = (-c - by)/a 132 blx, brx := sort2f64(pts[2].x, ix) 133 self.FillAlignedQuad(pts[0].y, pts[2].y, tlx, trx, blx, brx) // fill trapeze 134 self.FillAlignedQuad(pts[2].y, pts[3].y, blx, brx, pts[3].x, pts[3].x) // fill bottom triangle 135 // ...remaining code is barely documented as it doesn't introduce any new ideas 136 } else if flatBottom { // quad can be drawn with a triangle and a trapeze 137 ia, ib, ic := toLinearFormABC(pts[0].x, pts[0].y, pts[pt0Conn].x, pts[pt0Conn].y) 138 ix := -(ic + ib*pts[1].y) / ia 139 blx, brx := sort2f64(pts[1].x, ix) 140 self.FillAlignedQuad(pts[0].y, pts[1].y, pts[0].x, pts[0].x, blx, brx) // fill top triangle 141 tlx, trx := blx, brx 142 blx, brx = sort2f64(pts[2].x, pts[3].x) 143 self.FillAlignedQuad(pts[1].y, pts[3].y, tlx, trx, blx, brx) // fill bottom trapeze 144 } else { // quad is drawn with a triangle, a trapeze and then yet another triangle 145 // notice: this could be the only case, as it works for the general case, 146 // but having separate cases improves performance in X% (TODO: benchmark) 147 ia, ib, ic := toLinearFormABC(pts[0].x, pts[0].y, pts[pt0Conn].x, pts[pt0Conn].y) 148 ix := -(ic + ib*pts[1].y) / ia // ax + by + c = 0, then x = (-c - by)/a 149 blx, brx := sort2f64(pts[1].x, ix) 150 self.FillAlignedQuad(pts[0].y, pts[1].y, pts[0].x, pts[0].x, blx, brx) // fill top triangle 151 vertIdOpp2 := 3 - pt0Conn // vertex id opposite to vert 2 152 ia, ib, ic = toLinearFormABC(pts[vertIdOpp2].x, pts[vertIdOpp2].y, pts[3].x, pts[3].y) 153 ix = -(ic + ib*pts[2].y) / ia 154 tlx, trx := blx, brx 155 blx, brx = sort2f64(pts[2].x, ix) 156 self.FillAlignedQuad(pts[1].y, pts[2].y, tlx, trx, blx, brx) // fill trapeze 157 self.FillAlignedQuad(pts[2].y, pts[3].y, blx, brx, pts[3].x, pts[3].x) // fill bottom triangle 158 } 159 } 160 161 // Fills a quadrilateral defined by the given coordinates, where the 162 // top and bottom sides are perpendicular to the Y axis (which makes 163 // the quadrilateral a trapezoid, with flat top and bottom sides). 164 func (self *buffer) FillAlignedQuad(ty, by, tlx, trx, blx, brx float64) { 165 // assert validity of arguments order 166 if ty > by { 167 panic("ty > by") 168 } 169 if tlx > trx { 170 panic("tlx > trx") 171 } 172 if blx > brx { 173 panic("blx > brx") 174 } 175 176 // early return cases 177 if ty == by { 178 return 179 } 180 if tlx == trx && blx == brx { 181 return 182 } // line, no area 183 184 // prepare x advance deltas 185 dy := by - ty 186 dlx := (blx - tlx) / dy // left delta per y 187 drx := (brx - trx) / dy // right delta per y 188 dly := dy / math.Abs(tlx-blx) 189 dry := dy / math.Abs(trx-brx) 190 191 // lousily iterate each row 192 for { 193 // get next top y position 194 nextTy := math.Floor(ty + 1) 195 if nextTy > by { 196 nextTy = by 197 } 198 199 // prepare next bottom x coords for iterating the row 200 blxRow := tlx + (nextTy-ty)*dlx 201 brxRow := trx + (nextTy-ty)*drx 202 203 // corrections to blxRow and brxRow that may happen at 204 // most once due to floating point precision errors 205 if dlx > 0 { 206 if blxRow > blx { 207 blxRow = blx 208 } 209 } else if blxRow < blx { 210 blxRow = blx 211 } 212 if drx > 0 { 213 if brxRow > brx { 214 brxRow = brx 215 } 216 } else if brxRow < brx { 217 brxRow = brx 218 } 219 220 // fill the row 221 self.fillRow(ty, nextTy, tlx, trx, blxRow, brxRow, dly, dry) 222 tlx, trx = blxRow, brxRow 223 224 // update variables for next iteration 225 if nextTy == by { 226 break 227 } 228 ty = nextTy 229 } 230 } 231 232 func (self *buffer) fillRow(ty, by, tlx, trx, blx, brx float64, dly, dry float64) { 233 baseRowIndex := int(math.Floor(ty)) * self.Width 234 olx, orx := math.Max(tlx, blx), math.Min(trx, brx) 235 if olx <= orx { 236 // overlap case, center is a rect 237 left, right := tlx, trx 238 if tlx > blx { // left triangle with flat bottom 239 self.fillRowRectTriangle(by, -dly, blx, tlx, baseRowIndex) 240 } else if blx > tlx { // left triangle with flat top 241 left = blx 242 self.fillRowRectTriangle(ty, dly, tlx, blx, baseRowIndex) 243 } 244 245 if trx > brx { // right triangle with flat top 246 right = brx 247 self.fillRowRectTriangleRTL(ty, dry, trx, brx, baseRowIndex) 248 } else if brx > trx { // right triangle with flat bottom 249 self.fillRowRectTriangleRTL(by, -dry, brx, trx, baseRowIndex) 250 } 251 252 if left != right { 253 self.fillRowRect(ty, by, left, right, baseRowIndex) 254 } 255 } else { // tilted quad or triangle, but at least one part is flat 256 // non-overlap case, center can be a tilted quad 257 var qleft, qright float64 // quad left, quad right 258 qlty, qlby, qrty, qrby := ty, by, ty, by // quad left top y, quad left bottom y, etc. 259 if tlx > blx { // left triangle with flat bottom and right triangle with flat top case 260 qleft, qright = brx, tlx 261 qlty = self.fillRowRectTriangle(by, -dly, blx, brx, baseRowIndex) 262 qrby = self.fillRowRectTriangleRTL(ty, dry, trx, tlx, baseRowIndex) 263 if qrby > by { 264 qrby = by 265 } 266 if qlty < ty { 267 qlty = ty 268 } 269 } else if blx > tlx { // left triangle with flat top and right triangle with flat bottom case 270 qleft, qright = trx, blx 271 qlby = self.fillRowRectTriangle(ty, dly, tlx, trx, baseRowIndex) 272 qrty = self.fillRowRectTriangleRTL(by, -dry, brx, blx, baseRowIndex) 273 if qlby > by { 274 qlby = by 275 } 276 if qrty < ty { 277 qrty = ty 278 } 279 } else { 280 panic("unexpected tlx == blx") 281 } 282 283 if qleft != qright { 284 self.fillRowAlignedQuad(qleft, qright, qlty, qlby, qrty, qrby, dly, dry, baseRowIndex) 285 } 286 } 287 } 288 289 func (self *buffer) fillRowRectTriangle(startY, yChange, left, right float64, baseRowIndex int) float64 { 290 alignedLeft := math.Ceil(left) 291 alignedRight := math.Floor(right) 292 293 if alignedLeft > alignedRight { // single pixel special case 294 xdiff := right - left 295 ydiff := xdiff * yChange 296 self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0 297 return startY + ydiff 298 } 299 300 y := startY 301 if left != alignedLeft { // fractional left part 302 xdiff := alignedLeft - left 303 ydiff := xdiff * yChange 304 self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0 305 y += ydiff 306 } 307 308 partialChange := math.Abs(yChange) / 2.0 309 for x := int(alignedLeft); x < int(alignedRight); x++ { 310 self.Values[baseRowIndex+x] += partialChange + math.Abs(startY-y) 311 y += yChange 312 } 313 314 if right != alignedRight { // fractional right part 315 xdiff := right - alignedRight 316 ydiff := xdiff * yChange 317 self.Values[baseRowIndex+int(alignedRight)] += math.Abs(ydiff)*xdiff/2.0 + xdiff*math.Abs(startY-y) 318 y += ydiff 319 } 320 321 return y 322 } 323 324 func (self *buffer) fillRowRectTriangleRTL(startY, yChange, right, left float64, baseRowIndex int) float64 { 325 alignedLeft := math.Ceil(left) 326 alignedRight := math.Floor(right) 327 328 if alignedLeft > alignedRight { // single pixel special case 329 xdiff := right - left 330 ydiff := xdiff * yChange 331 self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0 332 return startY + ydiff 333 } 334 335 y := startY 336 if right != alignedRight { 337 xdiff := right - alignedRight 338 ydiff := xdiff * yChange 339 self.Values[baseRowIndex+int(alignedRight)] += math.Abs(ydiff) * xdiff / 2.0 340 y += ydiff 341 } 342 343 partialChange := math.Abs(yChange) / 2.0 344 for x := int(alignedRight) - 1; x >= int(alignedLeft); x-- { 345 self.Values[baseRowIndex+x] += partialChange + math.Abs(startY-y) 346 y += yChange 347 } 348 349 if left != alignedLeft { // fractional left part 350 xdiff := alignedLeft - left 351 ydiff := xdiff * yChange 352 self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff)*xdiff/2.0 + xdiff*math.Abs(startY-y) 353 y += ydiff 354 } 355 356 return y 357 } 358 359 func (self *buffer) fillRowRect(ty, by, left, right float64, baseRowIndex int) { 360 alignedLeft := math.Ceil(left) 361 alignedRight := math.Floor(right) 362 dy := by - ty 363 364 if alignedLeft > alignedRight { // single pixel special case 365 self.Values[baseRowIndex+int(math.Floor(left))] += dy * (right - left) 366 return 367 } 368 369 if left != alignedLeft { // fractional left part 370 xdiff := alignedLeft - left 371 self.Values[baseRowIndex+int(math.Floor(left))] += dy * xdiff 372 } 373 374 if alignedLeft != alignedRight { 375 if dy == 1 { 376 si, fi := baseRowIndex+int(alignedLeft), baseRowIndex+int(alignedRight) 377 fastFillFloat64(self.Values[si:fi], 1.0) 378 } else { 379 for x := int(alignedLeft); x < int(alignedRight); x++ { 380 self.Values[baseRowIndex+x] += dy 381 } 382 } 383 } 384 385 if right != alignedRight { // fractional left part 386 xdiff := right - alignedRight 387 self.Values[baseRowIndex+int(alignedRight)] += dy * xdiff 388 } 389 } 390 391 func (self *buffer) fillRowAlignedQuad(left, right, tly, bly, try, bry, dly, dry float64, baseRowIndex int) { 392 // figure out orientation 393 var dty, dby float64 // delta top y, delta bottom y 394 if try < tly || bry < bly { // moving upwards, negative 395 dty = -dly 396 dby = -dry 397 } else { // moving downwards 398 dty = dry 399 dby = dly 400 } 401 402 alignedLeft := math.Ceil(left) 403 alignedRight := math.Floor(right) 404 405 if alignedLeft > alignedRight { // single pixel special case 406 ab := (bly - tly) + (bry - try) 407 self.Values[baseRowIndex+int(math.Floor(left))] += ab * (right - left) / 2 408 return 409 } 410 411 if left != alignedLeft { // fractional left part 412 xdiff := alignedLeft - left 413 newTly, newBly := tly+dty*xdiff, bly+dby*xdiff 414 ab := (bly - tly) + (newBly - newTly) 415 self.Values[baseRowIndex+int(math.Floor(left))] += ab * xdiff / 2 416 tly, bly = newTly, newBly 417 418 } 419 420 // main loop 421 if dly == dry { // optimized version when dly == dry 422 partialChange := bly - tly // simplified from ((bly - tly) + (bly+dly - bry+dry))*1/2 423 for x := int(alignedLeft); x < int(alignedRight); x++ { 424 self.Values[baseRowIndex+x] += partialChange 425 } 426 iters := (alignedRight - alignedLeft) 427 tly, bly = tly+dty*iters, bly+dby*iters 428 } else { 429 for x := int(alignedLeft); x < int(alignedRight); x++ { 430 // TODO: optimize expression once tests are working, I 431 // doubt the compiler will catch this otherwise 432 newTly, newBly := tly+dty, bly+dby 433 ab := (bly - tly) + (newBly - newTly) 434 self.Values[baseRowIndex+x] += ab / 2 435 tly, bly = newTly, newBly 436 } 437 } 438 439 if right != alignedRight { // fractional right part 440 xdiff := right - alignedRight 441 ab := (bly - tly) + (bry - try) 442 self.Values[baseRowIndex+int(alignedRight)] += ab * xdiff / 2 443 } 444 }