Author Topic: Scan Line Filled Polygon  (Read 4458 times)

0 Members and 1 Guest are viewing this topic.

Offline AndyA

  • Newbie
  • Posts: 73
    • View Profile
Scan Line Filled Polygon
« on: December 02, 2019, 01:35:56 am »
This a fast scan line filled polygon SUB.

In the past I've used it to mask sprites for a jigsaw puzzle game. It's also ALPHA friendly as the lines are only drawn once to fill the polygon.

A bit of preparation is required to use in your own programs.
1)  A list of vertex coordinate pairs that define the polygon. Could be DATA or file on disk.
2) DIM enough array space to hold all of the vertex coordinates and the nodes list.
3) Find or include the bounding box of the polygon.

That's it!

Maybe someone can think of other applications for this SUB.

Code: QB64: [Select]
  1. 'http://alienryderflex.com/polygon_fill/
  2. 'public-domain code by Darel Rex Finley, 2007
  3.  
  4. 'QB64 version by Andy Amaya
  5.  
  6. _TITLE "Scan Line Filled Polygon"
  7.  
  8. DIM SHARED polyX#(500), polyY#(500), nodeX%(500)
  9. COLOR 15, 1
  10. LINE (0, 0)-(639, 479), 1, BF
  11.  
  12. 'Initialize maxima and minima then, find the bounding box of the polygon
  13. minX# = 9999
  14. maxX# = -9999
  15. minY# = 9999
  16. maxY# = -9999
  17.  
  18. scaleX# = 5.2 'X scaling factor
  19. scaleY# = 4.2 'Y scaling factor
  20. offsetX# = 40 'X and Y offsets to place polygon on screen
  21. offsetY# = 18
  22. numVerts% = 62
  23.  
  24. 'Read the polygon vertices into polyX() and poly() arrays
  25. 'Use scaling and offsets before placing into the two arrays
  26. RESTORE jigsawPiece
  27. FOR i% = 0 TO numVerts% - 1 'polygon has 62 sides (zero based counting)
  28.     READ zx%
  29.     READ zy%
  30.     polyX#(i%) = zx% * scaleX# + offsetX#
  31.     polyY#(i%) = zy% * scaleY# + offsetY#
  32.     minX# = min(minX#, polyX#(i%))
  33.     maxX# = max(maxX#, polyX#(i%))
  34.     minY# = min(minY#, polyY#(i%))
  35.     maxY# = max(maxY#, polyY#(i%))
  36.  
  37. st = TIMER(.001)
  38. polyFill numVerts% - 1, minX#, maxX#, minY#, maxY#
  39. et = TIMER(.001) - st
  40.  
  41. PRINT "Polygon with 62 vertices filled in: "; USING "###"; (et * 1000);
  42. PRINT " milliseconds"
  43.  
  44. jigsawPiece:
  45. DATA 4,4,7,2,27,0,36,1,35,6,32,13,32,20,36,24,45,27,61,31
  46. DATA 61,26,57,22,57,19,60,18,75,17,89,21,87,43,85,48,88,53,97,48
  47. DATA 99,47,106,48,107,51,109,58,106,76,103,78,99,75,95,73,93,74,92,82
  48. DATA 93,92,95,104,76,108,69,108,67,104,67,99,70,91,70,85,59,80,47,80
  49. DATA 45,81,46,86,48,92,42,93,22,88,20,87,16,73,15,65,14,58,15,55
  50. DATA 20,55,25,56,30,59,30,56,27,47,25,39,22,35,15,31,9,31,3,35
  51. DATA 0,34,0,27
  52.  
  53. SUB polyFill (numSides%, minX#, maxX#, minY#, maxY#)
  54.     FOR pixelY% = minY# TO maxY#
  55.         nodes% = 0
  56.         j% = numSides% - 1
  57.         fpixY# = pixelY%
  58.         'build node list
  59.         FOR i% = 0 TO numSides% - 1
  60.             b1% = ABS(polyY#(i%) < fpixY#)
  61.             b2% = ABS(polyY#(j%) >= fpixY#)
  62.             b3% = ABS(polyY#(j%) < fpixY#)
  63.             b4% = ABS(polyY#(i%) >= fpixY#)
  64.             IF ((ABS(b1% AND b2%)) OR (ABS(b3% AND b4%))) THEN
  65.                 f1# = fpixY# - polyY#(i%)
  66.                 f2# = polyY#(j%) - polyY#(i%)
  67.                 f3# = polyX#(j%) - polyX#(i%)
  68.                 nodeX%(nodes%) = INT(f1# / f2# * f3# + polyX#(i%))
  69.                 nodes% = nodes% + 1
  70.             END IF
  71.             j% = i%
  72.         NEXT
  73.         'sort nodes
  74.         i% = 0
  75.         WHILE i% < nodes% - 1
  76.             IF (nodeX%(i%) > nodeX%(i% + 1)) THEN
  77.                 SWAP nodeX%(i%), nodeX%(i% + 1)
  78.                 IF (i% > 0) THEN i% = i% - 1
  79.             ELSE
  80.                 i% = i% + 1
  81.             END IF
  82.         WEND
  83.         'draw scanlines
  84.         FOR i% = 0 TO nodes% - 1 STEP 2
  85.             IF (nodeX%(i%) <= maxX#) THEN
  86.                 IF (nodeX%(i% + 1) > minX#) THEN
  87.                     IF (nodeX%(i%) < minX#) THEN nodeX%(i%) = minX#
  88.                     IF (nodeX%(i% + 1) > maxX#) THEN nodeX%(i% + 1) = maxX#
  89.                     LINE (nodeX%(i%), pixelY%)-(nodeX%(i% + 1), pixelY%)
  90.                 END IF
  91.             END IF
  92.         NEXT
  93.     NEXT
  94.  
  95. FUNCTION min# (flt1#, flt2#)
  96.     IF flt1# < flt2# THEN min# = flt1# ELSE min# = flt2#
  97.  
  98. FUNCTION max# (flt1#, flt2#)
  99.     IF flt1# > flt2# THEN max# = flt1# ELSE max# = flt2#

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Scan Line Filled Polygon
« Reply #1 on: December 02, 2019, 10:01:33 am »
Oh this is interesting, I will give it some study, Thanks Andy!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: Scan Line Filled Polygon
« Reply #2 on: December 02, 2019, 11:03:11 am »
Same here, look into my DRF-Polygons library, I even enhanced the function to draw the surrounding border as well in a different color and made filling/border independently optional.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline AndyA

  • Newbie
  • Posts: 73
    • View Profile
Re: Scan Line Filled Polygon
« Reply #3 on: December 04, 2019, 11:24:20 pm »
Quote
Same here, look into my DRF-Polygons library, I even enhanced the function to draw the surrounding border as well in a different color and made filling/border independently optional.

That's a nice library but for simplicity, I like to use cut and paste-able functions instead of libraries. It's easier for me to follow the dependencies of a single function and make changes when it's not embedded in a library.

Yes drawing the border is a nice touch. I use pretty much the same method in the 'Point in Polygon' function.
https://www.qb64.org/forum/index.php?topic=1945.0

I just decided to omit the Sub for this demo.

Code: QB64: [Select]
  1. 'Code to draw a polygon border
  2. SUB drawPoly (verts AS INTEGER, colr AS INTEGER)
  3.     j% = verts - 1
  4.     FOR i% = 0 TO verts - 1
  5.         'Point in polygon array variables for line drawing
  6.         'LINE (xp(j%), yp(j%))-(xp(i%), yp(i%)), colr%
  7.         'polyFill array variables for line drawing
  8.         LINE (polyX#(j%), polyY#(j%)-(polyX#(i%),polyY#(i%)), colr%
  9.         j% = i%
  10.     NEXT
« Last Edit: December 04, 2019, 11:42:55 pm by AndyA »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Scan Line Filled Polygon
« Reply #4 on: December 05, 2019, 01:23:13 pm »
OK I finally got around to conducting some experiments. Alas, I am getting wildly different results comparing Andy's polyFill method with normal PAINT method but I am guessing Andy's polyFill better because it doesn't need the outline needed for PAINT. If I change order Andy's polyFill then PAINT, polyFill is clearly better usually.

Anyway, then I wanted to try paintImage versus polyFillImage and for that Andy's polyFill method is clearly faster though I slowed it down considerably to "paint" the Brick image.

Here are tests I tried:
Code: QB64: [Select]
  1. _TITLE "Scan Line Filled Polygon Test" 'b+ copy Andy Amaya's post 2019-12-05
  2. ' and compare to naive poly fill that b+ would do from Paint Image Brick pattern test
  3. '_TITLE "Brick Pattern Tile, click a spot to spray paint a brick image."
  4.  
  5. 'Andy's notes
  6. 'http://alienryderflex.com/polygon_fill/
  7. 'public-domain code by Darel Rex Finley, 2007
  8. 'QB64 version by Andy Amaya
  9. '_TITLE "Scan Line Filled Polygon"
  10.  
  11. 'B+ notes of mods after adding Brick Pattern Tile code test
  12. ' change to 32 bit color graphis screen, so change Andy's colors
  13.  
  14. CONST xmax = 800, ymax = 600
  15. SCREEN _NEWIMAGE(xmax, ymax, 32)
  16. _SCREENMOVE 300, 40
  17.  
  18. DIM SHARED polyX#(500), polyY#(500), nodeX%(500)
  19.  
  20. 'Initialize maxima and minima then, find the bounding box of the polygon
  21. minX# = 9999
  22. maxX# = -9999
  23. minY# = 9999
  24. maxY# = -9999
  25.  
  26. scaleX# = 5.2 'X scaling factor
  27. scaleY# = 4.2 'Y scaling factor
  28. offsetX# = 80 'X and Y offsets to place polygon on screen
  29. offsetY# = 70
  30. numVerts% = 62
  31.  
  32. 'Read the polygon vertices into polyX() and poly() arrays
  33. 'Use scaling and offsets before placing into the two arrays
  34. RESTORE jigsawPiece
  35. FOR i% = 0 TO numVerts% - 1 'polygon has 62 sides (zero based counting)
  36.     READ zx%
  37.     READ zy%
  38.     polyX#(i%) = zx% * scaleX# + offsetX#
  39.     polyY#(i%) = zy% * scaleY# + offsetY#
  40.     minX# = min(minX#, polyX#(i%))
  41.     maxX# = max(maxX#, polyX#(i%))
  42.     minY# = min(minY#, polyY#(i%))
  43.     maxY# = max(maxY#, polyY#(i%))
  44.  
  45. 'make brick pattern   B+ test for compare
  46. brickpat& = _NEWIMAGE(16, 16, 32)
  47. _DEST brickpat&
  48. LINE (0, 0)-(_WIDTH(brickpat&) - 1, _HEIGHT(brickpat&) - 1), _RGB32(128, 0, 0), BF
  49. LINE (0, 0)-(_WIDTH(brickpat&) - 1, 0), _RGB32(200, 200, 200), BF
  50. LINE (0, 7)-(_WIDTH(brickpat&) - 1, 8), _RGB32(200, 200, 200), BF
  51. LINE (0, 15)-(_WIDTH(brickpat&) - 1, 15), _RGB32(200, 200, 200), BF
  52. LINE (0, 0)-(1, 8), _RGB32(200, 200, 200), BF
  53. LINE (7, 8)-(8, 15), _RGB32(200, 200, 200), BF
  54. DIM SHARED BC AS _UNSIGNED LONG 'requires a unique border color for Paint Porder 1 of 16 million + colors you can find one!
  55. BC = _RGB32(119, 17, 2) 'for PAINT fill to BC
  56. BC = &HFFFFFFFF
  57.  
  58. COLOR &HFFFFFFFF, &HFF000088
  59. PAINT (10, 10), &HFF000088 ' <<<<<<<<< for some reason what ever goes first is really fast either Andy's polyFill or PAINT
  60. '                            <<<<<<<<<<<<<< so I am making this call to PAINT first!
  61.  
  62. st = TIMER(.001)
  63. 'draw polygon, use Andy's fixed code, missing ), and 32 bit color
  64. drawPoly numVerts%, BC
  65. PAINT (10 * scaleX# + offsetX#, 10 * scaleY# + offsetY#), BC, BC
  66. et = TIMER(.001) - st
  67. PRINT "Polygon with 62 vertices Andy's drawPoly and PAINT filled in: "; USING "###"; (et * 1000);
  68. PRINT " milliseconds, press key for Andy's, Scan Line Fill Poly test..."
  69.  
  70. st = TIMER(.001)
  71. polyFill numVerts% - 1, minX#, maxX#, minY#, maxY#
  72. et = TIMER(.001) - st
  73. PRINT "Polygon with 62 vertices using Andy's polyFill, filled in: "; USING "###"; (et * 1000);
  74. PRINT " milliseconds, press key for bplus paintImage fill... "
  75.  
  76. st = TIMER(.001)
  77. 'draw polygon, use Andy's fixed code, missing ), and 32 bit color
  78. drawPoly numVerts%, BC
  79. paintImage 10 * scaleX# + offsetX#, 10 * scaleY# + offsetY#, BC, 0, brickpat&
  80. et = TIMER(.001) - st
  81. PRINT "Polygon with 62 vertices Andy's drawPoly and bplus paintImage filled in: "; USING "###"; (et * 1000);
  82. PRINT " milliseconds, press key for NEW polyFillImage, bplus mod of Andy's polyFill... "
  83.  
  84. st = TIMER(.001)
  85. ' combine Andy's polyFill with my paintImage for polyFillImage
  86. 'SUB    polyFillImage (numSides%, minX#, maxX#, minY#, maxY#, imageH&)
  87. polyFillImage numVerts% - 1, minX#, maxX#, minY#, maxY#, brickpat&
  88. et = TIMER(.001) - st
  89. PRINT "Polygon with 62 vertices NEW polyFillImage filled in: "; USING "###"; (et * 1000);
  90. PRINT " milliseconds, beats the old paintImage! End of tests...  "
  91.  
  92. jigsawPiece:
  93. DATA 4,4,7,2,27,0,36,1,35,6,32,13,32,20,36,24,45,27,61,31
  94. DATA 61,26,57,22,57,19,60,18,75,17,89,21,87,43,85,48,88,53,97,48
  95. DATA 99,47,106,48,107,51,109,58,106,76,103,78,99,75,95,73,93,74,92,82
  96. DATA 93,92,95,104,76,108,69,108,67,104,67,99,70,91,70,85,59,80,47,80
  97. DATA 45,81,46,86,48,92,42,93,22,88,20,87,16,73,15,65,14,58,15,55
  98. DATA 20,55,25,56,30,59,30,56,27,47,25,39,22,35,15,31,9,31,3,35
  99. DATA 0,34,0,27
  100.  
  101. SUB polyFill (numSides%, minX#, maxX#, minY#, maxY#)
  102.     FOR pixelY% = minY# TO maxY#
  103.         nodes% = 0
  104.         j% = numSides% - 1
  105.         fpixY# = pixelY%
  106.         'build node list
  107.         FOR i% = 0 TO numSides% - 1
  108.             b1% = ABS(polyY#(i%) < fpixY#)
  109.             b2% = ABS(polyY#(j%) >= fpixY#)
  110.             b3% = ABS(polyY#(j%) < fpixY#)
  111.             b4% = ABS(polyY#(i%) >= fpixY#)
  112.             IF ((ABS(b1% AND b2%)) OR (ABS(b3% AND b4%))) THEN
  113.                 f1# = fpixY# - polyY#(i%)
  114.                 f2# = polyY#(j%) - polyY#(i%)
  115.                 f3# = polyX#(j%) - polyX#(i%)
  116.                 nodeX%(nodes%) = INT(f1# / f2# * f3# + polyX#(i%))
  117.                 nodes% = nodes% + 1
  118.             END IF
  119.             j% = i%
  120.         NEXT
  121.         'sort nodes
  122.         i% = 0
  123.         WHILE i% < nodes% - 1
  124.             IF (nodeX%(i%) > nodeX%(i% + 1)) THEN
  125.                 SWAP nodeX%(i%), nodeX%(i% + 1)
  126.                 IF (i% > 0) THEN i% = i% - 1
  127.             ELSE
  128.                 i% = i% + 1
  129.             END IF
  130.         WEND
  131.         'draw scanlines
  132.         FOR i% = 0 TO nodes% - 1 STEP 2
  133.             IF (nodeX%(i%) <= maxX#) THEN
  134.                 IF (nodeX%(i% + 1) > minX#) THEN
  135.                     IF (nodeX%(i%) < minX#) THEN nodeX%(i%) = minX#
  136.                     IF (nodeX%(i% + 1) > maxX#) THEN nodeX%(i% + 1) = maxX#
  137.                     LINE (nodeX%(i%), pixelY%)-(nodeX%(i% + 1), pixelY%)
  138.                 END IF
  139.             END IF
  140.         NEXT
  141.     NEXT
  142.  
  143. SUB polyFillImage (numSides%, minX#, maxX#, minY#, maxY#, imageH&)
  144.     s = _SOURCE ' <<<<<<<<<<<<<< save Source for this routine
  145.     _SOURCE imageH&
  146.     FOR pixelY% = minY# TO maxY#
  147.         nodes% = 0
  148.         j% = numSides% - 1
  149.         fpixY# = pixelY%
  150.         'build node list
  151.         FOR i% = 0 TO numSides% - 1
  152.             b1% = ABS(polyY#(i%) < fpixY#)
  153.             b2% = ABS(polyY#(j%) >= fpixY#)
  154.             b3% = ABS(polyY#(j%) < fpixY#)
  155.             b4% = ABS(polyY#(i%) >= fpixY#)
  156.             IF ((ABS(b1% AND b2%)) OR (ABS(b3% AND b4%))) THEN
  157.                 f1# = fpixY# - polyY#(i%)
  158.                 f2# = polyY#(j%) - polyY#(i%)
  159.                 f3# = polyX#(j%) - polyX#(i%)
  160.                 nodeX%(nodes%) = INT(f1# / f2# * f3# + polyX#(i%))
  161.                 nodes% = nodes% + 1
  162.             END IF
  163.             j% = i%
  164.         NEXT
  165.         'sort nodes
  166.         i% = 0
  167.         WHILE i% < nodes% - 1
  168.             IF (nodeX%(i%) > nodeX%(i% + 1)) THEN
  169.                 SWAP nodeX%(i%), nodeX%(i% + 1)
  170.                 IF (i% > 0) THEN i% = i% - 1
  171.             ELSE
  172.                 i% = i% + 1
  173.             END IF
  174.         WEND
  175.         'draw scanlines
  176.         FOR i% = 0 TO nodes% - 1 STEP 2
  177.             IF (nodeX%(i%) <= maxX#) THEN
  178.                 IF (nodeX%(i% + 1) > minX#) THEN
  179.                     IF (nodeX%(i%) < minX#) THEN nodeX%(i%) = minX#
  180.                     IF (nodeX%(i% + 1) > maxX#) THEN nodeX%(i% + 1) = maxX#
  181.  
  182.                     'LINE (nodeX%(i%), pixelY%)-(nodeX%(i% + 1), pixelY%)   '                              <<<<<<<<<<
  183.                     FOR ii% = nodeX%(i%) TO nodeX%(i% + 1) '                                               <<<<<<<<<<
  184.                         ' _SOURCE imageH&
  185.                         PSET (ii%, pixelY%), POINT(ii% MOD _WIDTH(imageH&), pixelY% MOD _HEIGHT(imageH&)) '<<<<<<<<<<
  186.                     NEXT '                                                                                 <<<<<<<<<<
  187.  
  188.                 END IF
  189.             END IF
  190.         NEXT
  191.     NEXT
  192.     _SOURCE s '<<<<<<<<<<<<< restore source
  193.  
  194. FUNCTION min# (flt1#, flt2#)
  195.     IF flt1# < flt2# THEN min# = flt1# ELSE min# = flt2#
  196.  
  197. FUNCTION max# (flt1#, flt2#)
  198.     IF flt1# > flt2# THEN max# = flt1# ELSE max# = flt2#
  199.  
  200. SUB paintImage (x, y, Border~&, destHandle&, imageHandle&)
  201.     d = _DEST: s = _SOURCE
  202.     _DEST destHandle&
  203.     PAINT (x, y), BC, Border~&
  204.     FOR y = 0 TO _HEIGHT(destHandle&)
  205.         FOR x = 0 TO _WIDTH(destHandle&)
  206.             _SOURCE destHandle&
  207.             IF POINT(x, y) = BC THEN
  208.                 _SOURCE imageHandle&
  209.                 PSET (x, y), POINT(x MOD _WIDTH(imageHandle&), y MOD _HEIGHT(imageHandle&))
  210.             END IF
  211.         NEXT
  212.     NEXT
  213.     _DEST d: _SOURCE s
  214.  
  215. 'Code to draw a polygon border
  216. SUB drawPoly (verts AS INTEGER, colr AS _UNSIGNED LONG) 'change color
  217.     j% = verts - 1
  218.     FOR i% = 0 TO verts - 1
  219.         'Point in polygon array variables for line drawing
  220.         'LINE (xp(j%), yp(j%))-(xp(i%), yp(i%)), colr%
  221.         'polyFill array variables for line drawing
  222.         LINE (polyX#(j%), polyY#(j%))-(polyX#(i%), polyY#(i%)), colr
  223.         j% = i%
  224.     NEXT
  225.  
  226.  
« Last Edit: December 05, 2019, 01:36:44 pm by bplus »