mirror of
https://github.com/jhasse/poly2tri.git
synced 2024-11-26 15:26:12 +01:00
removed old code
This commit is contained in:
parent
2c67b8cf39
commit
29bbeb2973
@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46"
|
||||
sodipodi:docname="15Point.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs4">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective10" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="375"
|
||||
inkscape:cy="829.15262"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:window-width="795"
|
||||
inkscape:window-height="711"
|
||||
inkscape:window-x="284"
|
||||
inkscape:window-y="20">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2383" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 400,472.36218 L 500,392.36218 L 520,272.36218 L 460,232.36218 L 580,212.36218 L 480,152.36218 L 360,172.36218 L 360,52.362183 L 300,112.36218 L 200,32.362183 L 120,92.362183 L 200,72.362183 L 340,272.36218 L 200,212.36218 L 180,352.36218 L 300,312.36218 L 400,472.36218 z"
|
||||
id="path2385" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,8 +0,0 @@
|
||||
474.80999000000003 555.15656999999999
|
||||
474.80999000000003 530.87086999999997
|
||||
509.09570000000002 530.87086999999997
|
||||
543.38142000000005 530.87086999999997
|
||||
543.38142000000005 555.15656999999999
|
||||
543.38142000000005 579.44227000000001
|
||||
509.09570000000002 579.44227000000001
|
||||
474.80999000000003 579.44227000000001
|
275
data/bird.dat
275
data/bird.dat
@ -1,275 +0,0 @@
|
||||
4.57998 4.03402
|
||||
4.06435 4.06435
|
||||
3.51839 4.21601
|
||||
3.09376 4.42832
|
||||
2.60846 4.57998
|
||||
2.09284 4.7013
|
||||
1.51655 4.82263
|
||||
0.909929 4.94395
|
||||
0.242648 5.06527
|
||||
-0.30331 5.0956
|
||||
-1.15258 5.12594
|
||||
-1.72887 5.12594
|
||||
-2.48714 5.12594
|
||||
-2.85111 5.03494
|
||||
-3.36674 5.30792
|
||||
-3.70038 5.52024
|
||||
-4.15534 5.9752
|
||||
-4.7013 6.27851
|
||||
-5.0956 6.61215
|
||||
-5.73255 6.67281
|
||||
-6.55149 6.73348
|
||||
-6.88513 6.61215
|
||||
-7.46142 6.36951
|
||||
-7.88605 6.18752
|
||||
-8.25003 5.91454
|
||||
-8.64433 5.61123
|
||||
-8.88698 5.30792
|
||||
-9.06896 5.00461
|
||||
-9.25095 4.88329
|
||||
-9.94856 4.73163
|
||||
-10.6462 4.64064
|
||||
-11.1011 4.54965
|
||||
-11.3741 4.42832
|
||||
-11.5561 4.21601
|
||||
-11.0101 4.21601
|
||||
-10.1305 3.94303
|
||||
-9.61492 3.73071
|
||||
-9.15996 3.4274
|
||||
-8.73532 3.00277
|
||||
-8.34102 2.6388
|
||||
-7.97705 2.36582
|
||||
-7.61308 2.03218
|
||||
-7.18844 1.45589
|
||||
-6.79414 1.12225
|
||||
-6.64248 0.788605
|
||||
-6.36951 0.242648
|
||||
-6.24818 -0.212317
|
||||
-6.00553 -0.515627
|
||||
-5.73255 -0.818936
|
||||
-5.24726 -1.2739
|
||||
-4.7923 -1.60754
|
||||
-4.42832 -2.00184
|
||||
-3.67005 -2.21416
|
||||
-3.18475 -2.39615
|
||||
-2.5478 -2.69946
|
||||
-1.91085 -2.79045
|
||||
-1.06158 -2.88144
|
||||
-0.333641 -2.88144
|
||||
0.242648 -2.85111
|
||||
0.94026 -2.82078
|
||||
1.2739 -2.85111
|
||||
1.42556 -3.0331
|
||||
1.42556 -3.30608
|
||||
1.33456 -3.57905
|
||||
1.15258 -4.00369
|
||||
1.03125 -4.57998
|
||||
0.849267 -5.15627
|
||||
0.63695 -5.5809
|
||||
0.30331 -5.91454
|
||||
0.060662 -6.15719
|
||||
-0.333641 -6.27851
|
||||
-0.697612 -6.27851
|
||||
-1.15258 -6.36951
|
||||
-1.57721 -6.39984
|
||||
-2.09284 -6.52116
|
||||
-2.36582 -6.79414
|
||||
-2.48714 -7.06712
|
||||
-2.18383 -6.97612
|
||||
-1.85019 -6.79414
|
||||
-1.42556 -6.76381
|
||||
-1.15258 -6.79414
|
||||
-1.36489 -6.88513
|
||||
-1.69853 -6.97612
|
||||
-1.97151 -7.12778
|
||||
-2.12317 -7.37043
|
||||
-2.27482 -7.64341
|
||||
-2.39615 -7.91639
|
||||
-2.36582 -8.21969
|
||||
-2.03218 -7.85572
|
||||
-1.81986 -7.7344
|
||||
-1.57721 -7.67374
|
||||
-1.36489 -7.49175
|
||||
-1.21324 -7.40076
|
||||
-0.849267 -7.2491
|
||||
-0.60662 -7.12778
|
||||
-0.242648 -6.91546
|
||||
0.030331 -6.70315
|
||||
0.363972 -6.4605
|
||||
0.242648 -6.61215
|
||||
0.152837 -6.72007
|
||||
-0.092855 -6.88818
|
||||
-0.506653 -7.15974
|
||||
-0.765276 -7.31491
|
||||
-1.01097 -7.41836
|
||||
-1.16614 -7.5606
|
||||
-1.32132 -7.71577
|
||||
-1.45063 -7.81922
|
||||
-1.50235 -8.06492
|
||||
-1.50235 -8.29768
|
||||
-1.46356 -8.53044
|
||||
-1.38597 -8.29768
|
||||
-1.28252 -8.05199
|
||||
-1.14028 -7.87095
|
||||
-0.985106 -7.84509
|
||||
-0.817001 -7.84509
|
||||
-0.623033 -7.70284
|
||||
-0.390272 -7.52181
|
||||
-0.105787 -7.31491
|
||||
0.178699 -7.06922
|
||||
0.489047 -6.84939
|
||||
0.670083 -6.66835
|
||||
0.928707 -6.47438
|
||||
1.16147 -6.33214
|
||||
1.47182 -6.13817
|
||||
1.82096 -5.91834
|
||||
2.04079 -5.84076
|
||||
2.15717 -5.71144
|
||||
2.18303 -5.45282
|
||||
2.06665 -5.28472
|
||||
1.87268 -5.3623
|
||||
1.49768 -5.63386
|
||||
1.22612 -5.81489
|
||||
1.03216 -5.91834
|
||||
0.876982 -5.95714
|
||||
0.954569 -5.80196
|
||||
1.00629 -5.60799
|
||||
1.16147 -5.29765
|
||||
1.3425 -4.9873
|
||||
1.45888 -4.65109
|
||||
1.47182 -4.4054
|
||||
1.73044 -3.95281
|
||||
1.84682 -3.6166
|
||||
1.98906 -3.30625
|
||||
2.14424 -2.95711
|
||||
2.26062 -2.75021
|
||||
2.42872 -2.59503
|
||||
2.63562 -2.50452
|
||||
2.98476 -2.51745
|
||||
3.12701 -2.71141
|
||||
3.06235 -3.09935
|
||||
2.9589 -3.4097
|
||||
2.86838 -3.75884
|
||||
2.79079 -4.12091
|
||||
2.70028 -4.43126
|
||||
2.55803 -4.75454
|
||||
2.48045 -5.03902
|
||||
2.3382 -5.37523
|
||||
2.29941 -5.59506
|
||||
2.23475 -5.90541
|
||||
2.11837 -6.21576
|
||||
1.7951 -6.65542
|
||||
1.39423 -7.05628
|
||||
1.09681 -7.26318
|
||||
0.838188 -7.37956
|
||||
0.41146 -7.49594
|
||||
-0.002337 -7.62526
|
||||
-0.416135 -7.7675
|
||||
-0.687689 -8.05199
|
||||
-0.907519 -8.40113
|
||||
-0.70062 -8.19423
|
||||
-0.312685 -8.05199
|
||||
-0.015268 -7.89681
|
||||
0.217493 -7.89681
|
||||
0.243355 -7.90974
|
||||
0.023525 -8.1425
|
||||
-0.157511 -8.25888
|
||||
-0.403203 -8.43992
|
||||
-0.648896 -8.75027
|
||||
-0.778207 -8.90544
|
||||
-0.881657 -9.18993
|
||||
-0.80407 -9.60372
|
||||
-0.597171 -9.177
|
||||
-0.14458 -8.9701
|
||||
0.269217 -8.62096
|
||||
0.695946 -8.28475
|
||||
1.13561 -8.00026
|
||||
1.52354 -7.62526
|
||||
1.82096 -7.26318
|
||||
1.95027 -7.09508
|
||||
1.9632 -7.15974
|
||||
1.66578 -7.58646
|
||||
1.45888 -7.84509
|
||||
1.13561 -8.20716
|
||||
0.760601 -8.65975
|
||||
0.450253 -8.99596
|
||||
0.269217 -9.28045
|
||||
0.126974 -9.65545
|
||||
0.19163 -10.2761
|
||||
0.333873 -9.84942
|
||||
0.63129 -9.68131
|
||||
0.980431 -9.26751
|
||||
1.26492 -8.72441
|
||||
1.60113 -8.31061
|
||||
1.98906 -7.7675
|
||||
2.36407 -7.34077
|
||||
2.79079 -7.00456
|
||||
3.13994 -6.7718
|
||||
3.68304 -6.46145
|
||||
4.14857 -6.33214
|
||||
4.7434 -6.09938
|
||||
5.19599 -6.13817
|
||||
4.85978 -5.87955
|
||||
4.29081 -5.76317
|
||||
3.77356 -5.81489
|
||||
3.34683 -6.07352
|
||||
2.77786 -6.47438
|
||||
2.41579 -6.60369
|
||||
2.41579 -6.28042
|
||||
2.59683 -5.84076
|
||||
2.79079 -5.42696
|
||||
2.99769 -4.90971
|
||||
3.25632 -4.30195
|
||||
3.50201 -3.52608
|
||||
3.83822 -2.63383
|
||||
4.07098 -2.40107
|
||||
4.39426 -2.28469
|
||||
4.79512 -2.23296
|
||||
4.54943 -2.02606
|
||||
4.49771 -1.6252
|
||||
4.54943 -1.50882
|
||||
4.91151 -1.50882
|
||||
5.54513 -1.45709
|
||||
6.12704 -1.39244
|
||||
6.85118 -1.32778
|
||||
7.44601 -1.14674
|
||||
7.85981 -0.78467
|
||||
7.79516 -0.409667
|
||||
7.49774 -0.151043
|
||||
7.84688 0.042924
|
||||
8.23481 0.314479
|
||||
8.64861 0.702414
|
||||
8.70034 1.09035
|
||||
8.41585 1.42656
|
||||
8.11843 1.62053
|
||||
8.3512 2.06019
|
||||
8.53223 2.38347
|
||||
8.67447 2.74554
|
||||
8.66154 3.22399
|
||||
8.80379 3.87055
|
||||
8.90724 4.36193
|
||||
9.1012 4.85332
|
||||
9.43741 5.40936
|
||||
9.90293 6.04298
|
||||
10.3167 6.58609
|
||||
10.7047 7.3749
|
||||
10.9374 7.96973
|
||||
11.1573 8.40939
|
||||
11.1573 8.84905
|
||||
10.9374 9.05595
|
||||
10.6659 9.28871
|
||||
10.3426 9.37922
|
||||
9.99345 9.34043
|
||||
9.63138 8.97836
|
||||
9.20465 8.48697
|
||||
8.86844 8.1249
|
||||
8.50637 7.72404
|
||||
8.17016 7.28438
|
||||
7.74343 6.88351
|
||||
7.43308 6.5473
|
||||
7.16153 6.1723
|
||||
6.70894 5.71971
|
||||
6.20462 5.25418
|
||||
5.72617 4.80159
|
||||
5.13134 4.41366
|
||||
4.87271 4.16797
|
@ -1,94 +0,0 @@
|
||||
280.35714 648.79075
|
||||
286.78571 662.8979
|
||||
263.28607 661.17871
|
||||
262.31092 671.41548
|
||||
250.53571 677.00504
|
||||
250.53571 683.43361
|
||||
256.42857 685.21933
|
||||
297.14286 669.50504
|
||||
289.28571 649.50504
|
||||
285 631.6479
|
||||
285 608.79075
|
||||
292.85714 585.21932
|
||||
306.42857 563.79075
|
||||
323.57143 548.79075
|
||||
339.28571 545.21932
|
||||
357.85714 547.36218
|
||||
375 550.21932
|
||||
391.42857 568.07647
|
||||
404.28571 588.79075
|
||||
413.57143 612.36218
|
||||
417.14286 628.07647
|
||||
438.57143 619.1479
|
||||
438.03572 618.96932
|
||||
437.5 609.50504
|
||||
426.96429 609.86218
|
||||
424.64286 615.57647
|
||||
419.82143 615.04075
|
||||
420.35714 605.04075
|
||||
428.39286 598.43361
|
||||
437.85714 599.68361
|
||||
443.57143 613.79075
|
||||
450.71429 610.21933
|
||||
431.42857 575.21932
|
||||
405.71429 550.21932
|
||||
372.85714 534.50504
|
||||
349.28571 531.6479
|
||||
346.42857 521.6479
|
||||
346.42857 511.6479
|
||||
350.71429 496.6479
|
||||
367.85714 476.6479
|
||||
377.14286 460.93361
|
||||
385.71429 445.21932
|
||||
388.57143 404.50504
|
||||
360 352.36218
|
||||
337.14286 325.93361
|
||||
330.71429 334.50504
|
||||
347.14286 354.50504
|
||||
337.85714 370.21932
|
||||
333.57143 359.50504
|
||||
319.28571 353.07647
|
||||
312.85714 366.6479
|
||||
350.71429 387.36218
|
||||
368.57143 408.07647
|
||||
375.71429 431.6479
|
||||
372.14286 454.50504
|
||||
366.42857 462.36218
|
||||
352.85714 462.36218
|
||||
336.42857 456.6479
|
||||
332.85714 438.79075
|
||||
338.57143 423.79075
|
||||
338.57143 411.6479
|
||||
327.85714 405.93361
|
||||
320.71429 407.36218
|
||||
315.71429 423.07647
|
||||
314.28571 440.21932
|
||||
325 447.71932
|
||||
324.82143 460.93361
|
||||
317.85714 470.57647
|
||||
304.28571 483.79075
|
||||
287.14286 491.29075
|
||||
263.03571 498.61218
|
||||
251.60714 503.07647
|
||||
251.25 533.61218
|
||||
260.71429 533.61218
|
||||
272.85714 528.43361
|
||||
286.07143 518.61218
|
||||
297.32143 508.25504
|
||||
297.85714 507.36218
|
||||
298.39286 506.46932
|
||||
307.14286 496.6479
|
||||
312.67857 491.6479
|
||||
317.32143 503.07647
|
||||
322.5 514.1479
|
||||
325.53571 521.11218
|
||||
327.14286 525.75504
|
||||
326.96429 535.04075
|
||||
311.78571 540.04075
|
||||
291.07143 552.71932
|
||||
274.82143 568.43361
|
||||
259.10714 592.8979
|
||||
254.28571 604.50504
|
||||
251.07143 621.11218
|
||||
250.53571 649.1479
|
||||
268.1955 654.36208
|
@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46"
|
||||
sodipodi:docname="dude.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs4">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective10" />
|
||||
<inkscape:perspective
|
||||
id="perspective2473"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="450.63058"
|
||||
inkscape:cy="608.37669"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1271"
|
||||
inkscape:window-height="749"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2383" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 320.72342,481.626 L 338.90617,465.96863 L 347.99754,480.61584 L 329.8148,510.41534 L 339.91632,480.11077 L 334.86556,478.09046 L 320.72342,481.626 z"
|
||||
id="chestHole"
|
||||
inkscape:label="#path2486" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 325,437.8979 L 320.71429,423.61218 L 329.82143,413.61218 L 332.67857,423.79075 L 325,437.8979 z"
|
||||
id="headHole"
|
||||
inkscape:label="#path2498" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="dude">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 280.35714,648.79075 L 286.78571,662.8979 L 263.28607,661.17871 L 262.31092,671.41548 L 250.53571,677.00504 L 250.53571,683.43361 L 256.42857,685.21933 L 297.14286,669.50504 L 289.28571,649.50504 L 285,631.6479 L 285,608.79075 L 292.85714,585.21932 L 306.42857,563.79075 L 323.57143,548.79075 L 339.28571,545.21932 L 357.85714,547.36218 L 375,550.21932 L 391.42857,568.07647 L 404.28571,588.79075 L 413.57143,612.36218 L 417.14286,628.07647 C 438.57143,619.1479 438.03572,618.96932 438.03572,618.96932 L 437.5,609.50504 L 426.96429,609.86218 L 424.64286,615.57647 L 419.82143,615.04075 L 420.35714,605.04075 L 428.39286,598.43361 L 437.85714,599.68361 L 443.57143,613.79075 L 450.71429,610.21933 L 431.42857,575.21932 L 405.71429,550.21932 L 372.85714,534.50504 L 349.28571,531.6479 L 346.42857,521.6479 L 346.42857,511.6479 L 350.71429,496.6479 L 367.85714,476.6479 L 377.14286,460.93361 L 385.71429,445.21932 L 388.57143,404.50504 L 360,352.36218 L 337.14286,325.93361 L 330.71429,334.50504 L 347.14286,354.50504 L 347.14286,374.50504 L 337.85714,370.21932 L 333.57143,359.50504 L 319.28571,353.07647 L 312.85714,366.6479 L 350.71429,387.36218 L 368.57143,408.07647 L 375.71429,431.6479 L 372.14286,454.50504 L 366.42857,462.36218 L 352.85714,462.36218 L 336.42857,456.6479 L 332.85714,438.79075 L 338.57143,423.79075 L 338.57143,411.6479 L 327.85714,405.93361 L 320.71429,407.36218 L 315.71429,423.07647 L 314.28571,440.21932 L 325,447.71932 L 324.82143,460.93361 L 317.85714,470.57647 L 304.28571,483.79075 L 287.14286,491.29075 L 263.03571,498.61218 L 251.60714,503.07647 L 251.25,533.61218 L 260.71429,533.61218 L 272.85714,528.43361 L 286.07143,518.61218 C 286.07143,518.61218 297.32143,508.25504 297.85714,507.36218 C 298.39286,506.46932 307.14286,496.6479 307.14286,496.6479 L 312.67857,491.6479 L 317.32143,503.07647 L 322.5,514.1479 L 325.53571,521.11218 L 327.14286,525.75504 L 326.96429,535.04075 L 311.78571,540.04075 L 291.07143,552.71932 L 274.82143,568.43361 L 259.10714,592.8979 L 254.28571,604.50504 L 251.07143,621.11218 L 250.53571,649.1479 L 268.1955,654.36208 L 280.35714,648.79075 z"
|
||||
id="path2482"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccscccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.4 KiB |
18
data/i.18
18
data/i.18
@ -1,18 +0,0 @@
|
||||
0 0
|
||||
10 7
|
||||
12 3
|
||||
20 8
|
||||
13 17
|
||||
10 12
|
||||
12 14
|
||||
14 9
|
||||
8 10
|
||||
6 14
|
||||
10 15
|
||||
7 18
|
||||
0 16
|
||||
1 13
|
||||
3 15
|
||||
5 8
|
||||
-2 9
|
||||
5 5
|
12
data/i.snake
12
data/i.snake
@ -1,12 +0,0 @@
|
||||
10 0
|
||||
20 10
|
||||
30 0
|
||||
40 10
|
||||
50 0
|
||||
50 10
|
||||
40 20
|
||||
30 10
|
||||
20 20
|
||||
10 10
|
||||
0 20
|
||||
0 10
|
@ -1,3 +0,0 @@
|
||||
0 0
|
||||
1 0
|
||||
1 1
|
1036
data/nazca_heron.dat
1036
data/nazca_heron.dat
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
||||
350 75
|
||||
379 161
|
||||
469 161
|
||||
397 215
|
||||
423 301
|
||||
350 250
|
||||
277 301
|
||||
303 215
|
||||
231 161
|
||||
321 161
|
@ -1,16 +0,0 @@
|
||||
400 472
|
||||
500 392
|
||||
520 272
|
||||
460 232
|
||||
580 212
|
||||
480 152
|
||||
360 172
|
||||
360 52
|
||||
300 112
|
||||
200 32
|
||||
120 92
|
||||
200 72
|
||||
340 272
|
||||
208 212
|
||||
180 352
|
||||
300 312
|
@ -1,55 +0,0 @@
|
||||
-37.317758 260.65677
|
||||
-37.317758 317.44301
|
||||
-34.741139 339.73612
|
||||
-27.330761 354.21222
|
||||
59.525828 406.25724
|
||||
444.17643 404.87856
|
||||
538.9604 368.68832
|
||||
555.15984 355.59089
|
||||
558.95121 344.90615
|
||||
559.64054 325.94936
|
||||
568.19908 315.85885
|
||||
572.586 322.56108
|
||||
584.65003 322.31737
|
||||
568.80837 296.11771
|
||||
557.59736 289.78104
|
||||
539.56224 286.49085
|
||||
443.31476 286.82943
|
||||
389.89106 280.79772
|
||||
405.74583 272.00866
|
||||
412.98388 262.01326
|
||||
475.5413 262.1856
|
||||
480.71134 267.01096
|
||||
514.66123 266.66629
|
||||
520.34827 262.01326
|
||||
669.93463 262.01326
|
||||
670.45162 264.08127
|
||||
676.91417 263.82277
|
||||
678.03434 262.09943
|
||||
680.61936 261.66859
|
||||
683.03204 255.46455
|
||||
682.51504 249.94985
|
||||
677.862 243.91814
|
||||
668.81445 243.4873
|
||||
665.28159 247.02016
|
||||
520.86527 246.33082
|
||||
514.66123 240.12678
|
||||
479.67733 239.95444
|
||||
475.5413 243.57347
|
||||
412.98388 243.05647
|
||||
397.64611 227.54636
|
||||
324.74862 221.16998
|
||||
323.88695 214.79361
|
||||
328.36764 211.86392
|
||||
326.6443 207.03856
|
||||
300.79412 207.03856
|
||||
295.62409 211.69159
|
||||
285.28402 208.2449
|
||||
272.01426 211.17458
|
||||
96.577738 209.62357
|
||||
80.205961 211.86392
|
||||
58.491817 232.7164
|
||||
74.863594 254.94755
|
||||
168.61356 269.25131
|
||||
175.16228 276.83403
|
||||
87.271676 260.11758
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
touch framework/framework.pyx
|
||||
rm -rf build
|
||||
python setup.py build_ext -i
|
@ -1,136 +0,0 @@
|
||||
##
|
||||
## GL convenience layer
|
||||
##
|
||||
from math import pi as PI
|
||||
|
||||
from gl cimport *
|
||||
include "polydecomp.pxi"
|
||||
include "seidel.pxi"
|
||||
|
||||
cdef extern from 'math.h':
|
||||
double cos(double)
|
||||
double sin(double)
|
||||
double sqrt(double)
|
||||
|
||||
SEGMENTS = 25
|
||||
INCREMENT = 2.0 * PI / SEGMENTS
|
||||
|
||||
def init_gl(width, height):
|
||||
glEnable(GL_LINE_SMOOTH)
|
||||
glEnable(GL_BLEND)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||
glHint (GL_LINE_SMOOTH_HINT, GL_NICEST)
|
||||
|
||||
def reset_zoom(float zoom, center, size):
|
||||
|
||||
zinv = 1.0 / zoom
|
||||
left = -size[0] * zinv
|
||||
right = size[0] * zinv
|
||||
bottom = -size[1] * zinv
|
||||
top = size[1] * zinv
|
||||
|
||||
# Reset viewport
|
||||
glLoadIdentity()
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
|
||||
# Reset ortho view
|
||||
glOrtho(left, right, bottom, top, 1, -1)
|
||||
glTranslatef(-center[0], -center[1], 0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
glDisable(GL_DEPTH_TEST)
|
||||
glLoadIdentity()
|
||||
|
||||
# Clear the screen
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
|
||||
def draw_polygon(verts, color):
|
||||
r, g, b = color
|
||||
glColor3f(r, g, b)
|
||||
glBegin(GL_LINE_LOOP)
|
||||
for v in verts:
|
||||
glVertex2f(v[0], v[1])
|
||||
glEnd()
|
||||
|
||||
def draw_line(p1, p2, color):
|
||||
r, g, b = color
|
||||
glColor3f(r, g, b)
|
||||
glBegin(GL_LINES)
|
||||
glVertex2f(p1[0], p1[1])
|
||||
glVertex2f(p2[0], p2[1])
|
||||
glEnd()
|
||||
|
||||
##
|
||||
## Game engine / main loop / UI
|
||||
##
|
||||
from glfw cimport *
|
||||
|
||||
import sys
|
||||
|
||||
# Keyboard callback wrapper
|
||||
kbd_callback_method = None
|
||||
|
||||
cdef extern void __stdcall kbd_callback(int id, int state):
|
||||
kbd_callback_method(id, state)
|
||||
|
||||
|
||||
cdef class Game:
|
||||
|
||||
title = "Poly2Tri"
|
||||
|
||||
def __init__(self, window_width, window_height):
|
||||
|
||||
glfwInit()
|
||||
|
||||
# 16 bit color, no depth, alpha or stencil buffers, windowed
|
||||
if not glfwOpenWindow(window_width, window_height, 8, 8, 8, 8, 24, 0, GLFW_WINDOW):
|
||||
glfwTerminate()
|
||||
raise SystemError('Unable to create GLFW window')
|
||||
|
||||
glfwEnable(GLFW_STICKY_KEYS)
|
||||
glfwSwapInterval(1) #VSync on
|
||||
|
||||
def register_kbd_callback(self, f):
|
||||
global kbd_callback_method
|
||||
glfwSetKeyCallback(kbd_callback)
|
||||
kbd_callback_method = f
|
||||
|
||||
def main_loop(self):
|
||||
|
||||
frame_count = 1
|
||||
start_time = glfwGetTime()
|
||||
|
||||
running = True
|
||||
while running:
|
||||
|
||||
current_time = glfwGetTime()
|
||||
|
||||
#Calculate and display FPS (frames per second)
|
||||
if (current_time - start_time) > 1 or frame_count == 0:
|
||||
frame_rate = frame_count / (current_time - start_time)
|
||||
t = self.title + " (%d FPS)" % frame_rate
|
||||
glfwSetWindowTitle(t)
|
||||
start_time = current_time
|
||||
frame_count = 0
|
||||
|
||||
frame_count = frame_count + 1
|
||||
|
||||
# Check if the ESC key was pressed or the window was closed
|
||||
running = ((not glfwGetKey(GLFW_KEY_ESC))
|
||||
and glfwGetWindowParam(GLFW_OPENED))
|
||||
|
||||
self.update()
|
||||
self.render()
|
||||
|
||||
glfwSwapBuffers()
|
||||
|
||||
|
||||
glfwTerminate()
|
||||
|
||||
property window_title:
|
||||
def __set__(self, title): self.title = title
|
||||
|
||||
property time:
|
||||
def __get__(self): return glfwGetTime()
|
||||
def __set__(self, t): glfwSetTime(t)
|
File diff suppressed because it is too large
Load Diff
@ -1,70 +0,0 @@
|
||||
# GLFW Declarations
|
||||
# Copyright 2009 Mason Green & Tom Novelli
|
||||
#
|
||||
# This file is part of OpenMelee.
|
||||
#
|
||||
# OpenMelee is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# OpenMelee is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with OpenMelee. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cdef extern from "GL/glfw.h":
|
||||
|
||||
ctypedef enum:
|
||||
GLFW_WINDOW
|
||||
GLFW_FULLSCREEN
|
||||
GLFW_STICKY_KEYS
|
||||
GLFW_OPENED
|
||||
GLFW_KEY_ESC
|
||||
GLFW_PRESS
|
||||
|
||||
# Function pointer types
|
||||
ctypedef void (__stdcall * GLFWwindowsizefun)(int, int)
|
||||
ctypedef int (__stdcall * GLFWwindowclosefun)()
|
||||
ctypedef void (__stdcall * GLFWwindowrefreshfun)()
|
||||
ctypedef void (__stdcall * GLFWmousebuttonfun)(int, int)
|
||||
ctypedef void (__stdcall * GLFWmouseposfun)(int, int)
|
||||
ctypedef void (__stdcall * GLFWmousewheelfun)(int)
|
||||
ctypedef void (__stdcall * GLFWkeyfun)(int, int)
|
||||
ctypedef void (__stdcall * GLFWcharfun)(int, int)
|
||||
ctypedef void (__stdcall * GLFWthreadfun)()
|
||||
|
||||
int glfwInit()
|
||||
void glfwTerminate()
|
||||
void glfwSetWindowTitle(char *title)
|
||||
int glfwOpenWindow( int width, int height,
|
||||
int redbits, int greenbits, int bluebits, int alphabits,
|
||||
int depthbits, int stencilbits, int mode )
|
||||
void glfwSwapInterval( int interval )
|
||||
void glfwSwapBuffers()
|
||||
void glfwEnable( int token )
|
||||
int glfwGetWindowParam( int param )
|
||||
int glfwGetKey( int key )
|
||||
int glfwGetWindowParam( int param )
|
||||
void glfwGetWindowSize( int *width, int *height )
|
||||
double glfwGetTime()
|
||||
void glfwSetTime(double time)
|
||||
|
||||
# Input handling
|
||||
void glfwPollEvents()
|
||||
void glfwWaitEvents()
|
||||
int glfwGetKey( int key )
|
||||
int glfwGetMouseButton( int button )
|
||||
void glfwGetMousePos( int *xpos, int *ypos )
|
||||
void glfwSetMousePos( int xpos, int ypos )
|
||||
int glfwGetMouseWheel()
|
||||
void glfwSetMouseWheel( int pos )
|
||||
void glfwSetKeyCallback(GLFWkeyfun cbfun)
|
||||
void glfwSetCharCallback( GLFWcharfun cbfun )
|
||||
void glfwSetMouseButtonCallback( GLFWmousebuttonfun cbfun )
|
||||
void glfwSetMousePosCallback( GLFWmouseposfun cbfun )
|
||||
void glfwSetMouseWheelCallback( GLFWmousewheelfun cbfun )
|
||||
|
@ -1,158 +0,0 @@
|
||||
##
|
||||
## Ported from PolyDeomp by Mark Bayazit
|
||||
## http://mnbayazit.com/406/bayazit
|
||||
##
|
||||
from sys import float_info
|
||||
|
||||
cdef extern from 'predicates.h':
|
||||
double orient2d(double *pa, double *pb, double *pc)
|
||||
|
||||
def make_ccw(list poly):
|
||||
cdef int br = 0
|
||||
# find bottom right point
|
||||
for i from 1 <= i < len(poly):
|
||||
if poly[i][1] < poly[br][1] or (poly[i][1] == poly[br][1] and poly[i][0] > poly[br][0]):
|
||||
br = i
|
||||
# reverse poly if clockwise
|
||||
if not left(at(poly, br - 1), at(poly, br), at(poly, br + 1)):
|
||||
poly.reverse()
|
||||
|
||||
cpdef list decompose_poly(list poly, list polys):
|
||||
|
||||
cdef list upperInt = [], lowerInt = [], p = [], closestVert = []
|
||||
cdef float upperDist, lowerDist, d, closestDist
|
||||
cdef int upper_index, lower_index, closest_index
|
||||
cdef list lower_poly = [], upper_poly = []
|
||||
|
||||
for i from 0 <= i < len(poly):
|
||||
if is_reflex(poly, i):
|
||||
upperDist = lowerDist = float_info.max
|
||||
for j from 0 <= j < len(poly):
|
||||
if left(at(poly, i - 1), at(poly, i), at(poly, j)) and rightOn(at(poly, i - 1), at(poly, i), at(poly, j - 1)):
|
||||
# if line intersects with an edge
|
||||
# find the point of intersection
|
||||
p = intersection(at(poly, i - 1), at(poly, i), at(poly, j), at(poly, j - 1))
|
||||
if right(at(poly, i + 1), at(poly, i), p):
|
||||
# make sure it's inside the poly
|
||||
d = sqdist(poly[i], p)
|
||||
if d < lowerDist:
|
||||
# keep only the closest intersection
|
||||
lowerDist = d
|
||||
lowerInt = p
|
||||
lower_index = j
|
||||
if left(at(poly, i + 1), at(poly, i), at(poly, j + 1)) and rightOn(at(poly, i + 1), at(poly, i), at(poly, j)):
|
||||
p = intersection(at(poly, i + 1), at(poly, i), at(poly, j), at(poly, j + 1))
|
||||
if left(at(poly, i - 1), at(poly, i), p):
|
||||
d = sqdist(poly[i], p)
|
||||
if d < upperDist:
|
||||
upperDist = d
|
||||
upperInt = p
|
||||
upper_index = j
|
||||
|
||||
# if there are no vertices to connect to, choose a point in the middle
|
||||
if lower_index == (upper_index + 1) % len(poly):
|
||||
p[0] = (lowerInt[0] + upperInt[0]) * 0.5
|
||||
p[1] = (lowerInt[1] + upperInt[1]) * 0.5
|
||||
|
||||
if i < upper_index:
|
||||
lower_poly.extend(poly[i:upper_index+1])
|
||||
lower_poly.append(p)
|
||||
upper_poly.append(p)
|
||||
if lower_index != 0:
|
||||
upper_poly.extend(poly[lower_index:])
|
||||
upper_poly.extend(poly[:i+1])
|
||||
else:
|
||||
if i != 0:
|
||||
lower_poly.extend(poly[i:])
|
||||
lower_poly.extend(poly[:upper_index+1])
|
||||
lower_poly.append(p)
|
||||
upper_poly.append(p)
|
||||
upper_poly.extend(poly[lower_index:i+1])
|
||||
else:
|
||||
|
||||
# connect to the closest point within the triangle
|
||||
|
||||
if lower_index > upper_index:
|
||||
upper_index += len(poly)
|
||||
|
||||
closestDist = float_info.max
|
||||
for j from lower_index <= j <= upper_index:
|
||||
if leftOn(at(poly, i - 1), at(poly, i), at(poly, j)) and rightOn(at(poly, i + 1), at(poly, i), at(poly, j)):
|
||||
d = sqdist(at(poly, i), at(poly, j))
|
||||
if d < closestDist:
|
||||
closestDist = d
|
||||
closestVert = at(poly, j)
|
||||
closest_index = j % len(poly)
|
||||
|
||||
if i < closest_index:
|
||||
lower_poly.extend(poly[i:closest_index+1])
|
||||
if closest_index != 0:
|
||||
upper_poly.extend(poly[closest_index:])
|
||||
upper_poly.extend(poly[:i+1])
|
||||
else:
|
||||
if i != 0:
|
||||
lower_poly.extend(poly[i:])
|
||||
lower_poly.extend(poly[:closest_index+1])
|
||||
upper_poly.extend(poly[closest_index:i+1])
|
||||
|
||||
# solve smallest poly first
|
||||
if len(lower_poly) < len(upper_poly):
|
||||
decompose_poly(lower_poly, polys)
|
||||
decompose_poly(upper_poly, polys)
|
||||
else:
|
||||
decompose_poly(upper_poly, polys)
|
||||
decompose_poly(lower_poly, polys)
|
||||
return
|
||||
|
||||
polys.append(poly)
|
||||
|
||||
cdef list intersection(list p1, list p2, list q1, list q2):
|
||||
cdef double pqx, pqy, bax, bay, t
|
||||
pqx = p1[0] - p2[0]
|
||||
pqy = p1[1] - p2[1]
|
||||
t = pqy*(q1[0]-p2[0]) - pqx*(q1[1]-p2[1])
|
||||
t /= pqx*(q2[1]-q1[1]) - pqy*(q2[0]-q1[0])
|
||||
bax = t*(q2[0]-q1[0]) + q1[0]
|
||||
bay = t*(q2[1]-q1[1]) + q1[1]
|
||||
return [bax, bay]
|
||||
|
||||
cdef bool eq(float a, float b):
|
||||
return abs(a - b) <= 1e-8
|
||||
|
||||
cdef list at(list v, int i):
|
||||
return v[i%len(v)]
|
||||
|
||||
cdef float area(list a, list b, list c):
|
||||
return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1])))
|
||||
|
||||
cdef bool left(list a, list b, list c):
|
||||
cdef double *x = [a[0], a[1]]
|
||||
cdef double *y = [b[0], b[1]]
|
||||
cdef double *z = [c[0], c[1]]
|
||||
return orient2d(x, y, z) > 0.0
|
||||
|
||||
cdef bool leftOn(list a, list b, list c):
|
||||
cdef double *x = [a[0], a[1]]
|
||||
cdef double *y = [b[0], b[1]]
|
||||
cdef double *z = [c[0], c[1]]
|
||||
return orient2d(x, y, z) >= 0.0
|
||||
|
||||
cdef bool right(list a, list b, list c):
|
||||
cdef double *x = [a[0], a[1]]
|
||||
cdef double *y = [b[0], b[1]]
|
||||
cdef double *z = [c[0], c[1]]
|
||||
return orient2d(x, y, z) < 0.0
|
||||
|
||||
cdef bool rightOn(list a, list b, list c):
|
||||
cdef double *x = [a[0], a[1]]
|
||||
cdef double *y = [b[0], b[1]]
|
||||
cdef double *z = [c[0], c[1]]
|
||||
return orient2d(x, y, z) <= 0.0
|
||||
|
||||
cdef float sqdist(list a, list b):
|
||||
cdef float dx = b[0] - a[0]
|
||||
cdef float dy = b[1] - a[1]
|
||||
return dx * dx + dy * dy
|
||||
|
||||
cdef bool is_reflex(list poly, int i):
|
||||
return right(at(poly, i - 1), at(poly, i), at(poly, i + 1))
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
||||
double orient2d(pa, pb, pc);
|
||||
double *pa;
|
||||
double *pb;
|
||||
double *pc;
|
@ -1,615 +0,0 @@
|
||||
#
|
||||
# Poly2Tri
|
||||
# Copyright (c) 2009, Mason Green
|
||||
# http://code.google.com/p/poly2tri/
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice,
|
||||
# self list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# self list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from self software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
from random import shuffle
|
||||
|
||||
##
|
||||
## Based on Raimund Seidel'e paper "A simple and fast incremental randomized
|
||||
## algorithm for computing trapezoidal decompositions and for triangulating polygons"
|
||||
## (Ported from poly2tri)
|
||||
##
|
||||
|
||||
# Shear transform. May effect numerical robustness
|
||||
SHEAR = 1e-6
|
||||
|
||||
cdef extern from 'math.h':
|
||||
double atan2(double, double)
|
||||
|
||||
cdef extern from 'predicates.h':
|
||||
double orient2d(double *pa, double *pb, double *pc)
|
||||
|
||||
class Point(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.next, self.prev = None, None
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
else:
|
||||
return Point(self.x - other, self.y - other)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
else:
|
||||
return Point(self.x + other, self.y + other)
|
||||
|
||||
def __mul__(self, f):
|
||||
return Point(self.x * f, self.y * f)
|
||||
|
||||
def __div__(self, a):
|
||||
return Point(self.x / a, self.y / a)
|
||||
|
||||
def cross(self, p):
|
||||
return self.x * p.y - self.y * p.x
|
||||
|
||||
def dot(self, p):
|
||||
return self.x * p.x + self.y * p.y
|
||||
|
||||
def length(self):
|
||||
return sqrt(self.x * self.x + self.y * self.y)
|
||||
|
||||
def normalize(self):
|
||||
return self / self.length()
|
||||
|
||||
def less(self, p):
|
||||
return self.x < p.x
|
||||
|
||||
def neq(self, other):
|
||||
return other.x != self.x or other.y != self.y
|
||||
|
||||
def clone(self):
|
||||
return Point(self.x, self.y)
|
||||
|
||||
class Edge(object):
|
||||
|
||||
def __init__(self, p, q):
|
||||
self.p = p
|
||||
self.q = q
|
||||
self.slope = (q.y - p.y) / (q.x - p.x)
|
||||
self.b = p.y - (p.x * self.slope)
|
||||
self.above, self.below = None, None
|
||||
self.mpoints = []
|
||||
self.mpoints.append(p)
|
||||
self.mpoints.append(q)
|
||||
|
||||
def is_above(self, point):
|
||||
cdef double *a = [self.p.x, self.p.y]
|
||||
cdef double *b = [self.q.x, self.q.y]
|
||||
cdef double *c = [point.x, point.y]
|
||||
return orient2d(a, b, c) < 0.0
|
||||
|
||||
def is_below(self, point):
|
||||
cdef double *a = [self.p.x, self.p.y]
|
||||
cdef double *b = [self.q.x, self.q.y]
|
||||
cdef double *c = [point.x, point.y]
|
||||
return orient2d(a, b, c) > 0.0
|
||||
|
||||
class Trapezoid(object):
|
||||
|
||||
def __init__(self, left_point, right_point, top, bottom):
|
||||
self.left_point = left_point
|
||||
self.right_point = right_point
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.upper_left = None
|
||||
self.upper_right = None
|
||||
self.lower_left = None
|
||||
self.lower_right = None
|
||||
self.inside = True
|
||||
self.sink = None
|
||||
self.key = hash(self)
|
||||
|
||||
def update_left(self, ul, ll):
|
||||
self.upper_left = ul
|
||||
if ul != None: ul.upper_right = self
|
||||
self.lower_left = ll
|
||||
if ll != None: ll.lower_right = self
|
||||
|
||||
def update_right(self, ur, lr):
|
||||
self.upper_right = ur
|
||||
if ur != None: ur.upper_left = self
|
||||
self.lower_right = lr
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def update_left_right(self, ul, ll, ur, lr):
|
||||
self.upper_left = ul
|
||||
if ul != None: ul.upper_right = self
|
||||
self.lower_left = ll
|
||||
if ll != None: ll.lower_right = self
|
||||
self.upper_right = ur
|
||||
if ur != None: ur.upper_left = self
|
||||
self.lower_right = lr
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def trim_neighbors(self):
|
||||
if self.inside:
|
||||
self.inside = False
|
||||
if self.upper_left != None: self.upper_left.trim_neighbors()
|
||||
if self.lower_left != None: self.lower_left.trim_neighbors()
|
||||
if self.upper_right != None: self.upper_right.trim_neighbors()
|
||||
if self.lower_right != None: self.lower_right.trim_neighbors()
|
||||
|
||||
def contains(self, point):
|
||||
return (point.x > self.left_point.x and point.x < self.right_point.x and
|
||||
self.top.is_above(point) and self.bottom.is_below(point))
|
||||
|
||||
def vertices(self):
|
||||
v1 = line_intersect(self.top, self.left_point.x)
|
||||
v2 = line_intersect(self.bottom, self.left_point.x)
|
||||
v3 = line_intersect(self.bottom, self.right_point.x)
|
||||
v4 = line_intersect(self.top, self.right_point.x)
|
||||
return v1, v2, v3, v4
|
||||
|
||||
def add_points(self):
|
||||
if self.left_point != self.bottom.p:
|
||||
self.bottom.mpoints.append(self.left_point.clone())
|
||||
if self.right_point != self.bottom.q:
|
||||
self.bottom.mpoints.append(self.right_point.clone())
|
||||
if self.left_point != self.top.p:
|
||||
self.top.mpoints.append(self.left_point.clone())
|
||||
if self.right_point != self.top.q:
|
||||
self.top.mpoints.append(self.right_point.clone())
|
||||
|
||||
def line_intersect(edge, x):
|
||||
y = edge.slope * x + edge.b
|
||||
return x, y
|
||||
|
||||
class Triangulator(object):
|
||||
|
||||
def __init__(self, poly_line):
|
||||
self.polygons = []
|
||||
self.trapezoids = []
|
||||
self.xmono_poly = []
|
||||
self.edge_list = self.init_edges(poly_line)
|
||||
self.trapezoidal_map = TrapezoidalMap()
|
||||
self.bounding_box = self.trapezoidal_map.bounding_box(self.edge_list)
|
||||
self.query_graph = QueryGraph(isink(self.bounding_box))
|
||||
|
||||
self.process()
|
||||
|
||||
def triangles(self):
|
||||
triangles = []
|
||||
for p in self.polygons:
|
||||
verts = []
|
||||
for v in p:
|
||||
verts.append((v.x, v.y))
|
||||
triangles.append(verts)
|
||||
return triangles
|
||||
|
||||
def trapezoid_map(self):
|
||||
return self.trapezoidal_map.map
|
||||
|
||||
# Build the trapezoidal map and query graph
|
||||
def process(self):
|
||||
for edge in self.edge_list:
|
||||
traps = self.query_graph.follow_edge(edge)
|
||||
for t in traps:
|
||||
# Remove old trapezods
|
||||
del self.trapezoidal_map.map[t.key]
|
||||
# Bisect old trapezoids and create new
|
||||
cp = t.contains(edge.p)
|
||||
cq = t.contains(edge.q)
|
||||
if cp and cq:
|
||||
tlist = self.trapezoidal_map.case1(t, edge)
|
||||
self.query_graph.case1(t.sink, edge, tlist)
|
||||
elif cp and not cq:
|
||||
tlist = self.trapezoidal_map.case2(t, edge)
|
||||
self.query_graph.case2(t.sink, edge, tlist)
|
||||
elif not cp and not cq:
|
||||
tlist = self.trapezoidal_map.case3(t, edge)
|
||||
self.query_graph.case3(t.sink, edge, tlist)
|
||||
else:
|
||||
tlist = self.trapezoidal_map.case4(t, edge)
|
||||
self.query_graph.case4(t.sink, edge, tlist)
|
||||
# Add new trapezoids to map
|
||||
for t in tlist:
|
||||
self.trapezoidal_map.map[t.key] = t
|
||||
self.trapezoidal_map.clear()
|
||||
|
||||
# Mark outside trapezoids w/ depth-first search
|
||||
for k, t in self.trapezoidal_map.map.items():
|
||||
self.mark_outside(t)
|
||||
|
||||
# Collect interior trapezoids
|
||||
for k, t in self.trapezoidal_map.map.items():
|
||||
if t.inside:
|
||||
self.trapezoids.append(t)
|
||||
t.add_points()
|
||||
|
||||
# Generate the triangles
|
||||
self.create_mountains()
|
||||
|
||||
def mono_polies(self):
|
||||
polies = []
|
||||
for x in self.xmono_poly:
|
||||
polies.append(x.monoPoly)
|
||||
return polies
|
||||
|
||||
def create_mountains(self):
|
||||
for edge in self.edge_list:
|
||||
if len(edge.mpoints) > 2:
|
||||
mountain = MonotoneMountain()
|
||||
points = merge_sort(edge.mpoints)
|
||||
for p in points:
|
||||
mountain.add(p)
|
||||
mountain.process()
|
||||
for t in mountain.triangles:
|
||||
self.polygons.append(t)
|
||||
self.xmono_poly.append(mountain)
|
||||
|
||||
def mark_outside(self, t):
|
||||
if t.top is self.bounding_box.top or t.bottom is self.bounding_box.bottom:
|
||||
t.trim_neighbors()
|
||||
|
||||
def init_edges(self, points):
|
||||
edge_list = []
|
||||
size = len(points)
|
||||
for i in range(size):
|
||||
j = i + 1 if i < size-1 else 0
|
||||
p = points[i][0], points[i][1]
|
||||
q = points[j][0], points[j][1]
|
||||
edge_list.append((p, q))
|
||||
return self.order_edges(edge_list)
|
||||
|
||||
def order_edges(self, edge_list):
|
||||
edges = []
|
||||
for e in edge_list:
|
||||
p = shear_transform(e[0])
|
||||
q = shear_transform(e[1])
|
||||
if p.x > q.x:
|
||||
edges.append(Edge(q, p))
|
||||
else:
|
||||
edges.append(Edge(p, q))
|
||||
# Randomized incremental algorithm
|
||||
shuffle(edges)
|
||||
return edges
|
||||
|
||||
def shear_transform(point):
|
||||
return Point(point[0] + SHEAR * point[1], point[1])
|
||||
|
||||
def merge_sort(l):
|
||||
if len(l)>1 :
|
||||
lleft = merge_sort(l[:len(l)/2])
|
||||
lright = merge_sort(l[len(l)/2:])
|
||||
p1, p2, p = 0, 0, 0
|
||||
while p1<len(lleft) and p2<len(lright):
|
||||
if lleft[p1].x < lright[p2].x:
|
||||
l[p]=lleft[p1]
|
||||
p+=1
|
||||
p1+=1
|
||||
else:
|
||||
l[p]=lright[p2]
|
||||
p+=1
|
||||
p2+=1
|
||||
if p1<len(lleft):l[p:]=lleft[p1:]
|
||||
elif p2<len(lright):l[p:]=lright[p2:]
|
||||
else : print "internal error"
|
||||
return l
|
||||
|
||||
class TrapezoidalMap(object):
|
||||
|
||||
def __init__(self):
|
||||
self.map = {}
|
||||
self.margin = 50.0
|
||||
self.bcross = None
|
||||
self.tcross = None
|
||||
|
||||
def clear(self):
|
||||
self.bcross = None
|
||||
self.tcross = None
|
||||
|
||||
def case1(self, t, e):
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, e, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, trapezoids[3], None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, trapezoids[3])
|
||||
trapezoids[3].update_right(t.upper_right, t.lower_right)
|
||||
return trapezoids
|
||||
|
||||
def case2(self, t, e):
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, rp, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, rp, e, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, t.upper_right, None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[1]
|
||||
e.below = trapezoids[2]
|
||||
return trapezoids
|
||||
|
||||
def case3(self, t, e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].update_right(t.upper_right, None)
|
||||
trapezoids[0].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, t.top, e))
|
||||
trapezoids[0].update_left_right(t.upper_left, e.above, t.upper_right, None)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].update_right(None, t.lower_right)
|
||||
trapezoids[1].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, e, t.bottom))
|
||||
trapezoids[1].update_left_right(e.below, t.lower_left, None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[0]
|
||||
e.below = trapezoids[1]
|
||||
return trapezoids
|
||||
|
||||
def case4(self, t, e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, t.top, e))
|
||||
trapezoids[0].update_left(t.upper_left, e.above)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, e, t.bottom))
|
||||
trapezoids[1].update_left(e.below, t.lower_left)
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[2].update_left_right(trapezoids[0], trapezoids[1], t.upper_right, t.lower_right)
|
||||
return trapezoids
|
||||
|
||||
def bounding_box(self, edges):
|
||||
margin = self.margin
|
||||
max = edges[0].p + margin
|
||||
min = edges[0].q - margin
|
||||
for e in edges:
|
||||
if e.p.x > max.x: max = Point(e.p.x + margin, max.y)
|
||||
if e.p.y > max.y: max = Point(max.x, e.p.y + margin)
|
||||
if e.q.x > max.x: max = Point(e.q.x + margin, max.y)
|
||||
if e.q.y > max.y: max = Point(max.x, e.q.y + margin)
|
||||
if e.p.x < min.x: min = Point(e.p.x - margin, min.y)
|
||||
if e.p.y < min.y: min = Point(min.x, e.p.y - margin)
|
||||
if e.q.x < min.x: min = Point(e.q.x - margin, min.y)
|
||||
if e.q.y < min.y: min = Point(min.x, e.q.y - margin)
|
||||
top = Edge(Point(min.x, max.y), Point(max.x, max.y))
|
||||
bottom = Edge(Point(min.x, min.y), Point(max.x, min.y))
|
||||
left = top.p
|
||||
right = top.q
|
||||
trap = Trapezoid(left, right, top, bottom)
|
||||
self.map[trap.key] = trap
|
||||
return trap
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, lchild, rchild):
|
||||
self.parent_list = []
|
||||
self.lchild = lchild
|
||||
self.rchild = rchild
|
||||
if lchild != None:
|
||||
lchild.parent_list.append(self)
|
||||
if rchild != None:
|
||||
rchild.parent_list.append(self)
|
||||
|
||||
def replace(self, node):
|
||||
for parent in node.parent_list:
|
||||
if parent.lchild is node:
|
||||
parent.lchild = self
|
||||
else:
|
||||
parent.rchild = self
|
||||
self.parent_list += node.parent_list
|
||||
|
||||
class Sink(Node):
|
||||
|
||||
def __init__(self, trapezoid):
|
||||
super(Sink, self).__init__(None, None)
|
||||
self.trapezoid = trapezoid
|
||||
trapezoid.sink = self
|
||||
|
||||
def locate(self, edge):
|
||||
return self
|
||||
|
||||
def isink(trapezoid):
|
||||
if trapezoid.sink is None:
|
||||
return Sink(trapezoid)
|
||||
return trapezoid.sink
|
||||
|
||||
class XNode(Node):
|
||||
|
||||
def __init__(self, point, lchild, rchild):
|
||||
super(XNode, self).__init__(lchild, rchild)
|
||||
self.point = point
|
||||
|
||||
def locate(self, edge):
|
||||
if edge.p.x >= self.point.x:
|
||||
return self.rchild.locate(edge)
|
||||
return self.lchild.locate(edge)
|
||||
|
||||
class YNode(Node):
|
||||
|
||||
def __init__(self, edge, lchild, rchild):
|
||||
super(YNode, self).__init__(lchild, rchild)
|
||||
self.edge = edge
|
||||
|
||||
def locate(self, edge):
|
||||
if self.edge.is_above(edge.p):
|
||||
return self.rchild.locate(edge)
|
||||
if self.edge.is_below(edge.p):
|
||||
return self.lchild.locate(edge)
|
||||
if self.edge.is_above(edge.q):
|
||||
return self.rchild.locate(edge)
|
||||
return self.lchild.locate(edge)
|
||||
|
||||
class QueryGraph:
|
||||
|
||||
def __init__(self, head):
|
||||
self.head = head
|
||||
|
||||
def locate(self, edge):
|
||||
return self.head.locate(edge).trapezoid
|
||||
|
||||
def follow_edge(self, edge):
|
||||
trapezoids = [self.locate(edge)]
|
||||
while(edge.q.x > trapezoids[-1].right_point.x):
|
||||
if edge.is_above(trapezoids[-1].right_point):
|
||||
trapezoids.append(trapezoids[-1].upper_right)
|
||||
else:
|
||||
trapezoids.append(trapezoids[-1].lower_right)
|
||||
return trapezoids
|
||||
|
||||
def replace(self, sink, node):
|
||||
if sink.parent_list:
|
||||
node.replace(sink)
|
||||
else:
|
||||
self.head = node
|
||||
|
||||
def case1(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[1]), isink(tlist[2]))
|
||||
qNode = XNode(edge.q, yNode, isink(tlist[3]))
|
||||
pNode = XNode(edge.p, isink(tlist[0]), qNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case2(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[1]), isink(tlist[2]))
|
||||
pNode = XNode(edge.p, isink(tlist[0]), yNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case3(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[0]), isink(tlist[1]))
|
||||
self.replace(sink, yNode)
|
||||
|
||||
def case4(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[0]), isink(tlist[1]))
|
||||
qNode = XNode(edge.q, yNode, isink(tlist[2]))
|
||||
self.replace(sink, qNode)
|
||||
|
||||
PI_SLOP = 3.1
|
||||
|
||||
class MonotoneMountain:
|
||||
|
||||
def __init__(self):
|
||||
self.size = 0
|
||||
self.tail = None
|
||||
self.head = None
|
||||
self.positive = False
|
||||
self.convex_points = []
|
||||
self.mono_poly = []
|
||||
self.triangles = []
|
||||
self.convex_polies = []
|
||||
|
||||
def add(self, point):
|
||||
if self.size is 0:
|
||||
self.head = point
|
||||
self.size = 1
|
||||
elif self.size is 1:
|
||||
if point.neq(self.head):
|
||||
self.tail = point
|
||||
self.tail.prev = self.head
|
||||
self.head.next = self.tail
|
||||
self.size = 2
|
||||
else:
|
||||
if point.neq(self.tail):
|
||||
self.tail.next = point
|
||||
point.prev = self.tail
|
||||
self.tail = point
|
||||
self.size += 1
|
||||
|
||||
def remove(self, point):
|
||||
next = point.next
|
||||
prev = point.prev
|
||||
point.prev.next = next
|
||||
point.next.prev = prev
|
||||
self.size -= 1
|
||||
|
||||
def process(self):
|
||||
self.positive = self.angle_sign()
|
||||
self.gen_mono_poly()
|
||||
p = self.head.next
|
||||
while p != self.tail:
|
||||
a = self.angle(p)
|
||||
if a >= PI_SLOP or a <= -PI_SLOP or a == 0:
|
||||
self.remove(p)
|
||||
elif self.is_convex(p):
|
||||
self.convex_points.append(p)
|
||||
p = p.next
|
||||
self.triangulate()
|
||||
|
||||
def triangulate(self):
|
||||
while self.convex_points:
|
||||
ear = self.convex_points.pop(0)
|
||||
a = ear.prev
|
||||
b = ear
|
||||
c = ear.next
|
||||
triangle = (a, b, c)
|
||||
self.triangles.append(triangle)
|
||||
self.remove(ear)
|
||||
if self.valid(a):
|
||||
self.convex_points.append(a)
|
||||
if self.valid(c):
|
||||
self.convex_points.append(c)
|
||||
#assert self.size <= 3, "Triangulation bug, please report"
|
||||
|
||||
def valid(self, p):
|
||||
return p != self.head and p != self.tail and self.is_convex(p)
|
||||
|
||||
def gen_mono_poly(self):
|
||||
p = self.head
|
||||
while(p != None):
|
||||
self.mono_poly.append(p)
|
||||
p = p.next
|
||||
|
||||
def angle(self, p):
|
||||
a = p.next - p
|
||||
b = p.prev - p
|
||||
return atan2(a.cross(b), a.dot(b))
|
||||
|
||||
def angle_sign(self):
|
||||
a = self.head.next - self.head
|
||||
b = self.tail - self.head
|
||||
return atan2(a.cross(b), a.dot(b)) >= 0
|
||||
|
||||
def is_convex(self, p):
|
||||
if self.positive != (self.angle(p) >= 0):
|
||||
return False
|
||||
return True
|
@ -1,707 +0,0 @@
|
||||
#
|
||||
# Poly2Tri
|
||||
# Copyright (c) 2009, Mason Green
|
||||
# http://code.google.com/p/poly2tri/
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice,
|
||||
# self list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# self list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from self software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
from random import shuffle
|
||||
|
||||
###
|
||||
### Based on Raimund Seidel'e paper "A simple and fast incremental randomized
|
||||
### algorithm for computing trapezoidal decompositions and for triangulating polygons"
|
||||
### (Ported from poly2tri)
|
||||
|
||||
cdef extern from 'math.h':
|
||||
double cos(double)
|
||||
double sin(double)
|
||||
double atan2(double, double)
|
||||
double floor(double)
|
||||
double sqrt(double)
|
||||
|
||||
class Point:
|
||||
|
||||
def __cinit__(self, float x, float y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
else:
|
||||
return Point(self.x - other, self.y - other)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
else:
|
||||
return Point(self.x + other, self.y + other)
|
||||
|
||||
def __mul__(self, float f):
|
||||
return Point(self.x * f, self.y * f)
|
||||
|
||||
def __div__(self, float a):
|
||||
return Point(self.x / a, self.y / a)
|
||||
|
||||
def cross(self, p):
|
||||
return self.x * p.y - self.y * p.x
|
||||
|
||||
def dot(self, p):
|
||||
return self.x * p.x + self.y * p.y
|
||||
|
||||
def length(self):
|
||||
return sqrt(self.x * self.x + self.y * self.y)
|
||||
|
||||
def normalize(self):
|
||||
return self / self.length()
|
||||
|
||||
def less(self, p):
|
||||
return self.x < p.x
|
||||
|
||||
def not_equal(self, p):
|
||||
return not (p.x == self.x and p.y == self.y)
|
||||
|
||||
def clone(self):
|
||||
return Point(self.x, self.y)
|
||||
|
||||
cdef class Edge:
|
||||
|
||||
cdef object above, below
|
||||
cdef float slope, b
|
||||
mpoints = []
|
||||
|
||||
def __cinit__(self, p, q):
|
||||
self.p = p
|
||||
self.q = q
|
||||
self.slope = (q.y - p.y)/(q.x - p.x)
|
||||
self.b = p.y - (p.x * self.slope)
|
||||
|
||||
property p:
|
||||
def __get__(self): return self.p
|
||||
|
||||
property q:
|
||||
def __get__(self): return self.q
|
||||
|
||||
property above:
|
||||
def __get__(self): return self.above
|
||||
|
||||
property below:
|
||||
def __get__(self): return self.below
|
||||
|
||||
cdef bool is_above(self, point):
|
||||
return (floor(point.y) < floor(self.slope * point.x + self.b))
|
||||
cdef bool is_below(self, point):
|
||||
return (floor(point.y) > floor(self.slope * point.x + self.b))
|
||||
|
||||
cdef float intersect(self, c, d):
|
||||
cdef float a1, a2, a3, a4, t
|
||||
cdef Point a, b
|
||||
a = self.p
|
||||
b = self.q
|
||||
a1 = self.signed_area(a, b, d)
|
||||
a2 = self.signed_area(a, b, c)
|
||||
if a1 != 0 and a2 != 0 and (a1 * a2) < 0:
|
||||
a3 = self.signed_area(c, d, a)
|
||||
a4 = a3 + a2 - a1
|
||||
if a3 * a4 < 0:
|
||||
t = a3 / (a3 - a4)
|
||||
return a + ((b - a) * t)
|
||||
return 0.0
|
||||
|
||||
cdef float signed_area(self, a, b, c):
|
||||
return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)
|
||||
|
||||
cdef class Trapezoid:
|
||||
|
||||
cdef Edge top, bottom
|
||||
cdef Trapezoid upper_left, lower_left
|
||||
cdef Trapezoid upper_right, lower_right
|
||||
cdef object sink
|
||||
cdef bool inside
|
||||
|
||||
def __cinit__(self, left_point, right_point, Edge top, Edge bottom):
|
||||
self.left_point = left_point
|
||||
self.right_point = right_point
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.upper_left = None
|
||||
self.upper_right = None
|
||||
self.lower_left = None
|
||||
self.lower_right = None
|
||||
self.inside = True
|
||||
self.sink = None
|
||||
|
||||
property inside:
|
||||
def __get__(self): return self.inside
|
||||
|
||||
property top:
|
||||
def __get__(self): return self.top
|
||||
|
||||
property bottom:
|
||||
def __get__(self): return self.bottom
|
||||
|
||||
property left_point:
|
||||
def __get__(self): return self.left_point
|
||||
def __set__(self, lp): self.left_point = lp
|
||||
|
||||
property right_point:
|
||||
def __get__(self): return self.right_point
|
||||
def __set__(self, rp): self.right_point = rp
|
||||
|
||||
property sink:
|
||||
def __get__(self): return self.sink
|
||||
def __set__(self, object s): self.sink = s
|
||||
|
||||
property upper_left:
|
||||
def __get__(self): return self.upper_left
|
||||
def __set__(self, Trapezoid other): self.upper_left = other
|
||||
|
||||
property upper_right:
|
||||
def __get__(self): return self.upper_right
|
||||
def __set__(self, Trapezoid other): self.upper_right = other
|
||||
|
||||
property lower_left:
|
||||
def __get__(self): return self.lower_left
|
||||
def __set__(self, Trapezoid other): self.lower_left = other
|
||||
|
||||
property lower_right:
|
||||
def __get__(self): return self.lower_right
|
||||
def __set__(self, Trapezoid other): self.lower_right = other
|
||||
|
||||
def update_left(self, Trapezoid ul, Trapezoid ll):
|
||||
self.upper_left = ul
|
||||
self.lower_left = ll
|
||||
if ul != None: ul.upper_right = self
|
||||
if ll != None: ll.lower_right = self
|
||||
|
||||
def update_right(self, Trapezoid ur, Trapezoid lr):
|
||||
self.upper_right = ur
|
||||
self.lower_right = lr
|
||||
if ur != None: ur.upper_left = self
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def update_left_right(self, Trapezoid ul, Trapezoid ll, Trapezoid ur, Trapezoid lr):
|
||||
self.upper_left = ul
|
||||
self.lower_left = ll
|
||||
self.upper_right = ur
|
||||
self.lower_right = lr
|
||||
if ul != None: ul.upper_right = self
|
||||
if ll != None: ll.lower_right = self
|
||||
if ur != None: ur.upper_left = self
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def trim_neighbors(self):
|
||||
if self.inside:
|
||||
self.inside = False
|
||||
if self.upper_left != None: self.upper_left.trim_neighbors()
|
||||
if self.lower_left != None: self.lower_left.trim_neighbors()
|
||||
if self.upper_right != None: self.upper_right.trim_neighbors()
|
||||
if self.lower_right != None: self.lower_right.trim_neighbors()
|
||||
|
||||
def contains(self, point):
|
||||
return (point.x > self.left_point.x and point.x < self.right_point.x and
|
||||
self.top.is_above(point) and self.bottom.is_below(point))
|
||||
|
||||
def vertices(self):
|
||||
cdef list verts = []
|
||||
verts.append(line_intersect(self.top, self.left_point.x))
|
||||
verts.append(line_intersect(self.bottom, self.left_point.x))
|
||||
verts.append(line_intersect(self.bottom, self.right_point.x))
|
||||
verts.append(line_intersect(self.top, self.right_point.x))
|
||||
return verts
|
||||
|
||||
def add_points(self):
|
||||
if self.left_point != self.bottom.p:
|
||||
self.bottom.mpoints.append(self.left_point.clone)
|
||||
if self.right_point != self.bottom.q:
|
||||
self.bottom.mpoints.append(self.right_point.clone)
|
||||
if self.left_point != self.top.p:
|
||||
self.top.mpoints.append(self.left_point.clone)
|
||||
if self.right_point != self.top.q:
|
||||
self.top.mpoints.append(self.right_point.clone)
|
||||
|
||||
cdef Point line_intersect(Edge e, float x):
|
||||
cdef float y = e.slope * x + e.b
|
||||
return Point(x, y)
|
||||
|
||||
class Triangulator:
|
||||
|
||||
def __init__(self, poly_line):
|
||||
self.polygons = []
|
||||
self.edge_list = self.init_edges(poly_line)
|
||||
self.trapezoids = []
|
||||
self.trapezoidal_map = TrapezoidalMap()
|
||||
self.bounding_box = self.trapezoidal_map.bounding_box(self.edge_list)
|
||||
self.query_graph = QueryGraph(isink(self.bounding_box))
|
||||
self.xmono_poly = []
|
||||
|
||||
self.process()
|
||||
|
||||
def trapezoidMap(self):
|
||||
return self.trapezoidal_map.map
|
||||
|
||||
# Build the trapezoidal map and query graph
|
||||
def process(self):
|
||||
|
||||
for e in self.edge_list:
|
||||
traps = self.query_graph.follow_edge(e)
|
||||
for t in traps:
|
||||
try:
|
||||
self.trapezoidal_map.map.remove(t)
|
||||
except:
|
||||
pass
|
||||
for t in traps:
|
||||
tlist = []
|
||||
cp = t.contains(e.p)
|
||||
cq = t.contains(e.q)
|
||||
if cp and cq:
|
||||
tlist = self.trapezoidal_map.case1(t, e)
|
||||
self.query_graph.case1(t.sink, e, tlist)
|
||||
elif cp and not cq:
|
||||
tlist = self.trapezoidal_map.case2(t, e)
|
||||
self.query_graph.case2(t.sink, e, tlist)
|
||||
elif not cp and not cq:
|
||||
tlist = self.trapezoidal_map.case3(t, e)
|
||||
self.query_graph.case3(t.sink, e, tlist)
|
||||
else:
|
||||
tlist = self.trapezoidal_map.case4(t, e)
|
||||
self.query_graph.case4(t.sink, e, tlist)
|
||||
# Add new trapezoids to map
|
||||
for t in tlist:
|
||||
self.trapezoidal_map.map.append(t)
|
||||
self.trapezoidal_map.clear()
|
||||
|
||||
# Mark outside trapezoids
|
||||
for t in self.trapezoidal_map.map:
|
||||
self.mark_outside(t)
|
||||
|
||||
# Collect interior trapezoids
|
||||
for t in self.trapezoidal_map.map:
|
||||
if t.inside:
|
||||
self.trapezoids.append(t)
|
||||
t.add_points()
|
||||
|
||||
self.create_mountains()
|
||||
|
||||
def mono_polies(self):
|
||||
polies = []
|
||||
for x in self.xmono_poly:
|
||||
polies.append(x.monoPoly)
|
||||
return polies
|
||||
|
||||
def create_mountains(self):
|
||||
for s in self.edge_list:
|
||||
if len(s.mpoints) > 0:
|
||||
mountain = MonotoneMountain()
|
||||
print s.mpoints
|
||||
k = merge_sort(s.mpoints)
|
||||
points = [s.p] + k + [s.q]
|
||||
for p in points:
|
||||
mountain.append(p)
|
||||
mountain.process()
|
||||
for t in mountain.triangles:
|
||||
self.polygons.append(t)
|
||||
self.xmono_poly.append(mountain)
|
||||
|
||||
def mark_outside(self, t):
|
||||
if t.top is self.bounding_box.top or t.bottom is self.bounding_box.bottom:
|
||||
t.trim_neighbors()
|
||||
|
||||
def init_edges(self, points):
|
||||
edges = []
|
||||
for i in range(len(points)-1):
|
||||
p = Point(points[i][0], points[i][1])
|
||||
q = Point(points[i+1][0], points[i+1][1])
|
||||
edges.append(Edge(p, q))
|
||||
p = Point(points[0][0], points[0][1])
|
||||
q = Point(points[-1][0], points[-1][1])
|
||||
edges.append(Edge(p, q))
|
||||
return self.order_edges(edges)
|
||||
|
||||
def order_edges(self, edges):
|
||||
segs = []
|
||||
for s in edges:
|
||||
p = self.shearTransform(s.p)
|
||||
q = self.shearTransform(s.q)
|
||||
if p.x > q.x:
|
||||
segs.append(Edge(q, p))
|
||||
elif p.x < q.x:
|
||||
segs.append(Edge(p, q))
|
||||
shuffle(segs)
|
||||
return segs
|
||||
|
||||
def shearTransform(self, point):
|
||||
return Point(point.x + 1e-4 * point.y, point.y)
|
||||
|
||||
cdef list merge_sort(l):
|
||||
cdef list lleft, lright
|
||||
cdef int p1, p2, p
|
||||
if len(l)>1 :
|
||||
lleft = merge_sort(l[:len(l)/2])
|
||||
lright = merge_sort(l[len(l)/2:])
|
||||
p1, p2, p = 0, 0, 0
|
||||
while p1<len(lleft) and p2<len(lright):
|
||||
if lleft[p1].x < lright[p2].x:
|
||||
l[p]=lleft[p1]
|
||||
p+=1
|
||||
p1+=1
|
||||
else:
|
||||
l[p]=lright[p2]
|
||||
p+=1
|
||||
p2+=1
|
||||
if p1<len(lleft):l[p:]=lleft[p1:]
|
||||
elif p2<len(lright):l[p:]=lright[p2:]
|
||||
else : print "internal error"
|
||||
return l
|
||||
|
||||
class TrapezoidalMap:
|
||||
|
||||
map = []
|
||||
margin = 50
|
||||
bcross = None
|
||||
tcross = None
|
||||
|
||||
def clear(self):
|
||||
self.bcross = None
|
||||
self.tcross = None
|
||||
|
||||
def case1(self, Trapezoid t, Edge e):
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, e, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, trapezoids[3], None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, trapezoids[3])
|
||||
trapezoids[3].update_right(t.upper_right, t.lower_right)
|
||||
return trapezoids
|
||||
|
||||
def case2(self, Trapezoid t, Edge e):
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, rp, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, rp, e, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, t.upper_right, None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[1]
|
||||
e.below = trapezoids[2]
|
||||
return trapezoids
|
||||
|
||||
def case3(self, Trapezoid t, Edge e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].update_right(t.upper_right, None)
|
||||
trapezoids[0].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, t.top, e))
|
||||
trapezoids[0].update_left_right(t.upper_left, e.above, t.upper_right, None)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].update_right(None, t.lower_right)
|
||||
trapezoids[1].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, e, t.bottom))
|
||||
trapezoids[1].update_left_right(e.below, t.lower_left, None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[0]
|
||||
e.below = trapezoids[1]
|
||||
return trapezoids
|
||||
|
||||
def case4(self, Trapezoid t, Edge e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, t.top, e))
|
||||
trapezoids[0].update_left(t.upper_left, e.above)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, e, t.bottom))
|
||||
trapezoids[1].update_left(e.below, t.lower_left)
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[2].update_left_right(trapezoids[0], trapezoids[1], t.upper_right, t.lower_right)
|
||||
|
||||
return trapezoids
|
||||
|
||||
def bounding_box(self, edges):
|
||||
margin = self.margin
|
||||
max = edges[0].p + margin
|
||||
min = edges[0].q - margin
|
||||
for e in edges:
|
||||
if e.p.x > max.x: max = Point(e.p.x + margin, max.y)
|
||||
if e.p.y > max.y: max = Point(max.x, e.p.y + margin)
|
||||
if e.q.x > max.x: max = Point(e.q.x + margin, max.y)
|
||||
if e.q.y > max.y: max = Point(max.x, e.q.y + margin)
|
||||
if e.p.x < min.x: min = Point(e.p.x - margin, min.y)
|
||||
if e.p.y < min.y: min = Point(min.x, e.p.y - margin)
|
||||
if e.q.x < min.x: min = Point(e.q.x - margin, min.y)
|
||||
if e.q.y < min.y: min = Point(min.x, e.q.y - margin)
|
||||
top = Edge(Point(min.x, max.y), Point(max.x, max.y))
|
||||
bottom = Edge(Point(min.x, min.y), Point(max.x, min.y))
|
||||
left = bottom.p
|
||||
right = top.q
|
||||
return Trapezoid(left, right, top, bottom)
|
||||
|
||||
cdef class Node:
|
||||
|
||||
cdef Node left, right
|
||||
cdef object parent_list
|
||||
|
||||
def __init__(self, Node left, Node right):
|
||||
self.parent_list = []
|
||||
self.left = left
|
||||
self.right = right
|
||||
if left != None:
|
||||
left.parent_list.append(self)
|
||||
if right != None:
|
||||
right.parent_list.append(self)
|
||||
|
||||
property left:
|
||||
def __get__(self): return self.left
|
||||
def __set__(self, Node left): self.left = left
|
||||
|
||||
property right:
|
||||
def __get__(self): return self.right
|
||||
def __set__(self, Node right): self.right = right
|
||||
|
||||
property parent_list:
|
||||
def __get__(self): return self.parent_list
|
||||
|
||||
def replace(self, Node node):
|
||||
for parent in node.parent_list:
|
||||
if parent.left is node:
|
||||
parent.left = self
|
||||
else:
|
||||
parent.right = self
|
||||
self.parent_list += node.parent_list
|
||||
|
||||
cdef class Sink(Node):
|
||||
|
||||
cdef Trapezoid trapezoid
|
||||
|
||||
def __init__(self, trapezoid):
|
||||
self.trapezoid = trapezoid
|
||||
super(Sink, self).__init__(None, None)
|
||||
trapezoid.sink = self
|
||||
|
||||
property trapezoid:
|
||||
def __get__(self): return self.trapezoid
|
||||
|
||||
def locate(self, e):
|
||||
return self
|
||||
|
||||
cdef Sink isink(Trapezoid trapezoid):
|
||||
if trapezoid.sink != None:
|
||||
return trapezoid.sink
|
||||
return Sink(trapezoid)
|
||||
|
||||
cdef class XNode(Node):
|
||||
|
||||
cdef Point point
|
||||
|
||||
def __init__(self, Point point, Node lchild, Node rchild):
|
||||
super(XNode, self).__init__(lchild, rchild)
|
||||
self.point = point
|
||||
|
||||
def locate(self, Edge e):
|
||||
if e.p.x >= self.point.x:
|
||||
return self.right.locate(e)
|
||||
return self.left.locate(e)
|
||||
|
||||
cdef class YNode(Node):
|
||||
|
||||
cdef Edge edge
|
||||
|
||||
def __init__(self, Edge edge, Node lchild, Node rchild):
|
||||
super(YNode, self).__init__(lchild, rchild)
|
||||
self.edge = edge
|
||||
|
||||
def locate(self, Edge e):
|
||||
if self.edge.is_above(e.p):
|
||||
return self.right.locate(e)
|
||||
elif self.edge.is_below(e.p):
|
||||
return self.left.locate(e)
|
||||
else:
|
||||
if e.slope < self.edge.slope:
|
||||
return self.right.locate(e)
|
||||
else:
|
||||
return self.left.locate(e)
|
||||
|
||||
cdef class QueryGraph:
|
||||
|
||||
cdef Node head
|
||||
|
||||
def __init__(self, Node head):
|
||||
self.head = head
|
||||
|
||||
def locate(self, Edge e):
|
||||
return self.head.locate(e).trapezoid
|
||||
|
||||
def follow_edge(self, Edge e):
|
||||
trapezoids = [self.locate(e)]
|
||||
cdef int j = 0
|
||||
while(e.q.x > trapezoids[j].right_point.x):
|
||||
if e.is_above(trapezoids[j].right_point):
|
||||
trapezoids.append(trapezoids[j].upper_right)
|
||||
else:
|
||||
trapezoids.append(trapezoids[j].lower_right)
|
||||
j += 1
|
||||
return trapezoids
|
||||
|
||||
def replace(self, Sink sink, Node node):
|
||||
if not sink.parent_list:
|
||||
self.head = node
|
||||
else:
|
||||
node.replace(sink)
|
||||
|
||||
def case1(self, Sink sink, Edge e, tlist):
|
||||
cdef Node yNode = YNode(e, isink(tlist[1]), isink(tlist[2]))
|
||||
cdef Node qNode = XNode(e.q, yNode, isink(tlist[3]))
|
||||
cdef Node pNode = XNode(e.p, isink(tlist[0]), qNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case2(self, Sink sink, Edge e, tlist):
|
||||
yNode = YNode(e, isink(tlist[1]), isink(tlist[2]))
|
||||
pNode = XNode(e.p, isink(tlist[0]), yNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case3(self, Sink sink, Edge e, tlist):
|
||||
yNode = YNode(e, isink(tlist[0]), isink(tlist[1]))
|
||||
self.replace(sink, yNode)
|
||||
|
||||
def case4(self, Sink sink, Edge e, tlist):
|
||||
yNode = YNode(e, isink(tlist[0]), isink(tlist[1]))
|
||||
qNode = XNode(e.q, yNode, isink(tlist[2]))
|
||||
self.replace(sink, qNode)
|
||||
|
||||
cdef float PI_SLOP = 3.1
|
||||
|
||||
cdef class MonotoneMountain:
|
||||
|
||||
cdef:
|
||||
Point tail, head
|
||||
int size
|
||||
list convex_points
|
||||
list mono_poly
|
||||
list triangles
|
||||
list convex_polies
|
||||
bool positive
|
||||
|
||||
def __init__(self):
|
||||
self.size = 0
|
||||
self.tail = None
|
||||
self.head = None
|
||||
self.positive = False
|
||||
self.convex_points = []
|
||||
self.mono_poly = []
|
||||
self.triangles = []
|
||||
self.convex_polies = []
|
||||
|
||||
def append(self, Point point):
|
||||
if self.size == 0:
|
||||
self.head = point
|
||||
self.size += 1
|
||||
elif self.size == 1:
|
||||
if point.not_equal(self.head):
|
||||
self.tail = point
|
||||
self.tail.prev = self.head
|
||||
self.head.next = self.tail
|
||||
self.size += 1
|
||||
else:
|
||||
if point.not_equal(self.tail):
|
||||
self.tail.next = point
|
||||
point.prev = self.tail
|
||||
self.tail = point
|
||||
self.size += 1
|
||||
|
||||
cdef void remove(self, Point point):
|
||||
cdef Point next, prev
|
||||
next = point.next
|
||||
prev = point.prev
|
||||
point.prev.next = next
|
||||
point.next.prev = prev
|
||||
self.size -= 1
|
||||
|
||||
def process(self):
|
||||
self.positive = self.angle_sign()
|
||||
self.gen_mono_poly()
|
||||
p = self.head.next
|
||||
while p != self.tail:
|
||||
a = self.angle(p)
|
||||
if a >= PI_SLOP or a <= -PI_SLOP: self.remove(p)
|
||||
elif self.is_convex(p): self.convex_points.append(p)
|
||||
p = p.next
|
||||
self.triangulate()
|
||||
|
||||
cdef void triangulate(self):
|
||||
while not len(self.convex_points) > 0:
|
||||
ear = self.convex_points.remove(0)
|
||||
a = ear.prev
|
||||
b = ear
|
||||
c = ear.next
|
||||
triangle = [a, b, c]
|
||||
self.triangles.append(triangle)
|
||||
self.remove(ear)
|
||||
if self.valid(a): self.convex_points.append(a)
|
||||
if self.valid(c): self.convex_points.append(c)
|
||||
assert(self.size <= 3, "Triangulation bug, please report")
|
||||
|
||||
cdef bool valid(self, Point p):
|
||||
return p != self.head and p != self.tail and self.is_convex(p)
|
||||
|
||||
cdef void gen_mono_poly(self):
|
||||
cdef Point p = self.head
|
||||
while(p != None):
|
||||
self.mono_poly.append(p)
|
||||
p = p.next
|
||||
|
||||
cdef float angle(self, Point p):
|
||||
cdef Point a = p.next - p
|
||||
cdef Point b = p.prev - p
|
||||
return atan2(a.cross(b), a.dot(b))
|
||||
|
||||
cdef float angle_sign(self):
|
||||
cdef Point a = self.head.next - self.head
|
||||
cdef Point b = self.tail - self.head
|
||||
return atan2(a.cross(b), a.dot(b)) >= 0
|
||||
|
||||
cdef bool is_convex(self, Point p):
|
||||
if self.positive != (self.angle(p) >= 0): return False
|
||||
return True
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
python -O poly2tri.py
|
@ -1,177 +0,0 @@
|
||||
#!/usr/bin/env python2.6
|
||||
from framework import Game, draw_polygon, reset_zoom, draw_line, decompose_poly, make_ccw, Triangulator
|
||||
|
||||
class Poly2Tri(Game):
|
||||
|
||||
screen_size = 800.0, 600.0
|
||||
|
||||
dude = [[174.50415,494.59368],[215.21844,478.87939],[207.36129,458.87939],[203.07558,441.02225],[203.07558,418.1651],
|
||||
[210.93272,394.59367],[224.50415,373.1651],[241.64701,358.1651],[257.36129,354.59367],[275.93272,356.73653],
|
||||
[293.07558,359.59367],[309.50415,377.45082],[322.36129,398.1651],[331.64701,421.73653],[335.21844,437.45082],
|
||||
[356.64701,428.52225],[356.1113,428.34367],[356.1113,428.34367],[368.78987,419.59368],[349.50415,384.59367],
|
||||
[323.78987,359.59367],[290.93272,343.87939],[267.36129,341.02225],[264.50415,331.02225],[264.50415,321.02225],
|
||||
[268.78987,306.02225],[285.93272,286.02225],[295.21844,270.30796],[303.78987,254.59367],[306.64701,213.87939],
|
||||
[320,202.36218],[265,202.36218],[286.64701,217.45082],[293.78987,241.02225],[285,257.36218],[270.93272,271.73653],
|
||||
[254.50415,266.02225],[250.93272,248.1651],[256.64701,233.1651],[256.64701,221.02225],[245.93272,215.30796],
|
||||
[238.78987,216.73653],[233.78987,232.45082],[232.36129,249.59367],[243.07558,257.09367],[242.89701,270.30796],
|
||||
[235.93272,279.95082],[222.36129,293.1651],[205.21844,300.6651],[185,297.36218],[170,242.36218],[175,327.36218],
|
||||
[185,322.36218],[195,317.36218],[230.75415,301.02225],[235.39701,312.45082],[240.57558,323.52225],
|
||||
[243.61129,330.48653],[245.21844,335.12939],[245.03987,344.4151],[229.86129,349.4151],[209.14701,362.09367],
|
||||
[192.89701,377.80796],[177.18272,402.27225],[172.36129,413.87939],[169.14701,430.48653],[168.61129,458.52225],
|
||||
[168.61129,492.80796]]
|
||||
|
||||
test = [[235.04275999999999, 739.07534999999996], [218.13066000000001, 719.73902999999996],
|
||||
[218.15215000000001, 709.96821], [218.17362, 700.19740000000002], [243.15215000000001, 685.27858000000003],
|
||||
[268.13065, 670.35974999999996], [268.13065, 660.81429000000003], [268.13065, 651.26882999999998],
|
||||
[314.55921999999998, 651.26882999999998], [360.98779000000002, 651.26882999999998],
|
||||
[360.98683999999997, 666.44740000000002], [360.98046561000007, 669.27438118000009],
|
||||
[360.96234088000011,672.68539864000013], [360.93345946999995, 676.58895225999993],
|
||||
[360.89481504000003, 680.89354191999996], [360.84740125000002, 685.50766749999991],
|
||||
[360.79221175999999, 690.33982888000003], [360.73024022999999, 695.29852593999999],
|
||||
[360.66248032000004, 700.29225856000005], [360.58992569000003, 705.22952662000012],
|
||||
[360.51357000000002, 710.01882999999998], [360.04131999999998, 738.41168000000005],
|
||||
[310.51454999999999, 738.41168000000005], [260.98779999999999, 738.41168000000005],
|
||||
[260.98779999999999, 748.41168000000005], [260.98779999999999, 758.41168000000005],
|
||||
[256.47133000000002, 758.41168000000005], [251.95484999999999, 758.41168000000005]]
|
||||
|
||||
test2 = [[101.25, 1006.8145], [-0.0, 869.65629999999999], [0.012800000000000001, 630.57820000000004],
|
||||
[0.025600000000000001, 391.5], [13.7628, 385.74239999999998], [20.536000000000001, 382.96260000000001],
|
||||
[26.871200000000002, 380.49279999999999],[32.864100000000001, 378.30540000000002],
|
||||
[38.610700000000001, 376.37279999999998], [44.206600000000002, 374.66730000000001],
|
||||
[49.747799999999998, 373.16129999999998], [55.329900000000002, 371.82709999999997],
|
||||
[61.0488, 370.63720000000001],[67.000299999999996, 369.56400000000002], [73.280299999999997, 368.5797],
|
||||
[77.521299999999997, 368.07459999999998], [82.578500000000005, 367.66539999999998],
|
||||
[88.263199999999998, 367.35390000000001], [94.386899999999997, 367.14170000000001],
|
||||
[100.7611, 367.0308], [107.1972, 367.02269999999999], [113.5067, 367.11930000000001],
|
||||
[119.5009, 367.32229999999998], [124.9913, 367.63339999999999], [129.7894, 368.05450000000002],
|
||||
[136.77860000000001, 368.94200000000001], [143.9999, 370.10390000000001], [151.3793, 371.52069999999998],
|
||||
[158.84270000000001, 373.17270000000002], [166.3159, 375.04050000000001], [173.72499999999999, 377.10449999999997],
|
||||
[180.9957, 379.34500000000003], [188.0539, 381.74250000000001], [194.82570000000001, 384.27749999999997],
|
||||
[201.23679999999999, 386.93029999999999], [212.5, 391.83980000000003], [212.08760000000001, 550.41989999999998],
|
||||
[211.67509999999999, 709.0], [274.00200000000001, 709.0], [336.3288, 709.0], [335.66520000000003, 636.25],
|
||||
[335.55739999999997, 623.91790000000003], [335.45409999999998, 611.09199999999998], [335.3569, 598.0163],
|
||||
[335.267, 584.93499999999995], [335.18599999999998, 572.09220000000005], [335.11540000000002, 559.73199999999997],
|
||||
[335.0566, 548.09860000000003], [335.0111, 537.43600000000004], [334.98039999999997, 527.98839999999996],
|
||||
[334.96589999999998, 520.0], [334.93029999999999, 476.5], [264.0222, 418.0], [193.114, 359.5],
|
||||
[193.05699999999999, 295.4984], [193.0, 231.49680000000001], [308.7516, 115.7484], [424.50319999999999, 0.0],
|
||||
[430.71390000000002, -0.0], [436.9246, -0.0], [458.9753, 20.0], [481.02589999999998, 40.0],
|
||||
[558.38530000000003, 40.0], [635.74469999999997, 40.0], [660.50120000000004, 19.9588],
|
||||
[685.25779999999997, -0.082400000000000001], [692.0471, 0.20880000000000001],
|
||||
[698.8365, 0.5], [809.42550000000006, 115.9161], [920.01459999999997, 231.3321], [919.75729999999999, 295.3526],
|
||||
[919.5, 359.37310000000002], [850.31790000000001, 416.4366], [781.13589999999999, 473.5],
|
||||
[781.06790000000001, 593.7577], [781.0, 714.0154], [842.25, 713.7577], [903.5, 713.5], [903.5, 552.5], [903.5, 391.5],
|
||||
[915.5, 386.2894], [925.01319999999998, 382.34390000000002], [934.29579999999999, 378.88010000000003],
|
||||
[943.42060000000004, 375.8827], [952.46050000000002,373.33629999999999], [961.48839999999996, 371.22570000000002],
|
||||
[970.57719999999995, 369.53559999999999], [979.79989999999998, 368.25069999999999],
|
||||
[989.22929999999997, 367.35570000000001], [998.9384, 366.83539999999999], [1009.0, 366.67430000000002],
|
||||
[1018.876, 366.87939999999998], [1028.7419, 367.4649], [1038.5688, 368.42529999999999],
|
||||
[1048.3277, 369.75490000000002], [1057.9897000000001, 371.44799999999998], [1067.5259000000001, 373.49900000000002],
|
||||
[1076.9074000000001, 375.90219999999999], [1086.1052, 378.65210000000002], [1095.0904, 381.74290000000002],
|
||||
[1103.8340000000001, 385.16899999999998], [1105.4327000000001, 385.83539999999999],
|
||||
[1107.0215000000001, 386.49689999999998], [1108.5744999999999, 387.1429], [1110.0654999999999, 387.76240000000001],
|
||||
[1111.4684, 388.34469999999999], [1112.7573, 388.87900000000002], [1113.9059, 389.3544],
|
||||
[1114.8883000000001, 389.76010000000002], [1115.6784, 390.08530000000002], [1116.25, 390.3193],
|
||||
[1119.0, 391.43849999999998], [1118.9577999999999, 633.4692], [1118.9155000000001, 875.5],[1016.0895, 1009.5],
|
||||
[913.26340000000005, 1143.5], [908.63170000000002, 1143.8047999999999], [904.0, 1144.1096], [904.0, 993.0548],
|
||||
[904.0, 842.0], [842.5, 842.0], [781.0, 842.0], [781.0, 918.5], [781.0, 995.0], [758.5, 995.0],
|
||||
[753.20910000000003, 995.00739999999996], [748.79459999999995, 995.03269999999998],
|
||||
[745.18129999999996, 995.08109999999999], [742.29399999999998, 995.15729999999996],
|
||||
[740.05730000000005, 995.26639999999998], [738.39599999999996, 995.41330000000005],
|
||||
[737.23490000000004, 995.60289999999998], [736.49869999999999, 995.84010000000001],
|
||||
[736.11210000000005, 996.13], [736.0, 996.47739999999999], [736.09749999999997, 996.82140000000004],
|
||||
[736.42740000000003, 997.11329999999998], [737.04610000000002, 997.3587], [738.01009999999997, 997.56290000000001],
|
||||
[739.37559999999996, 997.73140000000001], [741.19910000000004, 997.86959999999999], [743.53679999999997, 997.9828],
|
||||
[746.44510000000002, 998.07659999999998], [749.98040000000003,998.15629999999999],
|
||||
[754.19910000000004, 998.22739999999999], [772.39829999999995, 998.5], [823.72850000000005, 1071.0],
|
||||
[875.05859999999996, 1143.5], [558.86419999999998, 1143.7516000000001], [507.74619999999999, 1143.787],
|
||||
[459.17950000000002, 1143.8106], [413.82889999999998, 1143.8226], [372.35939999999999, 1143.8232],
|
||||
[335.43560000000002, 1143.8130000000001], [303.7226, 1143.7919999999999], [277.88499999999999, 1143.7606000000001],
|
||||
[258.58789999999999, 1143.7192], [246.49590000000001, 1143.6679999999999], [242.2739, 1143.6072999999999],
|
||||
[244.95830000000001, 1139.4844000000001], [252.41210000000001, 1128.4439], [263.45999999999998, 1112.2019],
|
||||
[276.92660000000001, 1092.4740999999999], [291.63650000000001, 1070.9766], [306.41430000000003, 1049.4251999999999],
|
||||
[320.0847, 1029.5360000000001], [331.47219999999999, 1013.0247000000001], [339.4015, 1001.6074],
|
||||
[342.69729999999998, 997.0], [342.98259999999999, 996.91470000000004], [343.6619, 996.82510000000002],
|
||||
[344.70080000000002, 996.7328], [346.06450000000001, 996.63959999999997], [347.71870000000001, 996.54729999999995],
|
||||
[349.62880000000001, 996.45770000000005], [351.76029999999997, 996.37249999999995],
|
||||
[354.07859999999999, 996.29349999999999], [356.54919999999998, 996.22249999999997], [359.1377, 996.16110000000003],
|
||||
[363.04469999999998, 996.07169999999996], [366.26229999999998, 995.97929999999997],
|
||||
[368.85410000000002, 995.87580000000003], [370.88319999999999, 995.75319999999999],
|
||||
[372.41320000000002, 995.60320000000002], [373.50729999999999,995.41790000000003],
|
||||
[374.22899999999998, 995.18920000000003], [374.64159999999998, 994.90880000000004],
|
||||
[374.80860000000001, 994.56889999999999], [374.79329999999999, 994.16110000000003],
|
||||
[374.63619999999997, 993.7568], [374.2833, 993.4194], [373.65809999999999, 993.14160000000004],
|
||||
[372.6841, 992.91570000000002], [371.28500000000003, 992.73419999999999], [369.3843, 992.58960000000002],
|
||||
[366.90559999999999, 992.47429999999997], [363.77249999999998, 992.38059999999996],
|
||||
[359.90839999999997, 992.30119999999999], [355.2371, 992.22839999999997], [336.0, 991.95680000000004],
|
||||
[336.0, 914.97839999999997], [336.0, 838.0], [274.0, 838.0], [212.0, 838.0], [212.0, 991.0], [212.0, 1144.0],
|
||||
[207.25, 1143.9864], [202.5, 1143.9727]]
|
||||
|
||||
def __init__(self):
|
||||
super(Poly2Tri, self).__init__(*self.screen_size)
|
||||
|
||||
# Load point set
|
||||
file_name = "../data/dude.dat"
|
||||
self.points = self.load_points(file_name)
|
||||
|
||||
# Triangulate
|
||||
t1 = self.time
|
||||
seidel = Triangulator(self.points)
|
||||
dt = (self.time - t1) * 1000.0
|
||||
|
||||
self.triangles = seidel.triangles()
|
||||
#self.trapezoids = seidel.trapezoids
|
||||
self.trapezoids = seidel.trapezoidal_map.map
|
||||
self.edges = seidel.edge_list
|
||||
print "time (ms) = %f , num triangles = %d" % (dt, len(self.triangles))
|
||||
|
||||
for p in self.dude:
|
||||
p[0] -= 75
|
||||
|
||||
make_ccw(self.dude)
|
||||
self.decomp_poly = []
|
||||
t1 = self.time
|
||||
decompose_poly(self.dude, self.decomp_poly)
|
||||
dt = (self.time - t1) * 1000.0
|
||||
print "time (ms) = %f , num polies = %d" % (dt, len(self.decomp_poly))
|
||||
|
||||
self.main_loop()
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def render(self):
|
||||
reset_zoom(2.0, (300, 450), self.screen_size)
|
||||
red = 255, 0, 0
|
||||
yellow = 255, 255, 0
|
||||
green = 0, 255, 0
|
||||
for t in self.triangles:
|
||||
draw_polygon(t, red)
|
||||
|
||||
for p in self.decomp_poly:
|
||||
draw_polygon(p, red)
|
||||
|
||||
'''
|
||||
for t in self.trapezoids:
|
||||
verts = self.trapezoids[t].vertices()
|
||||
#verts = t.vertices()
|
||||
draw_polygon(verts, yellow)
|
||||
'''
|
||||
for e in self.edges:
|
||||
p1 = e.p.x, e.p.y
|
||||
p2 = e.q.x, e.q.y
|
||||
draw_line(p1, p2, green)
|
||||
|
||||
|
||||
def load_points(self, file_name):
|
||||
infile = open(file_name, "r")
|
||||
points = []
|
||||
while infile:
|
||||
line = infile.readline()
|
||||
s = line.split()
|
||||
if len(s) == 0:
|
||||
break
|
||||
points.append((float(s[0]), float(s[1])))
|
||||
return points
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo = Poly2Tri()
|
627
python/seidel.py
627
python/seidel.py
@ -1,627 +0,0 @@
|
||||
#
|
||||
# Poly2Tri
|
||||
# Copyright (c) 2009, Mason Green
|
||||
# http://code.google.com/p/poly2tri/
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice,
|
||||
# self list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# self list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from self software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
from random import shuffle
|
||||
from math import atan2
|
||||
|
||||
##
|
||||
## Based on Raimund Seidel'e paper "A simple and fast incremental randomized
|
||||
## algorithm for computing trapezoidal decompositions and for triangulating polygons"
|
||||
## (Ported from poly2tri)
|
||||
##
|
||||
|
||||
# Shear transform. May effect numerical robustness
|
||||
SHEAR = 1e-6
|
||||
|
||||
class Point(object):
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.next, self.prev = None, None
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
else:
|
||||
return Point(self.x - other, self.y - other)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Point):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
else:
|
||||
return Point(self.x + other, self.y + other)
|
||||
|
||||
def __mul__(self, f):
|
||||
return Point(self.x * f, self.y * f)
|
||||
|
||||
def __div__(self, a):
|
||||
return Point(self.x / a, self.y / a)
|
||||
|
||||
def cross(self, p):
|
||||
return self.x * p.y - self.y * p.x
|
||||
|
||||
def dot(self, p):
|
||||
return self.x * p.x + self.y * p.y
|
||||
|
||||
def length(self):
|
||||
return sqrt(self.x * self.x + self.y * self.y)
|
||||
|
||||
def normalize(self):
|
||||
return self / self.length()
|
||||
|
||||
def less(self, p):
|
||||
return self.x < p.x
|
||||
|
||||
def neq(self, other):
|
||||
return other.x != self.x or other.y != self.y
|
||||
|
||||
def clone(self):
|
||||
return Point(self.x, self.y)
|
||||
|
||||
class Edge(object):
|
||||
|
||||
def __init__(self, p, q):
|
||||
self.p = p
|
||||
self.q = q
|
||||
self.slope = (q.y - p.y) / (q.x - p.x)
|
||||
self.b = p.y - (p.x * self.slope)
|
||||
self.above, self.below = None, None
|
||||
self.mpoints = []
|
||||
self.mpoints.append(p)
|
||||
self.mpoints.append(q)
|
||||
|
||||
##
|
||||
## NOTE Rounding accuracy significantly effects numerical robustness!!!
|
||||
##
|
||||
|
||||
def is_above(self, point):
|
||||
return (round(point.y, 2) < round(self.slope * point.x + self.b, 2))
|
||||
|
||||
def is_below(self, point):
|
||||
return (round(point.y, 2) > round(self.slope * point.x + self.b, 2))
|
||||
|
||||
def intersect(self, c, d):
|
||||
a = self.p
|
||||
b = self.q
|
||||
a1 = self.signed_area(a, b, d)
|
||||
a2 = self.signed_area(a, b, c)
|
||||
if a1 != 0.0 and a2 != 0.0 and (a1 * a2) < 0.0:
|
||||
a3 = self.signed_area(c, d, a)
|
||||
a4 = a3 + a2 - a1
|
||||
if a3 * a4 < 0.0:
|
||||
t = a3 / (a3 - a4)
|
||||
return a + ((b - a) * t)
|
||||
return 0.0
|
||||
|
||||
def signed_area(self, a, b, c):
|
||||
return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)
|
||||
|
||||
class Trapezoid(object):
|
||||
|
||||
def __init__(self, left_point, right_point, top, bottom):
|
||||
self.left_point = left_point
|
||||
self.right_point = right_point
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.upper_left = None
|
||||
self.upper_right = None
|
||||
self.lower_left = None
|
||||
self.lower_right = None
|
||||
self.inside = True
|
||||
self.sink = None
|
||||
self.key = hash(self)
|
||||
|
||||
def update_left(self, ul, ll):
|
||||
self.upper_left = ul
|
||||
if ul != None: ul.upper_right = self
|
||||
self.lower_left = ll
|
||||
if ll != None: ll.lower_right = self
|
||||
|
||||
def update_right(self, ur, lr):
|
||||
self.upper_right = ur
|
||||
if ur != None: ur.upper_left = self
|
||||
self.lower_right = lr
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def update_left_right(self, ul, ll, ur, lr):
|
||||
self.upper_left = ul
|
||||
if ul != None: ul.upper_right = self
|
||||
self.lower_left = ll
|
||||
if ll != None: ll.lower_right = self
|
||||
self.upper_right = ur
|
||||
if ur != None: ur.upper_left = self
|
||||
self.lower_right = lr
|
||||
if lr != None: lr.lower_left = self
|
||||
|
||||
def trim_neighbors(self):
|
||||
if self.inside:
|
||||
self.inside = False
|
||||
if self.upper_left != None: self.upper_left.trim_neighbors()
|
||||
if self.lower_left != None: self.lower_left.trim_neighbors()
|
||||
if self.upper_right != None: self.upper_right.trim_neighbors()
|
||||
if self.lower_right != None: self.lower_right.trim_neighbors()
|
||||
|
||||
def contains(self, point):
|
||||
return (point.x > self.left_point.x and point.x < self.right_point.x and
|
||||
self.top.is_above(point) and self.bottom.is_below(point))
|
||||
|
||||
def vertices(self):
|
||||
v1 = line_intersect(self.top, self.left_point.x)
|
||||
v2 = line_intersect(self.bottom, self.left_point.x)
|
||||
v3 = line_intersect(self.bottom, self.right_point.x)
|
||||
v4 = line_intersect(self.top, self.right_point.x)
|
||||
return v1, v2, v3, v4
|
||||
|
||||
def add_points(self):
|
||||
if self.left_point != self.bottom.p:
|
||||
self.bottom.mpoints.append(self.left_point.clone())
|
||||
if self.right_point != self.bottom.q:
|
||||
self.bottom.mpoints.append(self.right_point.clone())
|
||||
if self.left_point != self.top.p:
|
||||
self.top.mpoints.append(self.left_point.clone())
|
||||
if self.right_point != self.top.q:
|
||||
self.top.mpoints.append(self.right_point.clone())
|
||||
|
||||
def line_intersect(edge, x):
|
||||
y = edge.slope * x + edge.b
|
||||
return x, y
|
||||
|
||||
class Triangulator(object):
|
||||
|
||||
##
|
||||
## Number of points should be > 3
|
||||
##
|
||||
def __init__(self, poly_line):
|
||||
self.polygons = []
|
||||
self.trapezoids = []
|
||||
self.xmono_poly = []
|
||||
self.edge_list = self.init_edges(poly_line)
|
||||
self.trapezoidal_map = TrapezoidalMap()
|
||||
self.bounding_box = self.trapezoidal_map.bounding_box(self.edge_list)
|
||||
self.query_graph = QueryGraph(isink(self.bounding_box))
|
||||
|
||||
self.process()
|
||||
|
||||
def triangles(self):
|
||||
triangles = []
|
||||
for p in self.polygons:
|
||||
verts = []
|
||||
for v in p:
|
||||
verts.append((v.x, v.y))
|
||||
triangles.append(verts)
|
||||
return triangles
|
||||
|
||||
def trapezoid_map(self):
|
||||
return self.trapezoidal_map.map
|
||||
|
||||
# Build the trapezoidal map and query graph
|
||||
def process(self):
|
||||
for edge in self.edge_list:
|
||||
traps = self.query_graph.follow_edge(edge)
|
||||
for t in traps:
|
||||
# Remove old trapezods
|
||||
del self.trapezoidal_map.map[t.key]
|
||||
# Bisect old trapezoids and create new
|
||||
cp = t.contains(edge.p)
|
||||
cq = t.contains(edge.q)
|
||||
if cp and cq:
|
||||
tlist = self.trapezoidal_map.case1(t, edge)
|
||||
self.query_graph.case1(t.sink, edge, tlist)
|
||||
elif cp and not cq:
|
||||
tlist = self.trapezoidal_map.case2(t, edge)
|
||||
self.query_graph.case2(t.sink, edge, tlist)
|
||||
elif not cp and not cq:
|
||||
tlist = self.trapezoidal_map.case3(t, edge)
|
||||
self.query_graph.case3(t.sink, edge, tlist)
|
||||
else:
|
||||
tlist = self.trapezoidal_map.case4(t, edge)
|
||||
self.query_graph.case4(t.sink, edge, tlist)
|
||||
# Add new trapezoids to map
|
||||
for t in tlist:
|
||||
self.trapezoidal_map.map[t.key] = t
|
||||
self.trapezoidal_map.clear()
|
||||
|
||||
# Mark outside trapezoids w/ depth-first search
|
||||
for k, t in self.trapezoidal_map.map.items():
|
||||
self.mark_outside(t)
|
||||
|
||||
# Collect interior trapezoids
|
||||
for k, t in self.trapezoidal_map.map.items():
|
||||
if t.inside:
|
||||
self.trapezoids.append(t)
|
||||
t.add_points()
|
||||
|
||||
# Generate the triangles
|
||||
self.create_mountains()
|
||||
|
||||
def mono_polies(self):
|
||||
polies = []
|
||||
for x in self.xmono_poly:
|
||||
polies.append(x.monoPoly)
|
||||
return polies
|
||||
|
||||
def create_mountains(self):
|
||||
for edge in self.edge_list:
|
||||
if len(edge.mpoints) > 2:
|
||||
mountain = MonotoneMountain()
|
||||
points = merge_sort(edge.mpoints)
|
||||
for p in points:
|
||||
mountain.add(p)
|
||||
mountain.process()
|
||||
for t in mountain.triangles:
|
||||
self.polygons.append(t)
|
||||
self.xmono_poly.append(mountain)
|
||||
|
||||
def mark_outside(self, t):
|
||||
if t.top is self.bounding_box.top or t.bottom is self.bounding_box.bottom:
|
||||
t.trim_neighbors()
|
||||
|
||||
def init_edges(self, points):
|
||||
edge_list = []
|
||||
size = len(points)
|
||||
for i in range(size):
|
||||
j = i + 1 if i < size-1 else 0
|
||||
p = points[i][0], points[i][1]
|
||||
q = points[j][0], points[j][1]
|
||||
edge_list.append((p, q))
|
||||
return self.order_edges(edge_list)
|
||||
|
||||
def order_edges(self, edge_list):
|
||||
edges = []
|
||||
for e in edge_list:
|
||||
p = shear_transform(e[0])
|
||||
q = shear_transform(e[1])
|
||||
if p.x > q.x:
|
||||
edges.append(Edge(q, p))
|
||||
else:
|
||||
edges.append(Edge(p, q))
|
||||
# Randomized incremental algorithm
|
||||
shuffle(edges)
|
||||
return edges
|
||||
|
||||
def shear_transform(point):
|
||||
return Point(point[0] + SHEAR * point[1], point[1])
|
||||
|
||||
def merge_sort(l):
|
||||
if len(l)>1 :
|
||||
lleft = merge_sort(l[:len(l)/2])
|
||||
lright = merge_sort(l[len(l)/2:])
|
||||
p1, p2, p = 0, 0, 0
|
||||
while p1<len(lleft) and p2<len(lright):
|
||||
if lleft[p1].x < lright[p2].x:
|
||||
l[p]=lleft[p1]
|
||||
p+=1
|
||||
p1+=1
|
||||
else:
|
||||
l[p]=lright[p2]
|
||||
p+=1
|
||||
p2+=1
|
||||
if p1<len(lleft):l[p:]=lleft[p1:]
|
||||
elif p2<len(lright):l[p:]=lright[p2:]
|
||||
else : print "internal error"
|
||||
return l
|
||||
|
||||
class TrapezoidalMap(object):
|
||||
|
||||
def __init__(self):
|
||||
self.map = {}
|
||||
self.margin = 50.0
|
||||
self.bcross = None
|
||||
self.tcross = None
|
||||
|
||||
def clear(self):
|
||||
self.bcross = None
|
||||
self.tcross = None
|
||||
|
||||
def case1(self, t, e):
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, e.q, e, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, trapezoids[3], None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, trapezoids[3])
|
||||
trapezoids[3].update_right(t.upper_right, t.lower_right)
|
||||
return trapezoids
|
||||
|
||||
def case2(self, t, e):
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
trapezoids.append(Trapezoid(t.left_point, e.p, t.top, t.bottom))
|
||||
trapezoids.append(Trapezoid(e.p, rp, t.top, e))
|
||||
trapezoids.append(Trapezoid(e.p, rp, e, t.bottom))
|
||||
trapezoids[0].update_left(t.upper_left, t.lower_left)
|
||||
trapezoids[1].update_left_right(trapezoids[0], None, t.upper_right, None)
|
||||
trapezoids[2].update_left_right(None, trapezoids[0], None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[1]
|
||||
e.below = trapezoids[2]
|
||||
return trapezoids
|
||||
|
||||
def case3(self, t, e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
rp = e.q if e.q.x == t.right_point.x else t.right_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].update_right(t.upper_right, None)
|
||||
trapezoids[0].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, t.top, e))
|
||||
trapezoids[0].update_left_right(t.upper_left, e.above, t.upper_right, None)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].update_right(None, t.lower_right)
|
||||
trapezoids[1].right_point = rp
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, rp, e, t.bottom))
|
||||
trapezoids[1].update_left_right(e.below, t.lower_left, None, t.lower_right)
|
||||
self.bcross = t.bottom
|
||||
self.tcross = t.top
|
||||
e.above = trapezoids[0]
|
||||
e.below = trapezoids[1]
|
||||
return trapezoids
|
||||
|
||||
def case4(self, t, e):
|
||||
lp = e.p if e.p.x == t.left_point.x else t.left_point
|
||||
trapezoids = []
|
||||
if self.tcross is t.top:
|
||||
trapezoids.append(t.upper_left)
|
||||
trapezoids[0].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, t.top, e))
|
||||
trapezoids[0].update_left(t.upper_left, e.above)
|
||||
if self.bcross is t.bottom:
|
||||
trapezoids.append(t.lower_left)
|
||||
trapezoids[1].right_point = e.q
|
||||
else:
|
||||
trapezoids.append(Trapezoid(lp, e.q, e, t.bottom))
|
||||
trapezoids[1].update_left(e.below, t.lower_left)
|
||||
trapezoids.append(Trapezoid(e.q, t.right_point, t.top, t.bottom))
|
||||
trapezoids[2].update_left_right(trapezoids[0], trapezoids[1], t.upper_right, t.lower_right)
|
||||
return trapezoids
|
||||
|
||||
def bounding_box(self, edges):
|
||||
margin = self.margin
|
||||
max = edges[0].p + margin
|
||||
min = edges[0].q - margin
|
||||
for e in edges:
|
||||
if e.p.x > max.x: max = Point(e.p.x + margin, max.y)
|
||||
if e.p.y > max.y: max = Point(max.x, e.p.y + margin)
|
||||
if e.q.x > max.x: max = Point(e.q.x + margin, max.y)
|
||||
if e.q.y > max.y: max = Point(max.x, e.q.y + margin)
|
||||
if e.p.x < min.x: min = Point(e.p.x - margin, min.y)
|
||||
if e.p.y < min.y: min = Point(min.x, e.p.y - margin)
|
||||
if e.q.x < min.x: min = Point(e.q.x - margin, min.y)
|
||||
if e.q.y < min.y: min = Point(min.x, e.q.y - margin)
|
||||
top = Edge(Point(min.x, max.y), Point(max.x, max.y))
|
||||
bottom = Edge(Point(min.x, min.y), Point(max.x, min.y))
|
||||
left = top.p
|
||||
right = top.q
|
||||
trap = Trapezoid(left, right, top, bottom)
|
||||
self.map[trap.key] = trap
|
||||
return trap
|
||||
|
||||
class Node(object):
|
||||
|
||||
def __init__(self, lchild, rchild):
|
||||
self.parent_list = []
|
||||
self.lchild = lchild
|
||||
self.rchild = rchild
|
||||
if lchild != None:
|
||||
lchild.parent_list.append(self)
|
||||
if rchild != None:
|
||||
rchild.parent_list.append(self)
|
||||
|
||||
def replace(self, node):
|
||||
for parent in node.parent_list:
|
||||
if parent.lchild is node:
|
||||
parent.lchild = self
|
||||
else:
|
||||
parent.rchild = self
|
||||
self.parent_list += node.parent_list
|
||||
|
||||
class Sink(Node):
|
||||
|
||||
def __init__(self, trapezoid):
|
||||
super(Sink, self).__init__(None, None)
|
||||
self.trapezoid = trapezoid
|
||||
trapezoid.sink = self
|
||||
|
||||
def locate(self, edge):
|
||||
return self
|
||||
|
||||
def isink(trapezoid):
|
||||
if trapezoid.sink is None:
|
||||
return Sink(trapezoid)
|
||||
return trapezoid.sink
|
||||
|
||||
class XNode(Node):
|
||||
|
||||
def __init__(self, point, lchild, rchild):
|
||||
super(XNode, self).__init__(lchild, rchild)
|
||||
self.point = point
|
||||
|
||||
def locate(self, edge):
|
||||
if edge.p.x >= self.point.x:
|
||||
return self.rchild.locate(edge)
|
||||
return self.lchild.locate(edge)
|
||||
|
||||
class YNode(Node):
|
||||
|
||||
def __init__(self, edge, lchild, rchild):
|
||||
super(YNode, self).__init__(lchild, rchild)
|
||||
self.edge = edge
|
||||
|
||||
def locate(self, edge):
|
||||
if self.edge.is_above(edge.p):
|
||||
return self.rchild.locate(edge)
|
||||
if self.edge.is_below(edge.p):
|
||||
return self.lchild.locate(edge)
|
||||
if edge.slope < self.edge.slope:
|
||||
return self.rchild.locate(edge)
|
||||
return self.lchild.locate(edge)
|
||||
|
||||
class QueryGraph:
|
||||
|
||||
def __init__(self, head):
|
||||
self.head = head
|
||||
|
||||
def locate(self, edge):
|
||||
return self.head.locate(edge).trapezoid
|
||||
|
||||
def follow_edge(self, edge):
|
||||
trapezoids = [self.locate(edge)]
|
||||
while(edge.q.x > trapezoids[-1].right_point.x):
|
||||
if edge.is_above(trapezoids[-1].right_point):
|
||||
trapezoids.append(trapezoids[-1].upper_right)
|
||||
else:
|
||||
trapezoids.append(trapezoids[-1].lower_right)
|
||||
return trapezoids
|
||||
|
||||
def replace(self, sink, node):
|
||||
if sink.parent_list:
|
||||
node.replace(sink)
|
||||
else:
|
||||
self.head = node
|
||||
|
||||
def case1(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[1]), isink(tlist[2]))
|
||||
qNode = XNode(edge.q, yNode, isink(tlist[3]))
|
||||
pNode = XNode(edge.p, isink(tlist[0]), qNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case2(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[1]), isink(tlist[2]))
|
||||
pNode = XNode(edge.p, isink(tlist[0]), yNode)
|
||||
self.replace(sink, pNode)
|
||||
|
||||
def case3(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[0]), isink(tlist[1]))
|
||||
self.replace(sink, yNode)
|
||||
|
||||
def case4(self, sink, edge, tlist):
|
||||
yNode = YNode(edge, isink(tlist[0]), isink(tlist[1]))
|
||||
qNode = XNode(edge.q, yNode, isink(tlist[2]))
|
||||
self.replace(sink, qNode)
|
||||
|
||||
PI_SLOP = 3.1
|
||||
|
||||
class MonotoneMountain:
|
||||
|
||||
def __init__(self):
|
||||
self.size = 0
|
||||
self.tail = None
|
||||
self.head = None
|
||||
self.positive = False
|
||||
self.convex_points = []
|
||||
self.mono_poly = []
|
||||
self.triangles = []
|
||||
self.convex_polies = []
|
||||
|
||||
def add(self, point):
|
||||
if self.size is 0:
|
||||
self.head = point
|
||||
self.size = 1
|
||||
elif self.size is 1:
|
||||
if point.neq(self.head):
|
||||
self.tail = point
|
||||
self.tail.prev = self.head
|
||||
self.head.next = self.tail
|
||||
self.size = 2
|
||||
else:
|
||||
if point.neq(self.tail):
|
||||
self.tail.next = point
|
||||
point.prev = self.tail
|
||||
self.tail = point
|
||||
self.size += 1
|
||||
|
||||
def remove(self, point):
|
||||
next = point.next
|
||||
prev = point.prev
|
||||
point.prev.next = next
|
||||
point.next.prev = prev
|
||||
self.size -= 1
|
||||
|
||||
def process(self):
|
||||
self.positive = self.angle_sign()
|
||||
self.gen_mono_poly()
|
||||
p = self.head.next
|
||||
while p != self.tail:
|
||||
a = self.angle(p)
|
||||
if a >= PI_SLOP or a <= -PI_SLOP or a == 0:
|
||||
self.remove(p)
|
||||
elif self.is_convex(p):
|
||||
self.convex_points.append(p)
|
||||
p = p.next
|
||||
self.triangulate()
|
||||
|
||||
def triangulate(self):
|
||||
while self.convex_points:
|
||||
ear = self.convex_points.pop(0)
|
||||
a = ear.prev
|
||||
b = ear
|
||||
c = ear.next
|
||||
triangle = (a, b, c)
|
||||
self.triangles.append(triangle)
|
||||
self.remove(ear)
|
||||
if self.valid(a):
|
||||
self.convex_points.append(a)
|
||||
if self.valid(c):
|
||||
self.convex_points.append(c)
|
||||
#assert self.size <= 3, "Triangulation bug, please report"
|
||||
|
||||
def valid(self, p):
|
||||
return p != self.head and p != self.tail and self.is_convex(p)
|
||||
|
||||
def gen_mono_poly(self):
|
||||
p = self.head
|
||||
while(p != None):
|
||||
self.mono_poly.append(p)
|
||||
p = p.next
|
||||
|
||||
def angle(self, p):
|
||||
a = p.next - p
|
||||
b = p.prev - p
|
||||
return atan2(a.cross(b), a.dot(b))
|
||||
|
||||
def angle_sign(self):
|
||||
a = self.head.next - self.head
|
||||
b = self.tail - self.head
|
||||
return atan2(a.cross(b), a.dot(b)) >= 0
|
||||
|
||||
def is_convex(self, p):
|
||||
if self.positive != (self.angle(p) >= 0):
|
||||
return False
|
||||
return True
|
@ -1,44 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
from Cython.Distutils import build_ext
|
||||
|
||||
# Usage: python setup.py build_ext --i
|
||||
|
||||
version = '0.1'
|
||||
|
||||
sourcefiles = ['framework/framework.pyx', 'framework/predicates.c']
|
||||
|
||||
# Platform-dependent submodules
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# MS Windows
|
||||
libs = ['glew32', 'glu32', 'glfw', 'opengl32']
|
||||
elif sys.platform == 'darwin':
|
||||
# Apple OSX
|
||||
raise SystemError('OSX is unsupported in this version')
|
||||
else:
|
||||
# GNU/Linux, BSD, etc
|
||||
libs = ['GLEW', 'GLU', 'glfw', 'GL']
|
||||
|
||||
mod_engine = Extension(
|
||||
"framework",
|
||||
sourcefiles,
|
||||
libraries = libs,
|
||||
language = 'c'
|
||||
)
|
||||
|
||||
setup(
|
||||
name = 'Poly2Tri',
|
||||
version = version,
|
||||
description = 'A 2D Polygon Triangulator',
|
||||
author = 'Mason Green (zzzzrrr)',
|
||||
author_email = '',
|
||||
maintainer = '',
|
||||
maintainer_email = '',
|
||||
url = 'http://code.google.com/p/poly2tri/',
|
||||
cmdclass = {'build_ext': build_ext},
|
||||
ext_modules = [mod_engine],
|
||||
)
|
@ -1,467 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri
|
||||
|
||||
import org.newdawn.slick.{BasicGame, GameContainer, Graphics, Color, AppGameContainer}
|
||||
import org.newdawn.slick.geom.{Polygon, Circle}
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.io.Source
|
||||
|
||||
import seidel.Triangulator
|
||||
import shapes.{Segment, Point, Triangle}
|
||||
import earClip.EarClip
|
||||
import cdt.CDT
|
||||
|
||||
// TODO: Lots of documentation!
|
||||
// : Add Hertel-Mehlhorn algorithm
|
||||
object Poly2Tri {
|
||||
|
||||
def main(args: Array[String]) {
|
||||
val container = new AppGameContainer(new Poly2TriDemo())
|
||||
container.setDisplayMode(800,600,false)
|
||||
container.start()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Poly2TriDemo extends BasicGame("Poly2Tri") {
|
||||
|
||||
object Algo extends Enumeration {
|
||||
type Algo = Value
|
||||
val CDT, Seidel, EarClip = Value
|
||||
}
|
||||
import Algo._
|
||||
|
||||
// Sedidel Triangulator
|
||||
var seidel: Triangulator = null
|
||||
var segments: ArrayBuffer[Segment] = null
|
||||
var chestSegs: ArrayBuffer[Segment] = null
|
||||
var headSegs: ArrayBuffer[Segment] = null
|
||||
|
||||
// EarClip Triangulator
|
||||
val earClip = new EarClip
|
||||
var earClipResults: Array[poly2tri.earClip.Triangle] = null
|
||||
|
||||
// Sweep Line Constraied Delauney Triangulator (CDT)
|
||||
var slCDT: CDT = null
|
||||
|
||||
var quit = false
|
||||
var drawMap = false
|
||||
var drawSegs = true
|
||||
var drawCDTMesh = false
|
||||
|
||||
val nazcaMonkey = "../data/nazca_monkey.dat"
|
||||
val nazcaHeron = "../data/nazca_heron_old.dat"
|
||||
val bird = "../data/bird.dat"
|
||||
val snake = "../data/i.snake"
|
||||
val star = "../data/star.dat"
|
||||
val strange = "../data/strange.dat"
|
||||
val i18 = "../data/i.18"
|
||||
val tank = "../data/tank.dat"
|
||||
val dude = "../data/dude.dat"
|
||||
val basic = "../data/basic.dat"
|
||||
|
||||
var currentModel = dude
|
||||
var doCDT = true
|
||||
// The current algorithm
|
||||
var algo = CDT
|
||||
|
||||
var mouseButton = 0
|
||||
var mousePressed = false
|
||||
var mouseDrag = false
|
||||
var mousePos = Point(0, 0)
|
||||
var mousePosOld = Point(0, 0)
|
||||
var deltaX = 0f
|
||||
var deltaY = 0f
|
||||
var scaleFactor = 0.85f
|
||||
|
||||
var gameContainer: GameContainer = null
|
||||
|
||||
def init(container: GameContainer) {
|
||||
gameContainer = container
|
||||
selectModel(currentModel)
|
||||
}
|
||||
|
||||
def update(gc: GameContainer, delta: Int) {
|
||||
if(quit) gc exit
|
||||
}
|
||||
|
||||
def render(container: GameContainer, g: Graphics) {
|
||||
|
||||
val red = new Color(1f, 0f,0.0f)
|
||||
val blue = new Color(0f, 0f, 1f)
|
||||
val green = new Color(0f, 1f, 0f)
|
||||
val yellow = new Color(1f, 1f, 0f)
|
||||
|
||||
g.setColor(green)
|
||||
g.drawString("'1-9' to cycle models, mouse to pan & zoom", 10, 522)
|
||||
g.drawString("'c,s,e' to switch CDT / Seidel / EarClip algos", 10, 537)
|
||||
g.drawString("'t' to show trapezoidal map (Seidel)", 10, 552)
|
||||
g.drawString("'m' to show triangle mesh (CDT)", 10, 567)
|
||||
g.drawString("'d' to draw edges", 10, 582)
|
||||
|
||||
g.scale(scaleFactor, scaleFactor)
|
||||
g.translate(deltaX, deltaY)
|
||||
|
||||
algo match {
|
||||
|
||||
case Algo.Seidel => {
|
||||
for(t <- seidel.polygons) {
|
||||
val poly = new Polygon
|
||||
t.foreach(p => poly.addPoint(p.x, p.y))
|
||||
g.setColor(red)
|
||||
g.draw(poly)
|
||||
}
|
||||
if (drawMap) {
|
||||
for(t <- seidel.trapezoidMap) {
|
||||
val polygon = new Polygon()
|
||||
for(v <- t.vertices) {
|
||||
polygon.addPoint(v.x, v.y)
|
||||
}
|
||||
g.setColor(red)
|
||||
g.draw(polygon)
|
||||
}/*
|
||||
for(mp <- seidel.monoPolies) {
|
||||
val poly = new Polygon
|
||||
mp.foreach(p => poly.addPoint(p.x, p.y))
|
||||
g.setColor(yellow)
|
||||
g.draw(poly)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
case Algo.EarClip => {
|
||||
earClipResults.foreach(t => {
|
||||
val triangle = new Polygon
|
||||
triangle.addPoint(t.x(0), t.y(0))
|
||||
triangle.addPoint(t.x(1), t.y(1))
|
||||
triangle.addPoint(t.x(2), t.y(2))
|
||||
g.setColor(red)
|
||||
g.draw(triangle)
|
||||
})
|
||||
}
|
||||
|
||||
case Algo.CDT => {
|
||||
val draw = if(drawCDTMesh) slCDT.triangleMesh else slCDT.triangles
|
||||
draw.foreach( t => {
|
||||
val triangle = new Polygon
|
||||
triangle.addPoint(t.points(0).x, t.points(0).y)
|
||||
triangle.addPoint(t.points(1).x, t.points(1).y)
|
||||
triangle.addPoint(t.points(2).x, t.points(2).y)
|
||||
g.setColor(red)
|
||||
g.draw(triangle)
|
||||
})
|
||||
|
||||
slCDT.debugTriangles.foreach( t => {
|
||||
val triangle = new Polygon
|
||||
triangle.addPoint(t.points(0).x, t.points(0).y)
|
||||
triangle.addPoint(t.points(1).x, t.points(1).y)
|
||||
triangle.addPoint(t.points(2).x, t.points(2).y)
|
||||
g.setColor(blue)
|
||||
g.draw(triangle)
|
||||
})
|
||||
|
||||
for(i <- 0 until slCDT.cList.size) {
|
||||
val circ = new Circle(slCDT.cList(i).x, slCDT.cList(i).y, 0.5f)
|
||||
g.setColor(blue); g.draw(circ); g.fill(circ)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
if(drawSegs) {
|
||||
g.setColor(green)
|
||||
for(i <- 0 until segments.size) {
|
||||
val s = segments(i)
|
||||
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
|
||||
}
|
||||
}
|
||||
|
||||
if(currentModel == "../data/dude.dat" && drawSegs) {
|
||||
g.setColor(green)
|
||||
for(i <- 0 until chestSegs.size) {
|
||||
val s = chestSegs(i)
|
||||
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
|
||||
}
|
||||
for(i <- 0 until headSegs.size) {
|
||||
val s = headSegs(i)
|
||||
g.drawLine(s.p.x,s.p.y,s.q.x,s.q.y)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouseDown events.
|
||||
* @param p The screen location that the mouse is down at.
|
||||
*/
|
||||
override def mousePressed(b: Int, x: Int, y: Int) {
|
||||
|
||||
mouseButton = b
|
||||
mousePressed = true
|
||||
mousePosOld = mousePos
|
||||
mousePos = Point(x, y)
|
||||
|
||||
// Right click
|
||||
// Correctly adjust for pan and zoom
|
||||
if(mouseButton == 1) {
|
||||
val point = mousePos/scaleFactor + Point(deltaX, deltaY)
|
||||
println(point)
|
||||
slCDT.addPoint(point)
|
||||
slCDT.triangulate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouseUp events.
|
||||
*/
|
||||
override def mouseReleased(b: Int, x: Int, y: Int) {
|
||||
mousePosOld = mousePos
|
||||
mousePos = Point(x,y)
|
||||
mousePressed = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouseMove events (TestbedMain also sends mouseDragged events here)
|
||||
* @param p The new mouse location (screen coordinates)
|
||||
*/
|
||||
override def mouseMoved(oldX: Int, oldY: Int, x: Int, y: Int) {
|
||||
mousePosOld = mousePos
|
||||
mousePos = Point(x,y)
|
||||
if(mousePressed) {
|
||||
deltaX += mousePos.x - mousePosOld.x
|
||||
deltaY += mousePos.y - mousePosOld.y
|
||||
}
|
||||
}
|
||||
|
||||
override def mouseWheelMoved(notches: Int) {
|
||||
if (notches < 0) {
|
||||
scaleFactor = Math.min(300f, scaleFactor * 1.05f);
|
||||
}
|
||||
else if (notches > 0) {
|
||||
scaleFactor = Math.max(.02f, scaleFactor / 1.05f);
|
||||
}
|
||||
}
|
||||
|
||||
override def keyPressed(key:Int, c:Char) {
|
||||
|
||||
// ESC
|
||||
if(key == 1) quit = true
|
||||
|
||||
if(c == '1') selectModel(nazcaMonkey)
|
||||
if(c == '2') selectModel(bird)
|
||||
if(c == '3') selectModel(strange)
|
||||
if(c == '4') selectModel(snake)
|
||||
if(c == '5') selectModel(star)
|
||||
if(c == '6') selectModel(i18)
|
||||
if(c == '7') selectModel(nazcaHeron)
|
||||
if(c == '8') selectModel(tank)
|
||||
if(c == '9') selectModel(dude)
|
||||
if(c == '0') selectModel(basic)
|
||||
|
||||
if(c == 'd') drawSegs = !drawSegs
|
||||
if(c == 'm') drawCDTMesh = !drawCDTMesh
|
||||
if(c == 't') drawMap = !drawMap
|
||||
|
||||
if(c == 's') {algo = Seidel; selectModel(currentModel) }
|
||||
if(c == 'c') {algo = CDT; selectModel(currentModel) }
|
||||
if(c == 'e') {algo = EarClip; selectModel(currentModel) }
|
||||
|
||||
// Experimental...
|
||||
if(c == 'r') slCDT.refine
|
||||
|
||||
}
|
||||
|
||||
def selectModel(model: String) {
|
||||
model match {
|
||||
case "../data/nazca_monkey.dat" =>
|
||||
val clearPoint = Point(418, 282)
|
||||
loadModel(nazcaMonkey, 4.5f, Point(400, 300), 1500, clearPoint)
|
||||
case "../data/bird.dat" =>
|
||||
val clearPoint = Point(400, 300)
|
||||
loadModel(bird, 25f, Point(400, 300), 350, clearPoint)
|
||||
case "../data/i.snake" =>
|
||||
val clearPoint = Point(336f, 196f)
|
||||
loadModel(snake, 10f, Point(600, 300), 10, clearPoint)
|
||||
case "../data/star.dat" =>
|
||||
val clearPoint = Point(400, 204)
|
||||
loadModel(star, -1f, Point(0f, 0f), 10, clearPoint)
|
||||
case "../data/strange.dat" =>
|
||||
val clearPoint = Point(400, 268)
|
||||
loadModel(strange, -1f, Point(0f, 0f), 15, clearPoint)
|
||||
case "../data/i.18" =>
|
||||
val clearPoint = Point(510, 385)
|
||||
loadModel(i18, 20f, Point(600f, 500f), 20, clearPoint)
|
||||
case "../data/nazca_heron_old.dat" =>
|
||||
val clearPoint = Point(85, 290)
|
||||
loadModel(nazcaHeron, 4.2f, Point(400f, 300f), 1500, clearPoint)
|
||||
case "../data/tank.dat" =>
|
||||
val clearPoint = Point(450, 350)
|
||||
loadModel(tank, -1f, Point(100f, 0f), 10, clearPoint)
|
||||
case "../data/dude.dat" =>
|
||||
val clearPoint = Point(365, 427)
|
||||
loadModel(dude, -1f, Point(100f, -200f), 10, clearPoint)
|
||||
case "../data/basic.dat" =>
|
||||
val clearPoint = Point(500, 450)
|
||||
loadModel(basic, -5f, Point(500, 450), 10, clearPoint)
|
||||
case _ =>
|
||||
}
|
||||
currentModel = model
|
||||
}
|
||||
|
||||
def loadModel(model: String, scale: Float, center: Point, maxTriangles: Int, clearPoint: Point) {
|
||||
|
||||
println
|
||||
println("************** " + model + " **************")
|
||||
|
||||
val polyX = new ArrayBuffer[Float]
|
||||
val polyY = new ArrayBuffer[Float]
|
||||
var points = new ArrayBuffer[Point]
|
||||
|
||||
val angle = Math.Pi
|
||||
for (line <- Source.fromFile(model).getLines) {
|
||||
val s = line.replaceAll("\n", "")
|
||||
val tokens = s.split("[ ]+")
|
||||
if(tokens.size == 2) {
|
||||
var x = tokens(0).toFloat
|
||||
var y = tokens(1).toFloat
|
||||
// Transform the shape
|
||||
polyX += (Math.cos(angle)*x - Math.sin(angle)*y).toFloat * scale + center.x
|
||||
polyY += (Math.sin(angle)*x + Math.cos(angle)*y).toFloat * scale + center.y
|
||||
points += new Point(polyX.last, polyY.last)
|
||||
} else {
|
||||
throw new Exception("Bad input file")
|
||||
}
|
||||
}
|
||||
|
||||
segments = new ArrayBuffer[Segment]
|
||||
for(i <- 0 until points.size-1)
|
||||
segments += new Segment(points(i), points(i+1))
|
||||
segments += new Segment(points.first, points.last)
|
||||
|
||||
println("Number of points = " + polyX.size)
|
||||
println
|
||||
|
||||
algo match {
|
||||
|
||||
case Algo.CDT => {
|
||||
|
||||
val pts = points.toArray
|
||||
|
||||
val t1 = System.nanoTime
|
||||
slCDT = new CDT(pts, clearPoint)
|
||||
|
||||
// Add some holes....
|
||||
if(model == "../data/dude.dat") {
|
||||
|
||||
val headHole = Array(Point(325f,437f), Point(320f,423f), Point(329f,413f), Point(332f,423f))
|
||||
val chestHole = Array(Point(320.72342f,480f), Point(338.90617f,465.96863f),
|
||||
Point(347.99754f,480.61584f), Point(329.8148f,510.41534f),
|
||||
Point(339.91632f,480.11077f), Point(334.86556f,478.09046f))
|
||||
|
||||
// Tramsform the points
|
||||
for(i <- 0 until headHole.size) {
|
||||
val hx = -headHole(i).x*scale + center.x
|
||||
val hy = -headHole(i).y*scale + center.y
|
||||
headHole(i) = Point(hx, hy)
|
||||
}
|
||||
for(i <- 0 until chestHole.size) {
|
||||
val cx = -chestHole(i).x*scale + center.x
|
||||
val cy = -chestHole(i).y*scale + center.y
|
||||
chestHole(i) = Point(cx, cy)
|
||||
}
|
||||
|
||||
chestSegs = new ArrayBuffer[Segment]
|
||||
for(i <- 0 until chestHole.size-1)
|
||||
chestSegs += new Segment(chestHole(i), chestHole(i+1))
|
||||
chestSegs += new Segment(chestHole.first, chestHole.last)
|
||||
|
||||
headSegs = new ArrayBuffer[Segment]
|
||||
for(i <- 0 until headHole.size-1)
|
||||
headSegs += new Segment(headHole(i), headHole(i+1))
|
||||
headSegs += new Segment(headHole.first, headHole.last)
|
||||
|
||||
// Add the holes
|
||||
slCDT.addHole(headHole)
|
||||
slCDT.addHole(chestHole)
|
||||
}
|
||||
|
||||
slCDT triangulate
|
||||
val runTime = System.nanoTime - t1
|
||||
|
||||
println("CDT average (ms) = " + runTime*1e-6)
|
||||
println("Number of triangles = " + slCDT.triangles.size)
|
||||
println
|
||||
}
|
||||
|
||||
case Algo.Seidel => {
|
||||
|
||||
// Sediel triangulation
|
||||
val t1 = System.nanoTime
|
||||
seidel = new Triangulator(points)
|
||||
val runTime = System.nanoTime - t1
|
||||
|
||||
println("Seidel average (ms) = " + runTime*1e-6)
|
||||
println("Number of triangles = " + seidel.polygons.size)
|
||||
|
||||
}
|
||||
|
||||
case Algo.EarClip => {
|
||||
|
||||
// Earclip
|
||||
|
||||
earClipResults = new Array[poly2tri.earClip.Triangle](maxTriangles)
|
||||
|
||||
for(i <- 0 until earClipResults.size) earClipResults(i) = new poly2tri.earClip.Triangle
|
||||
|
||||
var xVerts = polyX.toArray
|
||||
var yVerts = polyY.toArray
|
||||
|
||||
val xv = if(currentModel != "../data/strange.dat") xVerts.reverse.toArray else xVerts
|
||||
val yv = if(currentModel != "../data/strange.dat") yVerts.reverse.toArray else yVerts
|
||||
|
||||
val t1 = System.nanoTime
|
||||
earClip.triangulatePolygon(xv, yv, xVerts.size, earClipResults)
|
||||
val runTime = System.nanoTime - t1
|
||||
|
||||
println
|
||||
println("Earclip average (ms) = " + runTime*1e-6)
|
||||
println("Number of triangles = " + earClip.numTriangles)
|
||||
}
|
||||
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.cdt
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.{Point, Triangle, Segment}
|
||||
|
||||
// Advancing front
|
||||
class AFront(iTriangle: Triangle) {
|
||||
|
||||
// Doubly linked list
|
||||
var head = new Node(iTriangle.points(1), iTriangle)
|
||||
val middle = new Node(iTriangle.points(0), iTriangle)
|
||||
var tail = new Node(iTriangle.points(2), null)
|
||||
|
||||
head.next = middle
|
||||
middle.next = tail
|
||||
middle.prev = head
|
||||
tail.prev = middle
|
||||
|
||||
// TODO: Use Red-Black Tree or Interval Tree for better search performance!
|
||||
def locate(point: Point): Node = {
|
||||
var node = head
|
||||
while(node != tail) {
|
||||
if(point.x >= node.point.x && point.x < node.next.point.x)
|
||||
return node
|
||||
node = node.next
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
// Locate node containing given point
|
||||
def locatePoint(point: Point): Node = {
|
||||
var node = head
|
||||
while(node != null) {
|
||||
if(point == node.point)
|
||||
return node
|
||||
node = node.next
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
def insert(point: Point, triangle: Triangle, nNode: Node) = {
|
||||
val node = new Node(point, triangle)
|
||||
nNode.triangle = triangle
|
||||
nNode.next.prev = node
|
||||
node.next = nNode.next
|
||||
node.prev = nNode
|
||||
nNode.next = node
|
||||
node
|
||||
}
|
||||
|
||||
def insertLegalized(point: Point, triangle: Triangle, nNode: Node) = {
|
||||
val node = new Node(triangle.points(1), triangle)
|
||||
val rNode = nNode.next
|
||||
rNode.prev = node
|
||||
node.next = rNode
|
||||
nNode.next = node
|
||||
node.prev = nNode
|
||||
node
|
||||
}
|
||||
|
||||
// Update advancing front with constrained edge triangles
|
||||
def constrainedEdge(sNode: Node, eNode: Node, T1: ArrayBuffer[Triangle],
|
||||
T2: ArrayBuffer[Triangle], edge: Segment) {
|
||||
|
||||
var node = sNode.prev
|
||||
|
||||
val point1 = edge.q
|
||||
val point2 = edge.p
|
||||
|
||||
// Scan the advancing front and update Node triangle pointers
|
||||
// TODO: Make this more efficient
|
||||
while(node != null && node != eNode.next) {
|
||||
T2.foreach(t => {
|
||||
if(t.contains(node.point, node.next.point))
|
||||
node.triangle = t
|
||||
})
|
||||
T1.foreach(t => {
|
||||
if(t.contains(node.point, node.next.point))
|
||||
node.triangle = t
|
||||
})
|
||||
node = node.next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transition from AFront traversal to interior mesh traversal
|
||||
def aboveEdge(first: Node, pNode: Node, last: Triangle,
|
||||
point2: Point, ahead:Boolean): Node =
|
||||
if(ahead) {
|
||||
val n = new Node(point2, pNode.prev.triangle)
|
||||
link (first, n, last)
|
||||
n.next = pNode
|
||||
pNode.prev = n
|
||||
n
|
||||
} else {
|
||||
val n = new Node(point2, last)
|
||||
link (n, first, last)
|
||||
pNode.next = n
|
||||
n.prev = pNode
|
||||
pNode
|
||||
}
|
||||
|
||||
def -=(tuple: Tuple3[Node, Node, Triangle]) {
|
||||
val (node, kNode, triangle) = tuple
|
||||
kNode.next.prev = node
|
||||
node.next = kNode.next
|
||||
node.triangle = triangle
|
||||
}
|
||||
|
||||
def link(node1: Node, node2: Node, t: Triangle) {
|
||||
node1.next = node2
|
||||
node2.prev = node1
|
||||
node1.triangle = t
|
||||
}
|
||||
|
||||
// NOT IMPLEMENTED
|
||||
def basin(node: Node) {
|
||||
if(node.next != tail) {
|
||||
val p1 = node.point
|
||||
val p2 = node.next.point
|
||||
val slope = (p1.y - p2.y) / (p1.x - p2.x)
|
||||
if(slope < Math.Pi*3/4)
|
||||
println("basin slope = " + slope)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Advancing front node
|
||||
class Node(val point: Point, var triangle: Triangle) {
|
||||
var next: Node = null
|
||||
var prev: Node = null
|
||||
}
|
@ -1,549 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.cdt
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.{Segment, Point, Triangle}
|
||||
import utils.Util
|
||||
|
||||
/**
|
||||
* Sweep-line, Constrained Delauney Triangulation (CDT)
|
||||
* See: Domiter, V. and Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation',
|
||||
* International Journal of Geographical Information Science
|
||||
*/
|
||||
|
||||
// clearPoint is any interior point inside the polygon
|
||||
class CDT(polyLine: Array[Point], clearPoint: Point) {
|
||||
|
||||
// Triangle list
|
||||
def triangles = mesh.triangles
|
||||
def triangleMesh = mesh.map
|
||||
def debugTriangles = mesh.debug
|
||||
|
||||
val cList = new ArrayBuffer[Point]
|
||||
|
||||
var refined = false
|
||||
|
||||
// Initialize edges
|
||||
initEdges(polyLine)
|
||||
|
||||
// Add a hole
|
||||
def addHole(holePolyLine: Array[Point]) {
|
||||
initEdges(holePolyLine)
|
||||
points = points ++ holePolyLine.toList
|
||||
}
|
||||
|
||||
// Add an internal point
|
||||
// Good for manually refining the mesh
|
||||
def addPoint(point: Point) {
|
||||
points = point :: points
|
||||
}
|
||||
|
||||
// Triangulate simple polygon with holes
|
||||
def triangulate {
|
||||
|
||||
mesh.map.clear
|
||||
mesh.triangles.clear
|
||||
mesh.debug.clear
|
||||
|
||||
var xmax, xmin = points.first.x
|
||||
var ymax, ymin = points.first.y
|
||||
|
||||
// Calculate bounds
|
||||
for(i <- 0 until points.size) {
|
||||
val p = points(i)
|
||||
if(p.x > xmax) xmax = p.x
|
||||
if(p.x < xmin) xmin = p.x
|
||||
if(p.y > ymax) ymax = p.y
|
||||
if(p.y < ymin) ymin = p.y
|
||||
}
|
||||
|
||||
val deltaX = ALPHA * (xmax - xmin)
|
||||
val deltaY = ALPHA * (ymax - ymin)
|
||||
val p1 = Point(xmin - deltaX, ymin - deltaY)
|
||||
val p2 = Point(xmax + deltaX, p1.y)
|
||||
|
||||
// Sort the points along y-axis
|
||||
points = pointSort
|
||||
|
||||
// Initial triangle
|
||||
val iTriangle = new Triangle(Array(points(0), p1, p2))
|
||||
mesh.map += iTriangle
|
||||
aFront = new AFront(iTriangle)
|
||||
|
||||
// Sweep points; build mesh
|
||||
sweep
|
||||
// Finalize triangulation
|
||||
finalization
|
||||
|
||||
}
|
||||
|
||||
// Create edges and connect end points; update edge event pointer
|
||||
private def initEdges(pts: Array[Point]) {
|
||||
// Connect pts
|
||||
for(i <- 0 until pts.size-1) {
|
||||
val endPoints = validatePoints(pts(i), pts(i+1))
|
||||
val edge = new Segment(endPoints(0), endPoints(1))
|
||||
endPoints(1).edges += edge
|
||||
}
|
||||
// Connect endpoints
|
||||
val endPoints = validatePoints(pts.first, pts.last)
|
||||
val edge = new Segment(endPoints(0), endPoints(1))
|
||||
endPoints(1).edges += edge
|
||||
}
|
||||
|
||||
private def validatePoints(p1: Point, p2: Point): List[Point] = {
|
||||
if(p1.y > p2.y) {
|
||||
// For CDT we want q to be the point with > y
|
||||
return List(p2, p1)
|
||||
} else if(p1.y == p2.y) {
|
||||
// If y values are equal, make sure point with smaller x value
|
||||
// is to the left
|
||||
if(p1.x > p2.x) {
|
||||
return List(p2, p1)
|
||||
} else if(p1.x == p2.x) {
|
||||
throw new Exception("Duplicate point")
|
||||
}
|
||||
}
|
||||
List(p1, p2)
|
||||
}
|
||||
|
||||
// Merge sort: O(n log n)
|
||||
private def pointSort: List[Point] =
|
||||
Util.msort((p1: Point, p2: Point) => p1 > p2)(points)
|
||||
|
||||
// Implement sweep-line
|
||||
private def sweep {
|
||||
val size = if(refined) 1 else points.size
|
||||
for(i <- 1 until points.size) {
|
||||
val point = points(i)
|
||||
// Process Point event
|
||||
val node = pointEvent(point)
|
||||
// Process edge events
|
||||
point.edges.foreach(e => edgeEvent(e, node))
|
||||
}
|
||||
}
|
||||
|
||||
// Final step in the sweep-line CDT
|
||||
// Clean exterior triangles
|
||||
private def finalization {
|
||||
var found = false
|
||||
mesh.map.foreach(m => {
|
||||
if(!found)
|
||||
// Mark the originating clean triangle
|
||||
if(m.pointIn(clearPoint)) {
|
||||
found = true
|
||||
cleanTri = m
|
||||
}
|
||||
m.markNeighborEdges
|
||||
})
|
||||
// Collect interior triangles constrained by edges
|
||||
mesh clean cleanTri
|
||||
}
|
||||
|
||||
// Delauney Refinement: Refine triangules using Steiner points
|
||||
// Probably overkill for 2D games, and will create a large number of
|
||||
// triangles that are probably unoptimal for a physics engine like
|
||||
// Box2D.... Better to manually enter interior points for mesh "smoothing"
|
||||
// TODO: Finish implementation... Maybe!
|
||||
def refine {
|
||||
cList.clear
|
||||
mesh.triangles.foreach(t => {
|
||||
if(t.thin) {
|
||||
val center = Util.circumcenter(t.points(0), t.points(1), t.points(2))
|
||||
cList += center
|
||||
addPoint(center)
|
||||
}
|
||||
})
|
||||
// Retriangulate
|
||||
if(cList.size > 0)
|
||||
triangulate
|
||||
}
|
||||
|
||||
// Point event
|
||||
private def pointEvent(point: Point): Node = {
|
||||
|
||||
val node = aFront.locate(point)
|
||||
|
||||
// Projected point hits advancing front; create new triangle
|
||||
val pts = Array(point, node.point, node.next.point)
|
||||
val triangle = new Triangle(pts)
|
||||
|
||||
mesh.map += triangle
|
||||
|
||||
// Legalize
|
||||
val newNode = legalization(triangle, node)
|
||||
// Fill in adjacent triangles if required
|
||||
scanAFront(newNode)
|
||||
newNode
|
||||
|
||||
}
|
||||
|
||||
// EdgeEvent
|
||||
private def edgeEvent(edge: Segment, node: Node) {
|
||||
|
||||
// Locate the first intersected triangle
|
||||
val firstTriangle = if(!node.triangle.contains(edge.q))
|
||||
node.triangle
|
||||
else
|
||||
node.triangle.locateFirst(edge)
|
||||
|
||||
if(firstTriangle != null && !firstTriangle.contains(edge)) {
|
||||
|
||||
// Interior mesh traversal - edge is "burried" in the mesh
|
||||
// Constrained edge lies below the advancing front. Traverse through intersected triangles,
|
||||
// form empty pseudo-polygons, and re-triangulate
|
||||
|
||||
// Collect intersected triangles
|
||||
val tList = new ArrayBuffer[Triangle]
|
||||
tList += firstTriangle
|
||||
|
||||
while(tList.last != null && !tList.last.contains(edge.p))
|
||||
tList += tList.last.findNeighbor(edge.p)
|
||||
|
||||
// TODO: Finish implementing edge insertion which combines advancing front (AF)
|
||||
// and triangle traversal respectively. See figure 14(a) from Domiter et al.
|
||||
// Should only occur with complex patterns of interior points
|
||||
// Already added provision for transitioning from AFront traversal to
|
||||
// interior mesh traversal - may need to add the opposite case
|
||||
if(tList.last == null)
|
||||
throw new Exception("Not implemented yet - interior points too complex")
|
||||
|
||||
// Neighbor triangles
|
||||
// HashMap or set may improve performance
|
||||
val nTriangles = new ArrayBuffer[Triangle]
|
||||
|
||||
// Remove old triangles; collect neighbor triangles
|
||||
// Keep duplicates out
|
||||
tList.foreach(t => {
|
||||
t.neighbors.foreach(n => if(n != null && !tList.contains(n)) nTriangles += n)
|
||||
mesh.map -= t
|
||||
})
|
||||
|
||||
// Using a hashMap or set may improve performance
|
||||
val lPoints = new ArrayBuffer[Point]
|
||||
val rPoints = new ArrayBuffer[Point]
|
||||
|
||||
// Collect points left and right of edge
|
||||
tList.foreach(t => {
|
||||
t.points.foreach(p => {
|
||||
if(p != edge.q && p != edge.p) {
|
||||
if(Util.orient2d(edge.q, edge.p, p) > 0 ) {
|
||||
// Keep duplicate points out
|
||||
if(!lPoints.contains(p)) {
|
||||
lPoints += p
|
||||
}
|
||||
} else {
|
||||
// Keep duplicate points out
|
||||
if(!rPoints.contains(p))
|
||||
rPoints += p
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Triangulate empty areas.
|
||||
val T1 = new ArrayBuffer[Triangle]
|
||||
triangulate(lPoints.toArray, List(edge.q, edge.p), T1)
|
||||
val T2 = new ArrayBuffer[Triangle]
|
||||
triangulate(rPoints.toArray, List(edge.q, edge.p), T2)
|
||||
|
||||
// Update neighbors
|
||||
edgeNeighbors(nTriangles, T1)
|
||||
edgeNeighbors(nTriangles, T2)
|
||||
T1.last.markNeighbor(T2.last)
|
||||
|
||||
// Update advancing front
|
||||
|
||||
val ahead = (edge.p.x > edge.q.x)
|
||||
val point1 = if(ahead) edge.q else edge.p
|
||||
val point2 = if(ahead) edge.p else edge.q
|
||||
|
||||
val sNode = if(ahead) node else aFront.locate(point1)
|
||||
val eNode = aFront.locate(point2)
|
||||
|
||||
aFront.constrainedEdge(sNode, eNode, T1, T2, edge)
|
||||
|
||||
// Mark constrained edge
|
||||
T1.last markEdge(point1, point2)
|
||||
T2.last markEdge(point1, point2)
|
||||
// Copy constraied edges from old triangles
|
||||
T1.foreach(t => t.markEdge(tList))
|
||||
T2.foreach(t => t.markEdge(tList))
|
||||
|
||||
} else if(firstTriangle == null) {
|
||||
|
||||
// AFront traversal
|
||||
// No triangles are intersected by the edge; edge must lie outside the mesh
|
||||
// Apply constraint; traverse the advancing front, and build triangles
|
||||
|
||||
var pNode, first = node
|
||||
val points = new ArrayBuffer[Point]
|
||||
|
||||
// Neighbor triangles
|
||||
val nTriangles = new ArrayBuffer[Triangle]
|
||||
nTriangles += pNode.triangle
|
||||
|
||||
val ahead = (edge.p.x > edge.q.x)
|
||||
|
||||
// If this is true we transition from AFront traversal to
|
||||
// interior mesh traversal
|
||||
var aboveEdge = false
|
||||
|
||||
if(ahead) {
|
||||
// Scan right
|
||||
pNode = pNode.next
|
||||
while(pNode.point != edge.p && !aboveEdge) {
|
||||
points += pNode.point
|
||||
nTriangles += pNode.triangle
|
||||
pNode = pNode.next
|
||||
aboveEdge = edge < pNode.point
|
||||
}
|
||||
} else {
|
||||
// Scan left
|
||||
pNode = pNode.prev
|
||||
while(pNode.point != edge.p && !aboveEdge) {
|
||||
points += pNode.point
|
||||
nTriangles += pNode.triangle
|
||||
pNode = pNode.prev
|
||||
aboveEdge = edge < pNode.point
|
||||
}
|
||||
nTriangles += pNode.triangle
|
||||
}
|
||||
|
||||
val point2 = if(aboveEdge) {
|
||||
val p1 = pNode.point
|
||||
val p2 = if(ahead) pNode.prev.point else pNode.next.point
|
||||
edge.intersect(p1, p2)
|
||||
} else {
|
||||
edge.p
|
||||
}
|
||||
|
||||
// Triangulate empty areas.
|
||||
val T = new ArrayBuffer[Triangle]
|
||||
triangulate(points.toArray, List(edge.q, point2), T)
|
||||
|
||||
// Update neighbors
|
||||
edgeNeighbors(nTriangles, T)
|
||||
|
||||
// Mark constrained edge
|
||||
T.last markEdge(edge.q, point2)
|
||||
|
||||
// Update advancing front
|
||||
if(aboveEdge) {
|
||||
val iNode = aFront.aboveEdge(first, pNode, T.last, point2, ahead)
|
||||
edgeEvent(new Segment(edge.p, point2), iNode)
|
||||
} else {
|
||||
if(ahead)
|
||||
aFront link (first, pNode, T.last)
|
||||
else
|
||||
aFront link (pNode, first, T.last)
|
||||
}
|
||||
|
||||
} else if(firstTriangle.contains(edge.q, edge.p)) {
|
||||
// Constrained edge lies on the side of a triangle
|
||||
// Mark constrained edge
|
||||
firstTriangle markEdge(edge.q, edge.p)
|
||||
firstTriangle.finalized = true
|
||||
} else {
|
||||
throw new Exception("Triangulation error - unexpected case")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update neigbor pointers for edge event
|
||||
// Inneficient, but it works well...
|
||||
private def edgeNeighbors(nTriangles: ArrayBuffer[Triangle], T: ArrayBuffer[Triangle]) {
|
||||
|
||||
for(t1 <- nTriangles)
|
||||
for(t2 <- T)
|
||||
t2.markNeighbor(t1)
|
||||
|
||||
for(i <- 0 until T.size)
|
||||
for(j <- i+1 until T.size)
|
||||
T(i).markNeighbor(T(j))
|
||||
|
||||
}
|
||||
|
||||
// Marc Vigo Anglada's triangulate pseudo-polygon algo
|
||||
// See "An improved incremental algorithm for constructing restricted Delaunay triangulations"
|
||||
private def triangulate(P: Array[Point], ab: List[Point], T: ArrayBuffer[Triangle]) {
|
||||
|
||||
val a = ab(0)
|
||||
val b = ab(1)
|
||||
var i = 0
|
||||
|
||||
if(P.size > 1) {
|
||||
var c = P(0)
|
||||
for(j <- 1 until P.size) {
|
||||
if(illegal(a, b, c, P(j))) {
|
||||
c = P(j)
|
||||
i = j
|
||||
}
|
||||
}
|
||||
val PE = P.slice(0, i)
|
||||
val PD = P.slice(i+1, P.size)
|
||||
triangulate(PE, List(a, c), T)
|
||||
triangulate(PD, List(c, b), T)
|
||||
}
|
||||
|
||||
if(!P.isEmpty) {
|
||||
val ccw = Util.orient2d(a, P(i), b) > 0
|
||||
val points = if(ccw) Array(a, P(i), b) else Array(a, b, P(i))
|
||||
T += new Triangle(points)
|
||||
T.last.finalized = true
|
||||
mesh.map += T.last
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Scan left and right along AFront to fill holes
|
||||
private def scanAFront(n: Node) = {
|
||||
|
||||
var node1 = n.next
|
||||
// Update right
|
||||
if(node1.next != null) {
|
||||
var angle = 0.0
|
||||
do {
|
||||
angle = fill(node1)
|
||||
node1 = node1.next
|
||||
} while(angle <= PI_2 && angle >= -PI_2 && node1.next != null)
|
||||
}
|
||||
|
||||
var node2 = n.prev
|
||||
// Update left
|
||||
if(node2.prev != null) {
|
||||
var angle = 0.0
|
||||
do {
|
||||
angle = fill(node2)
|
||||
node2 = node2.prev
|
||||
} while(angle <= PI_2 && angle >= -PI_2 && node2.prev != null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fill empty space with a triangle
|
||||
private def fill(node: Node): Double = {
|
||||
|
||||
val a = (node.prev.point - node.point)
|
||||
val b = (node.next.point - node.point)
|
||||
val angle = Math.atan2(a cross b, a dot b)
|
||||
// Is the angle acute?
|
||||
if(angle <= PI_2 && angle >= -PI_2) {
|
||||
val points = Array(node.prev.point, node.point, node.next.point)
|
||||
val triangle = new Triangle(points)
|
||||
// Update neighbor pointers
|
||||
node.prev.triangle.markNeighbor(triangle)
|
||||
node.triangle.markNeighbor(triangle)
|
||||
mesh.map += triangle
|
||||
aFront -= (node.prev, node, triangle)
|
||||
}
|
||||
angle
|
||||
}
|
||||
|
||||
// Circumcircle test.
|
||||
// Determines if point d lies inside triangle abc's circumcircle
|
||||
private def illegal(a: Point, b: Point, c: Point, d: Point): Boolean = {
|
||||
val ccw = Util.orient2d(a, b, c) > 0
|
||||
// Make sure abc is oriented counter-clockwise
|
||||
if(ccw)
|
||||
Util.incircle(a, b, c, d)
|
||||
else
|
||||
Util.incircle(a, c, b, d)
|
||||
}
|
||||
|
||||
// Ensure adjacent triangles are legal
|
||||
// If illegal, flip edges and update triangle's pointers
|
||||
private def legalization(t1: Triangle, node: Node): Node = {
|
||||
|
||||
val t2 = node.triangle
|
||||
|
||||
val point = t1.points(0)
|
||||
val oPoint = t2 oppositePoint t1
|
||||
|
||||
// Pints are oriented ccw
|
||||
val illegal = Util.incircle(t1.points(1), t1.points(2), t1.points(0), oPoint)
|
||||
|
||||
if(illegal && !t2.finalized) {
|
||||
|
||||
// Flip edge and rotate everything clockwise
|
||||
|
||||
// Legalize points
|
||||
t1.legalize(oPoint)
|
||||
t2.legalize(oPoint, point)
|
||||
|
||||
// Update neighbors
|
||||
|
||||
// Copy old neighbors
|
||||
val neighbors = List(t2.neighbors(0), t2.neighbors(1), t2.neighbors(2))
|
||||
// Clear old neighbors
|
||||
t2.clearNeighbors
|
||||
// Update new neighbors
|
||||
for(n <- neighbors) {
|
||||
if(n != null) {
|
||||
t1.markNeighbor(n)
|
||||
t2.markNeighbor(n)
|
||||
}
|
||||
}
|
||||
t2.markNeighbor(t1)
|
||||
|
||||
// Don't legalize these triangles again
|
||||
t2.finalized = true
|
||||
t1.finalized = true
|
||||
|
||||
// Update advancing front
|
||||
aFront.insertLegalized(t1.points(1), t1, node)
|
||||
|
||||
} else {
|
||||
|
||||
// Update neighbor
|
||||
t2.markNeighbor(t1)
|
||||
// Update advancing front
|
||||
aFront.insert(point, t1, node)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// The triangle mesh
|
||||
private val mesh = new Mesh
|
||||
// Advancing front
|
||||
private var aFront: AFront = null
|
||||
// Sorted point list
|
||||
private var points = polyLine.toList
|
||||
// Half Pi
|
||||
private val PI_2 = Math.Pi/2
|
||||
// Inital triangle factor
|
||||
private val ALPHA = 0.3f
|
||||
// Triangle used to clean interior
|
||||
private var cleanTri: Triangle = null
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.cdt
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.{Point, Triangle}
|
||||
|
||||
class Mesh {
|
||||
|
||||
// Triangles that constitute the mesh
|
||||
val map = new ArrayBuffer[Triangle]
|
||||
// Debug triangles
|
||||
val debug = new ArrayBuffer[Triangle]
|
||||
val triangles = new ArrayBuffer[Triangle]
|
||||
|
||||
// Recursively collect interior triangles and clean the mesh
|
||||
// Excludes exterior triangles outside constrained edges
|
||||
// TODO: Investigate depth first search as an alternative
|
||||
def clean(triangle: Triangle) {
|
||||
|
||||
if(triangle != null && !triangle.interior) {
|
||||
triangle.interior = true
|
||||
triangles += triangle
|
||||
for(i <- 0 until 3) {
|
||||
if(!triangle.edges(i))
|
||||
clean(triangle.neighbors(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,373 +0,0 @@
|
||||
/**
|
||||
* Ported from jBox2D. Original author: ewjordan
|
||||
* Triangulates a polygon using simple ear-clipping algorithm. Returns
|
||||
* size of Triangle array unless the polygon can't be triangulated.
|
||||
* This should only happen if the polygon self-intersects,
|
||||
* though it will not _always_ return null for a bad polygon - it is the
|
||||
* caller's responsibility to check for self-intersection, and if it
|
||||
* doesn't, it should at least check that the return value is non-null
|
||||
* before using. You're warned!
|
||||
*
|
||||
* Triangles may be degenerate, especially if you have identical points
|
||||
* in the input to the algorithm. Check this before you use them.
|
||||
*
|
||||
* This is totally unoptimized, so for large polygons it should not be part
|
||||
* of the simulation loop.
|
||||
*
|
||||
* Returns:
|
||||
* -1 if algorithm fails (self-intersection most likely)
|
||||
* 0 if there are not enough vertices to triangulate anything.
|
||||
* Number of triangles if triangulation was successful.
|
||||
*
|
||||
* results will be filled with results - ear clipping always creates vNum - 2
|
||||
* or fewer (due to pinch point polygon snipping), so allocate an array of
|
||||
* this size.
|
||||
*/
|
||||
package org.poly2tri.earClip
|
||||
|
||||
import shapes.Point
|
||||
import utils.Util
|
||||
|
||||
class EarClip {
|
||||
|
||||
val tol = .001f
|
||||
var hasPinchPoint = false
|
||||
var pinchIndexA = -1
|
||||
var pinchIndexB = -1
|
||||
var pin: Poly = null
|
||||
|
||||
var numTriangles = 0
|
||||
|
||||
def triangulatePolygon(xv: Array[Float], yv: Array[Float], vn: Int, results: Array[Triangle]): Int = {
|
||||
|
||||
if (vn < 3) return 0
|
||||
var vNum = vn
|
||||
|
||||
//Recurse and split on pinch points
|
||||
val pA = new Poly
|
||||
val pB = new Poly
|
||||
pin = new Poly(xv, yv, vNum)
|
||||
if (resolvePinchPoint(pin,pA,pB)){
|
||||
val mergeA = new Array[Triangle](pA.nVertices)
|
||||
val mergeB = new Array[Triangle](pB.nVertices)
|
||||
for (i <- 0 until pA.nVertices) {
|
||||
mergeA(i) = new Triangle();
|
||||
}
|
||||
for (i <- 0 until pB.nVertices) {
|
||||
mergeB(i) = new Triangle();
|
||||
}
|
||||
val nA = triangulatePolygon(pA.x,pA.y,pA.nVertices,mergeA)
|
||||
val nB = triangulatePolygon(pB.x,pB.y,pB.nVertices,mergeB)
|
||||
if (nA == -1 || nB == -1){
|
||||
numTriangles = -1
|
||||
return numTriangles
|
||||
}
|
||||
for (i <- 0 until nA){
|
||||
results(i).set(mergeA(i));
|
||||
}
|
||||
for (i <- 0 until nB){
|
||||
results(nA+i).set(mergeB(i));
|
||||
}
|
||||
numTriangles = (nA+nB)
|
||||
return numTriangles
|
||||
}
|
||||
|
||||
val buffer = new Array[Triangle](vNum-2);
|
||||
for (i <- 0 until buffer.size) {
|
||||
buffer(i) = new Triangle();
|
||||
}
|
||||
var bufferSize = 0;
|
||||
var xrem = new Array[Float](vNum)
|
||||
var yrem = new Array[Float](vNum)
|
||||
for (i <- 0 until vNum) {
|
||||
xrem(i) = xv(i);
|
||||
yrem(i) = yv(i);
|
||||
}
|
||||
|
||||
val xremLength = vNum;
|
||||
|
||||
while (vNum > 3) {
|
||||
//System.out.println("vNum: "+vNum);
|
||||
// Find an ear
|
||||
var earIndex = -1;
|
||||
var earMaxMinCross = -1000.0f;
|
||||
for (i <- 0 until vNum) {
|
||||
if (isEar(i, xrem, yrem, vNum)) {
|
||||
val lower = remainder(i-1,vNum);
|
||||
val upper = remainder(i+1,vNum);
|
||||
var d1 = Point(xrem(upper)-xrem(i),yrem(upper)-yrem(i));
|
||||
var d2 = Point(xrem(i)-xrem(lower),yrem(i)-yrem(lower));
|
||||
var d3 = Point(xrem(lower)-xrem(upper),yrem(lower)-yrem(upper));
|
||||
|
||||
d1 = d1.normalize
|
||||
d2 = d2.normalize
|
||||
d3 = d3.normalize
|
||||
val cross12 = Math.abs( d1 cross d2 )
|
||||
val cross23 = Math.abs( d2 cross d3 )
|
||||
val cross31 = Math.abs( d3 cross d1 )
|
||||
//Find the maximum minimum angle
|
||||
val minCross = Math.min(cross12, Math.min(cross23,cross31))
|
||||
if (minCross > earMaxMinCross){
|
||||
earIndex = i
|
||||
earMaxMinCross = minCross
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't found an ear, we're screwed.
|
||||
// Note: sometimes this is happening because the
|
||||
// remaining points are collinear. Really these
|
||||
// should just be thrown out without halting triangulation.
|
||||
if (earIndex == -1){
|
||||
|
||||
System.out.println("Couldn't find an ear, dumping remaining poly:\n");
|
||||
System.out.println("Please submit this dump to ewjordan at Box2d forums\n");
|
||||
|
||||
assert(false)
|
||||
|
||||
for (i <- 0 until bufferSize) {
|
||||
results(i).set(buffer(i));
|
||||
}
|
||||
|
||||
if (bufferSize > 0) return bufferSize;
|
||||
else {
|
||||
numTriangles = -1
|
||||
return numTriangles
|
||||
}
|
||||
}
|
||||
|
||||
// Clip off the ear:
|
||||
// - remove the ear tip from the list
|
||||
|
||||
vNum -= 1;
|
||||
val newx = new Array[Float](vNum)
|
||||
val newy = new Array[Float](vNum)
|
||||
var currDest = 0;
|
||||
for (i <- 0 until vNum) {
|
||||
if (currDest == earIndex) currDest += 1
|
||||
newx(i) = xrem(currDest);
|
||||
newy(i) = yrem(currDest);
|
||||
currDest += 1;
|
||||
}
|
||||
|
||||
// - add the clipped triangle to the triangle list
|
||||
val under = if(earIndex == 0) (vNum) else (earIndex - 1)
|
||||
val over = if(earIndex == vNum) 0 else (earIndex + 1)
|
||||
val toAdd = new Triangle(xrem(earIndex), yrem(earIndex), xrem(over), yrem(over), xrem(under), yrem(under));
|
||||
buffer(bufferSize) = toAdd
|
||||
bufferSize += 1;
|
||||
|
||||
// - replace the old list with the new one
|
||||
xrem = newx;
|
||||
yrem = newy;
|
||||
}
|
||||
|
||||
val toAdd = new Triangle(xrem(1), yrem(1), xrem(2), yrem(2), xrem(0), yrem(0))
|
||||
buffer(bufferSize) = toAdd;
|
||||
bufferSize += 1;
|
||||
|
||||
assert(bufferSize == xremLength-2)
|
||||
|
||||
for (i <- 0 until bufferSize) {
|
||||
results(i).set(buffer(i))
|
||||
}
|
||||
numTriangles = bufferSize
|
||||
return numTriangles
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and fixes "pinch points," points where two polygon
|
||||
* vertices are at the same point.
|
||||
*
|
||||
* If a pinch point is found, pin is broken up into poutA and poutB
|
||||
* and true is returned; otherwise, returns false.
|
||||
*
|
||||
* Mostly for internal use.
|
||||
*
|
||||
* O(N^2) time, which sucks...
|
||||
*/
|
||||
private def resolvePinchPoint(pin: Poly, poutA: Poly, poutB: Poly): Boolean = {
|
||||
|
||||
if (pin.nVertices < 3) return false
|
||||
hasPinchPoint = false
|
||||
pinchIndexA = -1
|
||||
pinchIndexB = -1
|
||||
pinchIndex
|
||||
if (hasPinchPoint){
|
||||
val sizeA = pinchIndexB - pinchIndexA;
|
||||
if (sizeA == pin.nVertices) return false;
|
||||
val xA = new Array[Float](sizeA);
|
||||
val yA = new Array[Float](sizeA);
|
||||
for (i <- 0 until sizeA){
|
||||
val ind = remainder(pinchIndexA+i,pin.nVertices);
|
||||
xA(i) = pin.x(ind);
|
||||
yA(i) = pin.y(ind);
|
||||
}
|
||||
val tempA = new Poly(xA,yA,sizeA);
|
||||
poutA.set(tempA);
|
||||
|
||||
val sizeB = pin.nVertices - sizeA;
|
||||
val xB = new Array[Float](sizeB);
|
||||
val yB = new Array[Float](sizeB);
|
||||
for (i <- 0 until sizeB){
|
||||
val ind = remainder(pinchIndexB+i,pin.nVertices);
|
||||
xB(i) = pin.x(ind);
|
||||
yB(i) = pin.y(ind);
|
||||
}
|
||||
val tempB = new Poly(xB,yB,sizeB);
|
||||
poutB.set(tempB);
|
||||
}
|
||||
return hasPinchPoint;
|
||||
}
|
||||
|
||||
//Fix for obnoxious behavior for the % operator for negative numbers...
|
||||
private def remainder(x: Int, modulus: Int): Int = {
|
||||
var rem = x % modulus
|
||||
while (rem < 0){
|
||||
rem += modulus
|
||||
}
|
||||
return rem
|
||||
}
|
||||
|
||||
def pinchIndex: Boolean = {
|
||||
for (i <- 0 until pin.nVertices) {
|
||||
if(!hasPinchPoint) {
|
||||
for (j <- i+1 until pin.nVertices){
|
||||
//Don't worry about pinch points where the points
|
||||
//are actually just dupe neighbors
|
||||
if (Math.abs(pin.x(i)-pin.x(j))<tol&&Math.abs(pin.y(i)-pin.y(j))<tol&&j!=i+1){
|
||||
pinchIndexA = i
|
||||
pinchIndexB = j
|
||||
hasPinchPoint = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if vertex i is the tip of an ear in polygon defined by xv[] and
|
||||
* yv[].
|
||||
*
|
||||
* Assumes clockwise orientation of polygon...ick
|
||||
*/
|
||||
private def isEar(i: Int , xv: Array[Float], yv: Array[Float], xvLength: Int): Boolean = {
|
||||
|
||||
var dx0, dy0, dx1, dy1 = 0f
|
||||
if (i >= xvLength || i < 0 || xvLength < 3) {
|
||||
return false;
|
||||
}
|
||||
var upper = i + 1
|
||||
var lower = i - 1
|
||||
if (i == 0) {
|
||||
dx0 = xv(0) - xv(xvLength - 1)
|
||||
dy0 = yv(0) - yv(xvLength - 1)
|
||||
dx1 = xv(1) - xv(0)
|
||||
dy1 = yv(1) - yv(0)
|
||||
lower = xvLength - 1
|
||||
}
|
||||
else if (i == xvLength - 1) {
|
||||
dx0 = xv(i) - xv(i - 1);
|
||||
dy0 = yv(i) - yv(i - 1);
|
||||
dx1 = xv(0) - xv(i);
|
||||
dy1 = yv(0) - yv(i);
|
||||
upper = 0;
|
||||
}
|
||||
else {
|
||||
dx0 = xv(i) - xv(i - 1);
|
||||
dy0 = yv(i) - yv(i - 1);
|
||||
dx1 = xv(i + 1) - xv(i);
|
||||
dy1 = yv(i + 1) - yv(i);
|
||||
}
|
||||
val cross = dx0 * dy1 - dx1 * dy0;
|
||||
if (cross > 0)
|
||||
return false;
|
||||
val myTri = new Triangle(xv(i), yv(i), xv(upper), yv(upper), xv(lower), yv(lower))
|
||||
for (j <- 0 until xvLength) {
|
||||
if (!(j == i || j == lower || j == upper)) {
|
||||
if (myTri.containsPoint(xv(j), yv(j)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Poly(var x: Array[Float], var y: Array[Float], var nVertices: Int) {
|
||||
|
||||
var areaIsSet = false
|
||||
var area = 0f
|
||||
|
||||
def this(_x: Array[Float], _y: Array[Float]) = this(_x,_y,_x.size)
|
||||
def this() = this(null, null, 0)
|
||||
|
||||
def set(p: Poly ) {
|
||||
if (nVertices != p.nVertices){
|
||||
nVertices = p.nVertices;
|
||||
x = new Array[Float](nVertices)
|
||||
y = new Array[Float](nVertices)
|
||||
}
|
||||
|
||||
for (i <- 0 until nVertices) {
|
||||
x(i) = p.x(i)
|
||||
y(i) = p.y(i)
|
||||
}
|
||||
areaIsSet = false
|
||||
}
|
||||
}
|
||||
|
||||
class Triangle(var x1: Float, var y1: Float, var x2: Float, var y2: Float, var x3: Float, var y3: Float) {
|
||||
|
||||
def this() = this(0,0,0,0,0,0)
|
||||
|
||||
val x = new Array[Float](3)
|
||||
val y = new Array[Float](3)
|
||||
|
||||
// Automatically fixes orientation to ccw
|
||||
|
||||
val dx1 = x2-x1
|
||||
val dx2 = x3-x1
|
||||
val dy1 = y2-y1
|
||||
val dy2 = y3-y1
|
||||
val cross = dx1*dy2-dx2*dy1
|
||||
val ccw = (cross>0)
|
||||
if (ccw){
|
||||
x(0) = x1; x(1) = x2; x(2) = x3;
|
||||
y(0) = y1; y(1) = y2; y(2) = y3;
|
||||
} else{
|
||||
x(0) = x1; x(1) = x3; x(2) = x2;
|
||||
y(0) = y1; y(1) = y3; y(2) = y2;
|
||||
}
|
||||
|
||||
def set(t: Triangle) {
|
||||
x(0) = t.x(0)
|
||||
x(1) = t.x(1)
|
||||
x(2) = t.x(2)
|
||||
y(0) = t.y(0)
|
||||
y(1) = t.y(1)
|
||||
y(2) = t.y(2)
|
||||
}
|
||||
|
||||
|
||||
def containsPoint(_x: Float, _y: Float): Boolean = {
|
||||
|
||||
val vx2 = _x-x(0); val vy2 = _y-y(0);
|
||||
val vx1 = x(1)-x(0); val vy1 = y(1)-y(0);
|
||||
val vx0 = x(2)-x(0); val vy0 = y(2)-y(0);
|
||||
|
||||
val dot00 = vx0*vx0+vy0*vy0;
|
||||
val dot01 = vx0*vx1+vy0*vy1;
|
||||
val dot02 = vx0*vx2+vy0*vy2;
|
||||
val dot11 = vx1*vx1+vy1*vy1;
|
||||
val dot12 = vx1*vx2+vy1*vy2;
|
||||
val invDenom = 1.0f / (dot00*dot11 - dot01*dot01);
|
||||
val u = (dot11*dot02 - dot01*dot12)*invDenom;
|
||||
val v = (dot00*dot12 - dot01*dot02)*invDenom;
|
||||
|
||||
return ((u>=0)&&(v>=0)&&(u+v<=1));
|
||||
}
|
||||
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import scala.collection.mutable.{ArrayBuffer, Queue}
|
||||
|
||||
import shapes.Point
|
||||
|
||||
// Doubly linked list
|
||||
class MonotoneMountain {
|
||||
|
||||
var tail, head: Point = null
|
||||
var size = 0
|
||||
|
||||
private val convexPoints = new ArrayBuffer[Point]
|
||||
// Monotone mountain points
|
||||
val monoPoly = new ArrayBuffer[Point]
|
||||
// Triangles that constitute the mountain
|
||||
val triangles = new ArrayBuffer[Array[Point]]
|
||||
// Convex polygons that constitute the mountain
|
||||
val convexPolies = new ArrayBuffer[Array[Point]]
|
||||
// Used to track which side of the line we are on
|
||||
private var positive = false
|
||||
// Almost Pi!
|
||||
private val PI_SLOP = 3.1
|
||||
|
||||
// Append a point to the list
|
||||
def +=(point: Point) {
|
||||
size match {
|
||||
case 0 =>
|
||||
head = point
|
||||
size += 1
|
||||
case 1 =>
|
||||
// Keep repeat points out of the list
|
||||
if(point ! head) {
|
||||
tail = point
|
||||
tail.prev = head
|
||||
head.next = tail
|
||||
size += 1
|
||||
}
|
||||
case _ =>
|
||||
// Keep repeat points out of the list
|
||||
if(point ! tail) {
|
||||
tail.next = point
|
||||
point.prev = tail
|
||||
tail = point
|
||||
size += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a point from the list
|
||||
def remove(point: Point) {
|
||||
val next = point.next
|
||||
val prev = point.prev
|
||||
point.prev.next = next
|
||||
point.next.prev = prev
|
||||
size -= 1
|
||||
}
|
||||
|
||||
// Partition a x-monotone mountain into triangles O(n)
|
||||
// See "Computational Geometry in C", 2nd edition, by Joseph O'Rourke, page 52
|
||||
def process {
|
||||
|
||||
// Establish the proper sign
|
||||
positive = angleSign
|
||||
// create monotone polygon - for dubug purposes
|
||||
genMonoPoly
|
||||
|
||||
// Initialize internal angles at each nonbase vertex
|
||||
// Link strictly convex vertices into a list, ignore reflex vertices
|
||||
var p = head.next
|
||||
while(p != tail) {
|
||||
val a = angle(p)
|
||||
// If the point is almost colinear with it's neighbor, remove it!
|
||||
if(a >= PI_SLOP || a <= -PI_SLOP || a == 0.0)
|
||||
remove(p)
|
||||
else if(convex(p))
|
||||
convexPoints += p
|
||||
p = p.next
|
||||
}
|
||||
|
||||
triangulate
|
||||
|
||||
}
|
||||
|
||||
private def triangulate {
|
||||
|
||||
while(!convexPoints.isEmpty) {
|
||||
|
||||
val ear = convexPoints.remove(0)
|
||||
val a = ear.prev
|
||||
val b = ear
|
||||
val c = ear.next
|
||||
val triangle = Array(a, b, c)
|
||||
|
||||
triangles += triangle
|
||||
|
||||
// Remove ear, update angles and convex list
|
||||
remove(ear)
|
||||
if(valid(a)) convexPoints += a
|
||||
if(valid(c)) convexPoints += c
|
||||
}
|
||||
assert(size <= 3, "Triangulation bug, please report")
|
||||
|
||||
}
|
||||
|
||||
private def valid(p: Point) = (p != head && p != tail && convex(p))
|
||||
|
||||
// Create the monotone polygon
|
||||
private def genMonoPoly {
|
||||
var p = head
|
||||
while(p != null) {
|
||||
monoPoly += p
|
||||
p = p.next
|
||||
}
|
||||
}
|
||||
|
||||
private def angle(p: Point) = {
|
||||
val a = (p.next - p)
|
||||
val b = (p.prev - p)
|
||||
Math.atan2(a cross b, a dot b)
|
||||
}
|
||||
|
||||
private def angleSign = {
|
||||
val a = (head.next - head)
|
||||
val b = (tail - head)
|
||||
(Math.atan2(a cross b, a dot b) >= 0)
|
||||
}
|
||||
|
||||
// Determines if the inslide angle is convex or reflex
|
||||
private def convex(p: Point) = {
|
||||
if(positive != (angle(p) >= 0)) false
|
||||
else true
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.Segment
|
||||
|
||||
// Node for a Directed Acyclic graph (DAG)
|
||||
abstract class Node(var left: Node, var right: Node) {
|
||||
|
||||
if(left != null) left.parentList += this
|
||||
if(right != null) right.parentList += this
|
||||
|
||||
var parentList = new ArrayBuffer[Node]
|
||||
|
||||
def locate(s: Segment): Sink
|
||||
|
||||
// Replace a node in the graph with this node
|
||||
// Make sure parent pointers are updated
|
||||
def replace(node: Node) {
|
||||
for(parent <- node.parentList) {
|
||||
// Select the correct node to replace (left or right child)
|
||||
if(parent.left == node) parent.left = this
|
||||
else parent.right = this
|
||||
parentList += parent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.{Segment, Trapezoid}
|
||||
|
||||
// Directed Acyclic graph (DAG)
|
||||
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
|
||||
|
||||
class QueryGraph(var head: Node) {
|
||||
|
||||
def locate(s: Segment) = head.locate(s).trapezoid
|
||||
|
||||
def followSegment(s: Segment) = {
|
||||
|
||||
val trapezoids = new ArrayBuffer[Trapezoid]
|
||||
trapezoids += locate(s)
|
||||
var j = 0
|
||||
while(s.q.x > trapezoids(j).rightPoint.x) {
|
||||
if(s > trapezoids(j).rightPoint) {
|
||||
trapezoids += trapezoids(j).upperRight
|
||||
} else {
|
||||
trapezoids += trapezoids(j).lowerRight
|
||||
}
|
||||
j += 1
|
||||
}
|
||||
trapezoids
|
||||
}
|
||||
|
||||
def replace(sink: Sink, node: Node) {
|
||||
if(sink.parentList.size == 0) {
|
||||
head = node
|
||||
} else {
|
||||
node replace sink
|
||||
}
|
||||
}
|
||||
|
||||
def case1(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
|
||||
val qNode = new XNode(s.q, yNode, Sink.init(tList(3)))
|
||||
val pNode = new XNode(s.p, Sink.init(tList(0)), qNode)
|
||||
replace(sink, pNode)
|
||||
}
|
||||
|
||||
def case2(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(1)), Sink.init(tList(2)))
|
||||
val pNode = new XNode(s.p, Sink.init(tList(0)), yNode)
|
||||
replace(sink, pNode)
|
||||
}
|
||||
|
||||
def case3(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
|
||||
replace(sink, yNode)
|
||||
}
|
||||
|
||||
def case4(sink: Sink, s: Segment, tList: Array[Trapezoid]) {
|
||||
val yNode = new YNode(s, Sink.init(tList(0)), Sink.init(tList(1)))
|
||||
val qNode = new XNode(s.q, yNode, Sink.init(tList(2)))
|
||||
replace(sink, qNode)
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import shapes.{Segment, Trapezoid}
|
||||
|
||||
object Sink {
|
||||
|
||||
def init(trapezoid: Trapezoid) = {
|
||||
if(trapezoid.sink != null)
|
||||
trapezoid.sink
|
||||
else
|
||||
new Sink(trapezoid)
|
||||
}
|
||||
}
|
||||
|
||||
class Sink(val trapezoid: Trapezoid) extends Node(null, null) {
|
||||
|
||||
trapezoid.sink = this
|
||||
override def locate(s: Segment): Sink = this
|
||||
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import scala.collection.mutable.{HashSet, ArrayBuffer}
|
||||
|
||||
import shapes.{Point, Segment, Trapezoid}
|
||||
|
||||
// See "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
|
||||
|
||||
class TrapezoidalMap {
|
||||
|
||||
// Trapezoid container
|
||||
val map = HashSet.empty[Trapezoid]
|
||||
// AABB margin
|
||||
var margin = 50f
|
||||
|
||||
// Bottom segment that spans multiple trapezoids
|
||||
private var bCross: Segment = null
|
||||
// Top segment that spans multiple trapezoids
|
||||
private var tCross: Segment = null
|
||||
|
||||
def clear {
|
||||
bCross = null
|
||||
tCross = null
|
||||
}
|
||||
|
||||
// Case 1: segment completely enclosed by trapezoid
|
||||
// break trapezoid into 4 smaller trapezoids
|
||||
def case1(t: Trapezoid, s: Segment) = {
|
||||
|
||||
val trapezoids = new Array[Trapezoid](4)
|
||||
trapezoids(0) = new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
|
||||
trapezoids(1) = new Trapezoid(s.p, s.q, t.top, s)
|
||||
trapezoids(2) = new Trapezoid(s.p, s.q, s, t.bottom)
|
||||
trapezoids(3) = new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
|
||||
|
||||
trapezoids(0).updateLeft(t.upperLeft, t.lowerLeft)
|
||||
trapezoids(1).updateLeftRight(trapezoids(0), null, trapezoids(3), null)
|
||||
trapezoids(2).updateLeftRight(null, trapezoids(0), null, trapezoids(3))
|
||||
trapezoids(3).updateRight(t.upperRight, t.lowerRight)
|
||||
|
||||
trapezoids
|
||||
}
|
||||
|
||||
// Case 2: Trapezoid contains point p, q lies outside
|
||||
// break trapezoid into 3 smaller trapezoids
|
||||
def case2(t: Trapezoid, s: Segment) = {
|
||||
|
||||
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
|
||||
|
||||
val trapezoids = new Array[Trapezoid](3)
|
||||
trapezoids(0) = new Trapezoid(t.leftPoint, s.p, t.top, t.bottom)
|
||||
trapezoids(1) = new Trapezoid(s.p, rp, t.top, s)
|
||||
trapezoids(2) = new Trapezoid(s.p, rp, s, t.bottom)
|
||||
|
||||
trapezoids(0).updateLeft(t.upperLeft, t.lowerLeft)
|
||||
trapezoids(1).updateLeftRight(trapezoids(0), null, t.upperRight, null)
|
||||
trapezoids(2).updateLeftRight(null, trapezoids(0), null, t.lowerRight)
|
||||
|
||||
bCross = t.bottom
|
||||
tCross = t.top
|
||||
|
||||
s.above = trapezoids(1)
|
||||
s.below = trapezoids(2)
|
||||
|
||||
trapezoids
|
||||
}
|
||||
|
||||
// Case 3: Trapezoid is bisected
|
||||
def case3(t: Trapezoid, s: Segment) = {
|
||||
|
||||
val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint
|
||||
val rp = if(s.q.x == t.rightPoint.x) s.q else t.rightPoint
|
||||
|
||||
val trapezoids = new Array[Trapezoid](2)
|
||||
|
||||
if(tCross == t.top) {
|
||||
trapezoids(0) = t.upperLeft
|
||||
trapezoids(0).updateRight(t.upperRight, null)
|
||||
trapezoids(0).rightPoint = rp
|
||||
} else {
|
||||
trapezoids(0) = new Trapezoid(lp, rp, t.top, s)
|
||||
trapezoids(0).updateLeftRight(t.upperLeft, s.above, t.upperRight, null)
|
||||
}
|
||||
|
||||
if(bCross == t.bottom) {
|
||||
trapezoids(1) = t.lowerLeft
|
||||
trapezoids(1).updateRight(null, t.lowerRight)
|
||||
trapezoids(1).rightPoint = rp
|
||||
} else {
|
||||
trapezoids(1) = new Trapezoid(lp, rp, s, t.bottom)
|
||||
trapezoids(1).updateLeftRight(s.below, t.lowerLeft, null, t.lowerRight)
|
||||
}
|
||||
|
||||
bCross = t.bottom
|
||||
tCross = t.top
|
||||
|
||||
s.above = trapezoids(0)
|
||||
s.below = trapezoids(1)
|
||||
|
||||
trapezoids
|
||||
}
|
||||
|
||||
// Case 4: Trapezoid contains point q, p lies outside
|
||||
// break trapezoid into 3 smaller trapezoids
|
||||
def case4(t: Trapezoid, s: Segment) = {
|
||||
|
||||
val lp = if(s.p.x == t.leftPoint.x) s.p else t.leftPoint
|
||||
|
||||
val trapezoids = new Array[Trapezoid](3)
|
||||
|
||||
if(tCross == t.top) {
|
||||
trapezoids(0) = t.upperLeft
|
||||
trapezoids(0).rightPoint = s.q
|
||||
} else {
|
||||
trapezoids(0) = new Trapezoid(lp, s.q, t.top, s)
|
||||
trapezoids(0).updateLeft(t.upperLeft, s.above)
|
||||
}
|
||||
|
||||
if(bCross == t.bottom) {
|
||||
trapezoids(1) = t.lowerLeft
|
||||
trapezoids(1).rightPoint = s.q
|
||||
} else {
|
||||
trapezoids(1) = new Trapezoid(lp, s.q, s, t.bottom)
|
||||
trapezoids(1).updateLeft(s.below, t.lowerLeft)
|
||||
}
|
||||
|
||||
trapezoids(2) = new Trapezoid(s.q, t.rightPoint, t.top, t.bottom)
|
||||
trapezoids(2).updateLeftRight(trapezoids(0), trapezoids(1), t.upperRight, t.lowerRight)
|
||||
|
||||
trapezoids
|
||||
}
|
||||
|
||||
// Create an AABB around segments
|
||||
def boundingBox(segments: ArrayBuffer[Segment]): Trapezoid = {
|
||||
|
||||
var max = segments(0).p + margin
|
||||
var min = segments(0).q - margin
|
||||
|
||||
for(s <- segments) {
|
||||
if(s.p.x > max.x) max = Point(s.p.x + margin, max.y)
|
||||
if(s.p.y > max.y) max = Point(max.x, s.p.y + margin)
|
||||
if(s.q.x > max.x) max = Point(s.q.x+margin, max.y)
|
||||
if(s.q.y > max.y) max = Point(max.x, s.q.y+margin)
|
||||
if(s.p.x < min.x) min = Point(s.p.x-margin, min.y)
|
||||
if(s.p.y < min.y) min = Point(min.x, s.p.y-margin)
|
||||
if(s.q.x < min.x) min = Point(s.q.x-margin, min.y)
|
||||
if(s.q.y < min.y) min = Point(min.x, s.q.y-margin)
|
||||
}
|
||||
|
||||
val top = new Segment(Point(min.x, max.y), Point(max.x, max.y))
|
||||
val bottom = new Segment(Point(min.x, min.y), Point(max.x, min.y))
|
||||
val left = bottom.p
|
||||
val right = top.q
|
||||
|
||||
return new Trapezoid(left, right, top, bottom)
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import utils.{Util, Random}
|
||||
import shapes.{Point, Segment, Trapezoid}
|
||||
|
||||
// Based on Raimund Seidel's paper "A simple and fast incremental randomized
|
||||
// algorithm for computing trapezoidal decompositions and for triangulating polygons"
|
||||
// See also: "Computational Geometry", 3rd edition, by Mark de Berg et al, Chapter 6.2
|
||||
// "Computational Geometry in C", 2nd edition, by Joseph O'Rourke
|
||||
class Triangulator(points: ArrayBuffer[Point]) {
|
||||
|
||||
// Convex polygon list
|
||||
var polygons = new ArrayBuffer[Array[Point]]
|
||||
// Order and randomize the segments
|
||||
val segmentList = initSegments
|
||||
|
||||
// The trapezoidal map
|
||||
def trapezoidMap = trapezoidalMap.map
|
||||
// Trapezoid decomposition list
|
||||
var trapezoids = new ArrayBuffer[Trapezoid]
|
||||
|
||||
// Initialize trapezoidal map and query structure
|
||||
private val trapezoidalMap = new TrapezoidalMap
|
||||
private val boundingBox = trapezoidalMap.boundingBox(segmentList)
|
||||
private val queryGraph = new QueryGraph(Sink.init(boundingBox))
|
||||
private val xMonoPoly = new ArrayBuffer[MonotoneMountain]
|
||||
|
||||
process
|
||||
|
||||
// Build the trapezoidal map and query graph
|
||||
private def process {
|
||||
|
||||
var i = 0
|
||||
while(i < segmentList.size) {
|
||||
|
||||
val s = segmentList(i)
|
||||
var traps = queryGraph.followSegment(s)
|
||||
|
||||
// Remove trapezoids from trapezoidal Map
|
||||
var j = 0
|
||||
while(j < traps.size) {
|
||||
trapezoidalMap.map -= traps(j)
|
||||
j += 1
|
||||
}
|
||||
|
||||
j = 0
|
||||
while(j < traps.size) {
|
||||
val t = traps(j)
|
||||
var tList: Array[Trapezoid] = null
|
||||
val containsP = t.contains(s.p)
|
||||
val containsQ = t.contains(s.q)
|
||||
if(containsP && containsQ) {
|
||||
// Case 1
|
||||
tList = trapezoidalMap.case1(t,s)
|
||||
queryGraph.case1(t.sink, s, tList)
|
||||
} else if(containsP && !containsQ) {
|
||||
// Case 2
|
||||
tList = trapezoidalMap.case2(t,s)
|
||||
queryGraph.case2(t.sink, s, tList)
|
||||
} else if(!containsP && !containsQ) {
|
||||
// Case 3
|
||||
tList = trapezoidalMap.case3(t, s)
|
||||
queryGraph.case3(t.sink, s, tList)
|
||||
} else {
|
||||
// Case 4
|
||||
tList = trapezoidalMap.case4(t, s)
|
||||
queryGraph.case4(t.sink, s, tList)
|
||||
}
|
||||
|
||||
// Add new trapezoids to map
|
||||
var k = 0
|
||||
while(k < tList.size) {
|
||||
trapezoidalMap.map += tList(k)
|
||||
k += 1
|
||||
}
|
||||
j += 1
|
||||
}
|
||||
|
||||
trapezoidalMap.clear
|
||||
i += 1
|
||||
|
||||
}
|
||||
|
||||
// Mark outside trapezoids
|
||||
for(t <- trapezoidalMap.map)
|
||||
markOutside(t)
|
||||
|
||||
// Collect interior trapezoids
|
||||
for(t <- trapezoidalMap.map)
|
||||
if(t.inside) {
|
||||
trapezoids += t
|
||||
t addPoints
|
||||
}
|
||||
|
||||
// Generate the triangles
|
||||
createMountains
|
||||
|
||||
//println("# triangles = " + triangles.size)
|
||||
}
|
||||
|
||||
// Monotone polygons - these are monotone mountains
|
||||
def monoPolies: ArrayBuffer[ArrayBuffer[Point]] = {
|
||||
val polies = new ArrayBuffer[ArrayBuffer[Point]]
|
||||
for(i <- 0 until xMonoPoly.size)
|
||||
polies += xMonoPoly(i).monoPoly
|
||||
return polies
|
||||
}
|
||||
|
||||
// Build a list of x-monotone mountains
|
||||
private def createMountains {
|
||||
|
||||
var i = 0
|
||||
while(i < segmentList.size) {
|
||||
|
||||
val s = segmentList(i)
|
||||
|
||||
if(s.mPoints.size > 0) {
|
||||
|
||||
val mountain = new MonotoneMountain
|
||||
var k: List[Point] = null
|
||||
|
||||
// Sorting is a perfromance hit. Literature says this can be accomplised in
|
||||
// linear time, although I don't see a way around using traditional methods
|
||||
// when using a randomized incremental algorithm
|
||||
if(s.mPoints.size < 10)
|
||||
// Insertion sort is one of the fastest algorithms for sorting arrays containing
|
||||
// fewer than ten elements, or for lists that are already mostly sorted.
|
||||
k = Util.insertSort((p1: Point, p2: Point) => p1 < p2)(s.mPoints).toList
|
||||
else
|
||||
k = Util.msort((p1: Point, p2: Point) => p1 < p2)(s.mPoints.toList)
|
||||
|
||||
val points = s.p :: k ::: List(s.q)
|
||||
var j = 0
|
||||
while(j < points.size) {
|
||||
mountain += points(j)
|
||||
j += 1
|
||||
}
|
||||
|
||||
// Triangulate monotone mountain
|
||||
mountain process
|
||||
|
||||
// Extract the triangles into a single list
|
||||
j = 0
|
||||
while(j < mountain.triangles.size) {
|
||||
polygons += mountain.triangles(j)
|
||||
j += 1
|
||||
}
|
||||
|
||||
xMonoPoly += mountain
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the outside trapezoids surrounding the polygon
|
||||
private def markOutside(t: Trapezoid) {
|
||||
if(t.top == boundingBox.top || t.bottom == boundingBox.bottom) {
|
||||
t trimNeighbors
|
||||
}
|
||||
}
|
||||
|
||||
// Create segments and connect end points; update edge event pointer
|
||||
private def initSegments: ArrayBuffer[Segment] = {
|
||||
var segments = List[Segment]()
|
||||
for(i <- 0 until points.size-1)
|
||||
segments = new Segment(points(i), points(i+1)) :: segments
|
||||
segments = new Segment(points.first, points.last) :: segments
|
||||
orderSegments(segments)
|
||||
}
|
||||
|
||||
private def orderSegments(segments: List[Segment]) = {
|
||||
|
||||
// Ignore vertical segments!
|
||||
val segs = new ArrayBuffer[Segment]
|
||||
for(s <- segments) {
|
||||
val p = shearTransform(s.p)
|
||||
val q = shearTransform(s.q)
|
||||
// Point p must be to the left of point q
|
||||
if(p.x > q.x) {
|
||||
segs += new Segment(q, p)
|
||||
} else if(p.x < q.x) {
|
||||
segs += new Segment(p, q)
|
||||
}
|
||||
}
|
||||
// Randomized triangulation improves performance
|
||||
// See Seidel's paper, or O'Rourke's book, p. 57
|
||||
Random.shuffle(segs)
|
||||
segs
|
||||
}
|
||||
|
||||
// Prevents any two distinct endpoints from lying on a common vertical line, and avoiding
|
||||
// the degenerate case. See Mark de Berg et al, Chapter 6.3
|
||||
//val SHEER = 0.0001f
|
||||
def shearTransform(point: Point) = Point(point.x + 0.0001f * point.y, point.y)
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import shapes.{Point, Segment}
|
||||
|
||||
class XNode(point: Point, lChild: Node, rChild: Node) extends Node(lChild, rChild) {
|
||||
|
||||
override def locate(s: Segment): Sink = {
|
||||
|
||||
if(s.p.x >= point.x) {
|
||||
// Move to the right in the graph
|
||||
return right.locate(s)
|
||||
} else {
|
||||
// Move to the left in the graph
|
||||
return left.locate(s)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.seidel
|
||||
|
||||
import shapes.Segment
|
||||
|
||||
class YNode(segment: Segment, lChild: Node, rChild: Node) extends Node(lChild, rChild) {
|
||||
|
||||
override def locate(s: Segment): Sink = {
|
||||
if (segment > s.p) {
|
||||
// Move down the graph
|
||||
return right.locate(s)
|
||||
} else if (segment < s.p) {
|
||||
// Move up the graph
|
||||
return left.locate(s)
|
||||
} else {
|
||||
// s and segment share the same endpoint, p
|
||||
if (s.slope < segment.slope) {
|
||||
// Move down the graph
|
||||
return right.locate(s)
|
||||
} else {
|
||||
// Move up the graph
|
||||
return left.locate(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.shapes
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
case class Point(val x: Float, val y: Float) {
|
||||
|
||||
// Pointers to next and previous points in Monontone Mountain
|
||||
var next, prev: Point = null
|
||||
// The setment this point belongs to
|
||||
var segment: Segment = null
|
||||
// List of edges this point constitutes an upper ending point (CDT)
|
||||
var edges = new ArrayBuffer[Segment]
|
||||
|
||||
@inline def -(p: Point) = Point(x - p.x, y - p.y)
|
||||
@inline def +(p: Point) = Point(x + p.x, y + p.y)
|
||||
@inline def +(f: Float) = Point(x + f, y + f)
|
||||
@inline def -(f: Float) = Point(x - f, y - f)
|
||||
@inline def *(f: Float) = Point(x * f, y * f)
|
||||
@inline def /(a: Float) = Point(x / a, y / a)
|
||||
@inline def cross(p: Point) = x * p.y - y * p.x
|
||||
@inline def dot(p: Point) = x * p.x + y * p.y
|
||||
@inline def length = Math.sqrt(x * x + y * y).toFloat
|
||||
@inline def normalize = this / length
|
||||
// Sort along x axis
|
||||
@inline def <(p: Point) = (x < p.x)
|
||||
|
||||
// Sort along y axis
|
||||
@inline def >(p: Point) = {
|
||||
if(y < p.y)
|
||||
true
|
||||
else if(y > p.y)
|
||||
false
|
||||
else {
|
||||
if(x < p.x)
|
||||
true
|
||||
else
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@inline def !(p: Point) = !(p.x == x && p.y == y)
|
||||
@inline override def clone = Point(x, y)
|
||||
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.shapes
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
// Represents a simple polygon's edge
|
||||
// TODO: Rename this class to Edge?
|
||||
class Segment(var p: Point, var q: Point) {
|
||||
|
||||
// Pointers used for building trapezoidal map
|
||||
var above, below: Trapezoid = null
|
||||
// Montone mountain points
|
||||
val mPoints = new ArrayBuffer[Point]
|
||||
// Equation of a line: y = m*x + b
|
||||
// Slope of the line (m)
|
||||
val slope = (q.y - p.y)/(q.x - p.x)
|
||||
// Y intercept
|
||||
val b = p.y - (p.x * slope)
|
||||
|
||||
// Determines if this segment lies above the given point
|
||||
def > (point: Point) = (Math.floor(point.y) < Math.floor(slope * point.x + b))
|
||||
// Determines if this segment lies below the given point
|
||||
def < (point: Point) = (Math.floor(point.y) > Math.floor(slope * point.x + b))
|
||||
|
||||
private def signedArea(a: Point, b: Point, c: Point): Float =
|
||||
(a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)
|
||||
|
||||
def intersect(c: Point, d: Point): Point = {
|
||||
|
||||
val a = p
|
||||
val b = q
|
||||
|
||||
val a1 = signedArea(a, b, d)
|
||||
val a2 = signedArea(a, b, c)
|
||||
|
||||
if (a1 != 0.0f && a2 != 0.0f && a1*a2 < 0.0f) {
|
||||
val a3 = signedArea(c, d, a)
|
||||
val a4 = a3 + a2 - a1
|
||||
if (a3 * a4 < 0.0f) {
|
||||
val t = a3 / (a3 - a4)
|
||||
return a + ((b - a) * t)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Error")
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.shapes
|
||||
|
||||
import seidel.Sink
|
||||
|
||||
class Trapezoid(val leftPoint: Point, var rightPoint: Point, val top: Segment, val bottom: Segment) {
|
||||
|
||||
var sink: Sink = null
|
||||
var inside = true
|
||||
|
||||
// Neighbor pointers
|
||||
var upperLeft: Trapezoid = null
|
||||
var lowerLeft: Trapezoid = null
|
||||
var upperRight: Trapezoid = null
|
||||
var lowerRight: Trapezoid = null
|
||||
|
||||
// Update neighbors to the left
|
||||
def updateLeft(ul: Trapezoid, ll: Trapezoid) {
|
||||
upperLeft = ul; if(ul != null) ul.upperRight = this
|
||||
lowerLeft = ll; if(ll != null) ll.lowerRight = this
|
||||
}
|
||||
|
||||
// Update neighbors to the right
|
||||
def updateRight(ur: Trapezoid, lr: Trapezoid) {
|
||||
upperRight = ur; if(ur != null) ur.upperLeft = this
|
||||
lowerRight = lr; if(lr != null) lr.lowerLeft = this
|
||||
}
|
||||
|
||||
// Update neighbors on both sides
|
||||
def updateLeftRight(ul: Trapezoid, ll: Trapezoid, ur: Trapezoid, lr: Trapezoid) {
|
||||
upperLeft = ul; if(ul != null) ul.upperRight = this
|
||||
lowerLeft = ll; if(ll != null) ll.lowerRight = this
|
||||
upperRight = ur; if(ur != null) ur.upperLeft = this
|
||||
lowerRight = lr; if(lr != null) lr.lowerLeft = this
|
||||
}
|
||||
|
||||
// Recursively trim outside neighbors
|
||||
def trimNeighbors {
|
||||
if(inside) {
|
||||
inside = false
|
||||
if(upperLeft != null) upperLeft.trimNeighbors
|
||||
if(lowerLeft != null) lowerLeft.trimNeighbors
|
||||
if(upperRight != null) upperRight.trimNeighbors
|
||||
if(lowerRight != null) lowerRight.trimNeighbors
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if this point lies inside the trapezoid
|
||||
def contains(point: Point) = {
|
||||
(point.x > leftPoint.x && point.x < rightPoint.x && top > point && bottom < point)
|
||||
}
|
||||
|
||||
def vertices: Array[Point] = {
|
||||
val verts = new Array[Point](4)
|
||||
verts(0) = lineIntersect(top, leftPoint.x)
|
||||
verts(1) = lineIntersect(bottom, leftPoint.x)
|
||||
verts(2) = lineIntersect(bottom, rightPoint.x)
|
||||
verts(3) = lineIntersect(top, rightPoint.x)
|
||||
verts
|
||||
}
|
||||
|
||||
def lineIntersect(s: Segment, x: Float) = {
|
||||
val y = s.slope * x + s.b
|
||||
Point(x, y)
|
||||
}
|
||||
|
||||
// Add points to monotone mountain
|
||||
def addPoints {
|
||||
if(leftPoint != bottom.p) {bottom.mPoints += leftPoint.clone}
|
||||
if(rightPoint != bottom.q) {bottom.mPoints += rightPoint.clone}
|
||||
if(leftPoint != top.p) {top.mPoints += leftPoint.clone}
|
||||
if(rightPoint != top.q) {top.mPoints += rightPoint.clone}
|
||||
}
|
||||
|
||||
def debugData {
|
||||
println("LeftPoint = " + leftPoint + " | RightPoint = " + rightPoint)
|
||||
println("Top Segment: p = " + top.p + ", q = " + top.q)
|
||||
println("Bottom Segment: p = " + bottom.p + ", q = " + bottom.q)
|
||||
}
|
||||
}
|
@ -1,400 +0,0 @@
|
||||
/* Poly2Tri
|
||||
* Copyright (c) 2009, Mason Green
|
||||
* http://code.google.com/p/poly2tri/
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.poly2tri.shapes
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import utils.Util
|
||||
|
||||
// Triangle-based data structures are know to have better performance than quad-edge structures
|
||||
// See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator"
|
||||
// "Triangulations in CGAL"
|
||||
class Triangle(val points: Array[Point]) {
|
||||
|
||||
// Neighbor pointers
|
||||
var neighbors = new Array[Triangle](3)
|
||||
// Flags to determine if an edge is the final Delauney edge
|
||||
val edges = Array(false, false, false)
|
||||
// Finalization flag. Set true if legalized or edge constrained
|
||||
var finalized = false
|
||||
// Has this triangle been marked as an interoir triangle?
|
||||
var interior = false
|
||||
|
||||
def contains(p: Point): Boolean = (p == points(0) || p == points(1) || p == points(2))
|
||||
def contains(e: Segment): Boolean = (contains(e.p) && contains(e.q))
|
||||
def contains(p: Point, q: Point): Boolean = (contains(p) && contains(q))
|
||||
|
||||
// Update neighbor pointers
|
||||
private def markNeighbor(p1: Point, p2: Point, t: Triangle) {
|
||||
|
||||
if((p1 == points(2) && p2 == points(1)) || (p1 == points(1) && p2 == points(2)))
|
||||
neighbors(0) = t
|
||||
else if((p1 == points(0) && p2 == points(2)) || (p1 == points(2) && p2 == points(0)))
|
||||
neighbors(1) = t
|
||||
else if((p1 == points(0) && p2 == points(1)) || (p1 == points(1) && p2 == points(0)))
|
||||
neighbors(2) = t
|
||||
else {
|
||||
throw new Exception("Neighbor error, please report!")
|
||||
}
|
||||
}
|
||||
|
||||
/* Exhaustive search to update neighbor pointers */
|
||||
def markNeighbor(t: Triangle) {
|
||||
|
||||
if (t.contains(points(1), points(2))) {
|
||||
neighbors(0) = t
|
||||
t.markNeighbor(points(1), points(2), this)
|
||||
} else if(t.contains(points(0), points(2))) {
|
||||
neighbors(1) = t
|
||||
t.markNeighbor(points(0), points(2), this)
|
||||
} else if (t.contains(points(0), points(1))) {
|
||||
neighbors(2) = t
|
||||
t.markNeighbor(points(0), points(1), this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def clearNeighbors {
|
||||
neighbors = new Array[Triangle](3)
|
||||
}
|
||||
|
||||
def oppositePoint(t: Triangle): Point = {
|
||||
|
||||
assert(t != this, "self-pointer error")
|
||||
if(points(0) == t.points(1))
|
||||
points(1)
|
||||
else if(points(0) == t.points(2))
|
||||
points(2)
|
||||
else if(contains(t.points(1), t.points(2)))
|
||||
points(0)
|
||||
else {
|
||||
t.printDebug
|
||||
printDebug
|
||||
println(area + " | " + t.area)
|
||||
throw new Exception("Point location error, please report")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fast point in triangle test
|
||||
def pointIn(point: Point): Boolean = {
|
||||
|
||||
val ij = points(1) - points(0)
|
||||
val jk = points(2) - points(1)
|
||||
val pab = (point - points(0)).cross(ij)
|
||||
val pbc = (point - points(1)).cross(jk)
|
||||
var sameSign = Math.signum(pab) == Math.signum(pbc)
|
||||
if (!sameSign) return false
|
||||
|
||||
val ki = points(0) - points(2)
|
||||
val pca = (point - points(2)).cross(ki)
|
||||
sameSign = Math.signum(pab) == Math.signum(pca)
|
||||
if (!sameSign) return false
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Locate first triangle crossed by constrained edge
|
||||
def locateFirst(edge: Segment): Triangle = {
|
||||
|
||||
if(contains(edge)) return this
|
||||
|
||||
if(edge.q == points(0))
|
||||
search(points(1), points(2), edge, neighbors(2))
|
||||
else if(edge.q == points(1))
|
||||
search(points(0), points(2), edge, neighbors(0))
|
||||
else if(edge.q == points(2))
|
||||
search(points(0), points(1), edge, neighbors(1))
|
||||
else
|
||||
throw new Exception("Point not found")
|
||||
|
||||
}
|
||||
|
||||
def search(p1: Point, p2: Point, edge: Segment, neighbor: Triangle): Triangle = {
|
||||
|
||||
val o1 = Util.orient2d(edge.q, p1, edge.p)
|
||||
val o2 = Util.orient2d(edge.q, p2, edge.p)
|
||||
val sameSign = Math.signum(o1) == Math.signum(o2)
|
||||
|
||||
// Edge crosses this triangle
|
||||
if(!sameSign)
|
||||
return this
|
||||
|
||||
// Look at neighbor
|
||||
if(neighbor == null)
|
||||
null
|
||||
else
|
||||
neighbor.locateFirst(edge)
|
||||
|
||||
}
|
||||
|
||||
// Locate next triangle crossed by edge
|
||||
def findNeighbor(p: Point): Triangle = {
|
||||
|
||||
if(contains(p)) return this
|
||||
|
||||
if(Util.orient2d(points(1), points(0), p) > 0)
|
||||
return neighbors(2)
|
||||
else if(Util.orient2d(points(2), points(1), p) > 0)
|
||||
return neighbors(0)
|
||||
else if(Util.orient2d(points(0), points(2), p) > 0)
|
||||
return neighbors(1)
|
||||
else
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
// The neighbor clockwise to given point
|
||||
def neighborCW(point: Point): Triangle = {
|
||||
|
||||
if(point == points(0)) {
|
||||
neighbors(1)
|
||||
}else if(point == points(1)) {
|
||||
neighbors(2)
|
||||
} else
|
||||
neighbors(0)
|
||||
}
|
||||
|
||||
// The neighbor counter-clockwise to given point
|
||||
def neighborCCW(oPoint: Point): Triangle = {
|
||||
|
||||
if(oPoint == points(0)) {
|
||||
neighbors(2)
|
||||
}else if(oPoint == points(1)) {
|
||||
neighbors(0)
|
||||
} else
|
||||
neighbors(1)
|
||||
}
|
||||
|
||||
// The neighbor clockwise to given point
|
||||
def neighborAcross(point: Point): Triangle = {
|
||||
|
||||
if(point == points(0)) {
|
||||
neighbors(0)
|
||||
}else if(point == points(1)) {
|
||||
neighbors(1)
|
||||
} else
|
||||
neighbors(2)
|
||||
}
|
||||
|
||||
// The point counter-clockwise to given point
|
||||
def pointCCW(point: Point): Point = {
|
||||
|
||||
if(point == points(0)) {
|
||||
points(1)
|
||||
} else if(point == points(1)) {
|
||||
points(2)
|
||||
} else if(point == points(2)){
|
||||
points(0)
|
||||
} else {
|
||||
throw new Exception("point location error")
|
||||
}
|
||||
}
|
||||
|
||||
// The point counter-clockwise to given point
|
||||
def pointCW(point: Point): Point = {
|
||||
|
||||
if(point == points(0)) {
|
||||
points(2)
|
||||
} else if(point == points(1)) {
|
||||
points(0)
|
||||
} else if(point == points(2)){
|
||||
points(1)
|
||||
} else {
|
||||
throw new Exception("point location error")
|
||||
}
|
||||
}
|
||||
|
||||
// Legalized triangle by rotating clockwise around point(0)
|
||||
def legalize(oPoint: Point) {
|
||||
|
||||
points(1) = points(0)
|
||||
points(0) = points(2)
|
||||
points(2) = oPoint
|
||||
}
|
||||
|
||||
// Legalize triagnle by rotating clockwise around oPoint
|
||||
def legalize(oPoint: Point, nPoint: Point) {
|
||||
|
||||
if(oPoint == points(0)) {
|
||||
points(1) = points(0)
|
||||
points(0) = points(2)
|
||||
points(2) = nPoint
|
||||
} else if (oPoint == points(1)) {
|
||||
points(2) = points(1)
|
||||
points(1) = points(0)
|
||||
points(0) = nPoint
|
||||
} else if (oPoint == points(2)) {
|
||||
points(0) = points(2)
|
||||
points(2) = points(1)
|
||||
points(1) = nPoint
|
||||
} else {
|
||||
throw new Exception("legalization error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check if legalized triangle will be collinear
|
||||
def collinear(oPoint: Point): Boolean = Util.collinear(points(1), points(0), oPoint)
|
||||
|
||||
// Check if legalized triangle will be collinear
|
||||
def collinear(oPoint: Point, nPoint: Point): Boolean = {
|
||||
|
||||
if(oPoint == points(0)) {
|
||||
Util.collinear(points(0), points(2), nPoint)
|
||||
} else if (oPoint == points(1)) {
|
||||
Util.collinear(points(0), points(1), nPoint)
|
||||
} else {
|
||||
Util.collinear(points(2), points(1), nPoint)
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate neighbors clockwise around give point. Share diagnal with triangle
|
||||
def rotateNeighborsCW(oPoint: Point, triangle: Triangle) {
|
||||
|
||||
if(oPoint == points(0)) {
|
||||
neighbors(2) = neighbors(1)
|
||||
neighbors(1) = null
|
||||
neighbors(0) = triangle
|
||||
} else if (oPoint == points(1)) {
|
||||
neighbors(0) = neighbors(2)
|
||||
neighbors(2) = null
|
||||
neighbors(1) = triangle
|
||||
} else if (oPoint == points(2)) {
|
||||
neighbors(1) = neighbors(0)
|
||||
neighbors(0) = null
|
||||
neighbors(2) = triangle
|
||||
} else {
|
||||
throw new Exception("pointer bug")
|
||||
}
|
||||
}
|
||||
|
||||
def printDebug = println(points(0) + "," + points(1) + "," + points(2))
|
||||
|
||||
// Finalize edge marking
|
||||
def markNeighborEdges {
|
||||
|
||||
for(i <- 0 to 2)
|
||||
if(edges(i))
|
||||
i match {
|
||||
case 0 => if(neighbors(0) != null) neighbors(0).markEdge(points(1), points(2))
|
||||
case 1 => if(neighbors(1) != null) neighbors(1).markEdge(points(0), points(2))
|
||||
case 2 => if(neighbors(2) != null) neighbors(2).markEdge(points(0), points(1))
|
||||
}
|
||||
}
|
||||
|
||||
def markEdge(triangle: Triangle) {
|
||||
|
||||
for(i <- 0 to 2)
|
||||
if(edges(i))
|
||||
i match {
|
||||
case 0 => triangle.markEdge(points(1), points(2))
|
||||
case 1 => triangle.markEdge(points(0), points(2))
|
||||
case 2 => triangle.markEdge(points(0), points(1))
|
||||
}
|
||||
}
|
||||
|
||||
def markEdge(T: ArrayBuffer[Triangle]) {
|
||||
|
||||
for(t <- T) {
|
||||
for(i <- 0 to 2)
|
||||
if(t.edges(i))
|
||||
i match {
|
||||
case 0 => markEdge(t.points(1), t.points(2))
|
||||
case 1 => markEdge(t.points(0), t.points(2))
|
||||
case 2 => markEdge(t.points(0), t.points(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark edge as constrained
|
||||
def markEdge(p: Point, q: Point) {
|
||||
|
||||
if((q == points(0) && p == points(1)) || (q == points(1) && p == points(0))) {
|
||||
finalized = true
|
||||
edges(2) = true
|
||||
} else if ((q == points(0) && p == points(2)) || (q == points(2) && p == points(0))) {
|
||||
edges(1) = true
|
||||
finalized = true
|
||||
} else if ((q == points(1) && p == points(2)) || (q == points(2) && p == points(1))){
|
||||
finalized = true
|
||||
edges(0) = true
|
||||
}
|
||||
}
|
||||
|
||||
def area = {
|
||||
|
||||
val b = points(0).x - points(1).x
|
||||
val h = points(2).y - points(1).y
|
||||
|
||||
Math.abs((b*h*0.5f))
|
||||
}
|
||||
|
||||
def centroid: Point = {
|
||||
|
||||
val cx = (points(0).x + points(1).x + points(2).x)/3f
|
||||
val cy = (points(0).y + points(1).y + points(2).y)/3f
|
||||
Point(cx, cy)
|
||||
}
|
||||
|
||||
def thin: Boolean = {
|
||||
val a1 = (points(1) - points(0))
|
||||
val b1 = (points(2) - points(0))
|
||||
val a2 = (points(0) - points(1))
|
||||
val b2 = (points(2) - points(1))
|
||||
val angle1 = Math.abs(Math.atan2(a1 cross b1, a1 dot b1))
|
||||
val angle2 = Math.abs(Math.atan2(a2 cross b2, a2 dot b2))
|
||||
val angle3 = Math.Pi - angle1 - angle2
|
||||
// 30 degrees
|
||||
val minAngle = Math.Pi/6
|
||||
(angle1 <= minAngle || angle2 <= minAngle || angle3 <= minAngle)
|
||||
}
|
||||
|
||||
// Compute barycentric coordinates (u, v, w) for
|
||||
// point p with respect to triangle
|
||||
// From "Real-Time Collision Detection" by Christer Ericson
|
||||
def barycentric(p: Point): List[Float] = {
|
||||
val v0 = points(1) - points(0)
|
||||
val v1 = points(2) - points(0)
|
||||
val v2 = p - points(0)
|
||||
val d00 = v0 dot v0
|
||||
val d01 = v0 dot v1
|
||||
val d11 = v1 dot v1
|
||||
val d20 = v2 dot v0
|
||||
val d21 = v2 dot v1
|
||||
val denom = d00 * d11 - d01 * d01
|
||||
val v = (d11 * d20 - d01 * d21) / denom
|
||||
val w = (d00 * d21 - d01 * d20) / denom
|
||||
val u = 1.0f - v - w
|
||||
List(u, v, w)
|
||||
}
|
||||
|
||||
}
|
@ -1,339 +0,0 @@
|
||||
|
||||
package org.poly2tri.utils
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
import shapes.Point
|
||||
|
||||
object Util {
|
||||
|
||||
// Almost zero
|
||||
val COLLINEAR_SLOP = 0.1f
|
||||
|
||||
val epsilon = exactinit
|
||||
val ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon
|
||||
val iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon
|
||||
|
||||
// Refursive merge sort, from "Scala By Example," by Martin Odersky
|
||||
def msort[A](less: (A, A) => Boolean)(xs: List[A]): List[A] = {
|
||||
def merge(xs1: List[A], xs2: List[A]): List[A] =
|
||||
if (xs1.isEmpty) xs2
|
||||
else if (xs2.isEmpty) xs1
|
||||
else if (less(xs1.head, xs2.head)) xs1.head :: merge(xs1.tail, xs2)
|
||||
else xs2.head :: merge(xs1, xs2.tail)
|
||||
val n = xs.length/2
|
||||
if (n == 0) xs
|
||||
else merge(msort(less)(xs take n), msort(less)(xs drop n))
|
||||
}
|
||||
|
||||
// Insertion sort - best for lists <= 10 elements
|
||||
def insertSort[A](less: (A, A) => Boolean)(xs: ArrayBuffer[A]): ArrayBuffer[A] = {
|
||||
var j = 1
|
||||
while(j < xs.size){
|
||||
val key = xs(j)
|
||||
var i = j-1
|
||||
while(i >= 0 && less(key, xs(i)) ){
|
||||
xs(i+1) = xs(i)
|
||||
i -= 1
|
||||
}
|
||||
xs(i+1)=key
|
||||
j += 1
|
||||
}
|
||||
xs
|
||||
}
|
||||
|
||||
// Tests if the given points are collinear
|
||||
def collinear(p1: Point, p2: Point, p3: Point): Boolean = {
|
||||
|
||||
val d = Math.abs((p2-p1) cross (p1-p3))
|
||||
|
||||
if(d <= COLLINEAR_SLOP)
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
}
|
||||
|
||||
/* From Jonathan Shewchuk's "Adaptive Precision Floating-Point Arithmetic
|
||||
* and Fast Robust Predicates for Computational Geometry"
|
||||
* See: http://www.cs.cmu.edu/~quake/robust.html
|
||||
*/
|
||||
def exactinit = {
|
||||
|
||||
var every_other = true
|
||||
var half = 0.5
|
||||
var splitter = 1.0
|
||||
var epsilon = 1.0
|
||||
var check = 1.0
|
||||
var lastcheck = 0.0
|
||||
|
||||
do {
|
||||
lastcheck = check
|
||||
epsilon *= half
|
||||
if (every_other) {
|
||||
splitter *= 2.0
|
||||
}
|
||||
every_other = !every_other
|
||||
check = 1.0 + epsilon
|
||||
} while ((check != 1.0) && (check != lastcheck))
|
||||
|
||||
epsilon
|
||||
|
||||
}
|
||||
|
||||
// Approximate 2D orientation test. Nonrobust.
|
||||
// Return: positive if point a, b, and c are counterclockwise
|
||||
// negative if point a, b, and c are clockwise
|
||||
// zero if points are collinear
|
||||
// See: http://www-2.cs.cmu.edu/~quake/robust.html
|
||||
def orient(a: Point, b: Point, p: Point): Float = {
|
||||
val acx = a.x - p.x
|
||||
val bcx = b.x - p.x
|
||||
val acy = a.y - p.y
|
||||
val bcy = b.y - p.y
|
||||
acx * bcy - acy * bcx
|
||||
}
|
||||
|
||||
// Adaptive exact 2D orientation test. Robust. By Jonathan Shewchuk
|
||||
// Return: positive if point a, b, and c are counterclockwise
|
||||
// negative if point a, b, and c are clockwise
|
||||
// zero if points are collinear
|
||||
// See: http://www-2.cs.cmu.edu/~quake/robust.html
|
||||
def orient2d(pa: Point, pb: Point, pc: Point): Double = {
|
||||
|
||||
val detleft: Double = (pa.x - pc.x) * (pb.y - pc.y)
|
||||
val detright: Double = (pa.y - pc.y) * (pb.x - pc.x)
|
||||
val det = detleft - detright
|
||||
var detsum = 0.0
|
||||
|
||||
if (detleft > 0.0) {
|
||||
if (detright <= 0.0) {
|
||||
return det;
|
||||
} else {
|
||||
detsum = detleft + detright
|
||||
}
|
||||
} else if (detleft < 0.0) {
|
||||
if (detright >= 0.0) {
|
||||
return det
|
||||
} else {
|
||||
detsum = -detleft - detright
|
||||
}
|
||||
} else {
|
||||
return det
|
||||
}
|
||||
|
||||
val errbound = ccwerrboundA * detsum
|
||||
if ((det >= errbound) || (-det >= errbound)) {
|
||||
return det
|
||||
} else {
|
||||
// Cheat a little bit.... we have a degenerate triangle
|
||||
val c = pc * 0.1e-6f
|
||||
return orient2d(pa, pb, c)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Original by Jonathan Shewchuk
|
||||
// http://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html
|
||||
def circumcenter(a: Point, b: Point, c: Point): Point = {
|
||||
|
||||
/* Use coordinates relative to point `a' of the triangle. */
|
||||
val xba = b.x - a.x
|
||||
val yba = b.y - a.y
|
||||
val xca = c.x - a.x
|
||||
val yca = c.y - a.y
|
||||
/* Squares of lengths of the edges incident to `a'. */
|
||||
val balength = xba * xba + yba * yba
|
||||
val calength = xca * xca + yca * yca
|
||||
|
||||
/* Calculate the denominator of the formulae. */
|
||||
val denominator = 0.5 / orient2d(b, c, a)
|
||||
|
||||
/* Calculate offset (from `a') of circumcenter. */
|
||||
val xcirca = (yca * balength - yba * calength) * denominator
|
||||
val ycirca = (xba * calength - xca * balength) * denominator
|
||||
|
||||
a + Point(xcirca.toFloat, ycirca.toFloat)
|
||||
|
||||
}
|
||||
|
||||
// Returns triangle circumcircle point and radius
|
||||
def circumCircle(a: Point, b: Point, c: Point): Tuple2[Point, Float] = {
|
||||
|
||||
val A = det(a, b, c)
|
||||
val C = detC(a, b, c)
|
||||
|
||||
val bx1 = Point(a.x*a.x + a.y*a.y, a.y)
|
||||
val bx2 = Point(b.x*b.x + b.y*b.y, b.y)
|
||||
val bx3 = Point(c.x*c.x + c.y*c.y, c.y)
|
||||
val bx = det(bx1, bx2, bx3)
|
||||
|
||||
val by1 = Point(a.x*a.x + a.y*a.y, a.x)
|
||||
val by2 = Point(b.x*b.x + b.y*b.y, b.x)
|
||||
val by3 = Point(c.x*c.x + c.y*c.y, c.x)
|
||||
val by = det(by1, by2, by3)
|
||||
|
||||
val x = bx / (2*A)
|
||||
val y = by / (2*A)
|
||||
|
||||
val center = Point(x, y)
|
||||
val radius = Math.sqrt(bx*bx + by*by - 4*A*C).toFloat / (2*Math.abs(A))
|
||||
|
||||
(center, radius)
|
||||
}
|
||||
|
||||
private def det(p1: Point, p2: Point, p3: Point): Float = {
|
||||
|
||||
val a11 = p1.x
|
||||
val a12 = p1.y
|
||||
val a13,a23,a33 = 1f
|
||||
val a21 = p2.x
|
||||
val a22 = p2.y
|
||||
val a31 = p3.x
|
||||
val a32 = p3.y
|
||||
|
||||
a11*(a22*a33-a23*a32) - a12*(a21*a33 - a23*a31) + a13*(a21*a32-a22*a31)
|
||||
|
||||
}
|
||||
|
||||
private def detC(p1: Point, p2: Point, p3: Point): Float = {
|
||||
|
||||
val a11 = p1.x*p1.x + p1.y*p1.y
|
||||
val a12 = p1.x
|
||||
val a13 = p1.y
|
||||
val a21 = p2.x*p2.x + p2.y*p2.y
|
||||
val a22 = p2.x
|
||||
val a23 = p2.y
|
||||
val a31 = p3.x*p3.x + p3.y*p3.y
|
||||
val a32 = p3.x
|
||||
val a33 = p3.y
|
||||
|
||||
a11*(a22*a33-a23*a32) - a12*(a21*a33 - a23*a31) + a13*(a21*a32-a22*a31)
|
||||
|
||||
}
|
||||
|
||||
/* Approximate 2D incircle test. Nonrobust. By Jonathan Shewchuk
|
||||
* Return a positive value if the point pd lies inside the
|
||||
* circle passing through pa, pb, and pc; a negative value if
|
||||
* it lies outside; and zero if the four points are cocircular.
|
||||
* The points pa, pb, and pc must be in counterclockwise
|
||||
* order, or the sign of the result will be reversed.
|
||||
*/
|
||||
def incirclefast(pa: Point, pb: Point, pc: Point, pd: Point): Boolean = {
|
||||
|
||||
val adx = pa.x - pd.x
|
||||
val ady = pa.y - pd.y
|
||||
val bdx = pb.x - pd.x
|
||||
val bdy = pb.y - pd.y
|
||||
val cdx = pc.x - pd.x
|
||||
val cdy = pc.y - pd.y
|
||||
|
||||
val abdet = adx * bdy - bdx * ady
|
||||
val bcdet = bdx * cdy - cdx * bdy
|
||||
val cadet = cdx * ady - adx * cdy
|
||||
val alift = adx * adx + ady * ady
|
||||
val blift = bdx * bdx + bdy * bdy
|
||||
val clift = cdx * cdx + cdy * cdy
|
||||
|
||||
alift * bcdet + blift * cadet + clift * abdet >= 0
|
||||
|
||||
}
|
||||
|
||||
/* Robust 2D incircle test, modified. Original By Jonathan Shewchuk
|
||||
* Return a positive value if the point pd lies inside the
|
||||
* circle passing through pa, pb, and pc; a negative value if
|
||||
* it lies outside; and zero if the four points are cocircular.
|
||||
* The points pa, pb, and pc must be in counterclockwise
|
||||
* order, or the sign of the result will be reversed.
|
||||
*/
|
||||
def incircle(pa: Point, pb: Point, pc: Point, pd: Point): Boolean = {
|
||||
|
||||
val adx = pa.x - pd.x
|
||||
val bdx = pb.x - pd.x
|
||||
val cdx = pc.x - pd.x
|
||||
val ady = pa.y - pd.y
|
||||
val bdy = pb.y - pd.y
|
||||
val cdy = pc.y - pd.y
|
||||
|
||||
val bdxcdy = bdx * cdy
|
||||
val cdxbdy = cdx * bdy
|
||||
val alift = adx * adx + ady * ady
|
||||
|
||||
val cdxady = cdx * ady
|
||||
val adxcdy = adx * cdy
|
||||
val blift = bdx * bdx + bdy * bdy
|
||||
|
||||
val adxbdy = adx * bdy
|
||||
val bdxady = bdx * ady
|
||||
val clift = cdx * cdx + cdy * cdy
|
||||
|
||||
val det = alift * (bdxcdy - cdxbdy) +
|
||||
blift * (cdxady - adxcdy) +
|
||||
clift * (adxbdy - bdxady)
|
||||
|
||||
val permanent = (Math.abs(bdxcdy) + Math.abs(cdxbdy)) * alift +
|
||||
(Math.abs(cdxady) + Math.abs(adxcdy)) * blift +
|
||||
(Math.abs(adxbdy) + Math.abs(bdxady)) * clift
|
||||
|
||||
val errbound = iccerrboundA * permanent
|
||||
|
||||
if ((det > errbound) || (-det > errbound)) {
|
||||
return det >= 0
|
||||
} else {
|
||||
// Cheat a little bit.... we have a degenerate triangle
|
||||
val d = pd * 0.1e-6f
|
||||
return incircle(pa, pb, pc, d)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def closestPtSegment(c: Point, a: Point, b: Point): Point = {
|
||||
val ab = b - a
|
||||
var t = (c - a) dot ab
|
||||
if (t <= 0.0f)
|
||||
return a
|
||||
else {
|
||||
val denom = ab dot ab
|
||||
if (t >= denom)
|
||||
return b
|
||||
else {
|
||||
t = t / denom
|
||||
}
|
||||
}
|
||||
a + (ab * t)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** The object <code>Random</code> offers a default implementation
|
||||
* of scala.util.Random and random-related convenience methods.
|
||||
*
|
||||
* @since 2.8
|
||||
* From Scala 2.8 standard library
|
||||
*/
|
||||
object Random extends scala.util.Random {
|
||||
|
||||
/** Returns a new sequence in random order.
|
||||
* @param seq the sequence to shuffle
|
||||
* @return the shuffled sequence
|
||||
*/
|
||||
def shuffle[T](buf: ArrayBuffer[T]): ArrayBuffer[T] = {
|
||||
// It would be better if this preserved the shape of its container, but I have
|
||||
// again been defeated by the lack of higher-kinded type inference. I can
|
||||
// only make it work that way if it's called like
|
||||
// shuffle[Int,List](List.range(0,100))
|
||||
// which nicely defeats the "convenience" portion of "convenience method".
|
||||
|
||||
def swap(i1: Int, i2: Int) {
|
||||
val tmp = buf(i1)
|
||||
buf(i1) = buf(i2)
|
||||
buf(i2) = tmp
|
||||
}
|
||||
|
||||
for (n <- buf.length to 2 by -1) {
|
||||
val k = nextInt(n)
|
||||
swap(n - 1, k)
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user