Browse Source

add QField 1.10.0

kunickyd 3 years ago
parent
commit
ebef13bbac
100 changed files with 9409 additions and 0 deletions
  1. 103 0
      .ci/CI-schema.drawio
  2. BIN
      .ci/id_rsa.enc
  3. BIN
      .ci/play_developer.p12.enc
  4. 119 0
      .clang-format
  5. 25 0
      .cmake-format.yaml
  6. 8 0
      .docker/testing/Dockerfile
  7. 21 0
      .docker/testing/build-test.sh
  8. 11 0
      .docker/testing/docker-compose-ci.yml
  9. 91 0
      .github/CONTRIBUTING.md
  10. 12 0
      .github/FUNDING.yml
  11. 29 0
      .github/ISSUE_TEMPLATE.md
  12. 6 0
      .github/dependabot.yaml
  13. 33 0
      .github/release-drafter.yml
  14. 23 0
      .github/support.yml
  15. 88 0
      .github/workflows/astyle.yml
  16. 18 0
      .github/workflows/backport.yml
  17. 83 0
      .github/workflows/code_layout.yml
  18. 184 0
      .github/workflows/continuous_integration.yml
  19. 162 0
      .github/workflows/ios.yml
  20. 17 0
      .github/workflows/release_drafter.yml
  21. 21 0
      .github/workflows/script_checks.yml
  22. 20 0
      .github/workflows/stale.yml
  23. 26 0
      .github/workflows/translations.yml
  24. 16 0
      .github/workflows/unstale.yml
  25. 314 0
      .github/workflows/vcpkg.yml
  26. 28 0
      .gitignore
  27. 3 0
      .gitmodules
  28. 49 0
      .pre-commit-config.yaml
  29. 16 0
      .tx/config
  30. 3 0
      3rdparty/tessellate/.gitignore
  31. 37 0
      3rdparty/tessellate/CMakeLists.txt
  32. 37 0
      3rdparty/tessellate/LICENSE
  33. 13 0
      3rdparty/tessellate/README.md
  34. 100 0
      3rdparty/tessellate/dict-list.h
  35. 111 0
      3rdparty/tessellate/dict.c
  36. 100 0
      3rdparty/tessellate/dict.h
  37. 264 0
      3rdparty/tessellate/geom.c
  38. 84 0
      3rdparty/tessellate/geom.h
  39. 356 0
      3rdparty/tessellate/glu.h
  40. 86 0
      3rdparty/tessellate/gluos.h
  41. 52 0
      3rdparty/tessellate/main.c
  42. 55 0
      3rdparty/tessellate/memalloc.c
  43. 54 0
      3rdparty/tessellate/memalloc.h
  44. 798 0
      3rdparty/tessellate/mesh.c
  45. 266 0
      3rdparty/tessellate/mesh.h
  46. 257 0
      3rdparty/tessellate/normal.c
  47. 45 0
      3rdparty/tessellate/normal.h
  48. 257 0
      3rdparty/tessellate/priorityq-heap.c
  49. 107 0
      3rdparty/tessellate/priorityq-heap.h
  50. 117 0
      3rdparty/tessellate/priorityq-sort.h
  51. 260 0
      3rdparty/tessellate/priorityq.c
  52. 117 0
      3rdparty/tessellate/priorityq.h
  53. 502 0
      3rdparty/tessellate/render.c
  54. 52 0
      3rdparty/tessellate/render.h
  55. 1361 0
      3rdparty/tessellate/sweep.c
  56. 77 0
      3rdparty/tessellate/sweep.h
  57. 632 0
      3rdparty/tessellate/tess.c
  58. 165 0
      3rdparty/tessellate/tess.h
  59. 231 0
      3rdparty/tessellate/tessellate.c
  60. 13 0
      3rdparty/tessellate/tessellate.h
  61. 19 0
      3rdparty/tessellate/tessellate.pro
  62. 201 0
      3rdparty/tessellate/tessmono.c
  63. 71 0
      3rdparty/tessellate/tessmono.h
  64. 302 0
      CMakeLists.txt
  65. 340 0
      LICENSE
  66. 1 0
      RELEASE_NAME
  67. 80 0
      android/build.gradle
  68. BIN
      android/gradle/wrapper/gradle-wrapper.jar
  69. 6 0
      android/gradle/wrapper/gradle-wrapper.properties
  70. 164 0
      android/gradlew
  71. 90 0
      android/gradlew.bat
  72. BIN
      android/res/drawable-hdpi/card.png
  73. BIN
      android/res/drawable-hdpi/dataset.png
  74. BIN
      android/res/drawable-hdpi/directory.png
  75. BIN
      android/res/drawable-hdpi/project.png
  76. BIN
      android/res/drawable-hdpi/qfield_logo.png
  77. BIN
      android/res/drawable-hdpi/qfield_logo_beta.png
  78. BIN
      android/res/drawable-hdpi/qfield_logo_pr.png
  79. BIN
      android/res/drawable-hdpi/tablet.png
  80. BIN
      android/res/drawable-mdpi/card.png
  81. BIN
      android/res/drawable-mdpi/dataset.png
  82. BIN
      android/res/drawable-mdpi/directory.png
  83. BIN
      android/res/drawable-mdpi/project.png
  84. BIN
      android/res/drawable-mdpi/qfield_logo.png
  85. BIN
      android/res/drawable-mdpi/qfield_logo_beta.png
  86. BIN
      android/res/drawable-mdpi/qfield_logo_pr.png
  87. BIN
      android/res/drawable-mdpi/tablet.png
  88. BIN
      android/res/drawable-xhdpi/card.png
  89. BIN
      android/res/drawable-xhdpi/dataset.png
  90. BIN
      android/res/drawable-xhdpi/directory.png
  91. BIN
      android/res/drawable-xhdpi/project.png
  92. BIN
      android/res/drawable-xhdpi/qfield_logo.png
  93. BIN
      android/res/drawable-xhdpi/qfield_logo_beta.png
  94. BIN
      android/res/drawable-xhdpi/qfield_logo_pr.png
  95. BIN
      android/res/drawable-xhdpi/tablet.png
  96. BIN
      android/res/drawable-xxhdpi/card.png
  97. BIN
      android/res/drawable-xxhdpi/dataset.png
  98. BIN
      android/res/drawable-xxhdpi/directory.png
  99. BIN
      android/res/drawable-xxhdpi/project.png
  100. BIN
      android/res/drawable-xxhdpi/qfield_logo.png

+ 103 - 0
.ci/CI-schema.drawio

@@ -0,0 +1,103 @@
+<mxfile host="www.draw.io" modified="2020-02-17T09:13:18.164Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" etag="mJpvVCE-6gXK4v96E_2O" version="12.7.0" type="github">
+  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
+    <mxGraphModel dx="1053" dy="942" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
+      <root>
+        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0"/>
+        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0"/>
+        <mxCell id="ur15OjHsovGPjOp776xv-2" value="GH Release v1.X.Y" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;fontStyle=1;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="441" y="108" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-4" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999999;exitY=1;exitPerimeter=1;entryX=0.5;entryY=0.026008046768488058;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-2" target="ur15OjHsovGPjOp776xv-7" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-5" value="release-1_X" style="html=1;jettySize=18;fontSize=9;fontColor=#333333;fontStyle=1;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999997;exitY=0.9739919532315111;exitPerimeter=1;entryX=0.4999999999999998;entryY=0.021248022495900597;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-7" target="ur15OjHsovGPjOp776xv-17" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-7" value="branch ?" style="html=1;whiteSpace=wrap;rhombus;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="441" y="215" width="84" height="72" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-8" value="create release-1_X branch" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="237" y="336" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-9" value="release-1_X branch already exists ?" style="html=1;whiteSpace=wrap;rhombus;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="225" y="209" width="108" height="84" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-10" value="master" style="html=1;jettySize=18;fontSize=9;fontColor=#333333;fontStyle=1;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.025737954566052663;exitY=0.5000000000000001;exitPerimeter=1;entryX=0.9788515138816369;entryY=0.5000000000000001;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-7" target="ur15OjHsovGPjOp776xv-9" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-12" value="NO" style="html=1;jettySize=18;fontSize=9;fontColor=#333333;fontStyle=1;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.5000000000000001;exitY=0.9787519775040995;exitPerimeter=1;entryX=0.5;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-9" target="ur15OjHsovGPjOp776xv-8" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-14" value="ERROR" style="html=1;whiteSpace=wrap;rounded=1;arcSize=50;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;fillColor=#ff3d3d;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="75" y="230" width="84" height="42" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-15" value="YES" style="html=1;jettySize=18;fontSize=11;fontColor=#333333;align=center;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.021148486118363997;exitY=0.5000000000000001;exitPerimeter=1;entryX=1;entryY=0.5;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-9" target="ur15OjHsovGPjOp776xv-14" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-17" value="release-1_X resource exists on TX ?" style="html=1;whiteSpace=wrap;rhombus;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="429" y="372" width="108" height="84" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-18" value="copy master resource to release-1_X on TX" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="231" y="456" width="96" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-19" value="Yes" style="html=1;jettySize=18;fontSize=11;fontColor=#333333;align=center;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999998;exitY=0.9787519775040995;exitPerimeter=1;entryX=0.12068965517241362;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-17" target="ur15OjHsovGPjOp776xv-28" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-21" value="No" style="html=1;jettySize=18;fontSize=11;fontColor=#333333;align=center;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.021148486118363997;exitY=0.5000000000000001;exitPerimeter=1;entryX=0.5;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-17" target="ur15OjHsovGPjOp776xv-18" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-23" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.5;exitY=1;exitPerimeter=1;entryX=0.5000000000000004;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-8" target="ur15OjHsovGPjOp776xv-18" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-24" value="Commit on master" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;fontStyle=1;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="705" y="108" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-25" value="Commit on PR ZZZ" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;fontStyle=1;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#1071e5;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="573" y="108" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-26" value="push to TX" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="825" y="552" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-27" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999999;exitY=1;exitPerimeter=1;entryX=0.5;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-24" target="ur15OjHsovGPjOp776xv-26" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-28" value="pull from TX" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="441" y="552" width="348" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-29" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.5;exitY=1.0000000000000007;exitPerimeter=1;entryX=0.12068965517241362;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-18" target="ur15OjHsovGPjOp776xv-28" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-30" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999999;exitY=1;exitPerimeter=1;entryX=0.8793103448275864;entryY=0;entryPerimeter=1;startSize=6;endSize=6;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-24" target="ur15OjHsovGPjOp776xv-28" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-31" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.4999999999999999;exitY=1;exitPerimeter=1;entryX=0.5000000000000001;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-25" target="ur15OjHsovGPjOp776xv-28" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-32" value="write coment with APK links in commit or PR on GH" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="441" y="654" width="348" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-33" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.12279351701888692;exitY=1;exitPerimeter=1;entryX=0.12279351701888687;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-28" target="ur15OjHsovGPjOp776xv-32" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-34" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.5000000000000001;exitY=1;exitPerimeter=1;entryX=0.5000000000000001;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-28" target="ur15OjHsovGPjOp776xv-32" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-35" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.8777235129899184;exitY=1;exitPerimeter=1;entryX=0.8777235129899189;entryY=7.12823771829956e-17;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-28" target="ur15OjHsovGPjOp776xv-32" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-36" value="Play Store
track: beta" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="705" y="768" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-37" value="Play Store
track: internal" style="html=1;whiteSpace=wrap;;fontSize=11;fontColor=#ffffff;spacing=5;strokeOpacity=0;fillOpacity=100;rounded=1;absoluteArcSize=1;arcSize=9.6;fillColor=#2db539;strokeWidth=1.2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
+          <mxGeometry x="441" y="768" width="84" height="60" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-38" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.12020814032035608;exitY=1;exitPerimeter=1;entryX=0.5;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-32" target="ur15OjHsovGPjOp776xv-37" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+        <mxCell id="ur15OjHsovGPjOp776xv-39" value="" style="html=1;jettySize=18;fontSize=11;strokeColor=#333333;strokeOpacity=100;strokeWidth=0.6;rounded=1;arcSize=24;edgeStyle=orthogonalEdgeStyle;startArrow=none;endArrow=block;endFill=0;exitX=0.8793103448275857;exitY=1;exitPerimeter=1;entryX=0.5;entryY=0;entryPerimeter=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="ur15OjHsovGPjOp776xv-32" target="ur15OjHsovGPjOp776xv-36" edge="1">
+          <mxGeometry width="100" height="100" relative="1" as="geometry"/>
+        </mxCell>
+      </root>
+    </mxGraphModel>
+  </diagram>
+</mxfile>

BIN
.ci/id_rsa.enc


BIN
.ci/play_developer.p12.enc


+ 119 - 0
.clang-format

@@ -0,0 +1,119 @@
+Language:        Cpp
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: No
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterCaseLabel:  true
+  AfterClass:      true
+  AfterControlStatement: true
+  AfterEnum:       true
+  AfterFunction:   true
+  AfterNamespace:  true
+  AfterObjCDeclaration: false
+  AfterStruct:     true
+  AfterUnion:      true
+  BeforeCatch:     true
+  BeforeElse:      true
+  IndentBraces:    false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: All
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 0
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 2
+ContinuationIndentWidth: 0
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+IncludeCategories:
+  - Regex:           '^<Q.*'
+    Priority:        300
+  - Regex:           '^<qgs.*'
+    Priority:        200
+  - Regex:           '<.*'
+    Priority:        400
+  - Regex:           '^".*'
+    Priority:        100
+  - Regex:           '.*'
+    Priority:        1
+IncludeIsMainRegex: false
+IncludeBlocks: Regroup
+IndentAccessModifiers: true
+IndentCaseLabels: true
+IndentWidth: 2
+IndentWrappedFunctionNames: true
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
+MacroBlockBegin: ""
+MacroBlockEnd:   ""
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 150
+PenaltyBreakBeforeFirstCallParameter: 300
+PenaltyBreakComment: 500
+PenaltyBreakFirstLessLess: 400
+PenaltyBreakString: 600
+PenaltyExcessCharacter: 50
+PenaltyReturnTypeOnItsOwnLine: 300
+PointerAlignment: Right
+ReflowComments:  false
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: true
+SpacesInParentheses: true
+SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        2
+UseTab:       Never
+
+---
+
+Language:        Java
+TabWidth:        4
+IndentWidth:     4
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None

+ 25 - 0
.cmake-format.yaml

@@ -0,0 +1,25 @@
+parse:
+  additional_commands:
+    add_qt_ios_app:
+      pargs: 1
+      kwargs:
+        NAME: '*'
+        VERSION: '*'
+        SHORT_VERSION: '*'
+        LONG_VERSION: '*'
+        ASSET_DIR: '*'
+        CATALOG_APPICON: '*'
+        LAUNCHSCREEN_STORYBOARD: '*'
+        RESOURCE_FILES: '*'
+        CUSTOM_PLIST: '*'
+        BUNDLE_IDENTIFIER: '*'
+      flags:
+        - ORIENTATION_PORTRAIT
+        - ORIENTATION_PORTRAIT_UPDOWN
+        - ORIENTATION_LANDSCAPE_LEFT
+        - ORIENTATION_LANDSCAPE_RIGHT
+        - REQUIRES_FULL_SCREEN
+        - HIDDEN_STATUS_BAR
+        - IPA
+        - UPLOAD_SYMBOL
+        - VERBOSE

+ 8 - 0
.docker/testing/Dockerfile

@@ -0,0 +1,8 @@
+ARG qfield_test_docker_version
+FROM opengisch/qfield-test-docker:${qfield_test_docker_version}
+MAINTAINER Matthias Kuhn <matthias@opengis.ch>
+
+RUN dnf install -y xorg-x11-server-Xvfb git python-pip qt5-qtcharts qt5-qtquickcontrols2
+
+WORKDIR /
+

+ 21 - 0
.docker/testing/build-test.sh

@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+/usr/src/vcpkg/base/bootstrap-vcpkg.sh
+/usr/src/vcpkg/base/vcpkg install --overlay-ports=/usr/src/vcpkg/overlay spix
+
+pip install -r /usr/src/test/spix/requirements.txt
+
+mkdir /usr/src/build
+cd /usr/src/build
+
+echo ::group::cmake
+cmake -GNinja -DSpix_DIR:PATH=/usr/src/vcpkg/base/installed/x64-linux/share/Spix/cmake -DAnyRPC_DIR:PATH=/usr/src/vcpkg/base/installed/x64-linux/share/anyrpc -DWITH_SPIX=TRUE /usr/src
+echo ::endgroup::
+
+echo ::group::make
+ninja -j2
+echo ::endgroup::
+
+LD_PRELOAD=/usr/lib64/libSegFault.so xvfb-run ctest --output-on-failure

+ 11 - 0
.docker/testing/docker-compose-ci.yml

@@ -0,0 +1,11 @@
+version: '3'
+services:
+  qgis:
+    build:
+      dockerfile: Dockerfile
+      context: .
+      args:
+        qfield_test_docker_version: ${QFIELD_SDK_VERSION}
+    tty: true
+    volumes:
+      - ${CI_BUILD_DIR}:/usr/src

+ 91 - 0
.github/CONTRIBUTING.md

@@ -0,0 +1,91 @@
+# Contributing to QField
+
+:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
+
+#### Table Of Contents
+
+### Reporting Bugs
+
+This section guides you through submitting a bug report for QField. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
+
+Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). If you'd like, you can use [this template](#template-for-submitting-bug-reports) to structure the information.
+
+#### Before Submitting A Bug Report
+
+* **Check the [documenation](https://opengisch.github.io/QField-docs/index.html)** for related information.
+* **Perform a [search](https://github.com/opengisch/QField/issues)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
+
+#### How Do I Submit A (Good) Bug Report?
+
+Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#atom-and-packages) your bug is related to, create an issue on that repository and provide the following information.
+
+Explain the problem and include additional details to help maintainers reproduce the problem:
+
+* **Use a clear and descriptive title** for the issue to identify the problem.
+* **Describe the exact steps which reproduce the problem** in as many details as possible.
+* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
+* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
+* **Explain which behavior you expected to see instead and why.**
+* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. [AZ Screen Recorder](https://play.google.com/store/apps/details?id=com.hecorat.screenrecorder.free&hl=en) allows to easily record videos on your device.
+* **If you're reporting that QField crashed**, include a crash report with a stack trace. If you are asked to submit a report after the crash happened, please agree. A [log from your app while reproducing the crash](https://github.com/opengisch/OSGeo4A/wiki/Debugging) will also help to nail down the issue.
+* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
+
+Provide more context by answering these questions:
+
+* **Did the problem start happening recently** (e.g. after updating to a new version of QField) or was this always a problem?
+* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
+
+Include details about your configuration and environment:
+
+* **Which version of QField are you using?** You can get the exact version by going to the QField menu and chose about or by long tapping the application icon and dragging it onto the info icon.
+* **What's the version of the Android you're using**?
+
+### Suggesting Enhancements
+
+This section guides you through submitting an enhancement suggestion for QField, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
+
+Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). If you'd like, you can use [this template](#template-for-submitting-enhancement-suggestions) to structure the information.
+
+#### Before Submitting An Enhancement Suggestion
+
+* **Check the [documentation](https://opengisch.github.io/QField-docs/index.html)** for tips — you might discover that the enhancement is already available. Most importantly, check if you're using the latest version of QField.
+* **Perform a [search](https://github.com/opengisch/QField/issues)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
+
+#### How Do I Submit A (Good) Enhancement Suggestion?
+
+Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/).
+
+* **Use a clear and descriptive title** for the issue to identify the suggestion.
+* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
+* **Provide specific examples to demonstrate the steps**.
+* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
+* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of QField which the suggestion is related to.
+* **Explain why this enhancement would be useful** to most QField users.
+* **Specify if there is existing QGIS functionality** on top of which this could build.
+
+#### Template For Submitting Enhancement Suggestions
+
+    [Short description of suggestion]
+
+    **Steps which explain the enhancement**
+
+    1. [First Step]
+    2. [Second Step]
+    3. [Other Steps...]
+
+    **Current and suggested behavior**
+
+    [Describe current and suggested behavior here]
+
+    **Why would the enhancement be useful to most users**
+
+    [Explain why the enhancement would be useful to most users]
+
+    **Screenshots, GIFs and mockups**
+
+    ![Screenshots, GIFs and mockups which demonstrate the steps or part of QField the enhancement suggestion is related to](url)
+
+    **QField Version:** [Enter QField version here]
+    **OS and Version:** [Enter OS name and version here]
+
+

+ 12 - 0
.github/FUNDING.yml

@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: [opengisch]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ["https://qfield.cloud"]

+ 29 - 0
.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,29 @@
+[Short description of problem here]
+
+### Reproduction Steps:
+
+1. [First Step]
+2. [Second Step]
+3. [Other Steps...]
+
+### Expected behavior:
+
+[Describe expected behavior here]
+
+### Observed behavior:
+
+[Describe observed behavior here]
+
+### Screenshots and GIFs
+
+![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url)
+
+[Please also attach additional files if a specific project/dataset is useful to investigate the problem.]
+
+**QField version:** [Enter QField version here including build number found in the about screen after the release name example 1.2.0 (10200993)]
+
+## Additional information:
+
+* Problem started happening recently, didn't happen in an older version of QField: [Yes/No]
+* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
+* Problem happens with all files and projects, not only some files or projects: [Yes/No]

+ 6 - 0
.github/dependabot.yaml

@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"

+ 33 - 0
.github/release-drafter.yml

@@ -0,0 +1,33 @@
+name-template: '$RESOLVED_VERSION 🌈'
+tag-template: 'v$RESOLVED_VERSION'
+categories:
+  - title: '🚀 Features'
+    labels:
+      - 'ENHANCEMENT'
+  - title: '🐛 Bug Fixes'
+    labels:
+      - 'BUG'
+  - title: '✨ Usability Improvements'
+    labels:
+      - 'UX/UI'
+include-labels:
+  - 'ENHANCEMENT'
+  - 'BUG'
+  - 'UX/UI'
+change-template: '- $TITLE (#$NUMBER)'
+change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+version-resolver:
+  major:
+    labels:
+      - 'major'
+  minor:
+    labels:
+      - 'minor'
+  patch:
+    labels:
+      - 'patch'
+  default: patch
+template: |
+  ## Changes
+
+  $CHANGES

+ 23 - 0
.github/support.yml

@@ -0,0 +1,23 @@
+# Configuration for Support Requests - https://github.com/dessant/support-requests
+
+# Label used to mark issues as support requests
+supportLabel: support
+
+# Comment to post on issues marked as support requests, `{issue-author}` is an
+# optional placeholder. Set to `false` to disable
+supportComment: >
+  :wave: @{issue-author}, we use the issue tracker exclusively for bug reports
+  and feature requests. However, this issue appears to be a support request.
+  Please use our support channels to get help with the project.
+
+# Close issues marked as support requests
+close: true
+
+# Lock issues marked as support requests
+lock: false
+
+# Assign `off-topic` as the reason for locking. Set to `false` to disable
+setLockReason: true
+
+# Repository to extend settings from
+# _extends: repo

+ 88 - 0
.github/workflows/astyle.yml

@@ -0,0 +1,88 @@
+---
+name: 🕴️ Style check
+
+on:
+  push:
+    branches:
+      - master
+      - release-**
+  pull_request:
+    branches:
+      - master
+      - release-**
+  issue_comment:
+    types: [created]
+
+jobs:
+  astyle-check:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/github-script@v5
+        if: ${{ github.event.issue.pull_request }}
+        id: get-pr
+        with:
+          script: |
+            const request = {
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              pull_number: context.issue.number
+            }
+            core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`)
+            try {
+              const result = await github.rest.pulls.get(request)
+              return result.data
+            } catch (err) {
+              core.setFailed(`Request failed with error ${err}`)
+            }
+
+      - uses: actions/checkout@v2
+        if: ${{ github.event.issue.pull_request }}
+        with:
+          repository: ${{ fromJSON(steps.get-pr.outputs.result).head.repo.full_name }}
+          ref: ${{ fromJSON(steps.get-pr.outputs.result).head.sha }} # or .head.ref for branch name
+
+      - uses: actions/checkout@v2
+        if: ${{ !github.event.issue.pull_request }}
+
+      - uses: khan/pull-request-comment-trigger@master
+        id: indent-check
+        if: ${{ github.event.issue.pull_request }}
+        with:
+          trigger: '@qfield-fairy style please'
+          reaction: rocket
+        env:
+          GITHUB_TOKEN: '${{ secrets.FAIRY_TOKEN }}'
+
+      - name: Check astyle
+        id: astyle-check
+        run: |
+          sudo apt install -y astyle
+          find src/ -name '*.cpp' -o -name '*.h' | xargs ./scripts/astyle.sh
+          find test/ -name '*.cpp' -o -name '*.h' | xargs ./scripts/astyle.sh
+          if [ ! -z "$(git status --porcelain)" ]; then
+            echo "::error::Indentation errors found (astyle)"
+            echo "::group::Indentation changes"
+            git diff
+            echo "::endgroup"
+            echo '::set-output name=changed::true'
+          fi
+
+      - name: Comment
+        uses: thollander/actions-comment-pull-request@main
+        if: steps.indent-check.outputs.changed == 'true' && steps.indent-check.outputs.triggered != 'true'
+        with:
+          message: |
+            Code formatting issues have been detected.
+
+            Reply with `@qfield-fairy style please` to fix it up 🪄.
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - uses: EndBug/add-and-commit@v7.4.0
+        if: steps.indent-check.outputs.triggered == 'true'
+        with:
+          author_name: Style Fairy
+          author_email: fairy@qfield.org
+          message: 'Committing astyle changes'
+          branch: ${{ fromJSON(steps.get-pr.outputs.result).head.ref }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.FAIRY_TOKEN }}

+ 18 - 0
.github/workflows/backport.yml

@@ -0,0 +1,18 @@
+---
+name: ♻ Backport
+on:
+  pull_request_target:
+    types:
+      - closed
+      - labeled
+
+jobs:
+  backport:
+    runs-on: ubuntu-18.04
+    name: Backport
+    steps:
+      - uses: actions/checkout@v2
+      - name: Backport
+        uses: m-kuhn/backport@v1.2.3
+        with:
+          github_token: ${{ secrets.FAIRY_TOKEN }}

+ 83 - 0
.github/workflows/code_layout.yml

@@ -0,0 +1,83 @@
+name: 🧹 Static Tests
+
+on:
+  push:
+    branches:
+      - master
+      - release-**
+  pull_request:
+    branches:
+      - master
+      - release-**
+
+jobs:
+  license_check:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Install Requirements
+        run: |
+          sudo apt install -y \
+              cpanminus
+            cpanm --notest App::Licensecheck
+
+      - name: Run License Check
+        run: ./scripts/test_licenses.sh
+
+  banned_keywords_check:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Run Banned Keywords Test
+        run: ./scripts/test_banned_keywords.sh
+
+  cppcheck-1_8:
+    runs-on: ubuntu-18.04 # cppcheck 1.8 shows some errors 1.9 does not show
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Requirements
+        run: |
+          sudo apt install -y cppcheck
+      - name: Run cppcheck test
+        run: ./scripts/cppcheck.sh
+
+  cppcheck-1_9:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Requirements
+        run: |
+          sudo apt install -y cppcheck
+      - name: Run cppcheck test
+        run: ./scripts/cppcheck.sh
+
+  qfield-sdk-check:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Check sdk pattern
+        run: |
+          grep -E 'osgeo4a_version=2[0-9]{7}' sdk.conf
+
+  pre-commit:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Requirements
+        run: |
+            sudo apt install -y \
+              shellcheck \
+              astyle
+
+      - name: Check pre-commit hooks
+        uses: pre-commit/action@v2.0.3

+ 184 - 0
.github/workflows/continuous_integration.yml

@@ -0,0 +1,184 @@
+name: 🧪 Android packages
+
+on:
+  push:
+    branches:
+      - master
+      - release-**
+  pull_request:
+  release:
+    types: ['published', 'released']
+
+jobs:
+  # Build Android packages
+  build:
+    runs-on: ubuntu-20.04
+    env:
+      TX_TOKEN: ${{ secrets.TX_TOKEN }}
+    strategy:
+      matrix:
+        arch: [armv7, arm64_v8a, x86, x86_64]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Setup signing key
+        env:
+          SIGNINGKEY: ${{ secrets.PLAYSTORE_SIGNINGKEY }}
+        run: |
+          echo "$SIGNINGKEY" | base64 --decode > ./keystore.p12
+
+      - name: Setup build environment
+        run: |
+          echo ::group::install packages
+          sudo apt update && sudo apt install -y qttools5-dev-tools qt5-default transifex-client
+          echo ::endgroup::
+          sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
+          git submodule update --init --recursive
+          ./scripts/ci/env_gh.sh
+          pip install wheel
+
+      - name: "🌍 Pull Translations"
+        run: |
+          if [[ -z "${TX_TOKEN}" ]]; then
+            echo "TX_TOKEN not set, skip tx pull"
+          else
+            ./scripts/ci/pull_translations.sh
+          fi
+
+#      - uses: actions/cache@v2
+#        with:
+#          path: |
+#            ~/cache/.gradle/caches
+#            ~/cache/.gradle/wrapper
+#          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
+#          restore-keys: |
+#            ${{ runner.os }}-gradle-
+
+      - name: Free additional space
+        run: |
+          df -h
+          rm -rf /tmp/workspace
+          rm -rf /usr/share/dotnet/sdk
+          sudo apt remove llvm-* ghc-* google-chrome-* dotnet-sdk-*
+          dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100
+          du -a /usr/share | sort -n -r | head -n 10
+          du -a /usr/local/share | sort -n -r | head -n 10
+          df -h
+          sudo apt clean
+          df -h
+
+      - name: Build
+        env:
+          ARCH: ${{ matrix.arch }}
+          KEYNAME: qfield
+          KEYPASS: ${{ secrets.KEYPASS }}
+          STOREPASS: ${{ secrets.STOREPASS }}
+          CACHE_DIR: ${{ env.GITHUB_WORKSPACE }}/cache
+        run: |
+          ./scripts/ci/docker_pull.sh
+          source ./scripts/version_number.sh
+          source ./scripts/ci/generate-version-details.sh
+          ./scripts/build.sh
+          ls $CACHE_DIR
+
+      - name: 🍺 Deploy
+        run: |
+          sudo apt install -y s3cmd
+          ./scripts/ci/upload_artifacts.sh
+        env:
+          S3CFG: ${{ secrets.S3CFG }}
+          ARCH: ${{ matrix.arch }}
+
+      - name: Upload release assets
+        uses: AButler/upload-release-assets@v2.0
+        if: ${{ github.event_name == 'release' }}
+        with:
+          files: /tmp/qfield-*.apk
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          release-tag: ${{ env.CI_TAG }}
+
+  deploy_to_playstore:
+    runs-on: ubuntu-20.04
+    needs: build
+    if: ${{ github.event_name == 'released' || ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Vars
+        id: vars
+        run: |
+          ./scripts/ci/env_gh.sh
+
+      - name: Download apks
+        run: |
+          wget https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-arm64_v8a.apk
+          wget https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-armv7.apk
+          wget https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86_64.apk
+          wget https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86.apk
+
+      - name: Upload to Google Play Store
+        run: |
+          pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib oauth2client
+
+          ./scripts/basic_upload_apks_service_account.py ch.opengis.${{ steps.vars.outputs.APP_PACKAGE_NAME }} beta "Update from commit ${GITHUB_SHA}" \
+              ${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-arm64_v8a.apk \
+              ${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-armv7.apk \
+              ${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86_64.apk \
+              ${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86.apk
+        env:
+          GOOGLE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }}
+
+
+  comment_pr:
+    runs-on: ubuntu-20.04
+    needs: build
+    if: ${{ github.event_name == 'pull_request' }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Vars
+        id: vars
+        run: |
+          ./scripts/ci/env_gh.sh
+      - uses: kanga333/comment-hider@master
+        name: Hide outdated comments from the default github user
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          hide_user_name: github-actions[bot]
+      - uses: kanga333/comment-hider@master
+        name: Hide outdated comments from qfield-fairy
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          hide_user_name: qfield-fairy
+      - name: Comment PR
+        uses: thollander/actions-comment-pull-request@main
+        with:
+          GITHUB_TOKEN: ${{ secrets.FAIRY_TOKEN }}
+          message: |
+            🎉 Ta-daaa, freshly created APKs are available for ${{ github.event.pull_request.head.sha }}:
+              - [**arm64_v8a**](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-arm64_v8a.apk)
+
+            Other architectures: [armv7](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-armv7.apk), [x86_64](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86_64.apk), [x86](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86.apk)
+
+  comment_commit:
+    runs-on: ubuntu-20.04
+    needs: build
+    if: ${{ github.event_name == 'push' }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Vars
+        id: vars
+        run: |
+          ./scripts/ci/env_gh.sh
+      - name: Comment commit
+        uses: peter-evans/commit-comment@v1
+        with:
+          token: ${{ secrets.FAIRY_TOKEN }}
+          body: |
+            🎉 Ta-daaa, freshly created APKs are available:
+              - [**arm64_v8a**](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-arm64_v8a.apk)
+
+            Other architectures: [armv7](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-armv7.apk), [x86_64](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86_64.apk), [x86](https://sos-ch-dk-2.exo.io/qfieldapks/ci-builds/${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-x86.apk)

+ 162 - 0
.github/workflows/ios.yml

@@ -0,0 +1,162 @@
+name: 🍏 iOS build
+
+on:
+  push:
+    branches:
+      - master
+  release:
+    types:
+      - published
+      - released
+  pull_request:
+    branches:
+      - master
+  workflow_dispatch:
+
+jobs:
+  BuildIpa:
+
+    runs-on: macOS-latest
+    env:
+      SDK_VERSION: '141'
+      DEPLOYMENT_TARGET: '12.0'
+      PLATFORM: 'OS64'
+      QT_VERSION: '5.15.2'
+      BUILD_TYPE: 'Release'
+      BITCODE: 'FALSE'
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: 'recursive'
+
+    - uses: actions/setup-python@v2
+      with:
+        python-version: '3.8'
+
+    - name: "🌍 Pull Translations"
+      run: |
+        if [[ -z "${TX_TOKEN}" ]]; then
+          echo "TX_TOKEN not set, skip tx pull"
+        else
+          pip install transifex-client
+          ./scripts/ci/pull_translations.sh
+        fi
+      env:
+        TX_TOKEN: ${{ secrets.TX_TOKEN }}
+
+    - name: Download artifact
+      env:
+        SDK_VERSION: ${{ env.SDK_VERSION }}
+      run: |
+        wget -O OSGeo4I-arm64.zip https://github.com/opengisch/qfield-sdk/releases/download/${SDK_VERSION}/OSGeo4I-Qt${{ env.QT_VERSION }}-arm64-${{ env.SDK_VERSION }}.zip
+        unzip OSGeo4I-arm64.zip -d ${{ github.workspace }}
+
+    - name: Cache Qt
+      id: cache-qt
+      uses: pat-s/always-upload-cache@v2.1.5
+      with:
+        path: ${{ github.workspace }}/Qt-${{ runner.os }}-${{ env.QT_VERSION }}
+        key: ${{ runner.os }}-QtCache-${{ env.QT_VERSION }}-ios
+
+    - name: ⬆️ Install Qt
+      uses: jurplel/install-qt-action@v2
+      with:
+        version: ${{ env.QT_VERSION }}
+        target: ios
+        dir: ${{ github.workspace }}/Qt-${{ runner.os }}-${{ env.QT_VERSION }}
+        modules: 'qtcharts'
+        cached: ${{ steps.cache-qt.outputs.cache-hit }}
+
+    - name: 🔥 Delete Qt built-in styles (QField use Material)
+      run: |
+        rm -rf ${Qt5_Dir}/qml/QtQuick/Controls.2/designer
+        rm -rf ${Qt5_Dir}/qml/QtQuick/Controls.2/Fusion
+        rm -rf ${Qt5_Dir}/qml/QtQuick/Controls.2/Imagine
+        rm -rf ${Qt5_Dir}/qml/QtQuick/Controls.2/Universal
+      shell: bash
+
+    - uses: Apple-Actions/import-codesign-certs@v1
+      with:
+        p12-file-base64: ${{ secrets.IOS_CERTIFICATES_FILE_BASE64 }}
+        p12-password: ${{ secrets.IOS_CERTIFICATES_PASSWORD }}
+
+    - uses: Apple-Actions/download-provisioning-profiles@v1
+      with:
+        bundle-id: ch.opengis.qfield
+        issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
+        api-key-id: ${{ secrets.IOS_APPSTORE_KEY_ID }}
+        api-private-key: ${{ secrets.IOS_APPSTORE_PRIVATE_KEY }}
+
+    - name: Prepare env
+      run: |
+        ./scripts/ci/env_gh.sh
+
+    - name: 🔧 Configure
+      run: |
+        source ./scripts/version_number.sh
+        source ./scripts/ci/generate-version-details.sh
+        mkdir -p build
+        cmake \
+          -G "Xcode" \
+          -DAPK_VERSION_CODE=${APK_VERSION_CODE} \
+          -DAPP_VERSION=${APP_VERSION} \
+          -DAPP_VERSION_STR=${APP_VERSION_STR} \
+          -DAPP_PACKAGE_NAME=${APP_PACKAGE_NAME} \
+          -DCMAKE_PREFIX_PATH=${{ github.workspace }}/OSGeo4I/arm64 \
+          -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/OSGeo4I/cmake/ios.toolchain.cmake \
+          -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \
+          -DDEPLOYMENT_TARGET=${{ env.DEPLOYMENT_TARGET }} \
+          -DFORCE_STATIC_LIBS=TRUE \
+          -DPLATFORM=${{ env.PLATFORM }} \
+          -DENABLE_VISIBILITY=FALSE \
+          -DENABLE_BITCODE=${{ env.BITCODE }} \
+          -DENABLE_ARC=TRUE \
+          -DQT_IOS_TEAM_ID=${{ secrets.IOS_TEAM_ID }} \
+          -DQT_IOS_CODE_SIGN_IDENTITY=${{ secrets.IOS_CODE_SIGN_IDENTITY }} \
+          -DQT_IOS_PROVISIONING_PROFILE_SPECIFIER=${{ secrets.IOS_PROVISIONING_PROFILE_SPECIFIER }} \
+          -DENABLE_TESTS=OFF \
+          -B ./build/ -S .
+
+    - name: 🔨 Build qfield
+      run: cmake --build build --target qfield --config "${{ env.BUILD_TYPE }}"
+
+    - name: 🔨 Build qfield Archive
+      run: cmake --build build --target qfieldArchive --config "${{ env.BUILD_TYPE }}"
+
+    - name: 🚀 Deploy qfield Ipa
+      run: |
+        rm /Users/runner/work/QField/QField/build/output/bin/qfield.app
+        cmake --build build --target qfieldIpa --config "${{ env.BUILD_TYPE }}"
+
+    - name: 📦 Upload qfield App
+      uses: actions/upload-artifact@v2
+      with:
+        name: qfield-${{ env.PLATFORM }}-${{ env.DEPLOYMENT_TARGET }}.app
+        path: build/output/bin/${{ env.BUILD_TYPE }}/qfield.app
+
+    - name: 📦 Upload qfield Ipa
+      uses: actions/upload-artifact@v2
+      with:
+        name: qfield-i${{ env.PLATFORM }}-${{ env.DEPLOYMENT_TARGET }}.ipa
+        path: build/src/app/qfieldIpa/qfield.ipa
+
+    - uses: Apple-Actions/upload-testflight-build@v1
+      if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v')
+      name: 🚀 Release to TestFlight
+      with:
+        app-path: build/src/app/qfieldIpa/qfield.ipa
+        issuer-id: ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
+        api-key-id: ${{ secrets.IOS_APPSTORE_KEY_ID }}
+        api-private-key: ${{ secrets.IOS_APPSTORE_PRIVATE_KEY }}
+
+    - name: 🚀 Upload Release Asset
+      if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v')
+      uses: actions/upload-release-asset@v1
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        upload_url: ${{ github.event.release.upload_url }}
+        asset_path: build/src/app/qfieldIpa/qfield.ipa
+        asset_name: qfield-i${{ env.PLATFORM }}-${{ env.DEPLOYMENT_TARGET }}.ipa
+        asset_content_type: application/zip

+ 17 - 0
.github/workflows/release_drafter.yml

@@ -0,0 +1,17 @@
+name: 🚀 Release Drafter
+
+on:
+  push:
+    # branches to consider in the event; optional, defaults to all
+    branches:
+      - master
+
+jobs:
+  update_release_draft:
+    runs-on: ubuntu-latest
+    steps:
+      # Drafts your next Release notes as Pull Requests are merged into "master"
+      - uses: release-drafter/release-drafter@v5
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+

+ 21 - 0
.github/workflows/script_checks.yml

@@ -0,0 +1,21 @@
+name: 🤹 Script checks
+
+on:
+  push:
+    branches:
+      - master
+      - release-**
+  pull_request:
+  release:
+    types: ['published', 'released']
+
+jobs:
+  test:
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Check version number
+        run: |
+          ./test/test_version_number.sh

+ 20 - 0
.github/workflows/stale.yml

@@ -0,0 +1,20 @@
+name: 👓 Close stale issues
+on:
+  schedule:
+  - cron: "30 1 * * *"
+
+jobs:
+  stale:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/stale@v4
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        stale-issue-message: |
+                            The QField project highly values your report and would love to see it addressed. However, this issue has been left in feedback mode for the last 14 days and is being automatically marked as "stale". If you would like to continue with this issue, please provide any missing information or answer any open questions. If you could resolve the issue yourself meanwhile, please leave a note for future readers with the same problem and close the issue.
+                            In case you should have any uncertainty, please leave a comment and we will be happy to help you proceed with this issue.
+                            If there is no further activity on this issue, it will be closed in a week.
+        stale-issue-label: 'stale'
+        only-labels: 'feedback'
+        days-before-stale: 14
+        days-before-close: 7

+ 26 - 0
.github/workflows/translations.yml

@@ -0,0 +1,26 @@
+name: 🌎 Translations
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  translations:
+    runs-on: ubuntu-20.04
+    if: ${{ github.repository == 'opengisch/QField' }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Requirements
+        run: |
+          sudo apt update && sudo apt install qttools5-dev-tools qt5-default
+          sudo pip install wheel
+          sudo pip install pygithub transifex-client
+          ./scripts/ci/env_gh.sh
+
+      - name: "🌍 Push Translations"
+        env:
+          TX_TOKEN: ${{ secrets.TX_TOKEN }}
+        run: ./scripts/ci/update-translations.sh

+ 16 - 0
.github/workflows/unstale.yml

@@ -0,0 +1,16 @@
+name: 👓 Remove Labels
+
+on: [issue_comment]
+
+jobs:
+  remove_labels:
+    if: contains(github.event.issue.labels.*.name, 'stale')
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-ecosystem/action-remove-labels@v1
+        if: ${{ github.event.comment.user.url != 'https://github.com/apps/github-actions' }}
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          labels: |
+            stale

+ 314 - 0
.github/workflows/vcpkg.yml

@@ -0,0 +1,314 @@
+---
+name: 🎁 Package with vcpkg
+on:
+  push:
+    branches:
+      - master
+      - release-**
+  pull_request:
+  release:
+    types: ['published', 'released']
+
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - triplet: 'x64-windows'
+            os: 'windows-2019'
+
+          - triplet: 'x64-linux'
+            os: 'ubuntu-20.04'
+
+          - triplet: 'x64-osx'
+            os: 'macos-10.15'
+
+    steps:
+# Avoid that we run out of disk space
+#      - name: Free additional space
+#        uses: easimon/maximize-build-space@master
+#        if: ${{ matrix.triplet == 'x64-linux' }}
+#        with:
+#          remove-haskell: true # 9G
+#          remove-android: true # 18G
+#          remove-dotnet: true # 30G
+
+      - name: 🐣 Checkout
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+
+      - name: 🌾 Prepare vars
+        id: vars
+        shell: bash
+        run: |
+          ./scripts/ci/env_gh.sh
+
+          case ${{ matrix.triplet }} in
+            x64-windows)
+              BUILD_ROOT="C:"
+              echo "::set-output name=MONO::"
+              echo "::set-output name=PATHCONVERT::cygpath -u"
+              echo "::set-output name=EXCLUDE_TESTS::(smoke_test)"
+              ;;
+
+            x64-linux)
+              BUILD_ROOT="/home/runner"
+              echo "::set-output name=MONO::mono"
+              echo "::set-output name=OVERLAY::vcpkg/overlay_system_qt"
+              echo "::set-output name=PATHCONVERT::echo"
+              echo "::set-output name=INSTALL_QT::true"
+              echo "::set-output name=QT_TARGET::desktop"
+              echo "::set-output name=QT_ARCH::"
+              ;;
+
+            x64-osx)
+              BUILD_ROOT="/Users/runner"
+              echo "::set-output name=MONO::mono"
+              echo "::set-output name=OVERLAY::vcpkg/overlay_system_qt"
+              echo "::set-output name=PATHCONVERT::echo"
+              echo "::set-output name=INSTALL_QT::true"
+              echo "::set-output name=QT_TARGET::desktop"
+              echo "::set-output name=QT_ARCH::"
+              ;;
+          esac
+
+          echo "::set-output name=VCPKG_ROOT::${BUILD_ROOT}/src"
+          echo "::set-output name=BUILD_TYPE::Release"
+          echo "::set-output name=BUILD_ROOT::${BUILD_ROOT}"
+
+          echo "VCPKG_ROOT=${BUILD_ROOT}/src" >> $GITHUB_ENV
+          echo "CMAKE_BUILD_DIR=${BUILD_ROOT}/builddir" >> $GITHUB_ENV
+          echo "VCPKG_DEFAULT_BINARY_CACHE=${BUILD_ROOT}/vcpkg_cache" >> $GITHUB_ENV
+
+      - name: 🐩 Install CMake and Ninja
+        uses: lukka/get-cmake@latest
+
+      - name: 📪 Clone vcpkg
+        shell: bash
+        run: |
+          VCPKG_SHA=$(head -1 .git/modules/vcpkg/HEAD)
+          mkdir -p "${{ env.VCPKG_DEFAULT_BINARY_CACHE }}"
+          mkdir -p "${{ steps.vars.outputs.VCPKG_ROOT }}"
+          cd "${{ steps.vars.outputs.VCPKG_ROOT }}"
+          git init
+          git remote add origin https://github.com/microsoft/vcpkg.git
+          git fetch --depth 1 origin $VCPKG_SHA
+          git checkout FETCH_HEAD
+
+      - name: 📫 Cache vcpkg
+        id: cache-vcpkg-tool
+        uses: pat-s/always-upload-cache@v2.1.5
+        with:
+          path: |
+            ${{ steps.vars.outputs.VCPKG_ROOT }}/vcpkg
+            ${{ steps.vars.outputs.VCPKG_ROOT }}/vcpkg.exe
+          key: ${{ runner.os }}-${{ hashFiles('.git/modules/vcpkg/HEAD') }}-x
+
+      - name: 📬 Bootstrap vcpkg
+        if: steps.cache-vcpkg-tool.outputs.cache-hit != 'true'
+        shell: bash
+        working-directory: ${{ steps.vars.outputs.VCPKG_ROOT }}
+        run: |
+          if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null ; then
+            ./bootstrap-vcpkg.bat
+          else
+            ./bootstrap-vcpkg.sh
+          fi
+
+      - name: 🔐 Setup NuGet Credentials
+        shell: bash
+        run: |
+          ${{ steps.vars.outputs.VCPKG_ROOT }}/vcpkg fetch nuget
+          ${{ steps.vars.outputs.MONO }} $(${{ steps.vars.outputs.VCPKG_ROOT }}/vcpkg fetch nuget | tail -n 1) \
+          sources add \
+          -source "https://nuget.pkg.github.com/opengisch/index.json" \
+          -storepasswordincleartext \
+          -name "GitHub" \
+          -username "opengisch" \
+          -password "${{ secrets.GITHUB_TOKEN }}"
+
+          ${{ steps.vars.outputs.MONO }} $(${{ steps.vars.outputs.VCPKG_ROOT }}/vcpkg fetch nuget | tail -n 1) \
+          setapikey ${{ secrets.GITHUB_TOKEN }} -src "https://nuget.pkg.github.com/opengisch/index.json"
+
+      - name: 🧽 Developer Command Prompt for Microsoft Visual C++
+        uses: ilammy/msvc-dev-cmd@v1
+        if: ${{ matrix.os == 'windows-2019' }}
+
+      - name: 🔥 Free additional space
+        if: ${{ matrix.triplet == 'x64-linux' }}
+        run: |
+          df -h
+          sudo rm -rf /usr/share/dotnet/sdk
+          sudo rm -rf /usr/share/rust
+          sudo rm -rf /usr/share/swift
+          sudo rm -rf /usr/local/lib/android
+          sudo apt remove llvm-* ghc-* google-chrome-* dotnet-sdk-* azure-cli google-cloud-sdk google-chrome-stable firefox
+          dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100
+          du -a /usr/share | sort -n -r | head -n 10
+          du -a /usr/local/share | sort -n -r | head -n 10
+          df -h
+          sudo apt clean
+          df -h
+
+      - name: 💐 Install Qt
+        if: ${{ steps.vars.outputs.INSTALL_QT }}
+        uses: jurplel/install-qt-action@v2
+        with:
+          version: 5.15.2
+          modules: 'qtcharts'
+          target: ${{ steps.vars.outputs.QT_TARGET }}
+          arch: ${{ steps.vars.outputs.QT_ARCH }}
+
+      - name: 🐧 Prepare linux build env
+        if: ${{ matrix.triplet == 'x64-linux' }}
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y gperf autopoint '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev
+          sudo apt-get remove -y libopenexr-dev # Avoid gdal picking this system lib up
+          # Required to run unit tests on linux
+          echo "QT_QPA_PLATFORM=offscreen" >> $GITHUB_ENV
+          echo "TESTWRAPPER=xvfb-run" >> $GITHUB_ENV
+          echo "EXTRA_CMAKE_ARGS=-GNinja -DCMAKE_INSTALL_PREFIX=/usr -DLINUXDEPLOY_EXECUTABLE=${{ steps.vars.outputs.BUILD_ROOT }}/linuxdeploy-x86_64.AppImage" >> $GITHUB_ENV
+
+      - name: Install linuxdeploy
+        if: ${{ matrix.triplet == 'x64-linux' }}
+        uses: miurahr/install-linuxdeploy-action@v1
+        with:
+          dir: ${{ steps.vars.outputs.BUILD_ROOT }}
+          plugins: qt appimage
+
+      # transifex-client is not compatible with py >= 3.10
+      # temporary band aid
+      - uses: actions/setup-python@v2
+        with:
+          python-version: '3.9'
+
+      - name: 🌍 Pull Translations
+        shell: bash
+        env:
+          TX_TOKEN: ${{ secrets.TX_TOKEN }}
+        run: |
+          if [[ -z "${TX_TOKEN}" ]]; then
+            echo "TX_TOKEN not set, skip tx pull"
+          else
+            pip install transifex-client>=0.14.3
+            ./scripts/ci/pull_translations.sh
+          fi
+
+      - name: Prepare osx build env
+        if: ${{ matrix.os == 'macos-10.15' }}
+        run: |
+          brew install automake bison flex
+          echo $(brew --prefix bison)/bin >> $GITHUB_PATH
+          echo $(brew --prefix flex)/bin >> $GITHUB_PATH
+          echo "EXTRA_CMAKE_ARGS=-GXcode -DUSE_MAC_BUNDLING=OFF" >> $GITHUB_ENV
+          sudo xcode-select -s /Applications/Xcode_12.app/Contents/Developer
+
+      - name: 🌱 Install dependencies and generate project files
+        shell: bash
+        env:
+          WORKSPACE: ${{ github.workspace }}
+          VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite'
+        run: |
+          # Convert paths to bash compatible ones. Thanks to whoever decided to use drive letters and backslashes.
+          CMAKE_BUILD_DIR=$( ${{ steps.vars.outputs.PATHCONVERT }} "${CMAKE_BUILD_DIR}" )
+          VCPKG_ROOT=$( ${{ steps.vars.outputs.PATHCONVERT }} "${VCPKG_ROOT}" )
+          SOURCE_DIR=$( ${{ steps.vars.outputs.PATHCONVERT }} "${WORKSPACE}" )
+
+          source ./scripts/version_number.sh
+          source ./scripts/ci/generate-version-details.sh
+
+          overlay_ports=(${WORKSPACE}/${{ steps.vars.outputs.OVERLAY }} ${WORKSPACE}/vcpkg/overlay)
+          echo "Building with $(IFS=\; ; echo "${overlay_ports[*]}")"
+          cmake -S "${SOURCE_DIR}" \
+                -B "${CMAKE_BUILD_DIR}" \
+                -DCMAKE_BUILD_TYPE=${{ steps.vars.outputs.BUILD_TYPE }} \
+                -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \
+                -DVCPKG_OVERLAY_PORTS=$(IFS=\; ; echo "${overlay_ports[*]}") \
+                -DVCPKG_TARGET_TRIPLET="${{ matrix.triplet }}" \
+                -DWITH_VCPKG=ON \
+                -DWITH_SPIX=ON \
+                -DAPP_VERSION="${APP_VERSION}" \
+                -DAPP_VERSION_STR="${APP_VERSION_STR}" \
+                -DAPP_PACKAGE_NAME="${APP_PACKAGE_NAME}" \
+                -DENABLE_TESTS=ON \
+                ${EXTRA_CMAKE_ARGS}
+
+      - name: 📑 Upload Build Logs
+        uses: actions/upload-artifact@v2
+        if: failure()
+        with:
+          name: build-logs-${{ matrix.triplet }}
+          path: ${{ steps.vars.outputs.VCPKG_ROOT }}/buildtrees/**/*.log
+
+      - name: 🌋 Build
+        run: |
+          cmake --build "${{ env.CMAKE_BUILD_DIR }}" --config ${{ steps.vars.outputs.BUILD_TYPE }} # --target qfield
+
+      - name: 🧫 Test
+        shell: bash
+        env:
+          WORKSPACE: ${{ github.workspace }}
+        run: |
+          SOURCE_DIR=$( ${{ steps.vars.outputs.PATHCONVERT }} "${WORKSPACE}" )
+          pip install -r "${SOURCE_DIR}/test/spix/requirements.txt"
+          cd "${{ env.CMAKE_BUILD_DIR }}"
+          ${TESTWRAPPER} ctest --output-on-failure -E '${{ steps.vars.outputs.EXCLUDE_TESTS }}' -C ${{ steps.vars.outputs.BUILD_TYPE }}
+
+      - name: Package
+        shell: bash
+        run: |
+          export LD_LIBRARY_PATH="${{ env.CMAKE_BUILD_DIR }}/vcpkg_installed/${{ matrix.triplet }}/lib/":${LD_LIBRARY_PATH}
+          cmake --build  "${{ env.CMAKE_BUILD_DIR }}" --target bundle --config Release
+          case ${{ matrix.triplet }} in
+            x64-linux)
+              echo "ARTIFACT_PATHNAME=${{ env.CMAKE_BUILD_DIR }}/QField-x86_64.AppImage" >> $GITHUB_ENV
+              echo "ARTIFACT_NAME=${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-linux-x64.AppImage" >> $GITHUB_ENV
+              ;;
+
+            x64-windows)
+              ARTIFACT_PATHNAME=$(ls ${{ env.CMAKE_BUILD_DIR }}/QField-*-win64.* | head -n 1)
+              ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME)
+              echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV
+              echo "ARTIFACT_NAME=${{ steps.vars.outputs.CI_PACKAGE_FILE_BASENAME }}-windows-x64.exe" >> $GITHUB_ENV
+              ;;
+          esac
+
+      - name: 📑 Upload Package Logs
+        uses: actions/upload-artifact@v2
+        if: failure()
+        with:
+          name: package-logs-${{ matrix.triplet }}
+          path: ${{ steps.vars.outputs.BUILD_ROOT }}/builddir/_CPack_Packages/**/*.log
+
+      - name: 📦 Upload package
+        if: ${{ env.ARTIFACT_NAME != null }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: "QField-dev-${{ matrix.triplet }}-${{ steps.vars.outputs.BUILD_TYPE }}"
+          path: ${{ env.ARTIFACT_PATHNAME }}
+
+      - name: 📊 Upload test report
+        uses: actions/upload-artifact@v2
+        with:
+          name: "test-report-${{ matrix.triplet }}-${{ steps.vars.outputs.BUILD_TYPE }}"
+          path: "${{ env.CMAKE_BUILD_DIR }}/report"
+
+      - name: 🪔 Get the current release version
+        id: get_version
+        shell: bash
+        run: |
+          echo "::set-output name=version::${GITHUB_REF##*/}"
+
+      - name: 🚀 Upload Release Asset
+        if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') && env.ARTIFACT_NAME != null
+        uses: shogo82148/actions-upload-release-asset@v1
+        with:
+          upload_url: ${{ github.event.release.upload_url }}
+          asset_path: ${{ env.ARTIFACT_PATHNAME }}
+          asset_name: ${{ env.ARTIFACT_NAME }}
+          overwrite: true

+ 28 - 0
.gitignore

@@ -0,0 +1,28 @@
+CMakeLists.txt.user*
+sha-*.diff
+astyle.*.diff
+*.prepare
+*.orig
+*.iml
+*.swp
+*.pyc
+__pycache__
+*.autosave
+.DS_Store
+.idea
+/keystore.p12
+/QField.pro.user
+/QField.pro.user.*
+/config.pri
+/build*
+/scripts/astyle
+/android/AndroidManifest.xml
+/android/gradle.properties
+/android/local.properties
+/android/res/values/generated.xml
+/android/res/values-*_*/strings.xml
+/android/.idea
+/android/.build
+/android/.gradle
+/.vscode
+*~

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "vcpkg"]
+	path = vcpkg/base
+	url = https://github.com/microsoft/vcpkg.git

+ 49 - 0
.pre-commit-config.yaml

@@ -0,0 +1,49 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+exclude: |
+  (?x)^(
+    vcpkg/|
+    3rdparty/ |
+    .clang-format$
+  )
+
+repos:
+# Base
+- repo: https://github.com/pre-commit/pre-commit-hooks
+  rev: v3.2.0
+  hooks:
+    - id: trailing-whitespace
+    - id: check-yaml
+    - id: check-added-large-files
+
+# Shellcheck
+- repo: https://github.com/jumanjihouse/pre-commit-hooks
+  rev: 2.1.5
+  hooks:
+    - id: shellcheck
+      args: ['-e', 'SC2016,SC2015,SC2086,SC2002,SC1117,SC2154,SC2076,SC2046,SC1090,SC2038,SC2031,SC2030,SC2162,SC2044,SC2119,SC1001,SC2120,SC2059,SC2128,SC2005,SC2013,SC2027,SC2090,SC2089,SC2124,SC2001,SC2010,SC1072,SC1073,SC1009,SC2166,SC2045,SC2028,SC1091,SC1083,SC2021']
+      exclude: android
+
+- repo: local
+  hooks:
+    - id: astyle
+      name: AStyle
+      description: Art is what I call style
+      entry: ./scripts/astyle.sh
+      language: script
+      types: [text]
+
+- repo: https://github.com/ambv/black
+  rev: 21.9b0
+  hooks:
+    - id: black
+
+
+- repo: https://github.com/cheshirekow/cmake-format-precommit
+  rev: v0.6.13
+  hooks:
+  - id: cmake-format
+    additional_dependencies: [pyyaml>=5.1]
+    args: [--in-place]
+    files: '^(src|test)/.*/CMakeLists.txt'
+

+ 16 - 0
.tx/config

@@ -0,0 +1,16 @@
+[main]
+host = https://www.transifex.com
+lang_map = zh-Hant: zh_TW
+
+[qfield-for-qgis.qfield]
+file_filter = i18n/qfield_<lang>.ts
+source_file = i18n/qfield_en.ts
+source_lang = en
+type = QT
+
+[qfield-for-qgis.qfield_android]
+file_filter = android/res/values-<lang>/strings.xml
+source_file = android/res/values/strings.xml
+source_lang = en
+type = ANDROID
+

+ 3 - 0
3rdparty/tessellate/.gitignore

@@ -0,0 +1,3 @@
+*~
+*dSym
+tessellate

+ 37 - 0
3rdparty/tessellate/CMakeLists.txt

@@ -0,0 +1,37 @@
+
+project(libtess)
+
+set(TESS_SRC
+  dict.c  
+  geom.c  
+  # main.c  
+  memalloc.c  
+  mesh.c  
+  normal.c  
+  priorityq.c  
+  # priorityq-heap.c  
+  render.c  
+  sweep.c  
+  tess.c  
+  tessellate.c  
+  tessmono.c
+)
+
+include_directories(include)
+
+set (CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+
+add_library(tess STATIC ${TESS_SRC})
+if(NOT MSVC)
+    target_link_libraries(tess m)
+endif()
+
+
+set_property(TARGET tess PROPERTY AUTOMOC OFF)
+set_property(TARGET tess PROPERTY AUTOUIC OFF)
+set_property(TARGET tess PROPERTY AUTOGEN OFF)
+set_property(TARGET tess PROPERTY AUTORCC OFF)
+
+
+# add_executable(tess_example main.c)
+# target_link_libraries(tess_example tess)

+ 37 - 0
3rdparty/tessellate/LICENSE

@@ -0,0 +1,37 @@
+Copyright notice and license for the libtess files (all source files besides
+tessellate.[ch] and main.c):
+
+SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice including the dates of first publication and
+either this permission notice or a reference to
+http://oss.sgi.com/projects/FreeB/
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Except as contained in this notice, the name of Silicon Graphics, Inc.
+shall not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization from
+Silicon Graphics, Inc.
+
+--------------------------------------------------------------------------------
+
+Copyright notice for the other files:
+
+SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+Copyright (C) 2013 AT&T Intellectual Property. All Rights Reserved.

+ 13 - 0
3rdparty/tessellate/README.md

@@ -0,0 +1,13 @@
+# A minimal, self-contained port of SGI's GLU libtess
+
+Polygon tessellation is a major pain in the neck. Have you ever tried
+writing fast and robust code for it? libtess is, to my knowledge, the
+only GPL-compatible, liberally-licensed, high-quality polygon
+triangulator out there.
+
+This repository includes a self-contained function (tessellate, in
+tessellate.c) that you can call to triangulate a polygon that is
+potentially self-intersecting, with holes, or with duplicate
+vertices. Simple examples of calling the tessellate function directly
+are located in main.c.
+

+ 100 - 0
3rdparty/tessellate/dict-list.h

@@ -0,0 +1,100 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __dict_list_h_
+#define __dict_list_h_
+
+/* Use #define's so that another heap implementation can use this one */
+
+#define DictKey		DictListKey
+#define Dict		DictList
+#define DictNode	DictListNode
+
+#define dictNewDict(frame,leq)		__gl_dictListNewDict(frame,leq)
+#define dictDeleteDict(dict)		__gl_dictListDeleteDict(dict)
+
+#define dictSearch(dict,key)		__gl_dictListSearch(dict,key)
+#define dictInsert(dict,key)		__gl_dictListInsert(dict,key)
+#define dictInsertBefore(dict,node,key)	__gl_dictListInsertBefore(dict,node,key)
+#define dictDelete(dict,node)		__gl_dictListDelete(dict,node)
+
+#define dictKey(n)			__gl_dictListKey(n)
+#define dictSucc(n)			__gl_dictListSucc(n)
+#define dictPred(n)			__gl_dictListPred(n)
+#define dictMin(d)			__gl_dictListMin(d)
+#define dictMax(d)			__gl_dictListMax(d)
+
+
+
+typedef void *DictKey;
+typedef struct Dict Dict;
+typedef struct DictNode DictNode;
+
+Dict		*dictNewDict(
+			void *frame,
+			int (*leq)(void *frame, DictKey key1, DictKey key2) );
+			
+void		dictDeleteDict( Dict *dict );
+
+/* Search returns the node with the smallest key greater than or equal
+ * to the given key.  If there is no such key, returns a node whose
+ * key is NULL.  Similarly, Succ(Max(d)) has a NULL key, etc.
+ */
+DictNode	*dictSearch( Dict *dict, DictKey key );
+DictNode	*dictInsertBefore( Dict *dict, DictNode *node, DictKey key );
+void		dictDelete( Dict *dict, DictNode *node );
+
+#define		__gl_dictListKey(n)	((n)->key)
+#define		__gl_dictListSucc(n)	((n)->next)
+#define		__gl_dictListPred(n)	((n)->prev)
+#define		__gl_dictListMin(d)	((d)->head.next)
+#define		__gl_dictListMax(d)	((d)->head.prev)
+#define	       __gl_dictListInsert(d,k) (dictInsertBefore((d),&(d)->head,(k)))
+
+
+/*** Private data structures ***/
+
+struct DictNode {
+  DictKey	key;
+  DictNode	*next;
+  DictNode	*prev;
+};
+
+struct Dict {
+  DictNode	head;
+  void		*frame;
+  int		(*leq)(void *frame, DictKey key1, DictKey key2);
+};
+
+#endif

+ 111 - 0
3rdparty/tessellate/dict.c

@@ -0,0 +1,111 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include <stddef.h>
+#include "dict-list.h"
+#include "memalloc.h"
+
+/* really __gl_dictListNewDict */
+Dict *dictNewDict( void *frame,
+		   int (*leq)(void *frame, DictKey key1, DictKey key2) )
+{
+  Dict *dict = (Dict *) memAlloc( sizeof( Dict ));
+  DictNode *head;
+
+  if (dict == NULL) return NULL;
+
+  head = &dict->head;
+
+  head->key = NULL;
+  head->next = head;
+  head->prev = head;
+
+  dict->frame = frame;
+  dict->leq = leq;
+
+  return dict;
+}
+
+/* really __gl_dictListDeleteDict */
+void dictDeleteDict( Dict *dict )
+{
+  DictNode *node, *next;
+
+  for( node = dict->head.next; node != &dict->head; node = next ) {
+    next = node->next;
+    memFree( node );
+  }
+  memFree( dict );
+}
+
+/* really __gl_dictListInsertBefore */
+DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key )
+{
+  DictNode *newNode;
+
+  do {
+    node = node->prev;
+  } while( node->key != NULL && ! (*dict->leq)(dict->frame, node->key, key));
+
+  newNode = (DictNode *) memAlloc( sizeof( DictNode ));
+  if (newNode == NULL) return NULL;
+
+  newNode->key = key;
+  newNode->next = node->next;
+  node->next->prev = newNode;
+  newNode->prev = node;
+  node->next = newNode;
+
+  return newNode;
+}
+
+/* really __gl_dictListDelete */
+void dictDelete( Dict *dict, DictNode *node ) /*ARGSUSED*/
+{
+  node->next->prev = node->prev;
+  node->prev->next = node->next;
+  memFree( node );
+}
+
+/* really __gl_dictListSearch */
+DictNode *dictSearch( Dict *dict, DictKey key )
+{
+  DictNode *node = &dict->head;
+
+  do {
+    node = node->next;
+  } while( node->key != NULL && ! (*dict->leq)(dict->frame, key, node->key));
+
+  return node;
+}

+ 100 - 0
3rdparty/tessellate/dict.h

@@ -0,0 +1,100 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __dict_list_h_
+#define __dict_list_h_
+
+/* Use #define's so that another heap implementation can use this one */
+
+#define DictKey		DictListKey
+#define Dict		DictList
+#define DictNode	DictListNode
+
+#define dictNewDict(frame,leq)		__gl_dictListNewDict(frame,leq)
+#define dictDeleteDict(dict)		__gl_dictListDeleteDict(dict)
+
+#define dictSearch(dict,key)		__gl_dictListSearch(dict,key)
+#define dictInsert(dict,key)		__gl_dictListInsert(dict,key)
+#define dictInsertBefore(dict,node,key)	__gl_dictListInsertBefore(dict,node,key)
+#define dictDelete(dict,node)		__gl_dictListDelete(dict,node)
+
+#define dictKey(n)			__gl_dictListKey(n)
+#define dictSucc(n)			__gl_dictListSucc(n)
+#define dictPred(n)			__gl_dictListPred(n)
+#define dictMin(d)			__gl_dictListMin(d)
+#define dictMax(d)			__gl_dictListMax(d)
+
+
+
+typedef void *DictKey;
+typedef struct Dict Dict;
+typedef struct DictNode DictNode;
+
+Dict		*dictNewDict(
+			void *frame,
+			int (*leq)(void *frame, DictKey key1, DictKey key2) );
+			
+void		dictDeleteDict( Dict *dict );
+
+/* Search returns the node with the smallest key greater than or equal
+ * to the given key.  If there is no such key, returns a node whose
+ * key is NULL.  Similarly, Succ(Max(d)) has a NULL key, etc.
+ */
+DictNode	*dictSearch( Dict *dict, DictKey key );
+DictNode	*dictInsertBefore( Dict *dict, DictNode *node, DictKey key );
+void		dictDelete( Dict *dict, DictNode *node );
+
+#define		__gl_dictListKey(n)	((n)->key)
+#define		__gl_dictListSucc(n)	((n)->next)
+#define		__gl_dictListPred(n)	((n)->prev)
+#define		__gl_dictListMin(d)	((d)->head.next)
+#define		__gl_dictListMax(d)	((d)->head.prev)
+#define	       __gl_dictListInsert(d,k) (dictInsertBefore((d),&(d)->head,(k)))
+
+
+/*** Private data structures ***/
+
+struct DictNode {
+  DictKey	key;
+  DictNode	*next;
+  DictNode	*prev;
+};
+
+struct Dict {
+  DictNode	head;
+  void		*frame;
+  int		(*leq)(void *frame, DictKey key1, DictKey key2);
+};
+
+#endif

+ 264 - 0
3rdparty/tessellate/geom.c

@@ -0,0 +1,264 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <assert.h>
+#include "mesh.h"
+#include "geom.h"
+
+int __gl_vertLeq( GLUvertex *u, GLUvertex *v )
+{
+  /* Returns TRUE if u is lexicographically <= v. */
+
+  return VertLeq( u, v );
+}
+
+GLdouble __gl_edgeEval( GLUvertex *u, GLUvertex *v, GLUvertex *w )
+{
+  /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w),
+   * evaluates the t-coord of the edge uw at the s-coord of the vertex v.
+   * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v.
+   * If uw is vertical (and thus passes thru v), the result is zero.
+   *
+   * The calculation is extremely accurate and stable, even when v
+   * is very close to u or w.  In particular if we set v->t = 0 and
+   * let r be the negated result (this evaluates (uw)(v->s)), then
+   * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t).
+   */
+  GLdouble gapL, gapR;
+
+  assert( VertLeq( u, v ) && VertLeq( v, w ));
+  
+  gapL = v->s - u->s;
+  gapR = w->s - v->s;
+
+  if( gapL + gapR > 0 ) {
+    if( gapL < gapR ) {
+      return (v->t - u->t) + (u->t - w->t) * (gapL / (gapL + gapR));
+    } else {
+      return (v->t - w->t) + (w->t - u->t) * (gapR / (gapL + gapR));
+    }
+  }
+  /* vertical line */
+  return 0;
+}
+
+GLdouble __gl_edgeSign( GLUvertex *u, GLUvertex *v, GLUvertex *w )
+{
+  /* Returns a number whose sign matches EdgeEval(u,v,w) but which
+   * is cheaper to evaluate.  Returns > 0, == 0 , or < 0
+   * as v is above, on, or below the edge uw.
+   */
+  GLdouble gapL, gapR;
+
+  assert( VertLeq( u, v ) && VertLeq( v, w ));
+  
+  gapL = v->s - u->s;
+  gapR = w->s - v->s;
+
+  if( gapL + gapR > 0 ) {
+    return (v->t - w->t) * gapL + (v->t - u->t) * gapR;
+  }
+  /* vertical line */
+  return 0;
+}
+
+
+/***********************************************************************
+ * Define versions of EdgeSign, EdgeEval with s and t transposed.
+ */
+
+GLdouble __gl_transEval( GLUvertex *u, GLUvertex *v, GLUvertex *w )
+{
+  /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w),
+   * evaluates the t-coord of the edge uw at the s-coord of the vertex v.
+   * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v.
+   * If uw is vertical (and thus passes thru v), the result is zero.
+   *
+   * The calculation is extremely accurate and stable, even when v
+   * is very close to u or w.  In particular if we set v->s = 0 and
+   * let r be the negated result (this evaluates (uw)(v->t)), then
+   * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s).
+   */
+  GLdouble gapL, gapR;
+
+  assert( TransLeq( u, v ) && TransLeq( v, w ));
+  
+  gapL = v->t - u->t;
+  gapR = w->t - v->t;
+
+  if( gapL + gapR > 0 ) {
+    if( gapL < gapR ) {
+      return (v->s - u->s) + (u->s - w->s) * (gapL / (gapL + gapR));
+    } else {
+      return (v->s - w->s) + (w->s - u->s) * (gapR / (gapL + gapR));
+    }
+  }
+  /* vertical line */
+  return 0;
+}
+
+GLdouble __gl_transSign( GLUvertex *u, GLUvertex *v, GLUvertex *w )
+{
+  /* Returns a number whose sign matches TransEval(u,v,w) but which
+   * is cheaper to evaluate.  Returns > 0, == 0 , or < 0
+   * as v is above, on, or below the edge uw.
+   */
+  GLdouble gapL, gapR;
+
+  assert( TransLeq( u, v ) && TransLeq( v, w ));
+  
+  gapL = v->t - u->t;
+  gapR = w->t - v->t;
+
+  if( gapL + gapR > 0 ) {
+    return (v->s - w->s) * gapL + (v->s - u->s) * gapR;
+  }
+  /* vertical line */
+  return 0;
+}
+
+
+int __gl_vertCCW( GLUvertex *u, GLUvertex *v, GLUvertex *w )
+{
+  /* For almost-degenerate situations, the results are not reliable.
+   * Unless the floating-point arithmetic can be performed without
+   * rounding errors, *any* implementation will give incorrect results
+   * on some degenerate inputs, so the client must have some way to
+   * handle this situation.
+   */
+  return (u->s*(v->t - w->t) + v->s*(w->t - u->t) + w->s*(u->t - v->t)) >= 0;
+}
+
+/* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b),
+ * or (x+y)/2 if a==b==0.  It requires that a,b >= 0, and enforces
+ * this in the rare case that one argument is slightly negative.
+ * The implementation is extremely stable numerically.
+ * In particular it guarantees that the result r satisfies
+ * MIN(x,y) <= r <= MAX(x,y), and the results are very accurate
+ * even when a and b differ greatly in magnitude.
+ */
+#define RealInterpolate(a,x,b,y)			\
+  (a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b,		\
+  ((a <= b) ? ((b == 0) ? ((x+y) / 2)			\
+                        : (x + (y-x) * (a/(a+b))))	\
+            : (y + (x-y) * (b/(a+b)))))
+
+#ifndef FOR_TRITE_TEST_PROGRAM
+#define Interpolate(a,x,b,y)	RealInterpolate(a,x,b,y)
+#else
+
+/* Claim: the ONLY property the sweep algorithm relies on is that
+ * MIN(x,y) <= r <= MAX(x,y).  This is a nasty way to test that.
+ */
+#include <stdlib.h>
+extern int RandomInterpolate;
+
+GLdouble Interpolate( GLdouble a, GLdouble x, GLdouble b, GLdouble y)
+{
+printf("*********************%d\n",RandomInterpolate);
+  if( RandomInterpolate ) {
+    a = 1.2 * drand48() - 0.1;
+    a = (a < 0) ? 0 : ((a > 1) ? 1 : a);
+    b = 1.0 - a;
+  }
+  return RealInterpolate(a,x,b,y);
+}
+
+#endif
+
+#define Swap(a,b)	do { GLUvertex *t = a; a = b; b = t; } while (0)
+
+void __gl_edgeIntersect( GLUvertex *o1, GLUvertex *d1,
+			 GLUvertex *o2, GLUvertex *d2,
+			 GLUvertex *v )
+/* Given edges (o1,d1) and (o2,d2), compute their point of intersection.
+ * The computed point is guaranteed to lie in the intersection of the
+ * bounding rectangles defined by each edge.
+ */
+{
+  GLdouble z1, z2;
+
+  /* This is certainly not the most efficient way to find the intersection
+   * of two line segments, but it is very numerically stable.
+   *
+   * Strategy: find the two middle vertices in the VertLeq ordering,
+   * and interpolate the intersection s-value from these.  Then repeat
+   * using the TransLeq ordering to find the intersection t-value.
+   */
+
+  if( ! VertLeq( o1, d1 )) { Swap( o1, d1 ); }
+  if( ! VertLeq( o2, d2 )) { Swap( o2, d2 ); }
+  if( ! VertLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); }
+
+  if( ! VertLeq( o2, d1 )) {
+    /* Technically, no intersection -- do our best */
+    v->s = (o2->s + d1->s) / 2;
+  } else if( VertLeq( d1, d2 )) {
+    /* Interpolate between o2 and d1 */
+    z1 = EdgeEval( o1, o2, d1 );
+    z2 = EdgeEval( o2, d1, d2 );
+    if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+    v->s = Interpolate( z1, o2->s, z2, d1->s );
+  } else {
+    /* Interpolate between o2 and d2 */
+    z1 = EdgeSign( o1, o2, d1 );
+    z2 = -EdgeSign( o1, d2, d1 );
+    if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+    v->s = Interpolate( z1, o2->s, z2, d2->s );
+  }
+
+  /* Now repeat the process for t */
+
+  if( ! TransLeq( o1, d1 )) { Swap( o1, d1 ); }
+  if( ! TransLeq( o2, d2 )) { Swap( o2, d2 ); }
+  if( ! TransLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); }
+
+  if( ! TransLeq( o2, d1 )) {
+    /* Technically, no intersection -- do our best */
+    v->t = (o2->t + d1->t) / 2;
+  } else if( TransLeq( d1, d2 )) {
+    /* Interpolate between o2 and d1 */
+    z1 = TransEval( o1, o2, d1 );
+    z2 = TransEval( o2, d1, d2 );
+    if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+    v->t = Interpolate( z1, o2->t, z2, d1->t );
+  } else {
+    /* Interpolate between o2 and d2 */
+    z1 = TransSign( o1, o2, d1 );
+    z2 = -TransSign( o1, d2, d1 );
+    if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
+    v->t = Interpolate( z1, o2->t, z2, d2->t );
+  }
+}

+ 84 - 0
3rdparty/tessellate/geom.h

@@ -0,0 +1,84 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __geom_h_
+#define __geom_h_
+
+#include "mesh.h"
+
+#ifdef NO_BRANCH_CONDITIONS
+/* MIPS architecture has special instructions to evaluate boolean
+ * conditions -- more efficient than branching, IF you can get the
+ * compiler to generate the right instructions (SGI compiler doesn't)
+ */
+#define VertEq(u,v)	(((u)->s == (v)->s) & ((u)->t == (v)->t))
+#define VertLeq(u,v)	(((u)->s < (v)->s) | \
+                         ((u)->s == (v)->s & (u)->t <= (v)->t))
+#else
+#define VertEq(u,v)	((u)->s == (v)->s && (u)->t == (v)->t)
+#define VertLeq(u,v)	(((u)->s < (v)->s) || \
+                         ((u)->s == (v)->s && (u)->t <= (v)->t))
+#endif
+
+#define EdgeEval(u,v,w) __gl_edgeEval(u,v,w)
+#define EdgeSign(u,v,w) __gl_edgeSign(u,v,w)
+
+/* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */
+
+#define TransLeq(u,v)	(((u)->t < (v)->t) || \
+                         ((u)->t == (v)->t && (u)->s <= (v)->s))
+#define TransEval(u,v,w)	__gl_transEval(u,v,w)
+#define TransSign(u,v,w)	__gl_transSign(u,v,w)
+
+
+#define EdgeGoesLeft(e) 	VertLeq( (e)->Dst, (e)->Org )
+#define EdgeGoesRight(e)	VertLeq( (e)->Org, (e)->Dst )
+
+#undef	ABS
+#define ABS(x)	((x) < 0 ? -(x) : (x))
+#define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t))
+
+#define VertCCW(u,v,w)	__gl_vertCCW(u,v,w)
+
+int		__gl_vertLeq( GLUvertex *u, GLUvertex *v );
+GLdouble	__gl_edgeEval( GLUvertex *u, GLUvertex *v, GLUvertex *w );
+GLdouble	__gl_edgeSign( GLUvertex *u, GLUvertex *v, GLUvertex *w );
+GLdouble	__gl_transEval( GLUvertex *u, GLUvertex *v, GLUvertex *w );
+GLdouble	__gl_transSign( GLUvertex *u, GLUvertex *v, GLUvertex *w );
+int		__gl_vertCCW( GLUvertex *u, GLUvertex *v, GLUvertex *w );
+void		__gl_edgeIntersect( GLUvertex *o1, GLUvertex *d1,
+				    GLUvertex *o2, GLUvertex *d2,
+				    GLUvertex *v );
+
+#endif

+ 356 - 0
3rdparty/tessellate/glu.h

@@ -0,0 +1,356 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+
+#ifndef __glu_h__
+#define __glu_h__
+
+#define GLAPIENTRYP *
+#define GLAPIENTRY
+#define GLAPI
+
+typedef int GLint;
+typedef unsigned int GLenum;
+typedef unsigned int GLsizei;
+typedef float GLfloat;
+typedef double GLdouble;
+typedef unsigned char GLubyte;
+typedef int GLboolean;
+typedef void GLvoid;
+
+#define GL_FALSE 0
+#define GL_TRUE 1
+#define GL_LINE_LOOP                      0x0002
+#define GL_LINE_STRIP                     0x0003
+#define GL_TRIANGLES                      0x0004
+#define GL_TRIANGLE_STRIP                 0x0005
+#define GL_TRIANGLE_FAN                   0x0006
+
+// #if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(BUILD_GLU32)
+// # undef GLAPI
+// # define GLAPI __declspec(dllexport)
+// #elif (defined(_MSC_VER) || defined(__MINGW32__)) && defined(_DLL)
+// /* tag specifying we're building for DLL runtime support */
+// # undef GLAPI
+// # define GLAPI __declspec(dllimport)
+// #elif !defined(GLAPI)
+// /* for use with static link lib build of Win32 edition only */
+// # define GLAPI extern
+// #endif /* _STATIC_MESA support */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*************************************************************/
+
+/* Extensions */
+#define GLU_EXT_object_space_tess          1
+#define GLU_EXT_nurbs_tessellator          1
+
+/* Boolean */
+#define GLU_FALSE                          0
+#define GLU_TRUE                           1
+
+/* Version */
+#define GLU_VERSION_1_1                    1
+#define GLU_VERSION_1_2                    1
+#define GLU_VERSION_1_3                    1
+
+/* StringName */
+#define GLU_VERSION                        100800
+#define GLU_EXTENSIONS                     100801
+
+/* ErrorCode */
+#define GLU_INVALID_ENUM                   100900
+#define GLU_INVALID_VALUE                  100901
+#define GLU_OUT_OF_MEMORY                  100902
+#define GLU_INCOMPATIBLE_GL_VERSION        100903
+#define GLU_INVALID_OPERATION              100904
+
+/* NurbsDisplay */
+/*      GLU_FILL */
+#define GLU_OUTLINE_POLYGON                100240
+#define GLU_OUTLINE_PATCH                  100241
+
+/* NurbsCallback */
+#define GLU_NURBS_ERROR                    100103
+#define GLU_ERROR                          100103
+#define GLU_NURBS_BEGIN                    100164
+#define GLU_NURBS_BEGIN_EXT                100164
+#define GLU_NURBS_VERTEX                   100165
+#define GLU_NURBS_VERTEX_EXT               100165
+#define GLU_NURBS_NORMAL                   100166
+#define GLU_NURBS_NORMAL_EXT               100166
+#define GLU_NURBS_COLOR                    100167
+#define GLU_NURBS_COLOR_EXT                100167
+#define GLU_NURBS_TEXTURE_COORD            100168
+#define GLU_NURBS_TEX_COORD_EXT            100168
+#define GLU_NURBS_END                      100169
+#define GLU_NURBS_END_EXT                  100169
+#define GLU_NURBS_BEGIN_DATA               100170
+#define GLU_NURBS_BEGIN_DATA_EXT           100170
+#define GLU_NURBS_VERTEX_DATA              100171
+#define GLU_NURBS_VERTEX_DATA_EXT          100171
+#define GLU_NURBS_NORMAL_DATA              100172
+#define GLU_NURBS_NORMAL_DATA_EXT          100172
+#define GLU_NURBS_COLOR_DATA               100173
+#define GLU_NURBS_COLOR_DATA_EXT           100173
+#define GLU_NURBS_TEXTURE_COORD_DATA       100174
+#define GLU_NURBS_TEX_COORD_DATA_EXT       100174
+#define GLU_NURBS_END_DATA                 100175
+#define GLU_NURBS_END_DATA_EXT             100175
+
+/* NurbsError */
+#define GLU_NURBS_ERROR1                   100251
+#define GLU_NURBS_ERROR2                   100252
+#define GLU_NURBS_ERROR3                   100253
+#define GLU_NURBS_ERROR4                   100254
+#define GLU_NURBS_ERROR5                   100255
+#define GLU_NURBS_ERROR6                   100256
+#define GLU_NURBS_ERROR7                   100257
+#define GLU_NURBS_ERROR8                   100258
+#define GLU_NURBS_ERROR9                   100259
+#define GLU_NURBS_ERROR10                  100260
+#define GLU_NURBS_ERROR11                  100261
+#define GLU_NURBS_ERROR12                  100262
+#define GLU_NURBS_ERROR13                  100263
+#define GLU_NURBS_ERROR14                  100264
+#define GLU_NURBS_ERROR15                  100265
+#define GLU_NURBS_ERROR16                  100266
+#define GLU_NURBS_ERROR17                  100267
+#define GLU_NURBS_ERROR18                  100268
+#define GLU_NURBS_ERROR19                  100269
+#define GLU_NURBS_ERROR20                  100270
+#define GLU_NURBS_ERROR21                  100271
+#define GLU_NURBS_ERROR22                  100272
+#define GLU_NURBS_ERROR23                  100273
+#define GLU_NURBS_ERROR24                  100274
+#define GLU_NURBS_ERROR25                  100275
+#define GLU_NURBS_ERROR26                  100276
+#define GLU_NURBS_ERROR27                  100277
+#define GLU_NURBS_ERROR28                  100278
+#define GLU_NURBS_ERROR29                  100279
+#define GLU_NURBS_ERROR30                  100280
+#define GLU_NURBS_ERROR31                  100281
+#define GLU_NURBS_ERROR32                  100282
+#define GLU_NURBS_ERROR33                  100283
+#define GLU_NURBS_ERROR34                  100284
+#define GLU_NURBS_ERROR35                  100285
+#define GLU_NURBS_ERROR36                  100286
+#define GLU_NURBS_ERROR37                  100287
+
+/* NurbsProperty */
+#define GLU_AUTO_LOAD_MATRIX               100200
+#define GLU_CULLING                        100201
+#define GLU_SAMPLING_TOLERANCE             100203
+#define GLU_DISPLAY_MODE                   100204
+#define GLU_PARAMETRIC_TOLERANCE           100202
+#define GLU_SAMPLING_METHOD                100205
+#define GLU_U_STEP                         100206
+#define GLU_V_STEP                         100207
+#define GLU_NURBS_MODE                     100160
+#define GLU_NURBS_MODE_EXT                 100160
+#define GLU_NURBS_TESSELLATOR              100161
+#define GLU_NURBS_TESSELLATOR_EXT          100161
+#define GLU_NURBS_RENDERER                 100162
+#define GLU_NURBS_RENDERER_EXT             100162
+
+/* NurbsSampling */
+#define GLU_OBJECT_PARAMETRIC_ERROR        100208
+#define GLU_OBJECT_PARAMETRIC_ERROR_EXT    100208
+#define GLU_OBJECT_PATH_LENGTH             100209
+#define GLU_OBJECT_PATH_LENGTH_EXT         100209
+#define GLU_PATH_LENGTH                    100215
+#define GLU_PARAMETRIC_ERROR               100216
+#define GLU_DOMAIN_DISTANCE                100217
+
+/* NurbsTrim */
+#define GLU_MAP1_TRIM_2                    100210
+#define GLU_MAP1_TRIM_3                    100211
+
+/* QuadricDrawStyle */
+#define GLU_POINT                          100010
+#define GLU_LINE                           100011
+#define GLU_FILL                           100012
+#define GLU_SILHOUETTE                     100013
+
+/* QuadricCallback */
+/*      GLU_ERROR */
+
+/* QuadricNormal */
+#define GLU_SMOOTH                         100000
+#define GLU_FLAT                           100001
+#define GLU_NONE                           100002
+
+/* QuadricOrientation */
+#define GLU_OUTSIDE                        100020
+#define GLU_INSIDE                         100021
+
+/* TessCallback */
+#define GLU_TESS_BEGIN                     100100
+#define GLU_BEGIN                          100100
+#define GLU_TESS_VERTEX                    100101
+#define GLU_VERTEX                         100101
+#define GLU_TESS_END                       100102
+#define GLU_END                            100102
+#define GLU_TESS_ERROR                     100103
+#define GLU_TESS_EDGE_FLAG                 100104
+#define GLU_EDGE_FLAG                      100104
+#define GLU_TESS_COMBINE                   100105
+#define GLU_TESS_BEGIN_DATA                100106
+#define GLU_TESS_VERTEX_DATA               100107
+#define GLU_TESS_END_DATA                  100108
+#define GLU_TESS_ERROR_DATA                100109
+#define GLU_TESS_EDGE_FLAG_DATA            100110
+#define GLU_TESS_COMBINE_DATA              100111
+
+/* TessContour */
+#define GLU_CW                             100120
+#define GLU_CCW                            100121
+#define GLU_INTERIOR                       100122
+#define GLU_EXTERIOR                       100123
+#define GLU_UNKNOWN                        100124
+
+/* TessProperty */
+#define GLU_TESS_WINDING_RULE              100140
+#define GLU_TESS_BOUNDARY_ONLY             100141
+#define GLU_TESS_TOLERANCE                 100142
+
+/* TessError */
+#define GLU_TESS_ERROR1                    100151
+#define GLU_TESS_ERROR2                    100152
+#define GLU_TESS_ERROR3                    100153
+#define GLU_TESS_ERROR4                    100154
+#define GLU_TESS_ERROR5                    100155
+#define GLU_TESS_ERROR6                    100156
+#define GLU_TESS_ERROR7                    100157
+#define GLU_TESS_ERROR8                    100158
+#define GLU_TESS_MISSING_BEGIN_POLYGON     100151
+#define GLU_TESS_MISSING_BEGIN_CONTOUR     100152
+#define GLU_TESS_MISSING_END_POLYGON       100153
+#define GLU_TESS_MISSING_END_CONTOUR       100154
+#define GLU_TESS_COORD_TOO_LARGE           100155
+#define GLU_TESS_NEED_COMBINE_CALLBACK     100156
+
+/* TessWinding */
+#define GLU_TESS_WINDING_ODD               100130
+#define GLU_TESS_WINDING_NONZERO           100131
+#define GLU_TESS_WINDING_POSITIVE          100132
+#define GLU_TESS_WINDING_NEGATIVE          100133
+#define GLU_TESS_WINDING_ABS_GEQ_TWO       100134
+
+/*************************************************************/
+
+
+#ifdef __cplusplus
+class GLUnurbs;
+class GLUquadric;
+class GLUtesselator;
+#else
+typedef struct GLUnurbs GLUnurbs;
+typedef struct GLUquadric GLUquadric;
+typedef struct GLUtesselator GLUtesselator;
+#endif
+
+typedef GLUnurbs GLUnurbsObj;
+typedef GLUquadric GLUquadricObj;
+typedef GLUtesselator GLUtesselatorObj;
+typedef GLUtesselator GLUtriangulatorObj;
+
+#define GLU_TESS_MAX_COORD 1.0e150
+
+/* Internal convenience typedefs */
+typedef void (GLAPIENTRYP _GLUfuncptr)(void);
+
+GLAPI void GLAPIENTRY gluBeginCurve (GLUnurbs* nurb);
+GLAPI void GLAPIENTRY gluBeginPolygon (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluBeginSurface (GLUnurbs* nurb);
+GLAPI void GLAPIENTRY gluBeginTrim (GLUnurbs* nurb);
+GLAPI GLint GLAPIENTRY gluBuild1DMipmapLevels (GLenum target, GLint internalFormat, GLsizei width, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data);
+GLAPI GLint GLAPIENTRY gluBuild1DMipmaps (GLenum target, GLint internalFormat, GLsizei width, GLenum format, GLenum type, const void *data);
+GLAPI GLint GLAPIENTRY gluBuild2DMipmapLevels (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data);
+GLAPI GLint GLAPIENTRY gluBuild2DMipmaps (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
+GLAPI GLint GLAPIENTRY gluBuild3DMipmapLevels (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data);
+GLAPI GLint GLAPIENTRY gluBuild3DMipmaps (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
+GLAPI GLboolean GLAPIENTRY gluCheckExtension (const GLubyte *extName, const GLubyte *extString);
+GLAPI void GLAPIENTRY gluCylinder (GLUquadric* quad, GLdouble base, GLdouble top, GLdouble height, GLint slices, GLint stacks);
+GLAPI void GLAPIENTRY gluDeleteNurbsRenderer (GLUnurbs* nurb);
+GLAPI void GLAPIENTRY gluDeleteQuadric (GLUquadric* quad);
+GLAPI void GLAPIENTRY gluDeleteTess (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluDisk (GLUquadric* quad, GLdouble inner, GLdouble outer, GLint slices, GLint loops);
+GLAPI void GLAPIENTRY gluEndCurve (GLUnurbs* nurb);
+GLAPI void GLAPIENTRY gluEndPolygon (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluEndSurface (GLUnurbs* nurb);
+GLAPI void GLAPIENTRY gluEndTrim (GLUnurbs* nurb);
+GLAPI const GLubyte * GLAPIENTRY gluErrorString (GLenum error);
+GLAPI void GLAPIENTRY gluGetNurbsProperty (GLUnurbs* nurb, GLenum property, GLfloat* data);
+GLAPI const GLubyte * GLAPIENTRY gluGetString (GLenum name);
+GLAPI void GLAPIENTRY gluGetTessProperty (GLUtesselator* tess, GLenum which, GLdouble* data);
+GLAPI void GLAPIENTRY gluLoadSamplingMatrices (GLUnurbs* nurb, const GLfloat *model, const GLfloat *perspective, const GLint *view);
+GLAPI void GLAPIENTRY gluLookAt (GLdouble eyeX, GLdouble eyeY, GLdouble eyeZ, GLdouble centerX, GLdouble centerY, GLdouble centerZ, GLdouble upX, GLdouble upY, GLdouble upZ);
+GLAPI GLUnurbs* GLAPIENTRY gluNewNurbsRenderer (void);
+GLAPI GLUquadric* GLAPIENTRY gluNewQuadric (void);
+GLAPI GLUtesselator* GLAPIENTRY gluNewTess (void);
+GLAPI void GLAPIENTRY gluNextContour (GLUtesselator* tess, GLenum type);
+GLAPI void GLAPIENTRY gluNurbsCallback (GLUnurbs* nurb, GLenum which, _GLUfuncptr CallBackFunc);
+GLAPI void GLAPIENTRY gluNurbsCallbackData (GLUnurbs* nurb, GLvoid* userData);
+GLAPI void GLAPIENTRY gluNurbsCallbackDataEXT (GLUnurbs* nurb, GLvoid* userData);
+GLAPI void GLAPIENTRY gluNurbsCurve (GLUnurbs* nurb, GLint knotCount, GLfloat *knots, GLint stride, GLfloat *control, GLint order, GLenum type);
+GLAPI void GLAPIENTRY gluNurbsProperty (GLUnurbs* nurb, GLenum property, GLfloat value);
+GLAPI void GLAPIENTRY gluNurbsSurface (GLUnurbs* nurb, GLint sKnotCount, GLfloat* sKnots, GLint tKnotCount, GLfloat* tKnots, GLint sStride, GLint tStride, GLfloat* control, GLint sOrder, GLint tOrder, GLenum type);
+GLAPI void GLAPIENTRY gluOrtho2D (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);
+GLAPI void GLAPIENTRY gluPartialDisk (GLUquadric* quad, GLdouble inner, GLdouble outer, GLint slices, GLint loops, GLdouble start, GLdouble sweep);
+GLAPI void GLAPIENTRY gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
+GLAPI void GLAPIENTRY gluPickMatrix (GLdouble x, GLdouble y, GLdouble delX, GLdouble delY, GLint *viewport);
+GLAPI GLint GLAPIENTRY gluProject (GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble *model, const GLdouble *proj, const GLint *view, GLdouble* winX, GLdouble* winY, GLdouble* winZ);
+GLAPI void GLAPIENTRY gluPwlCurve (GLUnurbs* nurb, GLint count, GLfloat* data, GLint stride, GLenum type);
+GLAPI void GLAPIENTRY gluQuadricCallback (GLUquadric* quad, GLenum which, _GLUfuncptr CallBackFunc);
+GLAPI void GLAPIENTRY gluQuadricDrawStyle (GLUquadric* quad, GLenum draw);
+GLAPI void GLAPIENTRY gluQuadricNormals (GLUquadric* quad, GLenum normal);
+GLAPI void GLAPIENTRY gluQuadricOrientation (GLUquadric* quad, GLenum orientation);
+GLAPI void GLAPIENTRY gluQuadricTexture (GLUquadric* quad, GLboolean texture);
+GLAPI GLint GLAPIENTRY gluScaleImage (GLenum format, GLsizei wIn, GLsizei hIn, GLenum typeIn, const void *dataIn, GLsizei wOut, GLsizei hOut, GLenum typeOut, GLvoid* dataOut);
+GLAPI void GLAPIENTRY gluSphere (GLUquadric* quad, GLdouble radius, GLint slices, GLint stacks);
+GLAPI void GLAPIENTRY gluTessBeginContour (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluTessBeginPolygon (GLUtesselator* tess, GLvoid* data);
+GLAPI void GLAPIENTRY gluTessCallback (GLUtesselator* tess, GLenum which, _GLUfuncptr CallBackFunc);
+GLAPI void GLAPIENTRY gluTessEndContour (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluTessEndPolygon (GLUtesselator* tess);
+GLAPI void GLAPIENTRY gluTessNormal (GLUtesselator* tess, GLdouble valueX, GLdouble valueY, GLdouble valueZ);
+GLAPI void GLAPIENTRY gluTessProperty (GLUtesselator* tess, GLenum which, GLdouble data);
+GLAPI void GLAPIENTRY gluTessVertex (GLUtesselator* tess, GLdouble *location, GLvoid* data);
+GLAPI GLint GLAPIENTRY gluUnProject (GLdouble winX, GLdouble winY, GLdouble winZ, const GLdouble *model, const GLdouble *proj, const GLint *view, GLdouble* objX, GLdouble* objY, GLdouble* objZ);
+GLAPI GLint GLAPIENTRY gluUnProject4 (GLdouble winX, GLdouble winY, GLdouble winZ, GLdouble clipW, const GLdouble *model, const GLdouble *proj, const GLint *view, GLdouble nearVal, GLdouble farVal, GLdouble* objX, GLdouble* objY, GLdouble* objZ, GLdouble* objW);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __glu_h__ */

+ 86 - 0
3rdparty/tessellate/gluos.h

@@ -0,0 +1,86 @@
+/*
+** gluos.h - operating system dependencies for GLU
+**
+*/
+#ifdef __VMS
+#ifdef __cplusplus
+#pragma message disable nocordel
+#pragma message disable codeunreachable
+#pragma message disable codcauunr
+#endif
+#endif
+
+#ifdef __WATCOMC__
+/* Disable *lots* of warnings to get a clean build. I can't be bothered fixing the
+ * code at the moment, as it is pretty ugly.
+ */
+#pragma warning 7   10
+#pragma warning 13  10
+#pragma warning 14  10
+#pragma warning 367 10
+#pragma warning 379 10
+#pragma warning 726 10
+#pragma warning 836 10
+#endif
+
+#ifdef BUILD_FOR_SNAP
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <malloc.h>
+
+#elif defined(_WIN32)
+
+#include <stdlib.h>	    /* For _MAX_PATH definition */
+#include <stdio.h>
+#include <malloc.h>
+
+#define WIN32_LEAN_AND_MEAN
+#define NOGDI
+#define NOIME
+#define NOMINMAX
+
+#ifdef __MINGW64_VERSION_MAJOR
+  #undef _WIN32_WINNT
+#endif
+
+#ifndef _WIN32_WINNT
+  /* XXX: Workaround a bug in mingw-w64's headers when NOGDI is set and
+   * _WIN32_WINNT >= 0x0600 */
+  #define _WIN32_WINNT 0x0400
+#endif
+#ifndef STRICT
+  #define STRICT 1
+#endif
+
+#include <windows.h>
+
+/* Disable warnings */
+#if defined(_MSC_VER)
+#pragma warning(disable : 4101)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4761)
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1300
+#pragma comment(linker, "/OPT:NOWIN98")
+#endif
+
+#ifndef WINGDIAPI
+#define WINGDIAPI
+#endif
+
+#elif defined(__OS2__)
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <malloc.h>
+#define WINGDIAPI
+
+#else
+
+/* Disable Microsoft-specific keywords */
+#define GLAPIENTRY
+#define WINGDIAPI
+
+#endif

+ 52 - 0
3rdparty/tessellate/main.c

@@ -0,0 +1,52 @@
+#include "tessellate.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+void run_example(const double vertices_array[],
+                 const double *contours_array[],
+                 int contours_size)
+{
+    double *coordinates_out;
+    int *tris_out;
+    int nverts, ntris, i;
+
+    const double *p = vertices_array;
+    /* const double **contours = contours_array; */
+
+    tessellate(&coordinates_out, &nverts,
+               &tris_out, &ntris,
+               contours_array, contours_array + contours_size);
+
+    for (i=0; i<2 * nverts; ++i) {
+        fprintf(stdout, "%g ", coordinates_out[i]);
+    }
+    fprintf(stdout, "\n");
+    for (i=0; i<3 * ntris; ++i) {
+        fprintf(stdout, "%d ", tris_out[i]);
+    }
+    fprintf(stdout, "\n");
+    free(coordinates_out);
+    if (tris_out)
+        free(tris_out);
+}
+
+int main()
+{
+    double a1[] = { 0, 0, 1, 5, 2, 0, -1, 3, 3, 3 };
+    const double *c1[] = {a1, a1+10};
+    int s1 = 2;
+    run_example(a1, c1, 2);
+
+    double a2[] = { 0, 0, 3, 0, 3, 3, 0, 3, 
+                    1, 1, 2, 1, 2, 2, 1, 2 };
+    const double *c2[] = {a2, a2+8, a2+16};
+    int s2 = 3;
+    run_example(a2, c2, s2);
+
+    double a3[] = { 441, 0, 326, 0, 326, 889, 12, 889, 12, 992, 755, 992, 755, 889, 441, 889 };
+    const double *c3[] = { a3, a3+16 };
+    int s3 = 2;
+    run_example(a3, c3, s3);
+
+    return 0;
+}

+ 55 - 0
3rdparty/tessellate/memalloc.c

@@ -0,0 +1,55 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "memalloc.h"
+#include "string.h"
+
+int __gl_memInit( size_t maxFast )
+{
+#ifndef NO_MALLOPT
+/*  mallopt( M_MXFAST, maxFast );*/
+#ifdef MEMORY_DEBUG
+  mallopt( M_DEBUG, 1 );
+#endif
+#endif
+   return 1;
+}
+
+#ifdef MEMORY_DEBUG
+void *__gl_memAlloc( size_t n )
+{
+  return memset( malloc( n ), 0xa5, n );
+}
+#endif
+

+ 54 - 0
3rdparty/tessellate/memalloc.h

@@ -0,0 +1,54 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __memalloc_simple_h_
+#define __memalloc_simple_h_
+
+#include <stdlib.h>
+
+#define memRealloc	realloc
+#define memFree		free
+
+#define memInit		__gl_memInit
+/*extern void		__gl_memInit( size_t );*/
+extern int		__gl_memInit( size_t );
+
+#ifndef MEMORY_DEBUG
+#define memAlloc	malloc
+#else
+#define memAlloc	__gl_memAlloc
+extern void *		__gl_memAlloc( size_t );
+#endif
+
+#endif

+ 798 - 0
3rdparty/tessellate/mesh.c

@@ -0,0 +1,798 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <stddef.h>
+#include <assert.h>
+#include "mesh.h"
+#include "memalloc.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+static GLUvertex *allocVertex()
+{
+   return (GLUvertex *)memAlloc( sizeof( GLUvertex ));
+}
+
+static GLUface *allocFace()
+{
+   return (GLUface *)memAlloc( sizeof( GLUface ));
+}
+
+/************************ Utility Routines ************************/
+
+/* Allocate and free half-edges in pairs for efficiency.
+ * The *only* place that should use this fact is allocation/free.
+ */
+typedef struct { GLUhalfEdge e, eSym; } EdgePair;
+
+/* MakeEdge creates a new pair of half-edges which form their own loop.
+ * No vertex or face structures are allocated, but these must be assigned
+ * before the current edge operation is completed.
+ */
+static GLUhalfEdge *MakeEdge( GLUhalfEdge *eNext )
+{
+  GLUhalfEdge *e;
+  GLUhalfEdge *eSym;
+  GLUhalfEdge *ePrev;
+  EdgePair *pair = (EdgePair *)memAlloc( sizeof( EdgePair ));
+  if (pair == NULL) return NULL;
+
+  e = &pair->e;
+  eSym = &pair->eSym;
+
+  /* Make sure eNext points to the first edge of the edge pair */
+  if( eNext->Sym < eNext ) { eNext = eNext->Sym; }
+
+  /* Insert in circular doubly-linked list before eNext.
+   * Note that the prev pointer is stored in Sym->next.
+   */
+  ePrev = eNext->Sym->next;
+  eSym->next = ePrev;
+  ePrev->Sym->next = e;
+  e->next = eNext;
+  eNext->Sym->next = eSym;
+
+  e->Sym = eSym;
+  e->Onext = e;
+  e->Lnext = eSym;
+  e->Org = NULL;
+  e->Lface = NULL;
+  e->winding = 0;
+  e->activeRegion = NULL;
+
+  eSym->Sym = e;
+  eSym->Onext = eSym;
+  eSym->Lnext = e;
+  eSym->Org = NULL;
+  eSym->Lface = NULL;
+  eSym->winding = 0;
+  eSym->activeRegion = NULL;
+
+  return e;
+}
+
+/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the
+ * CS348a notes (see mesh.h).  Basically it modifies the mesh so that
+ * a->Onext and b->Onext are exchanged.  This can have various effects
+ * depending on whether a and b belong to different face or vertex rings.
+ * For more explanation see __gl_meshSplice() below.
+ */
+static void Splice( GLUhalfEdge *a, GLUhalfEdge *b )
+{
+  GLUhalfEdge *aOnext = a->Onext;
+  GLUhalfEdge *bOnext = b->Onext;
+
+  aOnext->Sym->Lnext = b;
+  bOnext->Sym->Lnext = a;
+  a->Onext = bOnext;
+  b->Onext = aOnext;
+}
+
+/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the
+ * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
+ * a place to insert the new vertex in the global vertex list.  We insert
+ * the new vertex *before* vNext so that algorithms which walk the vertex
+ * list will not see the newly created vertices.
+ */
+static void MakeVertex( GLUvertex *newVertex, 
+			GLUhalfEdge *eOrig, GLUvertex *vNext )
+{
+  GLUhalfEdge *e;
+  GLUvertex *vPrev;
+  GLUvertex *vNew = newVertex;
+
+  assert(vNew != NULL);
+
+  /* insert in circular doubly-linked list before vNext */
+  vPrev = vNext->prev;
+  vNew->prev = vPrev;
+  vPrev->next = vNew;
+  vNew->next = vNext;
+  vNext->prev = vNew;
+
+  vNew->anEdge = eOrig;
+  vNew->data = NULL;
+  /* leave coords, s, t undefined */
+
+  /* fix other edges on this vertex loop */
+  e = eOrig;
+  do {
+    e->Org = vNew;
+    e = e->Onext;
+  } while( e != eOrig );
+}
+
+/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left
+ * face of all edges in the face loop to which eOrig belongs.  "fNext" gives
+ * a place to insert the new face in the global face list.  We insert
+ * the new face *before* fNext so that algorithms which walk the face
+ * list will not see the newly created faces.
+ */
+static void MakeFace( GLUface *newFace, GLUhalfEdge *eOrig, GLUface *fNext )
+{
+  GLUhalfEdge *e;
+  GLUface *fPrev;
+  GLUface *fNew = newFace;
+
+  assert(fNew != NULL); 
+
+  /* insert in circular doubly-linked list before fNext */
+  fPrev = fNext->prev;
+  fNew->prev = fPrev;
+  fPrev->next = fNew;
+  fNew->next = fNext;
+  fNext->prev = fNew;
+
+  fNew->anEdge = eOrig;
+  fNew->data = NULL;
+  fNew->trail = NULL;
+  fNew->marked = FALSE;
+
+  /* The new face is marked "inside" if the old one was.  This is a
+   * convenience for the common case where a face has been split in two.
+   */
+  fNew->inside = fNext->inside;
+
+  /* fix other edges on this face loop */
+  e = eOrig;
+  do {
+    e->Lface = fNew;
+    e = e->Lnext;
+  } while( e != eOrig );
+}
+
+/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym),
+ * and removes from the global edge list.
+ */
+static void KillEdge( GLUhalfEdge *eDel )
+{
+  GLUhalfEdge *ePrev, *eNext;
+
+  /* Half-edges are allocated in pairs, see EdgePair above */
+  if( eDel->Sym < eDel ) { eDel = eDel->Sym; }
+
+  /* delete from circular doubly-linked list */
+  eNext = eDel->next;
+  ePrev = eDel->Sym->next;
+  eNext->Sym->next = ePrev;
+  ePrev->Sym->next = eNext;
+
+  memFree( eDel );
+}
+
+
+/* KillVertex( vDel ) destroys a vertex and removes it from the global
+ * vertex list.  It updates the vertex loop to point to a given new vertex.
+ */
+static void KillVertex( GLUvertex *vDel, GLUvertex *newOrg )
+{
+  GLUhalfEdge *e, *eStart = vDel->anEdge;
+  GLUvertex *vPrev, *vNext;
+
+  /* change the origin of all affected edges */
+  e = eStart;
+  do {
+    e->Org = newOrg;
+    e = e->Onext;
+  } while( e != eStart );
+
+  /* delete from circular doubly-linked list */
+  vPrev = vDel->prev;
+  vNext = vDel->next;
+  vNext->prev = vPrev;
+  vPrev->next = vNext;
+
+  memFree( vDel );
+}
+
+/* KillFace( fDel ) destroys a face and removes it from the global face
+ * list.  It updates the face loop to point to a given new face.
+ */
+static void KillFace( GLUface *fDel, GLUface *newLface )
+{
+  GLUhalfEdge *e, *eStart = fDel->anEdge;
+  GLUface *fPrev, *fNext;
+
+  /* change the left face of all affected edges */
+  e = eStart;
+  do {
+    e->Lface = newLface;
+    e = e->Lnext;
+  } while( e != eStart );
+
+  /* delete from circular doubly-linked list */
+  fPrev = fDel->prev;
+  fNext = fDel->next;
+  fNext->prev = fPrev;
+  fPrev->next = fNext;
+
+  memFree( fDel );
+}
+
+
+/****************** Basic Edge Operations **********************/
+
+/* __gl_meshMakeEdge creates one edge, two vertices, and a loop (face).
+ * The loop consists of the two new half-edges.
+ */
+GLUhalfEdge *__gl_meshMakeEdge( GLUmesh *mesh )
+{
+  GLUvertex *newVertex1= allocVertex();
+  GLUvertex *newVertex2= allocVertex();
+  GLUface *newFace= allocFace();
+  GLUhalfEdge *e;
+
+  /* if any one is null then all get freed */
+  if (newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) {
+     if (newVertex1 != NULL) memFree(newVertex1);
+     if (newVertex2 != NULL) memFree(newVertex2);
+     if (newFace != NULL) memFree(newFace);     
+     return NULL;
+  } 
+
+  e = MakeEdge( &mesh->eHead );
+  if (e == NULL) {
+     memFree(newVertex1);
+     memFree(newVertex2);
+     memFree(newFace);
+     return NULL;
+  }
+
+  MakeVertex( newVertex1, e, &mesh->vHead );
+  MakeVertex( newVertex2, e->Sym, &mesh->vHead );
+  MakeFace( newFace, e, &mesh->fHead );
+  return e;
+}
+  
+
+/* __gl_meshSplice( eOrg, eDst ) is the basic operation for changing the
+ * mesh connectivity and topology.  It changes the mesh so that
+ *	eOrg->Onext <- OLD( eDst->Onext )
+ *	eDst->Onext <- OLD( eOrg->Onext )
+ * where OLD(...) means the value before the meshSplice operation.
+ *
+ * This can have two effects on the vertex structure:
+ *  - if eOrg->Org != eDst->Org, the two vertices are merged together
+ *  - if eOrg->Org == eDst->Org, the origin is split into two vertices
+ * In both cases, eDst->Org is changed and eOrg->Org is untouched.
+ *
+ * Similarly (and independently) for the face structure,
+ *  - if eOrg->Lface == eDst->Lface, one loop is split into two
+ *  - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
+ * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
+ *
+ * Some special cases:
+ * If eDst == eOrg, the operation has no effect.
+ * If eDst == eOrg->Lnext, the new face will have a single edge.
+ * If eDst == eOrg->Lprev, the old face will have a single edge.
+ * If eDst == eOrg->Onext, the new vertex will have a single edge.
+ * If eDst == eOrg->Oprev, the old vertex will have a single edge.
+ */
+int __gl_meshSplice( GLUhalfEdge *eOrg, GLUhalfEdge *eDst )
+{
+  int joiningLoops = FALSE;
+  int joiningVertices = FALSE;
+
+  if( eOrg == eDst ) return 1;
+
+  if( eDst->Org != eOrg->Org ) {
+    /* We are merging two disjoint vertices -- destroy eDst->Org */
+    joiningVertices = TRUE;
+    KillVertex( eDst->Org, eOrg->Org );
+  }
+  if( eDst->Lface != eOrg->Lface ) {
+    /* We are connecting two disjoint loops -- destroy eDst->Lface */
+    joiningLoops = TRUE;
+    KillFace( eDst->Lface, eOrg->Lface );
+  }
+
+  /* Change the edge structure */
+  Splice( eDst, eOrg );
+
+  if( ! joiningVertices ) {
+    GLUvertex *newVertex= allocVertex();
+    if (newVertex == NULL) return 0;
+
+    /* We split one vertex into two -- the new vertex is eDst->Org.
+     * Make sure the old vertex points to a valid half-edge.
+     */
+    MakeVertex( newVertex, eDst, eOrg->Org );
+    eOrg->Org->anEdge = eOrg;
+  }
+  if( ! joiningLoops ) {
+    GLUface *newFace= allocFace();  
+    if (newFace == NULL) return 0;
+
+    /* We split one loop into two -- the new loop is eDst->Lface.
+     * Make sure the old face points to a valid half-edge.
+     */
+    MakeFace( newFace, eDst, eOrg->Lface );
+    eOrg->Lface->anEdge = eOrg;
+  }
+
+  return 1;
+}
+
+
+/* __gl_meshDelete( eDel ) removes the edge eDel.  There are several cases:
+ * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
+ * eDel->Lface is deleted.  Otherwise, we are splitting one loop into two;
+ * the newly created loop will contain eDel->Dst.  If the deletion of eDel
+ * would create isolated vertices, those are deleted as well.
+ *
+ * This function could be implemented as two calls to __gl_meshSplice
+ * plus a few calls to memFree, but this would allocate and delete
+ * unnecessary vertices and faces.
+ */
+int __gl_meshDelete( GLUhalfEdge *eDel )
+{
+  GLUhalfEdge *eDelSym = eDel->Sym;
+  int joiningLoops = FALSE;
+
+  /* First step: disconnect the origin vertex eDel->Org.  We make all
+   * changes to get a consistent mesh in this "intermediate" state.
+   */
+  if( eDel->Lface != eDel->Rface ) {
+    /* We are joining two loops into one -- remove the left face */
+    joiningLoops = TRUE;
+    KillFace( eDel->Lface, eDel->Rface );
+  }
+
+  if( eDel->Onext == eDel ) {
+    KillVertex( eDel->Org, NULL );
+  } else {
+    /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */
+    eDel->Rface->anEdge = eDel->Oprev;
+    eDel->Org->anEdge = eDel->Onext;
+
+    Splice( eDel, eDel->Oprev );
+    if( ! joiningLoops ) {
+      GLUface *newFace= allocFace();
+      if (newFace == NULL) return 0; 
+
+      /* We are splitting one loop into two -- create a new loop for eDel. */
+      MakeFace( newFace, eDel, eDel->Lface );
+    }
+  }
+
+  /* Claim: the mesh is now in a consistent state, except that eDel->Org
+   * may have been deleted.  Now we disconnect eDel->Dst.
+   */
+  if( eDelSym->Onext == eDelSym ) {
+    KillVertex( eDelSym->Org, NULL );
+    KillFace( eDelSym->Lface, NULL );
+  } else {
+    /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */
+    eDel->Lface->anEdge = eDelSym->Oprev;
+    eDelSym->Org->anEdge = eDelSym->Onext;
+    Splice( eDelSym, eDelSym->Oprev );
+  }
+
+  /* Any isolated vertices or faces have already been freed. */
+  KillEdge( eDel );
+
+  return 1;
+}
+
+
+/******************** Other Edge Operations **********************/
+
+/* All these routines can be implemented with the basic edge
+ * operations above.  They are provided for convenience and efficiency.
+ */
+
+
+/* __gl_meshAddEdgeVertex( eOrg ) creates a new edge eNew such that
+ * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
+ * eOrg and eNew will have the same left face.
+ */
+GLUhalfEdge *__gl_meshAddEdgeVertex( GLUhalfEdge *eOrg )
+{
+  GLUhalfEdge *eNewSym;
+  GLUhalfEdge *eNew = MakeEdge( eOrg );
+  if (eNew == NULL) return NULL;
+
+  eNewSym = eNew->Sym;
+
+  /* Connect the new edge appropriately */
+  Splice( eNew, eOrg->Lnext );
+
+  /* Set the vertex and face information */
+  eNew->Org = eOrg->Dst;
+  {
+    GLUvertex *newVertex= allocVertex();
+    if (newVertex == NULL) return NULL;
+
+    MakeVertex( newVertex, eNewSym, eNew->Org );
+  }
+  eNew->Lface = eNewSym->Lface = eOrg->Lface;
+
+  return eNew;
+}
+
+
+/* __gl_meshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
+ * such that eNew == eOrg->Lnext.  The new vertex is eOrg->Dst == eNew->Org.
+ * eOrg and eNew will have the same left face.
+ */
+GLUhalfEdge *__gl_meshSplitEdge( GLUhalfEdge *eOrg )
+{
+  GLUhalfEdge *eNew;
+  GLUhalfEdge *tempHalfEdge= __gl_meshAddEdgeVertex( eOrg );
+  if (tempHalfEdge == NULL) return NULL;
+
+  eNew = tempHalfEdge->Sym;
+
+  /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */
+  Splice( eOrg->Sym, eOrg->Sym->Oprev );
+  Splice( eOrg->Sym, eNew );
+
+  /* Set the vertex and face information */
+  eOrg->Dst = eNew->Org;
+  eNew->Dst->anEdge = eNew->Sym;	/* may have pointed to eOrg->Sym */
+  eNew->Rface = eOrg->Rface;
+  eNew->winding = eOrg->winding;	/* copy old winding information */
+  eNew->Sym->winding = eOrg->Sym->winding;
+
+  return eNew;
+}
+
+
+/* __gl_meshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
+ * to eDst->Org, and returns the corresponding half-edge eNew.
+ * If eOrg->Lface == eDst->Lface, this splits one loop into two,
+ * and the newly created loop is eNew->Lface.  Otherwise, two disjoint
+ * loops are merged into one, and the loop eDst->Lface is destroyed.
+ *
+ * If (eOrg == eDst), the new face will have only two edges.
+ * If (eOrg->Lnext == eDst), the old face is reduced to a single edge.
+ * If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges.
+ */
+GLUhalfEdge *__gl_meshConnect( GLUhalfEdge *eOrg, GLUhalfEdge *eDst )
+{
+  GLUhalfEdge *eNewSym;
+  int joiningLoops = FALSE;  
+  GLUhalfEdge *eNew = MakeEdge( eOrg );
+  if (eNew == NULL) return NULL;
+
+  eNewSym = eNew->Sym;
+
+  if( eDst->Lface != eOrg->Lface ) {
+    /* We are connecting two disjoint loops -- destroy eDst->Lface */
+    joiningLoops = TRUE;
+    KillFace( eDst->Lface, eOrg->Lface );
+  }
+
+  /* Connect the new edge appropriately */
+  Splice( eNew, eOrg->Lnext );
+  Splice( eNewSym, eDst );
+
+  /* Set the vertex and face information */
+  eNew->Org = eOrg->Dst;
+  eNewSym->Org = eDst->Org;
+  eNew->Lface = eNewSym->Lface = eOrg->Lface;
+
+  /* Make sure the old face points to a valid half-edge */
+  eOrg->Lface->anEdge = eNewSym;
+
+  if( ! joiningLoops ) {
+    GLUface *newFace= allocFace();
+    if (newFace == NULL) return NULL;
+
+    /* We split one loop into two -- the new loop is eNew->Lface */
+    MakeFace( newFace, eNew, eOrg->Lface );
+  }
+  return eNew;
+}
+
+
+/******************** Other Operations **********************/
+
+/* __gl_meshZapFace( fZap ) destroys a face and removes it from the
+ * global face list.  All edges of fZap will have a NULL pointer as their
+ * left face.  Any edges which also have a NULL pointer as their right face
+ * are deleted entirely (along with any isolated vertices this produces).
+ * An entire mesh can be deleted by zapping its faces, one at a time,
+ * in any order.  Zapped faces cannot be used in further mesh operations!
+ */
+void __gl_meshZapFace( GLUface *fZap )
+{
+  GLUhalfEdge *eStart = fZap->anEdge;
+  GLUhalfEdge *e, *eNext, *eSym;
+  GLUface *fPrev, *fNext;
+
+  /* walk around face, deleting edges whose right face is also NULL */
+  eNext = eStart->Lnext;
+  do {
+    e = eNext;
+    eNext = e->Lnext;
+
+    e->Lface = NULL;
+    if( e->Rface == NULL ) {
+      /* delete the edge -- see __gl_MeshDelete above */
+
+      if( e->Onext == e ) {
+	KillVertex( e->Org, NULL );
+      } else {
+	/* Make sure that e->Org points to a valid half-edge */
+	e->Org->anEdge = e->Onext;
+	Splice( e, e->Oprev );
+      }
+      eSym = e->Sym;
+      if( eSym->Onext == eSym ) {
+	KillVertex( eSym->Org, NULL );
+      } else {
+	/* Make sure that eSym->Org points to a valid half-edge */
+	eSym->Org->anEdge = eSym->Onext;
+	Splice( eSym, eSym->Oprev );
+      }
+      KillEdge( e );
+    }
+  } while( e != eStart );
+
+  /* delete from circular doubly-linked list */
+  fPrev = fZap->prev;
+  fNext = fZap->next;
+  fNext->prev = fPrev;
+  fPrev->next = fNext;
+
+  memFree( fZap );
+}
+
+
+/* __gl_meshNewMesh() creates a new mesh with no edges, no vertices,
+ * and no loops (what we usually call a "face").
+ */
+GLUmesh *__gl_meshNewMesh( void )
+{
+  GLUvertex *v;
+  GLUface *f;
+  GLUhalfEdge *e;
+  GLUhalfEdge *eSym;
+  GLUmesh *mesh = (GLUmesh *)memAlloc( sizeof( GLUmesh ));
+  if (mesh == NULL) {
+     return NULL;
+  }
+  
+  v = &mesh->vHead;
+  f = &mesh->fHead;
+  e = &mesh->eHead;
+  eSym = &mesh->eHeadSym;
+
+  v->next = v->prev = v;
+  v->anEdge = NULL;
+  v->data = NULL;
+
+  f->next = f->prev = f;
+  f->anEdge = NULL;
+  f->data = NULL;
+  f->trail = NULL;
+  f->marked = FALSE;
+  f->inside = FALSE;
+
+  e->next = e;
+  e->Sym = eSym;
+  e->Onext = NULL;
+  e->Lnext = NULL;
+  e->Org = NULL;
+  e->Lface = NULL;
+  e->winding = 0;
+  e->activeRegion = NULL;
+
+  eSym->next = eSym;
+  eSym->Sym = e;
+  eSym->Onext = NULL;
+  eSym->Lnext = NULL;
+  eSym->Org = NULL;
+  eSym->Lface = NULL;
+  eSym->winding = 0;
+  eSym->activeRegion = NULL;
+
+  return mesh;
+}
+
+
+/* __gl_meshUnion( mesh1, mesh2 ) forms the union of all structures in
+ * both meshes, and returns the new mesh (the old meshes are destroyed).
+ */
+GLUmesh *__gl_meshUnion( GLUmesh *mesh1, GLUmesh *mesh2 )
+{
+  GLUface *f1 = &mesh1->fHead;
+  GLUvertex *v1 = &mesh1->vHead;
+  GLUhalfEdge *e1 = &mesh1->eHead;
+  GLUface *f2 = &mesh2->fHead;
+  GLUvertex *v2 = &mesh2->vHead;
+  GLUhalfEdge *e2 = &mesh2->eHead;
+
+  /* Add the faces, vertices, and edges of mesh2 to those of mesh1 */
+  if( f2->next != f2 ) {
+    f1->prev->next = f2->next;
+    f2->next->prev = f1->prev;
+    f2->prev->next = f1;
+    f1->prev = f2->prev;
+  }
+
+  if( v2->next != v2 ) {
+    v1->prev->next = v2->next;
+    v2->next->prev = v1->prev;
+    v2->prev->next = v1;
+    v1->prev = v2->prev;
+  }
+
+  if( e2->next != e2 ) {
+    e1->Sym->next->Sym->next = e2->next;
+    e2->next->Sym->next = e1->Sym->next;
+    e2->Sym->next->Sym->next = e1;
+    e1->Sym->next = e2->Sym->next;
+  }
+
+  memFree( mesh2 );
+  return mesh1;
+}
+
+
+#ifdef DELETE_BY_ZAPPING
+
+/* __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh.
+ */
+void __gl_meshDeleteMesh( GLUmesh *mesh )
+{
+  GLUface *fHead = &mesh->fHead;
+
+  while( fHead->next != fHead ) {
+    __gl_meshZapFace( fHead->next );
+  }
+  assert( mesh->vHead.next == &mesh->vHead );
+
+  memFree( mesh );
+}
+
+#else
+
+/* __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh.
+ */
+void __gl_meshDeleteMesh( GLUmesh *mesh )
+{
+  GLUface *f, *fNext;
+  GLUvertex *v, *vNext;
+  GLUhalfEdge *e, *eNext;
+
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) {
+    fNext = f->next;
+    memFree( f );
+  }
+
+  for( v = mesh->vHead.next; v != &mesh->vHead; v = vNext ) {
+    vNext = v->next;
+    memFree( v );
+  }
+
+  for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) {
+    /* One call frees both e and e->Sym (see EdgePair above) */
+    eNext = e->next;
+    memFree( e );
+  }
+
+  memFree( mesh );
+}
+
+#endif
+
+#ifndef NDEBUG
+
+/* __gl_meshCheckMesh( mesh ) checks a mesh for self-consistency.
+ */
+void __gl_meshCheckMesh( GLUmesh *mesh )
+{
+  GLUface *fHead = &mesh->fHead;
+  GLUvertex *vHead = &mesh->vHead;
+  GLUhalfEdge *eHead = &mesh->eHead;
+  GLUface *f, *fPrev;
+  GLUvertex *v, *vPrev;
+  GLUhalfEdge *e, *ePrev;
+
+  fPrev = fHead;
+  for( fPrev = fHead ; (f = fPrev->next) != fHead; fPrev = f) {
+    assert( f->prev == fPrev );
+    e = f->anEdge;
+    do {
+      assert( e->Sym != e );
+      assert( e->Sym->Sym == e );
+      assert( e->Lnext->Onext->Sym == e );
+      assert( e->Onext->Sym->Lnext == e );
+      assert( e->Lface == f );
+      e = e->Lnext;
+    } while( e != f->anEdge );
+  }
+  assert( f->prev == fPrev && f->anEdge == NULL && f->data == NULL );
+
+  vPrev = vHead;
+  for( vPrev = vHead ; (v = vPrev->next) != vHead; vPrev = v) {
+    assert( v->prev == vPrev );
+    e = v->anEdge;
+    do {
+      assert( e->Sym != e );
+      assert( e->Sym->Sym == e );
+      assert( e->Lnext->Onext->Sym == e );
+      assert( e->Onext->Sym->Lnext == e );
+      assert( e->Org == v );
+      e = e->Onext;
+    } while( e != v->anEdge );
+  }
+  assert( v->prev == vPrev && v->anEdge == NULL && v->data == NULL );
+
+  ePrev = eHead;
+  for( ePrev = eHead ; (e = ePrev->next) != eHead; ePrev = e) {
+    assert( e->Sym->next == ePrev->Sym );
+    assert( e->Sym != e );
+    assert( e->Sym->Sym == e );
+    assert( e->Org != NULL );
+    assert( e->Dst != NULL );
+    assert( e->Lnext->Onext->Sym == e );
+    assert( e->Onext->Sym->Lnext == e );
+  }
+  assert( e->Sym->next == ePrev->Sym
+       && e->Sym == &mesh->eHeadSym
+       && e->Sym->Sym == e
+       && e->Org == NULL && e->Dst == NULL
+       && e->Lface == NULL && e->Rface == NULL );
+}
+
+#endif

+ 266 - 0
3rdparty/tessellate/mesh.h

@@ -0,0 +1,266 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __mesh_h_
+#define __mesh_h_
+
+#include "glu.h"
+
+typedef struct GLUmesh GLUmesh; 
+
+typedef struct GLUvertex GLUvertex;
+typedef struct GLUface GLUface;
+typedef struct GLUhalfEdge GLUhalfEdge;
+
+typedef struct ActiveRegion ActiveRegion;	/* Internal data */
+
+/* The mesh structure is similar in spirit, notation, and operations
+ * to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives
+ * for the manipulation of general subdivisions and the computation of
+ * Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985).
+ * For a simplified description, see the course notes for CS348a,
+ * "Mathematical Foundations of Computer Graphics", available at the
+ * Stanford bookstore (and taught during the fall quarter).
+ * The implementation also borrows a tiny subset of the graph-based approach
+ * use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction
+ * to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988).
+ *
+ * The fundamental data structure is the "half-edge".  Two half-edges
+ * go together to make an edge, but they point in opposite directions.
+ * Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym),
+ * its origin vertex (Org), the face on its left side (Lface), and the
+ * adjacent half-edges in the CCW direction around the origin vertex
+ * (Onext) and around the left face (Lnext).  There is also a "next"
+ * pointer for the global edge list (see below).
+ *
+ * The notation used for mesh navigation:
+ *	Sym   = the mate of a half-edge (same edge, but opposite direction)
+ *	Onext = edge CCW around origin vertex (keep same origin)
+ *	Dnext = edge CCW around destination vertex (keep same dest)
+ *	Lnext = edge CCW around left face (dest becomes new origin)
+ *	Rnext = edge CCW around right face (origin becomes new dest)
+ *
+ * "prev" means to substitute CW for CCW in the definitions above.
+ *
+ * The mesh keeps global lists of all vertices, faces, and edges,
+ * stored as doubly-linked circular lists with a dummy header node.
+ * The mesh stores pointers to these dummy headers (vHead, fHead, eHead).
+ *
+ * The circular edge list is special; since half-edges always occur
+ * in pairs (e and e->Sym), each half-edge stores a pointer in only
+ * one direction.  Starting at eHead and following the e->next pointers
+ * will visit each *edge* once (ie. e or e->Sym, but not both).
+ * e->Sym stores a pointer in the opposite direction, thus it is
+ * always true that e->Sym->next->Sym->next == e.
+ *
+ * Each vertex has a pointer to next and previous vertices in the
+ * circular list, and a pointer to a half-edge with this vertex as
+ * the origin (NULL if this is the dummy header).  There is also a
+ * field "data" for client data.
+ *
+ * Each face has a pointer to the next and previous faces in the
+ * circular list, and a pointer to a half-edge with this face as
+ * the left face (NULL if this is the dummy header).  There is also
+ * a field "data" for client data.
+ *
+ * Note that what we call a "face" is really a loop; faces may consist
+ * of more than one loop (ie. not simply connected), but there is no
+ * record of this in the data structure.  The mesh may consist of
+ * several disconnected regions, so it may not be possible to visit
+ * the entire mesh by starting at a half-edge and traversing the edge
+ * structure.
+ *
+ * The mesh does NOT support isolated vertices; a vertex is deleted along
+ * with its last edge.  Similarly when two faces are merged, one of the
+ * faces is deleted (see __gl_meshDelete below).  For mesh operations,
+ * all face (loop) and vertex pointers must not be NULL.  However, once
+ * mesh manipulation is finished, __gl_MeshZapFace can be used to delete
+ * faces of the mesh, one at a time.  All external faces can be "zapped"
+ * before the mesh is returned to the client; then a NULL face indicates
+ * a region which is not part of the output polygon.
+ */
+
+struct GLUvertex {
+  GLUvertex	*next;		/* next vertex (never NULL) */
+  GLUvertex	*prev;		/* previous vertex (never NULL) */
+  GLUhalfEdge	*anEdge;	/* a half-edge with this origin */
+  void		*data;		/* client's data */
+
+  /* Internal data (keep hidden) */
+  GLdouble	coords[3];	/* vertex location in 3D */
+  GLdouble	s, t;		/* projection onto the sweep plane */
+  long		pqHandle;	/* to allow deletion from priority queue */
+};
+
+struct GLUface {
+  GLUface	*next;		/* next face (never NULL) */
+  GLUface	*prev;		/* previous face (never NULL) */
+  GLUhalfEdge	*anEdge;	/* a half edge with this left face */
+  void		*data;		/* room for client's data */
+
+  /* Internal data (keep hidden) */
+  GLUface	*trail;		/* "stack" for conversion to strips */
+  GLboolean	marked;		/* flag for conversion to strips */
+  GLboolean	inside;		/* this face is in the polygon interior */
+};
+
+struct GLUhalfEdge {
+  GLUhalfEdge	*next;		/* doubly-linked list (prev==Sym->next) */
+  GLUhalfEdge	*Sym;		/* same edge, opposite direction */
+  GLUhalfEdge	*Onext;		/* next edge CCW around origin */
+  GLUhalfEdge	*Lnext;		/* next edge CCW around left face */
+  GLUvertex	*Org;		/* origin vertex (Overtex too long) */
+  GLUface	*Lface;		/* left face */
+
+  /* Internal data (keep hidden) */
+  ActiveRegion	*activeRegion;	/* a region with this upper edge (sweep.c) */
+  int		winding;	/* change in winding number when crossing
+                                   from the right face to the left face */
+};
+
+#define	Rface	Sym->Lface
+#define Dst	Sym->Org
+
+#define Oprev	Sym->Lnext
+#define Lprev   Onext->Sym
+#define Dprev	Lnext->Sym
+#define Rprev	Sym->Onext
+#define Dnext	Rprev->Sym	/* 3 pointers */
+#define Rnext	Oprev->Sym	/* 3 pointers */
+
+
+struct GLUmesh {
+  GLUvertex	vHead;		/* dummy header for vertex list */
+  GLUface	fHead;		/* dummy header for face list */
+  GLUhalfEdge	eHead;		/* dummy header for edge list */
+  GLUhalfEdge	eHeadSym;	/* and its symmetric counterpart */
+};
+
+/* The mesh operations below have three motivations: completeness,
+ * convenience, and efficiency.  The basic mesh operations are MakeEdge,
+ * Splice, and Delete.  All the other edge operations can be implemented
+ * in terms of these.  The other operations are provided for convenience
+ * and/or efficiency.
+ *
+ * When a face is split or a vertex is added, they are inserted into the
+ * global list *before* the existing vertex or face (ie. e->Org or e->Lface).
+ * This makes it easier to process all vertices or faces in the global lists
+ * without worrying about processing the same data twice.  As a convenience,
+ * when a face is split, the "inside" flag is copied from the old face.
+ * Other internal data (v->data, v->activeRegion, f->data, f->marked,
+ * f->trail, e->winding) is set to zero.
+ *
+ * ********************** Basic Edge Operations **************************
+ *
+ * __gl_meshMakeEdge( mesh ) creates one edge, two vertices, and a loop.
+ * The loop (face) consists of the two new half-edges.
+ *
+ * __gl_meshSplice( eOrg, eDst ) is the basic operation for changing the
+ * mesh connectivity and topology.  It changes the mesh so that
+ *	eOrg->Onext <- OLD( eDst->Onext )
+ *	eDst->Onext <- OLD( eOrg->Onext )
+ * where OLD(...) means the value before the meshSplice operation.
+ *
+ * This can have two effects on the vertex structure:
+ *  - if eOrg->Org != eDst->Org, the two vertices are merged together
+ *  - if eOrg->Org == eDst->Org, the origin is split into two vertices
+ * In both cases, eDst->Org is changed and eOrg->Org is untouched.
+ *
+ * Similarly (and independently) for the face structure,
+ *  - if eOrg->Lface == eDst->Lface, one loop is split into two
+ *  - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
+ * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
+ *
+ * __gl_meshDelete( eDel ) removes the edge eDel.  There are several cases:
+ * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
+ * eDel->Lface is deleted.  Otherwise, we are splitting one loop into two;
+ * the newly created loop will contain eDel->Dst.  If the deletion of eDel
+ * would create isolated vertices, those are deleted as well.
+ *
+ * ********************** Other Edge Operations **************************
+ *
+ * __gl_meshAddEdgeVertex( eOrg ) creates a new edge eNew such that
+ * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
+ * eOrg and eNew will have the same left face.
+ *
+ * __gl_meshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
+ * such that eNew == eOrg->Lnext.  The new vertex is eOrg->Dst == eNew->Org.
+ * eOrg and eNew will have the same left face.
+ *
+ * __gl_meshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
+ * to eDst->Org, and returns the corresponding half-edge eNew.
+ * If eOrg->Lface == eDst->Lface, this splits one loop into two,
+ * and the newly created loop is eNew->Lface.  Otherwise, two disjoint
+ * loops are merged into one, and the loop eDst->Lface is destroyed.
+ *
+ * ************************ Other Operations *****************************
+ *
+ * __gl_meshNewMesh() creates a new mesh with no edges, no vertices,
+ * and no loops (what we usually call a "face").
+ *
+ * __gl_meshUnion( mesh1, mesh2 ) forms the union of all structures in
+ * both meshes, and returns the new mesh (the old meshes are destroyed).
+ *
+ * __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh.
+ *
+ * __gl_meshZapFace( fZap ) destroys a face and removes it from the
+ * global face list.  All edges of fZap will have a NULL pointer as their
+ * left face.  Any edges which also have a NULL pointer as their right face
+ * are deleted entirely (along with any isolated vertices this produces).
+ * An entire mesh can be deleted by zapping its faces, one at a time,
+ * in any order.  Zapped faces cannot be used in further mesh operations!
+ *
+ * __gl_meshCheckMesh( mesh ) checks a mesh for self-consistency.
+ */
+
+GLUhalfEdge	*__gl_meshMakeEdge( GLUmesh *mesh );
+int		__gl_meshSplice( GLUhalfEdge *eOrg, GLUhalfEdge *eDst );
+int		__gl_meshDelete( GLUhalfEdge *eDel );
+
+GLUhalfEdge	*__gl_meshAddEdgeVertex( GLUhalfEdge *eOrg );
+GLUhalfEdge	*__gl_meshSplitEdge( GLUhalfEdge *eOrg );
+GLUhalfEdge	*__gl_meshConnect( GLUhalfEdge *eOrg, GLUhalfEdge *eDst );
+
+GLUmesh		*__gl_meshNewMesh( void );
+GLUmesh		*__gl_meshUnion( GLUmesh *mesh1, GLUmesh *mesh2 );
+void		__gl_meshDeleteMesh( GLUmesh *mesh );
+void		__gl_meshZapFace( GLUface *fZap );
+
+#ifdef NDEBUG
+#define		__gl_meshCheckMesh( mesh )
+#else
+void		__gl_meshCheckMesh( GLUmesh *mesh );
+#endif
+
+#endif

+ 257 - 0
3rdparty/tessellate/normal.c

@@ -0,0 +1,257 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include "mesh.h"
+#include "tess.h"
+#include "normal.h"
+#include <math.h>
+#include <assert.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define Dot(u,v)	(u[0]*v[0] + u[1]*v[1] + u[2]*v[2])
+
+#if 0
+static void Normalize( GLdouble v[3] )
+{
+  GLdouble len = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+
+  assert( len > 0 );
+  len = sqrt( len );
+  v[0] /= len;
+  v[1] /= len;
+  v[2] /= len;
+}
+#endif
+
+#undef	ABS
+#define ABS(x)	((x) < 0 ? -(x) : (x))
+
+static int LongAxis( GLdouble v[3] )
+{
+  int i = 0;
+
+  if( ABS(v[1]) > ABS(v[0]) ) { i = 1; }
+  if( ABS(v[2]) > ABS(v[i]) ) { i = 2; }
+  return i;
+}
+
+static void ComputeNormal( GLUtesselator *tess, GLdouble norm[3] )
+{
+  GLUvertex *v, *v1, *v2;
+  GLdouble c, tLen2, maxLen2;
+  GLdouble maxVal[3], minVal[3], d1[3], d2[3], tNorm[3];
+  GLUvertex *maxVert[3], *minVert[3];
+  GLUvertex *vHead = &tess->mesh->vHead;
+  int i;
+
+  maxVal[0] = maxVal[1] = maxVal[2] = -2 * GLU_TESS_MAX_COORD;
+  minVal[0] = minVal[1] = minVal[2] = 2 * GLU_TESS_MAX_COORD;
+
+  for( v = vHead->next; v != vHead; v = v->next ) {
+    for( i = 0; i < 3; ++i ) {
+      c = v->coords[i];
+      if( c < minVal[i] ) { minVal[i] = c; minVert[i] = v; }
+      if( c > maxVal[i] ) { maxVal[i] = c; maxVert[i] = v; }
+    }
+  }
+
+  /* Find two vertices separated by at least 1/sqrt(3) of the maximum
+   * distance between any two vertices
+   */
+  i = 0;
+  if( maxVal[1] - minVal[1] > maxVal[0] - minVal[0] ) { i = 1; }
+  if( maxVal[2] - minVal[2] > maxVal[i] - minVal[i] ) { i = 2; }
+  if( minVal[i] >= maxVal[i] ) {
+    /* All vertices are the same -- normal doesn't matter */
+    norm[0] = 0; norm[1] = 0; norm[2] = 1;
+    return;
+  }
+
+  /* Look for a third vertex which forms the triangle with maximum area
+   * (Length of normal == twice the triangle area)
+   */
+  maxLen2 = 0;
+  v1 = minVert[i];
+  v2 = maxVert[i];
+  d1[0] = v1->coords[0] - v2->coords[0];
+  d1[1] = v1->coords[1] - v2->coords[1];
+  d1[2] = v1->coords[2] - v2->coords[2];
+  for( v = vHead->next; v != vHead; v = v->next ) {
+    d2[0] = v->coords[0] - v2->coords[0];
+    d2[1] = v->coords[1] - v2->coords[1];
+    d2[2] = v->coords[2] - v2->coords[2];
+    tNorm[0] = d1[1]*d2[2] - d1[2]*d2[1];
+    tNorm[1] = d1[2]*d2[0] - d1[0]*d2[2];
+    tNorm[2] = d1[0]*d2[1] - d1[1]*d2[0];
+    tLen2 = tNorm[0]*tNorm[0] + tNorm[1]*tNorm[1] + tNorm[2]*tNorm[2];
+    if( tLen2 > maxLen2 ) {
+      maxLen2 = tLen2;
+      norm[0] = tNorm[0];
+      norm[1] = tNorm[1];
+      norm[2] = tNorm[2];
+    }
+  }
+
+  if( maxLen2 <= 0 ) {
+    /* All points lie on a single line -- any decent normal will do */
+    norm[0] = norm[1] = norm[2] = 0;
+    norm[LongAxis(d1)] = 1;
+  }
+}
+
+
+static void CheckOrientation( GLUtesselator *tess )
+{
+  GLdouble area;
+  GLUface *f, *fHead = &tess->mesh->fHead;
+  GLUvertex *v, *vHead = &tess->mesh->vHead;
+  GLUhalfEdge *e;
+
+  /* When we compute the normal automatically, we choose the orientation
+   * so that the sum of the signed areas of all contours is non-negative.
+   */
+  area = 0;
+  for( f = fHead->next; f != fHead; f = f->next ) {
+    e = f->anEdge;
+    if( e->winding <= 0 ) continue;
+    do {
+      area += (e->Org->s - e->Dst->s) * (e->Org->t + e->Dst->t);
+      e = e->Lnext;
+    } while( e != f->anEdge );
+  }
+  if( area < 0 ) {
+    /* Reverse the orientation by flipping all the t-coordinates */
+    for( v = vHead->next; v != vHead; v = v->next ) {
+      v->t = - v->t;
+    }
+    tess->tUnit[0] = - tess->tUnit[0];
+    tess->tUnit[1] = - tess->tUnit[1];
+    tess->tUnit[2] = - tess->tUnit[2];
+  }
+}
+
+#ifdef FOR_TRITE_TEST_PROGRAM
+#include <stdlib.h>
+extern int RandomSweep;
+#define S_UNIT_X	(RandomSweep ? (2*drand48()-1) : 1.0)
+#define S_UNIT_Y	(RandomSweep ? (2*drand48()-1) : 0.0)
+#else
+#if defined(SLANTED_SWEEP)
+/* The "feature merging" is not intended to be complete.  There are
+ * special cases where edges are nearly parallel to the sweep line
+ * which are not implemented.  The algorithm should still behave
+ * robustly (ie. produce a reasonable tesselation) in the presence
+ * of such edges, however it may miss features which could have been
+ * merged.  We could minimize this effect by choosing the sweep line
+ * direction to be something unusual (ie. not parallel to one of the
+ * coordinate axes).
+ */
+#define S_UNIT_X	0.50941539564955385	/* Pre-normalized */
+#define S_UNIT_Y	0.86052074622010633
+#else
+#define S_UNIT_X	1.0
+#define S_UNIT_Y	0.0
+#endif
+#endif
+
+/* Determine the polygon normal and project vertices onto the plane
+ * of the polygon.
+ */
+void __gl_projectPolygon( GLUtesselator *tess )
+{
+  GLUvertex *v, *vHead = &tess->mesh->vHead;
+  GLdouble norm[3];
+  GLdouble *sUnit, *tUnit;
+  int i, computedNormal = FALSE;
+
+  norm[0] = tess->normal[0];
+  norm[1] = tess->normal[1];
+  norm[2] = tess->normal[2];
+  if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) {
+    ComputeNormal( tess, norm );
+    computedNormal = TRUE;
+  }
+  sUnit = tess->sUnit;
+  tUnit = tess->tUnit;
+  i = LongAxis( norm );
+
+#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT)
+  /* Choose the initial sUnit vector to be approximately perpendicular
+   * to the normal.
+   */
+  Normalize( norm );
+
+  sUnit[i] = 0;
+  sUnit[(i+1)%3] = S_UNIT_X;
+  sUnit[(i+2)%3] = S_UNIT_Y;
+
+  /* Now make it exactly perpendicular */
+  w = Dot( sUnit, norm );
+  sUnit[0] -= w * norm[0];
+  sUnit[1] -= w * norm[1];
+  sUnit[2] -= w * norm[2];
+  Normalize( sUnit );
+
+  /* Choose tUnit so that (sUnit,tUnit,norm) form a right-handed frame */
+  tUnit[0] = norm[1]*sUnit[2] - norm[2]*sUnit[1];
+  tUnit[1] = norm[2]*sUnit[0] - norm[0]*sUnit[2];
+  tUnit[2] = norm[0]*sUnit[1] - norm[1]*sUnit[0];
+  Normalize( tUnit );
+#else
+  /* Project perpendicular to a coordinate axis -- better numerically */
+  sUnit[i] = 0;
+  sUnit[(i+1)%3] = S_UNIT_X;
+  sUnit[(i+2)%3] = S_UNIT_Y;
+
+  tUnit[i] = 0;
+  tUnit[(i+1)%3] = (norm[i] > 0) ? -S_UNIT_Y : S_UNIT_Y;
+  tUnit[(i+2)%3] = (norm[i] > 0) ? S_UNIT_X : -S_UNIT_X;
+#endif
+
+  /* Project the vertices onto the sweep plane */
+  for( v = vHead->next; v != vHead; v = v->next ) {
+    v->s = Dot( v->coords, sUnit );
+    v->t = Dot( v->coords, tUnit );
+  }
+  if( computedNormal ) {
+    CheckOrientation( tess );
+  }
+}

+ 45 - 0
3rdparty/tessellate/normal.h

@@ -0,0 +1,45 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __normal_h_
+#define __normal_h_
+
+#include "tess.h"
+
+/* __gl_projectPolygon( tess ) determines the polygon normal
+ * and project vertices onto the plane of the polygon.
+ */
+void __gl_projectPolygon( GLUtesselator *tess );
+
+#endif

+ 257 - 0
3rdparty/tessellate/priorityq-heap.c

@@ -0,0 +1,257 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include <limits.h>
+#include <stddef.h>
+#include <assert.h>
+#include "priorityq-heap.h"
+#include "memalloc.h"
+
+#define INIT_SIZE	32
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifdef FOR_TRITE_TEST_PROGRAM
+#define LEQ(x,y)	(*pq->leq)(x,y)
+#else
+/* Violates modularity, but a little faster */
+#include "geom.h"
+#define LEQ(x,y)	VertLeq((GLUvertex *)x, (GLUvertex *)y)
+#endif
+
+/* really __gl_pqHeapNewPriorityQ */
+PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) )
+{
+  PriorityQ *pq = (PriorityQ *)memAlloc( sizeof( PriorityQ ));
+  if (pq == NULL) return NULL;
+
+  pq->size = 0;
+  pq->max = INIT_SIZE;
+  pq->nodes = (PQnode *)memAlloc( (INIT_SIZE + 1) * sizeof(pq->nodes[0]) );
+  if (pq->nodes == NULL) {
+     memFree(pq);
+     return NULL;
+  }
+
+  pq->handles = (PQhandleElem *)memAlloc( (INIT_SIZE + 1) * sizeof(pq->handles[0]) );
+  if (pq->handles == NULL) {
+     memFree(pq->nodes);
+     memFree(pq);
+     return NULL;
+  }
+
+  pq->initialized = FALSE;
+  pq->freeList = 0;
+  pq->leq = leq;
+
+  pq->nodes[1].handle = 1;	/* so that Minimum() returns NULL */
+  pq->handles[1].key = NULL;
+  return pq;
+}
+
+/* really __gl_pqHeapDeletePriorityQ */
+void pqDeletePriorityQ( PriorityQ *pq )
+{
+  memFree( pq->handles );
+  memFree( pq->nodes );
+  memFree( pq );
+}
+
+
+static void FloatDown( PriorityQ *pq, long curr )
+{
+  PQnode *n = pq->nodes;
+  PQhandleElem *h = pq->handles;
+  PQhandle hCurr, hChild;
+  long child;
+
+  hCurr = n[curr].handle;
+  for( ;; ) {
+    child = curr << 1;
+    if( child < pq->size && LEQ( h[n[child+1].handle].key,
+				 h[n[child].handle].key )) {
+      ++child;
+    }
+
+    assert(child <= pq->max);
+
+    hChild = n[child].handle;
+    if( child > pq->size || LEQ( h[hCurr].key, h[hChild].key )) {
+      n[curr].handle = hCurr;
+      h[hCurr].node = curr;
+      break;
+    }
+    n[curr].handle = hChild;
+    h[hChild].node = curr;
+    curr = child;
+  }
+}
+
+
+static void FloatUp( PriorityQ *pq, long curr )
+{
+  PQnode *n = pq->nodes;
+  PQhandleElem *h = pq->handles;
+  PQhandle hCurr, hParent;
+  long parent;
+
+  hCurr = n[curr].handle;
+  for( ;; ) {
+    parent = curr >> 1;
+    hParent = n[parent].handle;
+    if( parent == 0 || LEQ( h[hParent].key, h[hCurr].key )) {
+      n[curr].handle = hCurr;
+      h[hCurr].node = curr;
+      break;
+    }
+    n[curr].handle = hParent;
+    h[hParent].node = curr;
+    curr = parent;
+  }
+}
+
+/* really __gl_pqHeapInit */
+void pqInit( PriorityQ *pq )
+{
+  long i;
+
+  /* This method of building a heap is O(n), rather than O(n lg n). */
+
+  for( i = pq->size; i >= 1; --i ) {
+    FloatDown( pq, i );
+  }
+  pq->initialized = TRUE;
+}
+
+/* really __gl_pqHeapInsert */
+/* returns LONG_MAX iff out of memory */
+PQhandle pqInsert( PriorityQ *pq, PQkey keyNew )
+{
+  long curr;
+  PQhandle free_handle;
+
+  curr = ++ pq->size;
+  if( (curr*2) > pq->max ) {
+    PQnode *saveNodes= pq->nodes;
+    PQhandleElem *saveHandles= pq->handles;
+
+    /* If the heap overflows, double its size. */
+    pq->max <<= 1;
+    pq->nodes = (PQnode *)memRealloc( pq->nodes, 
+				     (size_t) 
+				     ((pq->max + 1) * sizeof( pq->nodes[0] )));
+    if (pq->nodes == NULL) {
+       pq->nodes = saveNodes;	/* restore ptr to free upon return */
+       return LONG_MAX;
+    }
+    pq->handles = (PQhandleElem *)memRealloc( pq->handles,
+			                     (size_t)
+			                      ((pq->max + 1) * 
+					       sizeof( pq->handles[0] )));
+    if (pq->handles == NULL) {
+       pq->handles = saveHandles; /* restore ptr to free upon return */
+       return LONG_MAX;
+    }
+  }
+
+  if( pq->freeList == 0 ) {
+    free_handle = curr;
+  } else {
+    free_handle = pq->freeList;
+    pq->freeList = pq->handles[free_handle].node;
+  }
+
+  pq->nodes[curr].handle = free_handle;
+  pq->handles[free_handle].node = curr;
+  pq->handles[free_handle].key = keyNew;
+
+  if( pq->initialized ) {
+    FloatUp( pq, curr );
+  }
+  assert(free_handle != LONG_MAX);
+  return free_handle;
+}
+
+/* really __gl_pqHeapExtractMin */
+PQkey pqExtractMin( PriorityQ *pq )
+{
+  PQnode *n = pq->nodes;
+  PQhandleElem *h = pq->handles;
+  PQhandle hMin = n[1].handle;
+  PQkey min = h[hMin].key;
+
+  if( pq->size > 0 ) {
+    n[1].handle = n[pq->size].handle;
+    h[n[1].handle].node = 1;
+
+    h[hMin].key = NULL;
+    h[hMin].node = pq->freeList;
+    pq->freeList = hMin;
+
+    if( -- pq->size > 0 ) {
+      FloatDown( pq, 1 );
+    }
+  }
+  return min;
+}
+
+/* really __gl_pqHeapDelete */
+void pqDelete( PriorityQ *pq, PQhandle hCurr )
+{
+  PQnode *n = pq->nodes;
+  PQhandleElem *h = pq->handles;
+  long curr;
+
+  assert( hCurr >= 1 && hCurr <= pq->max && h[hCurr].key != NULL );
+
+  curr = h[hCurr].node;
+  n[curr].handle = n[pq->size].handle;
+  h[n[curr].handle].node = curr;
+
+  if( curr <= -- pq->size ) {
+    if( curr <= 1 || LEQ( h[n[curr>>1].handle].key, h[n[curr].handle].key )) {
+      FloatDown( pq, curr );
+    } else {
+      FloatUp( pq, curr );
+    }
+  }
+  h[hCurr].key = NULL;
+  h[hCurr].node = pq->freeList;
+  pq->freeList = hCurr;
+}

+ 107 - 0
3rdparty/tessellate/priorityq-heap.h

@@ -0,0 +1,107 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __priorityq_heap_h_
+#define __priorityq_heap_h_
+
+/* Use #define's so that another heap implementation can use this one */
+
+#define PQkey			PQHeapKey
+#define PQhandle		PQHeapHandle
+#define PriorityQ		PriorityQHeap
+
+#define pqNewPriorityQ(leq)	__gl_pqHeapNewPriorityQ(leq)
+#define pqDeletePriorityQ(pq)	__gl_pqHeapDeletePriorityQ(pq)
+
+/* The basic operations are insertion of a new key (pqInsert),
+ * and examination/extraction of a key whose value is minimum
+ * (pqMinimum/pqExtractMin).  Deletion is also allowed (pqDelete);
+ * for this purpose pqInsert returns a "handle" which is supplied
+ * as the argument.
+ *
+ * An initial heap may be created efficiently by calling pqInsert
+ * repeatedly, then calling pqInit.  In any case pqInit must be called
+ * before any operations other than pqInsert are used.
+ *
+ * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key.
+ * This may also be tested with pqIsEmpty.
+ */
+#define pqInit(pq)		__gl_pqHeapInit(pq)
+#define pqInsert(pq,key)	__gl_pqHeapInsert(pq,key)
+#define pqMinimum(pq)		__gl_pqHeapMinimum(pq)
+#define pqExtractMin(pq)	__gl_pqHeapExtractMin(pq)
+#define pqDelete(pq,handle)	__gl_pqHeapDelete(pq,handle)
+#define pqIsEmpty(pq)		__gl_pqHeapIsEmpty(pq)
+
+
+/* Since we support deletion the data structure is a little more
+ * complicated than an ordinary heap.  "nodes" is the heap itself;
+ * active nodes are stored in the range 1..pq->size.  When the
+ * heap exceeds its allocated size (pq->max), its size doubles.
+ * The children of node i are nodes 2i and 2i+1.
+ *
+ * Each node stores an index into an array "handles".  Each handle
+ * stores a key, plus a pointer back to the node which currently
+ * represents that key (ie. nodes[handles[i].node].handle == i).
+ */
+
+typedef void *PQkey;
+typedef long PQhandle;
+typedef struct PriorityQ PriorityQ;
+
+typedef struct { PQhandle handle; } PQnode;
+typedef struct { PQkey key; PQhandle node; } PQhandleElem;
+
+struct PriorityQ {
+  PQnode	*nodes;
+  PQhandleElem	*handles;
+  long		size, max;
+  PQhandle	freeList;
+  int		initialized;
+  int		(*leq)(PQkey key1, PQkey key2);
+};
+  
+PriorityQ	*pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) );
+void		pqDeletePriorityQ( PriorityQ *pq );
+
+void		pqInit( PriorityQ *pq );
+PQhandle	pqInsert( PriorityQ *pq, PQkey key );
+PQkey		pqExtractMin( PriorityQ *pq );
+void		pqDelete( PriorityQ *pq, PQhandle handle );
+
+
+#define __gl_pqHeapMinimum(pq)	((pq)->handles[(pq)->nodes[1].handle].key)
+#define __gl_pqHeapIsEmpty(pq)	((pq)->size == 0)
+
+#endif

+ 117 - 0
3rdparty/tessellate/priorityq-sort.h

@@ -0,0 +1,117 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __priorityq_sort_h_
+#define __priorityq_sort_h_
+
+#include "priorityq-heap.h"
+
+#undef PQkey
+#undef PQhandle
+#undef PriorityQ
+#undef pqNewPriorityQ
+#undef pqDeletePriorityQ
+#undef pqInit
+#undef pqInsert
+#undef pqMinimum
+#undef pqExtractMin
+#undef pqDelete
+#undef pqIsEmpty
+
+/* Use #define's so that another heap implementation can use this one */
+
+#define PQkey			PQSortKey
+#define PQhandle		PQSortHandle
+#define PriorityQ		PriorityQSort
+
+#define pqNewPriorityQ(leq)	__gl_pqSortNewPriorityQ(leq)
+#define pqDeletePriorityQ(pq)	__gl_pqSortDeletePriorityQ(pq)
+
+/* The basic operations are insertion of a new key (pqInsert),
+ * and examination/extraction of a key whose value is minimum
+ * (pqMinimum/pqExtractMin).  Deletion is also allowed (pqDelete);
+ * for this purpose pqInsert returns a "handle" which is supplied
+ * as the argument.
+ *
+ * An initial heap may be created efficiently by calling pqInsert
+ * repeatedly, then calling pqInit.  In any case pqInit must be called
+ * before any operations other than pqInsert are used.
+ *
+ * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key.
+ * This may also be tested with pqIsEmpty.
+ */
+#define pqInit(pq)		__gl_pqSortInit(pq)
+#define pqInsert(pq,key)	__gl_pqSortInsert(pq,key)
+#define pqMinimum(pq)		__gl_pqSortMinimum(pq)
+#define pqExtractMin(pq)	__gl_pqSortExtractMin(pq)
+#define pqDelete(pq,handle)	__gl_pqSortDelete(pq,handle)
+#define pqIsEmpty(pq)		__gl_pqSortIsEmpty(pq)
+
+
+/* Since we support deletion the data structure is a little more
+ * complicated than an ordinary heap.  "nodes" is the heap itself;
+ * active nodes are stored in the range 1..pq->size.  When the
+ * heap exceeds its allocated size (pq->max), its size doubles.
+ * The children of node i are nodes 2i and 2i+1.
+ *
+ * Each node stores an index into an array "handles".  Each handle
+ * stores a key, plus a pointer back to the node which currently
+ * represents that key (ie. nodes[handles[i].node].handle == i).
+ */
+
+typedef PQHeapKey PQkey;
+typedef PQHeapHandle PQhandle;
+typedef struct PriorityQ PriorityQ;
+
+struct PriorityQ {
+  PriorityQHeap	*heap;
+  PQkey		*keys;
+  PQkey		**order;
+  PQhandle	size, max;
+  int		initialized;
+  int		(*leq)(PQkey key1, PQkey key2);
+};
+  
+PriorityQ	*pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) );
+void		pqDeletePriorityQ( PriorityQ *pq );
+
+int		pqInit( PriorityQ *pq );
+PQhandle	pqInsert( PriorityQ *pq, PQkey key );
+PQkey		pqExtractMin( PriorityQ *pq );
+void		pqDelete( PriorityQ *pq, PQhandle handle );
+
+PQkey		pqMinimum( PriorityQ *pq );
+int		pqIsEmpty( PriorityQ *pq );
+
+#endif

+ 260 - 0
3rdparty/tessellate/priorityq.c

@@ -0,0 +1,260 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <stddef.h>
+#include <assert.h>
+#include <limits.h>		/* LONG_MAX */
+#include "memalloc.h"
+
+/* Include all the code for the regular heap-based queue here. */
+
+#include "priorityq-heap.c"
+
+/* Now redefine all the function names to map to their "Sort" versions. */
+
+#include "priorityq-sort.h"
+
+/* really __gl_pqSortNewPriorityQ */
+PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) )
+{
+  PriorityQ *pq = (PriorityQ *)memAlloc( sizeof( PriorityQ ));
+  if (pq == NULL) return NULL;
+
+  pq->heap = __gl_pqHeapNewPriorityQ( leq );
+  if (pq->heap == NULL) {
+     memFree(pq);
+     return NULL;
+  }
+
+  pq->keys = (PQHeapKey *)memAlloc( INIT_SIZE * sizeof(pq->keys[0]) );
+  if (pq->keys == NULL) {
+     __gl_pqHeapDeletePriorityQ(pq->heap);
+     memFree(pq);
+     return NULL;
+  }
+
+  pq->size = 0;
+  pq->max = INIT_SIZE;
+  pq->initialized = FALSE;
+  pq->leq = leq;
+  return pq;
+}
+
+/* really __gl_pqSortDeletePriorityQ */
+void pqDeletePriorityQ( PriorityQ *pq )
+{
+  assert(pq != NULL); 
+  if (pq->heap != NULL) __gl_pqHeapDeletePriorityQ( pq->heap );
+  if (pq->order != NULL) memFree( pq->order );
+  if (pq->keys != NULL) memFree( pq->keys );
+  memFree( pq );
+}
+
+
+#define LT(x,y)		(! LEQ(y,x))
+#define GT(x,y)		(! LEQ(x,y))
+#define Swap(a,b)	do{PQkey *tmp = *a; *a = *b; *b = tmp;}while(0)
+
+/* really __gl_pqSortInit */
+int pqInit( PriorityQ *pq )
+{
+  PQkey **p, **r, **i, **j, *piv;
+  struct { PQkey **p, **r; } Stack[50], *top = Stack;
+  unsigned long seed = 2016473283;
+
+  /* Create an array of indirect pointers to the keys, so that we
+   * the handles we have returned are still valid.
+   */
+/*
+  pq->order = (PQHeapKey **)memAlloc( (size_t)
+                                  (pq->size * sizeof(pq->order[0])) );
+*/
+  pq->order = (PQHeapKey **)memAlloc( (size_t)
+                                  ((pq->size+1) * sizeof(pq->order[0])) );
+/* the previous line is a patch to compensate for the fact that IBM */
+/* machines return a null on a malloc of zero bytes (unlike SGI),   */
+/* so we have to put in this defense to guard against a memory      */
+/* fault four lines down. from fossum@austin.ibm.com.               */
+  if (pq->order == NULL) return 0;
+
+  p = pq->order;
+  r = p + pq->size - 1;
+  for( piv = pq->keys, i = p; i <= r; ++piv, ++i ) {
+    *i = piv;
+  }
+
+  /* Sort the indirect pointers in descending order,
+   * using randomized Quicksort
+   */
+  top->p = p; top->r = r; ++top;
+  while( --top >= Stack ) {
+    p = top->p;
+    r = top->r;
+    while( r > p + 10 ) {
+      seed = seed * 1539415821 + 1;
+      i = p + seed % (r - p + 1);
+      piv = *i;
+      *i = *p;
+      *p = piv;
+      i = p - 1;
+      j = r + 1;
+      do {
+	do { ++i; } while( GT( **i, *piv ));
+	do { --j; } while( LT( **j, *piv ));
+	Swap( i, j );
+      } while( i < j );
+      Swap( i, j );	/* Undo last swap */
+      if( i - p < r - j ) {
+	top->p = j+1; top->r = r; ++top;
+	r = i-1;
+      } else {
+	top->p = p; top->r = i-1; ++top;
+	p = j+1;
+      }
+    }
+    /* Insertion sort small lists */
+    for( i = p+1; i <= r; ++i ) {
+      piv = *i;
+      for( j = i; j > p && LT( **(j-1), *piv ); --j ) {
+	*j = *(j-1);
+      }
+      *j = piv;
+    }
+  }
+  pq->max = pq->size;
+  pq->initialized = TRUE;
+  __gl_pqHeapInit( pq->heap );	/* always succeeds */
+
+#ifndef NDEBUG
+  p = pq->order;
+  r = p + pq->size - 1;
+  for( i = p; i < r; ++i ) {
+    assert( LEQ( **(i+1), **i ));
+  }
+#endif
+
+  return 1;
+}
+
+/* really __gl_pqSortInsert */
+/* returns LONG_MAX iff out of memory */ 
+PQhandle pqInsert( PriorityQ *pq, PQkey keyNew )
+{
+  long curr;
+
+  if( pq->initialized ) {
+    return __gl_pqHeapInsert( pq->heap, keyNew );
+  }
+  curr = pq->size;
+  if( ++ pq->size >= pq->max ) {
+    PQkey *saveKey= pq->keys;
+
+    /* If the heap overflows, double its size. */
+    pq->max <<= 1;
+    pq->keys = (PQHeapKey *)memRealloc( pq->keys, 
+	 	                        (size_t)
+	                                 (pq->max * sizeof( pq->keys[0] )));
+    if (pq->keys == NULL) {	
+       pq->keys = saveKey;	/* restore ptr to free upon return */
+       return LONG_MAX;
+    }
+  }
+  assert(curr != LONG_MAX);	
+  pq->keys[curr] = keyNew;
+
+  /* Negative handles index the sorted array. */
+  return -(curr+1);
+}
+
+/* really __gl_pqSortExtractMin */
+PQkey pqExtractMin( PriorityQ *pq )
+{
+  PQkey sortMin, heapMin;
+
+  if( pq->size == 0 ) {
+    return __gl_pqHeapExtractMin( pq->heap );
+  }
+  sortMin = *(pq->order[pq->size-1]);
+  if( ! __gl_pqHeapIsEmpty( pq->heap )) {
+    heapMin = __gl_pqHeapMinimum( pq->heap );
+    if( LEQ( heapMin, sortMin )) {
+      return __gl_pqHeapExtractMin( pq->heap );
+    }
+  }
+  do {
+    -- pq->size;
+  } while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL );
+  return sortMin;
+}
+
+/* really __gl_pqSortMinimum */
+PQkey pqMinimum( PriorityQ *pq )
+{
+  PQkey sortMin, heapMin;
+
+  if( pq->size == 0 ) {
+    return __gl_pqHeapMinimum( pq->heap );
+  }
+  sortMin = *(pq->order[pq->size-1]);
+  if( ! __gl_pqHeapIsEmpty( pq->heap )) {
+    heapMin = __gl_pqHeapMinimum( pq->heap );
+    if( LEQ( heapMin, sortMin )) {
+      return heapMin;
+    }
+  }
+  return sortMin;
+}
+
+/* really __gl_pqSortIsEmpty */
+int pqIsEmpty( PriorityQ *pq )
+{
+  return (pq->size == 0) && __gl_pqHeapIsEmpty( pq->heap );
+}
+
+/* really __gl_pqSortDelete */
+void pqDelete( PriorityQ *pq, PQhandle curr )
+{
+  if( curr >= 0 ) {
+    __gl_pqHeapDelete( pq->heap, curr );
+    return;
+  }
+  curr = -(curr+1);
+  assert( curr < pq->max && pq->keys[curr] != NULL );
+
+  pq->keys[curr] = NULL;
+  while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ) {
+    -- pq->size;
+  }
+}

+ 117 - 0
3rdparty/tessellate/priorityq.h

@@ -0,0 +1,117 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __priorityq_sort_h_
+#define __priorityq_sort_h_
+
+#include "priorityq-heap.h"
+
+#undef PQkey
+#undef PQhandle
+#undef PriorityQ
+#undef pqNewPriorityQ
+#undef pqDeletePriorityQ
+#undef pqInit
+#undef pqInsert
+#undef pqMinimum
+#undef pqExtractMin
+#undef pqDelete
+#undef pqIsEmpty
+
+/* Use #define's so that another heap implementation can use this one */
+
+#define PQkey			PQSortKey
+#define PQhandle		PQSortHandle
+#define PriorityQ		PriorityQSort
+
+#define pqNewPriorityQ(leq)	__gl_pqSortNewPriorityQ(leq)
+#define pqDeletePriorityQ(pq)	__gl_pqSortDeletePriorityQ(pq)
+
+/* The basic operations are insertion of a new key (pqInsert),
+ * and examination/extraction of a key whose value is minimum
+ * (pqMinimum/pqExtractMin).  Deletion is also allowed (pqDelete);
+ * for this purpose pqInsert returns a "handle" which is supplied
+ * as the argument.
+ *
+ * An initial heap may be created efficiently by calling pqInsert
+ * repeatedly, then calling pqInit.  In any case pqInit must be called
+ * before any operations other than pqInsert are used.
+ *
+ * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key.
+ * This may also be tested with pqIsEmpty.
+ */
+#define pqInit(pq)		__gl_pqSortInit(pq)
+#define pqInsert(pq,key)	__gl_pqSortInsert(pq,key)
+#define pqMinimum(pq)		__gl_pqSortMinimum(pq)
+#define pqExtractMin(pq)	__gl_pqSortExtractMin(pq)
+#define pqDelete(pq,handle)	__gl_pqSortDelete(pq,handle)
+#define pqIsEmpty(pq)		__gl_pqSortIsEmpty(pq)
+
+
+/* Since we support deletion the data structure is a little more
+ * complicated than an ordinary heap.  "nodes" is the heap itself;
+ * active nodes are stored in the range 1..pq->size.  When the
+ * heap exceeds its allocated size (pq->max), its size doubles.
+ * The children of node i are nodes 2i and 2i+1.
+ *
+ * Each node stores an index into an array "handles".  Each handle
+ * stores a key, plus a pointer back to the node which currently
+ * represents that key (ie. nodes[handles[i].node].handle == i).
+ */
+
+typedef PQHeapKey PQkey;
+typedef PQHeapHandle PQhandle;
+typedef struct PriorityQ PriorityQ;
+
+struct PriorityQ {
+  PriorityQHeap	*heap;
+  PQkey		*keys;
+  PQkey		**order;
+  PQhandle	size, max;
+  int		initialized;
+  int		(*leq)(PQkey key1, PQkey key2);
+};
+  
+PriorityQ	*pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) );
+void		pqDeletePriorityQ( PriorityQ *pq );
+
+int		pqInit( PriorityQ *pq );
+PQhandle	pqInsert( PriorityQ *pq, PQkey key );
+PQkey		pqExtractMin( PriorityQ *pq );
+void		pqDelete( PriorityQ *pq, PQhandle handle );
+
+PQkey		pqMinimum( PriorityQ *pq );
+int		pqIsEmpty( PriorityQ *pq );
+
+#endif

+ 502 - 0
3rdparty/tessellate/render.c

@@ -0,0 +1,502 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <assert.h>
+#include <stddef.h>
+#include "mesh.h"
+#include "tess.h"
+#include "render.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* This structure remembers the information we need about a primitive
+ * to be able to render it later, once we have determined which
+ * primitive is able to use the most triangles.
+ */
+struct FaceCount {
+  long		size;		/* number of triangles used */
+  GLUhalfEdge	*eStart;	/* edge where this primitive starts */
+  void		(*render)(GLUtesselator *, GLUhalfEdge *, long);
+                                /* routine to render this primitive */
+};
+
+static struct FaceCount MaximumFan( GLUhalfEdge *eOrig );
+static struct FaceCount MaximumStrip( GLUhalfEdge *eOrig );
+
+static void RenderFan( GLUtesselator *tess, GLUhalfEdge *eStart, long size );
+static void RenderStrip( GLUtesselator *tess, GLUhalfEdge *eStart, long size );
+static void RenderTriangle( GLUtesselator *tess, GLUhalfEdge *eStart,
+			    long size );
+
+static void RenderMaximumFaceGroup( GLUtesselator *tess, GLUface *fOrig );
+static void RenderLonelyTriangles( GLUtesselator *tess, GLUface *head );
+
+
+
+/************************ Strips and Fans decomposition ******************/
+
+/* __gl_renderMesh( tess, mesh ) takes a mesh and breaks it into triangle
+ * fans, strips, and separate triangles.  A substantial effort is made
+ * to use as few rendering primitives as possible (ie. to make the fans
+ * and strips as large as possible).
+ *
+ * The rendering output is provided as callbacks (see the api).
+ */
+void __gl_renderMesh( GLUtesselator *tess, GLUmesh *mesh )
+{
+  GLUface *f;
+
+  /* Make a list of separate triangles so we can render them all at once */
+  tess->lonelyTriList = NULL;
+
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) {
+    f->marked = FALSE;
+  }
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) {
+
+    /* We examine all faces in an arbitrary order.  Whenever we find
+     * an unprocessed face F, we output a group of faces including F
+     * whose size is maximum.
+     */
+    if( f->inside && ! f->marked ) {
+      RenderMaximumFaceGroup( tess, f );
+      assert( f->marked );
+    }
+  }
+  if( tess->lonelyTriList != NULL ) {
+    RenderLonelyTriangles( tess, tess->lonelyTriList );
+    tess->lonelyTriList = NULL;
+  }
+}
+
+
+static void RenderMaximumFaceGroup( GLUtesselator *tess, GLUface *fOrig )
+{
+  /* We want to find the largest triangle fan or strip of unmarked faces
+   * which includes the given face fOrig.  There are 3 possible fans
+   * passing through fOrig (one centered at each vertex), and 3 possible
+   * strips (one for each CCW permutation of the vertices).  Our strategy
+   * is to try all of these, and take the primitive which uses the most
+   * triangles (a greedy approach).
+   */
+  GLUhalfEdge *e = fOrig->anEdge;
+  struct FaceCount max, newFace;
+
+  max.size = 1;
+  max.eStart = e;
+  max.render = &RenderTriangle;
+
+  if( ! tess->flagBoundary ) {
+    newFace = MaximumFan( e ); if( newFace.size > max.size ) { max = newFace; }
+    newFace = MaximumFan( e->Lnext ); if( newFace.size > max.size ) { max = newFace; }
+    newFace = MaximumFan( e->Lprev ); if( newFace.size > max.size ) { max = newFace; }
+
+    newFace = MaximumStrip( e ); if( newFace.size > max.size ) { max = newFace; }
+    newFace = MaximumStrip( e->Lnext ); if( newFace.size > max.size ) { max = newFace; }
+    newFace = MaximumStrip( e->Lprev ); if( newFace.size > max.size ) { max = newFace; }
+  }
+  (*(max.render))( tess, max.eStart, max.size );
+}
+
+
+/* Macros which keep track of faces we have marked temporarily, and allow
+ * us to backtrack when necessary.  With triangle fans, this is not
+ * really necessary, since the only awkward case is a loop of triangles
+ * around a single origin vertex.  However with strips the situation is
+ * more complicated, and we need a general tracking method like the
+ * one here.
+ */
+#define Marked(f)	(! (f)->inside || (f)->marked)
+
+#define AddToTrail(f,t)	((f)->trail = (t), (t) = (f), (f)->marked = TRUE)
+
+#define FreeTrail(t)	do { \
+			  while( (t) != NULL ) { \
+			    (t)->marked = FALSE; t = (t)->trail; \
+			  } \
+			} while(0) /* absorb trailing semicolon */
+
+
+
+static struct FaceCount MaximumFan( GLUhalfEdge *eOrig )
+{
+  /* eOrig->Lface is the face we want to render.  We want to find the size
+   * of a maximal fan around eOrig->Org.  To do this we just walk around
+   * the origin vertex as far as possible in both directions.
+   */
+  struct FaceCount newFace = { 0, NULL, &RenderFan };
+  GLUface *trail = NULL;
+  GLUhalfEdge *e;
+
+  for( e = eOrig; ! Marked( e->Lface ); e = e->Onext ) {
+    AddToTrail( e->Lface, trail );
+    ++newFace.size;
+  }
+  for( e = eOrig; ! Marked( e->Rface ); e = e->Oprev ) {
+    AddToTrail( e->Rface, trail );
+    ++newFace.size;
+  }
+  newFace.eStart = e;
+  /*LINTED*/
+  FreeTrail( trail );
+  return newFace;
+}
+
+
+#define IsEven(n)	(((n) & 1) == 0)
+
+static struct FaceCount MaximumStrip( GLUhalfEdge *eOrig )
+{
+  /* Here we are looking for a maximal strip that contains the vertices
+   * eOrig->Org, eOrig->Dst, eOrig->Lnext->Dst (in that order or the
+   * reverse, such that all triangles are oriented CCW).
+   *
+   * Again we walk forward and backward as far as possible.  However for
+   * strips there is a twist: to get CCW orientations, there must be
+   * an *even* number of triangles in the strip on one side of eOrig.
+   * We walk the strip starting on a side with an even number of triangles;
+   * if both side have an odd number, we are forced to shorten one side.
+   */
+  struct FaceCount newFace = { 0, NULL, &RenderStrip };
+  long headSize = 0, tailSize = 0;
+  GLUface *trail = NULL;
+  GLUhalfEdge *e, *eTail, *eHead;
+
+  for( e = eOrig; ! Marked( e->Lface ); ++tailSize, e = e->Onext ) {
+    AddToTrail( e->Lface, trail );
+    ++tailSize;
+    e = e->Dprev;
+    if( Marked( e->Lface )) break;
+    AddToTrail( e->Lface, trail );
+  }
+  eTail = e;
+
+  for( e = eOrig; ! Marked( e->Rface ); ++headSize, e = e->Dnext ) {
+    AddToTrail( e->Rface, trail );
+    ++headSize;
+    e = e->Oprev;
+    if( Marked( e->Rface )) break;
+    AddToTrail( e->Rface, trail );
+  }
+  eHead = e;
+
+  newFace.size = tailSize + headSize;
+  if( IsEven( tailSize )) {
+    newFace.eStart = eTail->Sym;
+  } else if( IsEven( headSize )) {
+    newFace.eStart = eHead;
+  } else {
+    /* Both sides have odd length, we must shorten one of them.  In fact,
+     * we must start from eHead to guarantee inclusion of eOrig->Lface.
+     */
+    --newFace.size;
+    newFace.eStart = eHead->Onext;
+  }
+  /*LINTED*/
+  FreeTrail( trail );
+  return newFace;
+}
+
+
+static void RenderTriangle( GLUtesselator *tess, GLUhalfEdge *e, long size )
+{
+  /* Just add the triangle to a triangle list, so we can render all
+   * the separate triangles at once.
+   */
+  assert( size == 1 );
+  AddToTrail( e->Lface, tess->lonelyTriList );
+}
+
+
+static void RenderLonelyTriangles( GLUtesselator *tess, GLUface *f )
+{
+  /* Now we render all the separate triangles which could not be
+   * grouped into a triangle fan or strip.
+   */
+  GLUhalfEdge *e;
+  int newState;
+  int edgeState = -1;	/* force edge state output for first vertex */
+
+  CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLES );
+
+  for( ; f != NULL; f = f->trail ) {
+    /* Loop once for each edge (there will always be 3 edges) */
+
+    e = f->anEdge;
+    do {
+      if( tess->flagBoundary ) {
+	/* Set the "edge state" to TRUE just before we output the
+	 * first vertex of each edge on the polygon boundary.
+	 */
+	newState = ! e->Rface->inside;
+	if( edgeState != newState ) {
+	  edgeState = newState;
+          CALL_EDGE_FLAG_OR_EDGE_FLAG_DATA( edgeState );
+	}
+      }
+      CALL_VERTEX_OR_VERTEX_DATA( e->Org->data );
+
+      e = e->Lnext;
+    } while( e != f->anEdge );
+  }
+  CALL_END_OR_END_DATA();
+}
+
+
+static void RenderFan( GLUtesselator *tess, GLUhalfEdge *e, long size )
+{
+  /* Render as many CCW triangles as possible in a fan starting from
+   * edge "e".  The fan *should* contain exactly "size" triangles
+   * (otherwise we've goofed up somewhere).
+   */
+  CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLE_FAN ); 
+  CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); 
+  CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); 
+
+  while( ! Marked( e->Lface )) {
+    e->Lface->marked = TRUE;
+    --size;
+    e = e->Onext;
+    CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); 
+  }
+
+  assert( size == 0 );
+  CALL_END_OR_END_DATA();
+}
+
+
+static void RenderStrip( GLUtesselator *tess, GLUhalfEdge *e, long size )
+{
+  /* Render as many CCW triangles as possible in a strip starting from
+   * edge "e".  The strip *should* contain exactly "size" triangles
+   * (otherwise we've goofed up somewhere).
+   */
+  CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLE_STRIP );
+  CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); 
+  CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); 
+
+  while( ! Marked( e->Lface )) {
+    e->Lface->marked = TRUE;
+    --size;
+    e = e->Dprev;
+    CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); 
+    if( Marked( e->Lface )) break;
+
+    e->Lface->marked = TRUE;
+    --size;
+    e = e->Onext;
+    CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); 
+  }
+
+  assert( size == 0 );
+  CALL_END_OR_END_DATA();
+}
+
+
+/************************ Boundary contour decomposition ******************/
+
+/* __gl_renderBoundary( tess, mesh ) takes a mesh, and outputs one
+ * contour for each face marked "inside".  The rendering output is
+ * provided as callbacks (see the api).
+ */
+void __gl_renderBoundary( GLUtesselator *tess, GLUmesh *mesh )
+{
+  GLUface *f;
+  GLUhalfEdge *e;
+
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) {
+    if( f->inside ) {
+      CALL_BEGIN_OR_BEGIN_DATA( GL_LINE_LOOP );
+      e = f->anEdge;
+      do {
+        CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); 
+	e = e->Lnext;
+      } while( e != f->anEdge );
+      CALL_END_OR_END_DATA();
+    }
+  }
+}
+
+
+/************************ Quick-and-dirty decomposition ******************/
+
+#define SIGN_INCONSISTENT 2
+
+static int ComputeNormal( GLUtesselator *tess, GLdouble norm[3], int check )
+/*
+ * If check==FALSE, we compute the polygon normal and place it in norm[].
+ * If check==TRUE, we check that each triangle in the fan from v0 has a
+ * consistent orientation with respect to norm[].  If triangles are
+ * consistently oriented CCW, return 1; if CW, return -1; if all triangles
+ * are degenerate return 0; otherwise (no consistent orientation) return
+ * SIGN_INCONSISTENT.
+ */
+{
+  CachedVertex *v0 = tess->cache;
+  CachedVertex *vn = v0 + tess->cacheCount;
+  CachedVertex *vc;
+  GLdouble dot, xc, yc, zc, xp, yp, zp, n[3];
+  int sign = 0;
+
+  /* Find the polygon normal.  It is important to get a reasonable
+   * normal even when the polygon is self-intersecting (eg. a bowtie).
+   * Otherwise, the computed normal could be very tiny, but perpendicular
+   * to the true plane of the polygon due to numerical noise.  Then all
+   * the triangles would appear to be degenerate and we would incorrectly
+   * decompose the polygon as a fan (or simply not render it at all).
+   *
+   * We use a sum-of-triangles normal algorithm rather than the more
+   * efficient sum-of-trapezoids method (used in CheckOrientation()
+   * in normal.c).  This lets us explicitly reverse the signed area
+   * of some triangles to get a reasonable normal in the self-intersecting
+   * case.
+   */
+  if( ! check ) {
+    norm[0] = norm[1] = norm[2] = 0.0;
+  }
+
+  vc = v0 + 1;
+  xc = vc->coords[0] - v0->coords[0];
+  yc = vc->coords[1] - v0->coords[1];
+  zc = vc->coords[2] - v0->coords[2];
+  while( ++vc < vn ) {
+    xp = xc; yp = yc; zp = zc;
+    xc = vc->coords[0] - v0->coords[0];
+    yc = vc->coords[1] - v0->coords[1];
+    zc = vc->coords[2] - v0->coords[2];
+
+    /* Compute (vp - v0) cross (vc - v0) */
+    n[0] = yp*zc - zp*yc;
+    n[1] = zp*xc - xp*zc;
+    n[2] = xp*yc - yp*xc;
+
+    dot = n[0]*norm[0] + n[1]*norm[1] + n[2]*norm[2];
+    if( ! check ) {
+      /* Reverse the contribution of back-facing triangles to get
+       * a reasonable normal for self-intersecting polygons (see above)
+       */
+      if( dot >= 0 ) {
+	norm[0] += n[0]; norm[1] += n[1]; norm[2] += n[2];
+      } else {
+	norm[0] -= n[0]; norm[1] -= n[1]; norm[2] -= n[2];
+      }
+    } else if( dot != 0 ) {
+      /* Check the new orientation for consistency with previous triangles */
+      if( dot > 0 ) {
+	if( sign < 0 ) return SIGN_INCONSISTENT;
+	sign = 1;
+      } else {
+	if( sign > 0 ) return SIGN_INCONSISTENT;
+	sign = -1;
+      }
+    }
+  }
+  return sign;
+}
+
+/* __gl_renderCache( tess ) takes a single contour and tries to render it
+ * as a triangle fan.  This handles convex polygons, as well as some
+ * non-convex polygons if we get lucky.
+ *
+ * Returns TRUE if the polygon was successfully rendered.  The rendering
+ * output is provided as callbacks (see the api).
+ */
+GLboolean __gl_renderCache( GLUtesselator *tess )
+{
+  CachedVertex *v0 = tess->cache;
+  CachedVertex *vn = v0 + tess->cacheCount;
+  CachedVertex *vc;
+  GLdouble norm[3];
+  int sign;
+
+  if( tess->cacheCount < 3 ) {
+    /* Degenerate contour -- no output */
+    return TRUE;
+  }
+
+  norm[0] = tess->normal[0];
+  norm[1] = tess->normal[1];
+  norm[2] = tess->normal[2];
+  if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) {
+    ComputeNormal( tess, norm, FALSE );
+  }
+
+  sign = ComputeNormal( tess, norm, TRUE );
+  if( sign == SIGN_INCONSISTENT ) {
+    /* Fan triangles did not have a consistent orientation */
+    return FALSE;
+  }
+  if( sign == 0 ) {
+    /* All triangles were degenerate */
+    return TRUE;
+  }
+
+  /* Make sure we do the right thing for each winding rule */
+  switch( tess->windingRule ) {
+  case GLU_TESS_WINDING_ODD:
+  case GLU_TESS_WINDING_NONZERO:
+    break;
+  case GLU_TESS_WINDING_POSITIVE:
+    if( sign < 0 ) return TRUE;
+    break;
+  case GLU_TESS_WINDING_NEGATIVE:
+    if( sign > 0 ) return TRUE;
+    break;
+  case GLU_TESS_WINDING_ABS_GEQ_TWO:
+    return TRUE;
+  }
+
+  CALL_BEGIN_OR_BEGIN_DATA( tess->boundaryOnly ? GL_LINE_LOOP
+			  : (tess->cacheCount > 3) ? GL_TRIANGLE_FAN
+			  : GL_TRIANGLES );
+
+  CALL_VERTEX_OR_VERTEX_DATA( v0->data ); 
+  if( sign > 0 ) {
+    for( vc = v0+1; vc < vn; ++vc ) {
+      CALL_VERTEX_OR_VERTEX_DATA( vc->data ); 
+    }
+  } else {
+    for( vc = vn-1; vc > v0; --vc ) {
+      CALL_VERTEX_OR_VERTEX_DATA( vc->data ); 
+    }
+  }
+  CALL_END_OR_END_DATA();
+  return TRUE;
+}

+ 52 - 0
3rdparty/tessellate/render.h

@@ -0,0 +1,52 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __render_h_
+#define __render_h_
+
+#include "mesh.h"
+
+/* __gl_renderMesh( tess, mesh ) takes a mesh and breaks it into triangle
+ * fans, strips, and separate triangles.  A substantial effort is made
+ * to use as few rendering primitives as possible (ie. to make the fans
+ * and strips as large as possible).
+ *
+ * The rendering output is provided as callbacks (see the api).
+ */
+void __gl_renderMesh( GLUtesselator *tess, GLUmesh *mesh );
+void __gl_renderBoundary( GLUtesselator *tess, GLUmesh *mesh );
+
+GLboolean __gl_renderCache( GLUtesselator *tess );
+
+#endif

+ 1361 - 0
3rdparty/tessellate/sweep.c

@@ -0,0 +1,1361 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <assert.h>
+#include <stddef.h>
+#include <setjmp.h>		/* longjmp */
+#include <limits.h>		/* LONG_MAX */
+
+#include "mesh.h"
+#include "geom.h"
+#include "tess.h"
+#include "dict.h"
+#include "priorityq.h"
+#include "memalloc.h"
+#include "sweep.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifdef FOR_TRITE_TEST_PROGRAM
+extern void DebugEvent( GLUtesselator *tess );
+#else
+#define DebugEvent( tess )
+#endif
+
+/*
+ * Invariants for the Edge Dictionary.
+ * - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2)
+ *   at any valid location of the sweep event
+ * - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2
+ *   share a common endpoint
+ * - for each e, e->Dst has been processed, but not e->Org
+ * - each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org)
+ *   where "event" is the current sweep line event.
+ * - no edge e has zero length
+ *
+ * Invariants for the Mesh (the processed portion).
+ * - the portion of the mesh left of the sweep line is a planar graph,
+ *   ie. there is *some* way to embed it in the plane
+ * - no processed edge has zero length
+ * - no two processed vertices have identical coordinates
+ * - each "inside" region is monotone, ie. can be broken into two chains
+ *   of monotonically increasing vertices according to VertLeq(v1,v2)
+ *   - a non-invariant: these chains may intersect (very slightly)
+ *
+ * Invariants for the Sweep.
+ * - if none of the edges incident to the event vertex have an activeRegion
+ *   (ie. none of these edges are in the edge dictionary), then the vertex
+ *   has only right-going edges.
+ * - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced
+ *   by ConnectRightVertex), then it is the only right-going edge from
+ *   its associated vertex.  (This says that these edges exist only
+ *   when it is necessary.)
+ */
+
+#undef	MAX
+#undef	MIN
+#define MAX(x,y)	((x) >= (y) ? (x) : (y))
+#define MIN(x,y)	((x) <= (y) ? (x) : (y))
+
+/* When we merge two edges into one, we need to compute the combined
+ * winding of the new edge.
+ */
+#define AddWinding(eDst,eSrc)	(eDst->winding += eSrc->winding, \
+                                 eDst->Sym->winding += eSrc->Sym->winding)
+
+static void SweepEvent( GLUtesselator *tess, GLUvertex *vEvent );
+static void WalkDirtyRegions( GLUtesselator *tess, ActiveRegion *regUp );
+static int CheckForRightSplice( GLUtesselator *tess, ActiveRegion *regUp );
+
+static int EdgeLeq( GLUtesselator *tess, ActiveRegion *reg1,
+		    ActiveRegion *reg2 )
+/*
+ * Both edges must be directed from right to left (this is the canonical
+ * direction for the upper edge of each region).
+ *
+ * The strategy is to evaluate a "t" value for each edge at the
+ * current sweep line position, given by tess->event.  The calculations
+ * are designed to be very stable, but of course they are not perfect.
+ *
+ * Special case: if both edge destinations are at the sweep event,
+ * we sort the edges by slope (they would otherwise compare equally).
+ */
+{
+  GLUvertex *event = tess->event;
+  GLUhalfEdge *e1, *e2;
+  GLdouble t1, t2;
+
+  e1 = reg1->eUp;
+  e2 = reg2->eUp;
+
+  if( e1->Dst == event ) {
+    if( e2->Dst == event ) {
+      /* Two edges right of the sweep line which meet at the sweep event.
+       * Sort them by slope.
+       */
+      if( VertLeq( e1->Org, e2->Org )) {
+	return EdgeSign( e2->Dst, e1->Org, e2->Org ) <= 0;
+      }
+      return EdgeSign( e1->Dst, e2->Org, e1->Org ) >= 0;
+    }
+    return EdgeSign( e2->Dst, event, e2->Org ) <= 0;
+  }
+  if( e2->Dst == event ) {
+    return EdgeSign( e1->Dst, event, e1->Org ) >= 0;
+  }
+
+  /* General case - compute signed distance *from* e1, e2 to event */
+  t1 = EdgeEval( e1->Dst, event, e1->Org );
+  t2 = EdgeEval( e2->Dst, event, e2->Org );
+  return (t1 >= t2);
+}
+
+
+static void DeleteRegion( GLUtesselator *tess, ActiveRegion *reg )
+{
+  if( reg->fixUpperEdge ) {
+    /* It was created with zero winding number, so it better be
+     * deleted with zero winding number (ie. it better not get merged
+     * with a real edge).
+     */
+    assert( reg->eUp->winding == 0 );
+  }
+  reg->eUp->activeRegion = NULL;
+  dictDelete( tess->dict, reg->nodeUp ); /* __gl_dictListDelete */
+  memFree( reg );
+}
+
+
+static int FixUpperEdge( ActiveRegion *reg, GLUhalfEdge *newEdge )
+/*
+ * Replace an upper edge which needs fixing (see ConnectRightVertex).
+ */
+{
+  assert( reg->fixUpperEdge );
+  if ( !__gl_meshDelete( reg->eUp ) ) return 0;
+  reg->fixUpperEdge = FALSE;
+  reg->eUp = newEdge;
+  newEdge->activeRegion = reg;
+
+  return 1;
+}
+
+static ActiveRegion *TopLeftRegion( ActiveRegion *reg )
+{
+  GLUvertex *org = reg->eUp->Org;
+  GLUhalfEdge *e;
+
+  /* Find the region above the uppermost edge with the same origin */
+  do {
+    reg = RegionAbove( reg );
+  } while( reg->eUp->Org == org );
+
+  /* If the edge above was a temporary edge introduced by ConnectRightVertex,
+   * now is the time to fix it.
+   */
+  if( reg->fixUpperEdge ) {
+    e = __gl_meshConnect( RegionBelow(reg)->eUp->Sym, reg->eUp->Lnext );
+    if (e == NULL) return NULL;
+    if ( !FixUpperEdge( reg, e ) ) return NULL;
+    reg = RegionAbove( reg );
+  }
+  return reg;
+}
+
+static ActiveRegion *TopRightRegion( ActiveRegion *reg )
+{
+  GLUvertex *dst = reg->eUp->Dst;
+
+  /* Find the region above the uppermost edge with the same destination */
+  do {
+    reg = RegionAbove( reg );
+  } while( reg->eUp->Dst == dst );
+  return reg;
+}
+
+static ActiveRegion *AddRegionBelow( GLUtesselator *tess,
+				     ActiveRegion *regAbove,
+				     GLUhalfEdge *eNewUp )
+/*
+ * Add a new active region to the sweep line, *somewhere* below "regAbove"
+ * (according to where the new edge belongs in the sweep-line dictionary).
+ * The upper edge of the new region will be "eNewUp".
+ * Winding number and "inside" flag are not updated.
+ */
+{
+  ActiveRegion *regNew = (ActiveRegion *)memAlloc( sizeof( ActiveRegion ));
+  if (regNew == NULL) longjmp(tess->env,1);
+
+  regNew->eUp = eNewUp;
+  /* __gl_dictListInsertBefore */
+  regNew->nodeUp = dictInsertBefore( tess->dict, regAbove->nodeUp, regNew );
+  if (regNew->nodeUp == NULL) longjmp(tess->env,1);
+  regNew->fixUpperEdge = FALSE;
+  regNew->sentinel = FALSE;
+  regNew->dirty = FALSE;
+
+  eNewUp->activeRegion = regNew;
+  return regNew;
+}
+
+static GLboolean IsWindingInside( GLUtesselator *tess, int n )
+{
+  switch( tess->windingRule ) {
+  case GLU_TESS_WINDING_ODD:
+    return (n & 1);
+  case GLU_TESS_WINDING_NONZERO:
+    return (n != 0);
+  case GLU_TESS_WINDING_POSITIVE:
+    return (n > 0);
+  case GLU_TESS_WINDING_NEGATIVE:
+    return (n < 0);
+  case GLU_TESS_WINDING_ABS_GEQ_TWO:
+    return (n >= 2) || (n <= -2);
+  }
+  /*LINTED*/
+  assert( FALSE );
+  /*NOTREACHED*/
+  return GL_FALSE;  /* avoid compiler complaints */
+}
+
+
+static void ComputeWinding( GLUtesselator *tess, ActiveRegion *reg )
+{
+  reg->windingNumber = RegionAbove(reg)->windingNumber + reg->eUp->winding;
+  reg->inside = IsWindingInside( tess, reg->windingNumber );
+}
+
+
+static void FinishRegion( GLUtesselator *tess, ActiveRegion *reg )
+/*
+ * Delete a region from the sweep line.  This happens when the upper
+ * and lower chains of a region meet (at a vertex on the sweep line).
+ * The "inside" flag is copied to the appropriate mesh face (we could
+ * not do this before -- since the structure of the mesh is always
+ * changing, this face may not have even existed until now).
+ */
+{
+  GLUhalfEdge *e = reg->eUp;
+  GLUface *f = e->Lface;
+
+  f->inside = reg->inside;
+  f->anEdge = e;   /* optimization for __gl_meshTessellateMonoRegion() */
+  DeleteRegion( tess, reg );
+}
+
+
+static GLUhalfEdge *FinishLeftRegions( GLUtesselator *tess,
+	       ActiveRegion *regFirst, ActiveRegion *regLast )
+/*
+ * We are given a vertex with one or more left-going edges.  All affected
+ * edges should be in the edge dictionary.  Starting at regFirst->eUp,
+ * we walk down deleting all regions where both edges have the same
+ * origin vOrg.  At the same time we copy the "inside" flag from the
+ * active region to the face, since at this point each face will belong
+ * to at most one region (this was not necessarily true until this point
+ * in the sweep).  The walk stops at the region above regLast; if regLast
+ * is NULL we walk as far as possible.	At the same time we relink the
+ * mesh if necessary, so that the ordering of edges around vOrg is the
+ * same as in the dictionary.
+ */
+{
+  ActiveRegion *reg, *regPrev;
+  GLUhalfEdge *e, *ePrev;
+
+  regPrev = regFirst;
+  ePrev = regFirst->eUp;
+  while( regPrev != regLast ) {
+    regPrev->fixUpperEdge = FALSE;	/* placement was OK */
+    reg = RegionBelow( regPrev );
+    e = reg->eUp;
+    if( e->Org != ePrev->Org ) {
+      if( ! reg->fixUpperEdge ) {
+	/* Remove the last left-going edge.  Even though there are no further
+	 * edges in the dictionary with this origin, there may be further
+	 * such edges in the mesh (if we are adding left edges to a vertex
+	 * that has already been processed).  Thus it is important to call
+	 * FinishRegion rather than just DeleteRegion.
+	 */
+	FinishRegion( tess, regPrev );
+	break;
+      }
+      /* If the edge below was a temporary edge introduced by
+       * ConnectRightVertex, now is the time to fix it.
+       */
+      e = __gl_meshConnect( ePrev->Lprev, e->Sym );
+      if (e == NULL) longjmp(tess->env,1);
+      if ( !FixUpperEdge( reg, e ) ) longjmp(tess->env,1);
+    }
+
+    /* Relink edges so that ePrev->Onext == e */
+    if( ePrev->Onext != e ) {
+      if ( !__gl_meshSplice( e->Oprev, e ) ) longjmp(tess->env,1);
+      if ( !__gl_meshSplice( ePrev, e ) ) longjmp(tess->env,1);
+    }
+    FinishRegion( tess, regPrev );	/* may change reg->eUp */
+    ePrev = reg->eUp;
+    regPrev = reg;
+  }
+  return ePrev;
+}
+
+
+static void AddRightEdges( GLUtesselator *tess, ActiveRegion *regUp,
+       GLUhalfEdge *eFirst, GLUhalfEdge *eLast, GLUhalfEdge *eTopLeft,
+       GLboolean cleanUp )
+/*
+ * Purpose: insert right-going edges into the edge dictionary, and update
+ * winding numbers and mesh connectivity appropriately.  All right-going
+ * edges share a common origin vOrg.  Edges are inserted CCW starting at
+ * eFirst; the last edge inserted is eLast->Oprev.  If vOrg has any
+ * left-going edges already processed, then eTopLeft must be the edge
+ * such that an imaginary upward vertical segment from vOrg would be
+ * contained between eTopLeft->Oprev and eTopLeft; otherwise eTopLeft
+ * should be NULL.
+ */
+{
+  ActiveRegion *reg, *regPrev;
+  GLUhalfEdge *e, *ePrev;
+  int firstTime = TRUE;
+
+  /* Insert the new right-going edges in the dictionary */
+  e = eFirst;
+  do {
+    assert( VertLeq( e->Org, e->Dst ));
+    AddRegionBelow( tess, regUp, e->Sym );
+    e = e->Onext;
+  } while ( e != eLast );
+
+  /* Walk *all* right-going edges from e->Org, in the dictionary order,
+   * updating the winding numbers of each region, and re-linking the mesh
+   * edges to match the dictionary ordering (if necessary).
+   */
+  if( eTopLeft == NULL ) {
+    eTopLeft = RegionBelow( regUp )->eUp->Rprev;
+  }
+  regPrev = regUp;
+  ePrev = eTopLeft;
+  for( ;; ) {
+    reg = RegionBelow( regPrev );
+    e = reg->eUp->Sym;
+    if( e->Org != ePrev->Org ) break;
+
+    if( e->Onext != ePrev ) {
+      /* Unlink e from its current position, and relink below ePrev */
+      if ( !__gl_meshSplice( e->Oprev, e ) ) longjmp(tess->env,1);
+      if ( !__gl_meshSplice( ePrev->Oprev, e ) ) longjmp(tess->env,1);
+    }
+    /* Compute the winding number and "inside" flag for the new regions */
+    reg->windingNumber = regPrev->windingNumber - e->winding;
+    reg->inside = IsWindingInside( tess, reg->windingNumber );
+
+    /* Check for two outgoing edges with same slope -- process these
+     * before any intersection tests (see example in __gl_computeInterior).
+     */
+    regPrev->dirty = TRUE;
+    if( ! firstTime && CheckForRightSplice( tess, regPrev )) {
+      AddWinding( e, ePrev );
+      DeleteRegion( tess, regPrev );
+      if ( !__gl_meshDelete( ePrev ) ) longjmp(tess->env,1);
+    }
+    firstTime = FALSE;
+    regPrev = reg;
+    ePrev = e;
+  }
+  regPrev->dirty = TRUE;
+  assert( regPrev->windingNumber - e->winding == reg->windingNumber );
+
+  if( cleanUp ) {
+    /* Check for intersections between newly adjacent edges. */
+    WalkDirtyRegions( tess, regPrev );
+  }
+}
+
+
+static void CallCombine( GLUtesselator *tess, GLUvertex *isect,
+			 void *data[4], GLfloat weights[4], int needed )
+{
+  GLdouble coords[3];
+
+  /* Copy coord data in case the callback changes it. */
+  coords[0] = isect->coords[0];
+  coords[1] = isect->coords[1];
+  coords[2] = isect->coords[2];
+
+  isect->data = NULL;
+  CALL_COMBINE_OR_COMBINE_DATA( coords, data, weights, &isect->data );
+  if( isect->data == NULL ) {
+    if( ! needed ) {
+      isect->data = data[0];
+    } else if( ! tess->fatalError ) {
+      /* The only way fatal error is when two edges are found to intersect,
+       * but the user has not provided the callback necessary to handle
+       * generated intersection points.
+       */
+      CALL_ERROR_OR_ERROR_DATA( GLU_TESS_NEED_COMBINE_CALLBACK );
+      tess->fatalError = TRUE;
+    }
+  }
+}
+
+static void SpliceMergeVertices( GLUtesselator *tess, GLUhalfEdge *e1,
+				 GLUhalfEdge *e2 )
+/*
+ * Two vertices with idential coordinates are combined into one.
+ * e1->Org is kept, while e2->Org is discarded.
+ */
+{
+  void *data[4] = { NULL, NULL, NULL, NULL };
+  GLfloat weights[4] = { 0.5, 0.5, 0.0, 0.0 };
+
+  data[0] = e1->Org->data;
+  data[1] = e2->Org->data;
+  CallCombine( tess, e1->Org, data, weights, FALSE );
+  if ( !__gl_meshSplice( e1, e2 ) ) longjmp(tess->env,1);
+}
+
+static void VertexWeights( GLUvertex *isect, GLUvertex *org, GLUvertex *dst,
+			   GLfloat *weights )
+/*
+ * Find some weights which describe how the intersection vertex is
+ * a linear combination of "org" and "dest".  Each of the two edges
+ * which generated "isect" is allocated 50% of the weight; each edge
+ * splits the weight between its org and dst according to the
+ * relative distance to "isect".
+ */
+{
+  GLdouble t1 = VertL1dist( org, isect );
+  GLdouble t2 = VertL1dist( dst, isect );
+
+  weights[0] = 0.5 * t2 / (t1 + t2);
+  weights[1] = 0.5 * t1 / (t1 + t2);
+  isect->coords[0] += weights[0]*org->coords[0] + weights[1]*dst->coords[0];
+  isect->coords[1] += weights[0]*org->coords[1] + weights[1]*dst->coords[1];
+  isect->coords[2] += weights[0]*org->coords[2] + weights[1]*dst->coords[2];
+}
+
+
+static void GetIntersectData( GLUtesselator *tess, GLUvertex *isect,
+       GLUvertex *orgUp, GLUvertex *dstUp,
+       GLUvertex *orgLo, GLUvertex *dstLo )
+/*
+ * We've computed a new intersection point, now we need a "data" pointer
+ * from the user so that we can refer to this new vertex in the
+ * rendering callbacks.
+ */
+{
+  void *data[4];
+  GLfloat weights[4];
+
+  data[0] = orgUp->data;
+  data[1] = dstUp->data;
+  data[2] = orgLo->data;
+  data[3] = dstLo->data;
+
+  isect->coords[0] = isect->coords[1] = isect->coords[2] = 0;
+  VertexWeights( isect, orgUp, dstUp, &weights[0] );
+  VertexWeights( isect, orgLo, dstLo, &weights[2] );
+
+  CallCombine( tess, isect, data, weights, TRUE );
+}
+
+static int CheckForRightSplice( GLUtesselator *tess, ActiveRegion *regUp )
+/*
+ * Check the upper and lower edge of "regUp", to make sure that the
+ * eUp->Org is above eLo, or eLo->Org is below eUp (depending on which
+ * origin is leftmost).
+ *
+ * The main purpose is to splice right-going edges with the same
+ * dest vertex and nearly identical slopes (ie. we can't distinguish
+ * the slopes numerically).  However the splicing can also help us
+ * to recover from numerical errors.  For example, suppose at one
+ * point we checked eUp and eLo, and decided that eUp->Org is barely
+ * above eLo.  Then later, we split eLo into two edges (eg. from
+ * a splice operation like this one).  This can change the result of
+ * our test so that now eUp->Org is incident to eLo, or barely below it.
+ * We must correct this condition to maintain the dictionary invariants.
+ *
+ * One possibility is to check these edges for intersection again
+ * (ie. CheckForIntersect).  This is what we do if possible.  However
+ * CheckForIntersect requires that tess->event lies between eUp and eLo,
+ * so that it has something to fall back on when the intersection
+ * calculation gives us an unusable answer.  So, for those cases where
+ * we can't check for intersection, this routine fixes the problem
+ * by just splicing the offending vertex into the other edge.
+ * This is a guaranteed solution, no matter how degenerate things get.
+ * Basically this is a combinatorial solution to a numerical problem.
+ */
+{
+  ActiveRegion *regLo = RegionBelow(regUp);
+  GLUhalfEdge *eUp = regUp->eUp;
+  GLUhalfEdge *eLo = regLo->eUp;
+
+  if( VertLeq( eUp->Org, eLo->Org )) {
+    if( EdgeSign( eLo->Dst, eUp->Org, eLo->Org ) > 0 ) return FALSE;
+
+    /* eUp->Org appears to be below eLo */
+    if( ! VertEq( eUp->Org, eLo->Org )) {
+      /* Splice eUp->Org into eLo */
+      if ( __gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1);
+      if ( !__gl_meshSplice( eUp, eLo->Oprev ) ) longjmp(tess->env,1);
+      regUp->dirty = regLo->dirty = TRUE;
+
+    } else if( eUp->Org != eLo->Org ) {
+      /* merge the two vertices, discarding eUp->Org */
+      pqDelete( tess->pq, eUp->Org->pqHandle ); /* __gl_pqSortDelete */
+      SpliceMergeVertices( tess, eLo->Oprev, eUp );
+    }
+  } else {
+    if( EdgeSign( eUp->Dst, eLo->Org, eUp->Org ) < 0 ) return FALSE;
+
+    /* eLo->Org appears to be above eUp, so splice eLo->Org into eUp */
+    RegionAbove(regUp)->dirty = regUp->dirty = TRUE;
+    if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1);
+    if ( !__gl_meshSplice( eLo->Oprev, eUp ) ) longjmp(tess->env,1);
+  }
+  return TRUE;
+}
+
+static int CheckForLeftSplice( GLUtesselator *tess, ActiveRegion *regUp )
+/*
+ * Check the upper and lower edge of "regUp", to make sure that the
+ * eUp->Dst is above eLo, or eLo->Dst is below eUp (depending on which
+ * destination is rightmost).
+ *
+ * Theoretically, this should always be true.  However, splitting an edge
+ * into two pieces can change the results of previous tests.  For example,
+ * suppose at one point we checked eUp and eLo, and decided that eUp->Dst
+ * is barely above eLo.  Then later, we split eLo into two edges (eg. from
+ * a splice operation like this one).  This can change the result of
+ * the test so that now eUp->Dst is incident to eLo, or barely below it.
+ * We must correct this condition to maintain the dictionary invariants
+ * (otherwise new edges might get inserted in the wrong place in the
+ * dictionary, and bad stuff will happen).
+ *
+ * We fix the problem by just splicing the offending vertex into the
+ * other edge.
+ */
+{
+  ActiveRegion *regLo = RegionBelow(regUp);
+  GLUhalfEdge *eUp = regUp->eUp;
+  GLUhalfEdge *eLo = regLo->eUp;
+  GLUhalfEdge *e;
+
+  assert( ! VertEq( eUp->Dst, eLo->Dst ));
+
+  if( VertLeq( eUp->Dst, eLo->Dst )) {
+    if( EdgeSign( eUp->Dst, eLo->Dst, eUp->Org ) < 0 ) return FALSE;
+
+    /* eLo->Dst is above eUp, so splice eLo->Dst into eUp */
+    RegionAbove(regUp)->dirty = regUp->dirty = TRUE;
+    e = __gl_meshSplitEdge( eUp );
+    if (e == NULL) longjmp(tess->env,1);
+    if ( !__gl_meshSplice( eLo->Sym, e ) ) longjmp(tess->env,1);
+    e->Lface->inside = regUp->inside;
+  } else {
+    if( EdgeSign( eLo->Dst, eUp->Dst, eLo->Org ) > 0 ) return FALSE;
+
+    /* eUp->Dst is below eLo, so splice eUp->Dst into eLo */
+    regUp->dirty = regLo->dirty = TRUE;
+    e = __gl_meshSplitEdge( eLo );
+    if (e == NULL) longjmp(tess->env,1);
+    if ( !__gl_meshSplice( eUp->Lnext, eLo->Sym ) ) longjmp(tess->env,1);
+    e->Rface->inside = regUp->inside;
+  }
+  return TRUE;
+}
+
+
+static int CheckForIntersect( GLUtesselator *tess, ActiveRegion *regUp )
+/*
+ * Check the upper and lower edges of the given region to see if
+ * they intersect.  If so, create the intersection and add it
+ * to the data structures.
+ *
+ * Returns TRUE if adding the new intersection resulted in a recursive
+ * call to AddRightEdges(); in this case all "dirty" regions have been
+ * checked for intersections, and possibly regUp has been deleted.
+ */
+{
+  ActiveRegion *regLo = RegionBelow(regUp);
+  GLUhalfEdge *eUp = regUp->eUp;
+  GLUhalfEdge *eLo = regLo->eUp;
+  GLUvertex *orgUp = eUp->Org;
+  GLUvertex *orgLo = eLo->Org;
+  GLUvertex *dstUp = eUp->Dst;
+  GLUvertex *dstLo = eLo->Dst;
+  GLdouble tMinUp, tMaxLo;
+  GLUvertex isect, *orgMin;
+  GLUhalfEdge *e;
+
+  assert( ! VertEq( dstLo, dstUp ));
+  assert( EdgeSign( dstUp, tess->event, orgUp ) <= 0 );
+  assert( EdgeSign( dstLo, tess->event, orgLo ) >= 0 );
+  assert( orgUp != tess->event && orgLo != tess->event );
+  assert( ! regUp->fixUpperEdge && ! regLo->fixUpperEdge );
+
+  if( orgUp == orgLo ) return FALSE;	/* right endpoints are the same */
+
+  tMinUp = MIN( orgUp->t, dstUp->t );
+  tMaxLo = MAX( orgLo->t, dstLo->t );
+  if( tMinUp > tMaxLo ) return FALSE;	/* t ranges do not overlap */
+
+  if( VertLeq( orgUp, orgLo )) {
+    if( EdgeSign( dstLo, orgUp, orgLo ) > 0 ) return FALSE;
+  } else {
+    if( EdgeSign( dstUp, orgLo, orgUp ) < 0 ) return FALSE;
+  }
+
+  /* At this point the edges intersect, at least marginally */
+  DebugEvent( tess );
+
+  __gl_edgeIntersect( dstUp, orgUp, dstLo, orgLo, &isect );
+  /* The following properties are guaranteed: */
+  assert( MIN( orgUp->t, dstUp->t ) <= isect.t );
+  assert( isect.t <= MAX( orgLo->t, dstLo->t ));
+  assert( MIN( dstLo->s, dstUp->s ) <= isect.s );
+  assert( isect.s <= MAX( orgLo->s, orgUp->s ));
+
+  if( VertLeq( &isect, tess->event )) {
+    /* The intersection point lies slightly to the left of the sweep line,
+     * so move it until it''s slightly to the right of the sweep line.
+     * (If we had perfect numerical precision, this would never happen
+     * in the first place).  The easiest and safest thing to do is
+     * replace the intersection by tess->event.
+     */
+    isect.s = tess->event->s;
+    isect.t = tess->event->t;
+  }
+  /* Similarly, if the computed intersection lies to the right of the
+   * rightmost origin (which should rarely happen), it can cause
+   * unbelievable inefficiency on sufficiently degenerate inputs.
+   * (If you have the test program, try running test54.d with the
+   * "X zoom" option turned on).
+   */
+  orgMin = VertLeq( orgUp, orgLo ) ? orgUp : orgLo;
+  if( VertLeq( orgMin, &isect )) {
+    isect.s = orgMin->s;
+    isect.t = orgMin->t;
+  }
+
+  if( VertEq( &isect, orgUp ) || VertEq( &isect, orgLo )) {
+    /* Easy case -- intersection at one of the right endpoints */
+    (void) CheckForRightSplice( tess, regUp );
+    return FALSE;
+  }
+
+  if(	 (! VertEq( dstUp, tess->event )
+	  && EdgeSign( dstUp, tess->event, &isect ) >= 0)
+      || (! VertEq( dstLo, tess->event )
+	  && EdgeSign( dstLo, tess->event, &isect ) <= 0 ))
+  {
+    /* Very unusual -- the new upper or lower edge would pass on the
+     * wrong side of the sweep event, or through it.  This can happen
+     * due to very small numerical errors in the intersection calculation.
+     */
+    if( dstLo == tess->event ) {
+      /* Splice dstLo into eUp, and process the new region(s) */
+      if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1);
+      if ( !__gl_meshSplice( eLo->Sym, eUp ) ) longjmp(tess->env,1);
+      regUp = TopLeftRegion( regUp );
+      if (regUp == NULL) longjmp(tess->env,1);
+      eUp = RegionBelow(regUp)->eUp;
+      FinishLeftRegions( tess, RegionBelow(regUp), regLo );
+      AddRightEdges( tess, regUp, eUp->Oprev, eUp, eUp, TRUE );
+      return TRUE;
+    }
+    if( dstUp == tess->event ) {
+      /* Splice dstUp into eLo, and process the new region(s) */
+      if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1);
+      if ( !__gl_meshSplice( eUp->Lnext, eLo->Oprev ) ) longjmp(tess->env,1);
+      regLo = regUp;
+      regUp = TopRightRegion( regUp );
+      e = RegionBelow(regUp)->eUp->Rprev;
+      regLo->eUp = eLo->Oprev;
+      eLo = FinishLeftRegions( tess, regLo, NULL );
+      AddRightEdges( tess, regUp, eLo->Onext, eUp->Rprev, e, TRUE );
+      return TRUE;
+    }
+    /* Special case: called from ConnectRightVertex.  If either
+     * edge passes on the wrong side of tess->event, split it
+     * (and wait for ConnectRightVertex to splice it appropriately).
+     */
+    if( EdgeSign( dstUp, tess->event, &isect ) >= 0 ) {
+      RegionAbove(regUp)->dirty = regUp->dirty = TRUE;
+      if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1);
+      eUp->Org->s = tess->event->s;
+      eUp->Org->t = tess->event->t;
+    }
+    if( EdgeSign( dstLo, tess->event, &isect ) <= 0 ) {
+      regUp->dirty = regLo->dirty = TRUE;
+      if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1);
+      eLo->Org->s = tess->event->s;
+      eLo->Org->t = tess->event->t;
+    }
+    /* leave the rest for ConnectRightVertex */
+    return FALSE;
+  }
+
+  /* General case -- split both edges, splice into new vertex.
+   * When we do the splice operation, the order of the arguments is
+   * arbitrary as far as correctness goes.  However, when the operation
+   * creates a new face, the work done is proportional to the size of
+   * the new face.  We expect the faces in the processed part of
+   * the mesh (ie. eUp->Lface) to be smaller than the faces in the
+   * unprocessed original contours (which will be eLo->Oprev->Lface).
+   */
+  if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1);
+  if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1);
+  if ( !__gl_meshSplice( eLo->Oprev, eUp ) ) longjmp(tess->env,1);
+  eUp->Org->s = isect.s;
+  eUp->Org->t = isect.t;
+  eUp->Org->pqHandle = pqInsert( tess->pq, eUp->Org ); /* __gl_pqSortInsert */
+  if (eUp->Org->pqHandle == LONG_MAX) {
+     pqDeletePriorityQ(tess->pq);	/* __gl_pqSortDeletePriorityQ */
+     tess->pq = NULL;
+     longjmp(tess->env,1);
+  }
+  GetIntersectData( tess, eUp->Org, orgUp, dstUp, orgLo, dstLo );
+  RegionAbove(regUp)->dirty = regUp->dirty = regLo->dirty = TRUE;
+  return FALSE;
+}
+
+static void WalkDirtyRegions( GLUtesselator *tess, ActiveRegion *regUp )
+/*
+ * When the upper or lower edge of any region changes, the region is
+ * marked "dirty".  This routine walks through all the dirty regions
+ * and makes sure that the dictionary invariants are satisfied
+ * (see the comments at the beginning of this file).  Of course
+ * new dirty regions can be created as we make changes to restore
+ * the invariants.
+ */
+{
+  ActiveRegion *regLo = RegionBelow(regUp);
+  GLUhalfEdge *eUp, *eLo;
+
+  for( ;; ) {
+    /* Find the lowest dirty region (we walk from the bottom up). */
+    while( regLo->dirty ) {
+      regUp = regLo;
+      regLo = RegionBelow(regLo);
+    }
+    if( ! regUp->dirty ) {
+      regLo = regUp;
+      regUp = RegionAbove( regUp );
+      if( regUp == NULL || ! regUp->dirty ) {
+	/* We've walked all the dirty regions */
+	return;
+      }
+    }
+    regUp->dirty = FALSE;
+    eUp = regUp->eUp;
+    eLo = regLo->eUp;
+
+    if( eUp->Dst != eLo->Dst ) {
+      /* Check that the edge ordering is obeyed at the Dst vertices. */
+      if( CheckForLeftSplice( tess, regUp )) {
+
+	/* If the upper or lower edge was marked fixUpperEdge, then
+	 * we no longer need it (since these edges are needed only for
+	 * vertices which otherwise have no right-going edges).
+	 */
+	if( regLo->fixUpperEdge ) {
+	  DeleteRegion( tess, regLo );
+	  if ( !__gl_meshDelete( eLo ) ) longjmp(tess->env,1);
+	  regLo = RegionBelow( regUp );
+	  eLo = regLo->eUp;
+	} else if( regUp->fixUpperEdge ) {
+	  DeleteRegion( tess, regUp );
+	  if ( !__gl_meshDelete( eUp ) ) longjmp(tess->env,1);
+	  regUp = RegionAbove( regLo );
+	  eUp = regUp->eUp;
+	}
+      }
+    }
+    if( eUp->Org != eLo->Org ) {
+      if(    eUp->Dst != eLo->Dst
+	  && ! regUp->fixUpperEdge && ! regLo->fixUpperEdge
+	  && (eUp->Dst == tess->event || eLo->Dst == tess->event) )
+      {
+	/* When all else fails in CheckForIntersect(), it uses tess->event
+	 * as the intersection location.  To make this possible, it requires
+	 * that tess->event lie between the upper and lower edges, and also
+	 * that neither of these is marked fixUpperEdge (since in the worst
+	 * case it might splice one of these edges into tess->event, and
+	 * violate the invariant that fixable edges are the only right-going
+	 * edge from their associated vertex).
+	 */
+	if( CheckForIntersect( tess, regUp )) {
+	  /* WalkDirtyRegions() was called recursively; we're done */
+	  return;
+	}
+      } else {
+	/* Even though we can't use CheckForIntersect(), the Org vertices
+	 * may violate the dictionary edge ordering.  Check and correct this.
+	 */
+	(void) CheckForRightSplice( tess, regUp );
+      }
+    }
+    if( eUp->Org == eLo->Org && eUp->Dst == eLo->Dst ) {
+      /* A degenerate loop consisting of only two edges -- delete it. */
+      AddWinding( eLo, eUp );
+      DeleteRegion( tess, regUp );
+      if ( !__gl_meshDelete( eUp ) ) longjmp(tess->env,1);
+      regUp = RegionAbove( regLo );
+    }
+  }
+}
+
+
+static void ConnectRightVertex( GLUtesselator *tess, ActiveRegion *regUp,
+				GLUhalfEdge *eBottomLeft )
+/*
+ * Purpose: connect a "right" vertex vEvent (one where all edges go left)
+ * to the unprocessed portion of the mesh.  Since there are no right-going
+ * edges, two regions (one above vEvent and one below) are being merged
+ * into one.  "regUp" is the upper of these two regions.
+ *
+ * There are two reasons for doing this (adding a right-going edge):
+ *  - if the two regions being merged are "inside", we must add an edge
+ *    to keep them separated (the combined region would not be monotone).
+ *  - in any case, we must leave some record of vEvent in the dictionary,
+ *    so that we can merge vEvent with features that we have not seen yet.
+ *    For example, maybe there is a vertical edge which passes just to
+ *    the right of vEvent; we would like to splice vEvent into this edge.
+ *
+ * However, we don't want to connect vEvent to just any vertex.  We don''t
+ * want the new edge to cross any other edges; otherwise we will create
+ * intersection vertices even when the input data had no self-intersections.
+ * (This is a bad thing; if the user's input data has no intersections,
+ * we don't want to generate any false intersections ourselves.)
+ *
+ * Our eventual goal is to connect vEvent to the leftmost unprocessed
+ * vertex of the combined region (the union of regUp and regLo).
+ * But because of unseen vertices with all right-going edges, and also
+ * new vertices which may be created by edge intersections, we don''t
+ * know where that leftmost unprocessed vertex is.  In the meantime, we
+ * connect vEvent to the closest vertex of either chain, and mark the region
+ * as "fixUpperEdge".  This flag says to delete and reconnect this edge
+ * to the next processed vertex on the boundary of the combined region.
+ * Quite possibly the vertex we connected to will turn out to be the
+ * closest one, in which case we won''t need to make any changes.
+ */
+{
+  GLUhalfEdge *eNew;
+  GLUhalfEdge *eTopLeft = eBottomLeft->Onext;
+  ActiveRegion *regLo = RegionBelow(regUp);
+  GLUhalfEdge *eUp = regUp->eUp;
+  GLUhalfEdge *eLo = regLo->eUp;
+  int degenerate = FALSE;
+
+  if( eUp->Dst != eLo->Dst ) {
+    (void) CheckForIntersect( tess, regUp );
+  }
+
+  /* Possible new degeneracies: upper or lower edge of regUp may pass
+   * through vEvent, or may coincide with new intersection vertex
+   */
+  if( VertEq( eUp->Org, tess->event )) {
+    if ( !__gl_meshSplice( eTopLeft->Oprev, eUp ) ) longjmp(tess->env,1);
+    regUp = TopLeftRegion( regUp );
+    if (regUp == NULL) longjmp(tess->env,1);
+    eTopLeft = RegionBelow( regUp )->eUp;
+    FinishLeftRegions( tess, RegionBelow(regUp), regLo );
+    degenerate = TRUE;
+  }
+  if( VertEq( eLo->Org, tess->event )) {
+    if ( !__gl_meshSplice( eBottomLeft, eLo->Oprev ) ) longjmp(tess->env,1);
+    eBottomLeft = FinishLeftRegions( tess, regLo, NULL );
+    degenerate = TRUE;
+  }
+  if( degenerate ) {
+    AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE );
+    return;
+  }
+
+  /* Non-degenerate situation -- need to add a temporary, fixable edge.
+   * Connect to the closer of eLo->Org, eUp->Org.
+   */
+  if( VertLeq( eLo->Org, eUp->Org )) {
+    eNew = eLo->Oprev;
+  } else {
+    eNew = eUp;
+  }
+  eNew = __gl_meshConnect( eBottomLeft->Lprev, eNew );
+  if (eNew == NULL) longjmp(tess->env,1);
+
+  /* Prevent cleanup, otherwise eNew might disappear before we've even
+   * had a chance to mark it as a temporary edge.
+   */
+  AddRightEdges( tess, regUp, eNew, eNew->Onext, eNew->Onext, FALSE );
+  eNew->Sym->activeRegion->fixUpperEdge = TRUE;
+  WalkDirtyRegions( tess, regUp );
+}
+
+/* Because vertices at exactly the same location are merged together
+ * before we process the sweep event, some degenerate cases can't occur.
+ * However if someone eventually makes the modifications required to
+ * merge features which are close together, the cases below marked
+ * TOLERANCE_NONZERO will be useful.  They were debugged before the
+ * code to merge identical vertices in the main loop was added.
+ */
+#define TOLERANCE_NONZERO	FALSE
+
+static void ConnectLeftDegenerate( GLUtesselator *tess,
+				   ActiveRegion *regUp, GLUvertex *vEvent )
+/*
+ * The event vertex lies exacty on an already-processed edge or vertex.
+ * Adding the new vertex involves splicing it into the already-processed
+ * part of the mesh.
+ */
+{
+  GLUhalfEdge *e, *eTopLeft, *eTopRight, *eLast;
+  ActiveRegion *reg;
+
+  e = regUp->eUp;
+  if( VertEq( e->Org, vEvent )) {
+    /* e->Org is an unprocessed vertex - just combine them, and wait
+     * for e->Org to be pulled from the queue
+     */
+    assert( TOLERANCE_NONZERO );
+    SpliceMergeVertices( tess, e, vEvent->anEdge );
+    return;
+  }
+
+  if( ! VertEq( e->Dst, vEvent )) {
+    /* General case -- splice vEvent into edge e which passes through it */
+    if (__gl_meshSplitEdge( e->Sym ) == NULL) longjmp(tess->env,1);
+    if( regUp->fixUpperEdge ) {
+      /* This edge was fixable -- delete unused portion of original edge */
+      if ( !__gl_meshDelete( e->Onext ) ) longjmp(tess->env,1);
+      regUp->fixUpperEdge = FALSE;
+    }
+    if ( !__gl_meshSplice( vEvent->anEdge, e ) ) longjmp(tess->env,1);
+    SweepEvent( tess, vEvent ); /* recurse */
+    return;
+  }
+
+  /* vEvent coincides with e->Dst, which has already been processed.
+   * Splice in the additional right-going edges.
+   */
+  assert( TOLERANCE_NONZERO );
+  regUp = TopRightRegion( regUp );
+  reg = RegionBelow( regUp );
+  eTopRight = reg->eUp->Sym;
+  eTopLeft = eLast = eTopRight->Onext;
+  if( reg->fixUpperEdge ) {
+    /* Here e->Dst has only a single fixable edge going right.
+     * We can delete it since now we have some real right-going edges.
+     */
+    assert( eTopLeft != eTopRight );   /* there are some left edges too */
+    DeleteRegion( tess, reg );
+    if ( !__gl_meshDelete( eTopRight ) ) longjmp(tess->env,1);
+    eTopRight = eTopLeft->Oprev;
+  }
+  if ( !__gl_meshSplice( vEvent->anEdge, eTopRight ) ) longjmp(tess->env,1);
+  if( ! EdgeGoesLeft( eTopLeft )) {
+    /* e->Dst had no left-going edges -- indicate this to AddRightEdges() */
+    eTopLeft = NULL;
+  }
+  AddRightEdges( tess, regUp, eTopRight->Onext, eLast, eTopLeft, TRUE );
+}
+
+
+static void ConnectLeftVertex( GLUtesselator *tess, GLUvertex *vEvent )
+/*
+ * Purpose: connect a "left" vertex (one where both edges go right)
+ * to the processed portion of the mesh.  Let R be the active region
+ * containing vEvent, and let U and L be the upper and lower edge
+ * chains of R.  There are two possibilities:
+ *
+ * - the normal case: split R into two regions, by connecting vEvent to
+ *   the rightmost vertex of U or L lying to the left of the sweep line
+ *
+ * - the degenerate case: if vEvent is close enough to U or L, we
+ *   merge vEvent into that edge chain.  The subcases are:
+ *	- merging with the rightmost vertex of U or L
+ *	- merging with the active edge of U or L
+ *	- merging with an already-processed portion of U or L
+ */
+{
+  ActiveRegion *regUp, *regLo, *reg;
+  GLUhalfEdge *eUp, *eLo, *eNew;
+  ActiveRegion tmp;
+
+  /* assert( vEvent->anEdge->Onext->Onext == vEvent->anEdge ); */
+
+  /* Get a pointer to the active region containing vEvent */
+  tmp.eUp = vEvent->anEdge->Sym;
+  /* __GL_DICTLISTKEY */ /* __gl_dictListSearch */
+  regUp = (ActiveRegion *)dictKey( dictSearch( tess->dict, &tmp ));
+  regLo = RegionBelow( regUp );
+  eUp = regUp->eUp;
+  eLo = regLo->eUp;
+
+  /* Try merging with U or L first */
+  if( EdgeSign( eUp->Dst, vEvent, eUp->Org ) == 0 ) {
+    ConnectLeftDegenerate( tess, regUp, vEvent );
+    return;
+  }
+
+  /* Connect vEvent to rightmost processed vertex of either chain.
+   * e->Dst is the vertex that we will connect to vEvent.
+   */
+  reg = VertLeq( eLo->Dst, eUp->Dst ) ? regUp : regLo;
+
+  if( regUp->inside || reg->fixUpperEdge) {
+    if( reg == regUp ) {
+      eNew = __gl_meshConnect( vEvent->anEdge->Sym, eUp->Lnext );
+      if (eNew == NULL) longjmp(tess->env,1);
+    } else {
+      GLUhalfEdge *tempHalfEdge= __gl_meshConnect( eLo->Dnext, vEvent->anEdge);
+      if (tempHalfEdge == NULL) longjmp(tess->env,1);
+
+      eNew = tempHalfEdge->Sym;
+    }
+    if( reg->fixUpperEdge ) {
+      if ( !FixUpperEdge( reg, eNew ) ) longjmp(tess->env,1);
+    } else {
+      ComputeWinding( tess, AddRegionBelow( tess, regUp, eNew ));
+    }
+    SweepEvent( tess, vEvent );
+  } else {
+    /* The new vertex is in a region which does not belong to the polygon.
+     * We don''t need to connect this vertex to the rest of the mesh.
+     */
+    AddRightEdges( tess, regUp, vEvent->anEdge, vEvent->anEdge, NULL, TRUE );
+  }
+}
+
+
+static void SweepEvent( GLUtesselator *tess, GLUvertex *vEvent )
+/*
+ * Does everything necessary when the sweep line crosses a vertex.
+ * Updates the mesh and the edge dictionary.
+ */
+{
+  ActiveRegion *regUp, *reg;
+  GLUhalfEdge *e, *eTopLeft, *eBottomLeft;
+
+  tess->event = vEvent; 	/* for access in EdgeLeq() */
+  DebugEvent( tess );
+
+  /* Check if this vertex is the right endpoint of an edge that is
+   * already in the dictionary.  In this case we don't need to waste
+   * time searching for the location to insert new edges.
+   */
+  e = vEvent->anEdge;
+  while( e->activeRegion == NULL ) {
+    e = e->Onext;
+    if( e == vEvent->anEdge ) {
+      /* All edges go right -- not incident to any processed edges */
+      ConnectLeftVertex( tess, vEvent );
+      return;
+    }
+  }
+
+  /* Processing consists of two phases: first we "finish" all the
+   * active regions where both the upper and lower edges terminate
+   * at vEvent (ie. vEvent is closing off these regions).
+   * We mark these faces "inside" or "outside" the polygon according
+   * to their winding number, and delete the edges from the dictionary.
+   * This takes care of all the left-going edges from vEvent.
+   */
+  regUp = TopLeftRegion( e->activeRegion );
+  if (regUp == NULL) longjmp(tess->env,1);
+  reg = RegionBelow( regUp );
+  eTopLeft = reg->eUp;
+  eBottomLeft = FinishLeftRegions( tess, reg, NULL );
+
+  /* Next we process all the right-going edges from vEvent.  This
+   * involves adding the edges to the dictionary, and creating the
+   * associated "active regions" which record information about the
+   * regions between adjacent dictionary edges.
+   */
+  if( eBottomLeft->Onext == eTopLeft ) {
+    /* No right-going edges -- add a temporary "fixable" edge */
+    ConnectRightVertex( tess, regUp, eBottomLeft );
+  } else {
+    AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE );
+  }
+}
+
+
+/* Make the sentinel coordinates big enough that they will never be
+ * merged with real input features.  (Even with the largest possible
+ * input contour and the maximum tolerance of 1.0, no merging will be
+ * done with coordinates larger than 3 * GLU_TESS_MAX_COORD).
+ */
+#define SENTINEL_COORD	(4 * GLU_TESS_MAX_COORD)
+
+static void AddSentinel( GLUtesselator *tess, GLdouble t )
+/*
+ * We add two sentinel edges above and below all other edges,
+ * to avoid special cases at the top and bottom.
+ */
+{
+  GLUhalfEdge *e;
+  ActiveRegion *reg = (ActiveRegion *)memAlloc( sizeof( ActiveRegion ));
+  if (reg == NULL) longjmp(tess->env,1);
+
+  e = __gl_meshMakeEdge( tess->mesh );
+  if (e == NULL) longjmp(tess->env,1);
+
+  e->Org->s = SENTINEL_COORD;
+  e->Org->t = t;
+  e->Dst->s = -SENTINEL_COORD;
+  e->Dst->t = t;
+  tess->event = e->Dst; 	/* initialize it */
+
+  reg->eUp = e;
+  reg->windingNumber = 0;
+  reg->inside = FALSE;
+  reg->fixUpperEdge = FALSE;
+  reg->sentinel = TRUE;
+  reg->dirty = FALSE;
+  reg->nodeUp = dictInsert( tess->dict, reg ); /* __gl_dictListInsertBefore */
+  if (reg->nodeUp == NULL) longjmp(tess->env,1);
+}
+
+
+static void InitEdgeDict( GLUtesselator *tess )
+/*
+ * We maintain an ordering of edge intersections with the sweep line.
+ * This order is maintained in a dynamic dictionary.
+ */
+{
+  /* __gl_dictListNewDict */
+  tess->dict = dictNewDict( tess, (int (*)(void *, DictKey, DictKey)) EdgeLeq );
+  if (tess->dict == NULL) longjmp(tess->env,1);
+
+  AddSentinel( tess, -SENTINEL_COORD );
+  AddSentinel( tess, SENTINEL_COORD );
+}
+
+
+static void DoneEdgeDict( GLUtesselator *tess )
+{
+  ActiveRegion *reg;
+#ifndef NDEBUG
+  int fixedEdges = 0;
+#endif
+
+  /* __GL_DICTLISTKEY */ /* __GL_DICTLISTMIN */
+  while( (reg = (ActiveRegion *)dictKey( dictMin( tess->dict ))) != NULL ) {
+    /*
+     * At the end of all processing, the dictionary should contain
+     * only the two sentinel edges, plus at most one "fixable" edge
+     * created by ConnectRightVertex().
+     */
+    if( ! reg->sentinel ) {
+      assert( reg->fixUpperEdge );
+      assert( ++fixedEdges == 1 );
+    }
+    assert( reg->windingNumber == 0 );
+    DeleteRegion( tess, reg );
+/*    __gl_meshDelete( reg->eUp );*/
+  }
+  dictDeleteDict( tess->dict ); /* __gl_dictListDeleteDict */
+}
+
+
+static void RemoveDegenerateEdges( GLUtesselator *tess )
+/*
+ * Remove zero-length edges, and contours with fewer than 3 vertices.
+ */
+{
+  GLUhalfEdge *e, *eNext, *eLnext;
+  GLUhalfEdge *eHead = &tess->mesh->eHead;
+
+  /*LINTED*/
+  for( e = eHead->next; e != eHead; e = eNext ) {
+    eNext = e->next;
+    eLnext = e->Lnext;
+
+    if( VertEq( e->Org, e->Dst ) && e->Lnext->Lnext != e ) {
+      /* Zero-length edge, contour has at least 3 edges */
+
+      SpliceMergeVertices( tess, eLnext, e );	/* deletes e->Org */
+      if ( !__gl_meshDelete( e ) ) longjmp(tess->env,1); /* e is a self-loop */
+      e = eLnext;
+      eLnext = e->Lnext;
+    }
+    if( eLnext->Lnext == e ) {
+      /* Degenerate contour (one or two edges) */
+
+      if( eLnext != e ) {
+	if( eLnext == eNext || eLnext == eNext->Sym ) { eNext = eNext->next; }
+	if ( !__gl_meshDelete( eLnext ) ) longjmp(tess->env,1);
+      }
+      if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; }
+      if ( !__gl_meshDelete( e ) ) longjmp(tess->env,1);
+    }
+  }
+}
+
+static int InitPriorityQ( GLUtesselator *tess )
+/*
+ * Insert all vertices into the priority queue which determines the
+ * order in which vertices cross the sweep line.
+ */
+{
+  PriorityQ *pq;
+  GLUvertex *v, *vHead;
+
+  /* __gl_pqSortNewPriorityQ */
+  pq = tess->pq = pqNewPriorityQ( (int (*)(PQkey, PQkey)) __gl_vertLeq );
+  if (pq == NULL) return 0;
+
+  vHead = &tess->mesh->vHead;
+  for( v = vHead->next; v != vHead; v = v->next ) {
+    v->pqHandle = pqInsert( pq, v ); /* __gl_pqSortInsert */
+    if (v->pqHandle == LONG_MAX) break;
+  }
+  if (v != vHead || !pqInit( pq ) ) { /* __gl_pqSortInit */
+    pqDeletePriorityQ(tess->pq);	/* __gl_pqSortDeletePriorityQ */
+    tess->pq = NULL;
+    return 0;
+  }
+
+  return 1;
+}
+
+
+static void DonePriorityQ( GLUtesselator *tess )
+{
+  pqDeletePriorityQ( tess->pq ); /* __gl_pqSortDeletePriorityQ */
+}
+
+
+static int RemoveDegenerateFaces( GLUmesh *mesh )
+/*
+ * Delete any degenerate faces with only two edges.  WalkDirtyRegions()
+ * will catch almost all of these, but it won't catch degenerate faces
+ * produced by splice operations on already-processed edges.
+ * The two places this can happen are in FinishLeftRegions(), when
+ * we splice in a "temporary" edge produced by ConnectRightVertex(),
+ * and in CheckForLeftSplice(), where we splice already-processed
+ * edges to ensure that our dictionary invariants are not violated
+ * by numerical errors.
+ *
+ * In both these cases it is *very* dangerous to delete the offending
+ * edge at the time, since one of the routines further up the stack
+ * will sometimes be keeping a pointer to that edge.
+ */
+{
+  GLUface *f, *fNext;
+  GLUhalfEdge *e;
+
+  /*LINTED*/
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) {
+    fNext = f->next;
+    e = f->anEdge;
+    assert( e->Lnext != e );
+
+    if( e->Lnext->Lnext == e ) {
+      /* A face with only two edges */
+      AddWinding( e->Onext, e );
+      if ( !__gl_meshDelete( e ) ) return 0;
+    }
+  }
+  return 1;
+}
+
+int __gl_computeInterior( GLUtesselator *tess )
+/*
+ * __gl_computeInterior( tess ) computes the planar arrangement specified
+ * by the given contours, and further subdivides this arrangement
+ * into regions.  Each region is marked "inside" if it belongs
+ * to the polygon, according to the rule given by tess->windingRule.
+ * Each interior region is guaranteed be monotone.
+ */
+{
+  GLUvertex *v, *vNext;
+
+  tess->fatalError = FALSE;
+
+  /* Each vertex defines an event for our sweep line.  Start by inserting
+   * all the vertices in a priority queue.  Events are processed in
+   * lexicographic order, ie.
+   *
+   *	e1 < e2  iff  e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y)
+   */
+  RemoveDegenerateEdges( tess );
+  if ( !InitPriorityQ( tess ) ) return 0; /* if error */
+  InitEdgeDict( tess );
+
+  /* __gl_pqSortExtractMin */
+  while( (v = (GLUvertex *)pqExtractMin( tess->pq )) != NULL ) {
+    for( ;; ) {
+      vNext = (GLUvertex *)pqMinimum( tess->pq ); /* __gl_pqSortMinimum */
+      if( vNext == NULL || ! VertEq( vNext, v )) break;
+
+      /* Merge together all vertices at exactly the same location.
+       * This is more efficient than processing them one at a time,
+       * simplifies the code (see ConnectLeftDegenerate), and is also
+       * important for correct handling of certain degenerate cases.
+       * For example, suppose there are two identical edges A and B
+       * that belong to different contours (so without this code they would
+       * be processed by separate sweep events).  Suppose another edge C
+       * crosses A and B from above.  When A is processed, we split it
+       * at its intersection point with C.  However this also splits C,
+       * so when we insert B we may compute a slightly different
+       * intersection point.  This might leave two edges with a small
+       * gap between them.  This kind of error is especially obvious
+       * when using boundary extraction (GLU_TESS_BOUNDARY_ONLY).
+       */
+      vNext = (GLUvertex *)pqExtractMin( tess->pq ); /* __gl_pqSortExtractMin*/
+      SpliceMergeVertices( tess, v->anEdge, vNext->anEdge );
+    }
+    SweepEvent( tess, v );
+  }
+
+  /* Set tess->event for debugging purposes */
+  /* __GL_DICTLISTKEY */ /* __GL_DICTLISTMIN */
+  tess->event = ((ActiveRegion *) dictKey( dictMin( tess->dict )))->eUp->Org;
+  DebugEvent( tess );
+  DoneEdgeDict( tess );
+  DonePriorityQ( tess );
+
+  if ( !RemoveDegenerateFaces( tess->mesh ) ) return 0;
+  __gl_meshCheckMesh( tess->mesh );
+
+  return 1;
+}

+ 77 - 0
3rdparty/tessellate/sweep.h

@@ -0,0 +1,77 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __sweep_h_
+#define __sweep_h_
+
+#include "mesh.h"
+
+/* __gl_computeInterior( tess ) computes the planar arrangement specified
+ * by the given contours, and further subdivides this arrangement
+ * into regions.  Each region is marked "inside" if it belongs
+ * to the polygon, according to the rule given by tess->windingRule.
+ * Each interior region is guaranteed be monotone.
+ */
+int __gl_computeInterior( GLUtesselator *tess );
+
+
+/* The following is here *only* for access by debugging routines */
+
+#include "dict.h"
+
+/* For each pair of adjacent edges crossing the sweep line, there is
+ * an ActiveRegion to represent the region between them.  The active
+ * regions are kept in sorted order in a dynamic dictionary.  As the
+ * sweep line crosses each vertex, we update the affected regions.
+ */
+
+struct ActiveRegion {
+  GLUhalfEdge	*eUp;		/* upper edge, directed right to left */
+  DictNode	*nodeUp;	/* dictionary node corresponding to eUp */
+  int		windingNumber;	/* used to determine which regions are
+                                 * inside the polygon */
+  GLboolean	inside;		/* is this region inside the polygon? */
+  GLboolean	sentinel;	/* marks fake edges at t = +/-infinity */
+  GLboolean	dirty;		/* marks regions where the upper or lower
+                                 * edge has changed, but we haven't checked
+                                 * whether they intersect yet */
+  GLboolean	fixUpperEdge;	/* marks temporary edges introduced when
+                                 * we process a "right vertex" (one without
+                                 * any edges leaving to the right) */
+};
+
+#define RegionBelow(r)	((ActiveRegion *) dictKey(dictPred((r)->nodeUp)))
+#define RegionAbove(r)	((ActiveRegion *) dictKey(dictSucc((r)->nodeUp)))
+
+#endif

+ 632 - 0
3rdparty/tessellate/tess.c

@@ -0,0 +1,632 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <stddef.h>
+#include <assert.h>
+#include <setjmp.h>
+#include "memalloc.h"
+#include "tess.h"
+#include "mesh.h"
+#include "normal.h"
+#include "sweep.h"
+#include "tessmono.h"
+#include "render.h"
+
+#define GLU_TESS_DEFAULT_TOLERANCE 0.0
+#define GLU_TESS_MESH		100112	/* void (*)(GLUmesh *mesh)	    */
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/*ARGSUSED*/ static void GLAPIENTRY noBegin( GLenum type ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noEdgeFlag( GLboolean boundaryEdge ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noVertex( void *data ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noEnd( void ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noError( GLenum errnum ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noCombine( GLdouble coords[3], void *data[4],
+				    GLfloat weight[4], void **dataOut ) {}
+/*ARGSUSED*/ static void GLAPIENTRY noMesh( GLUmesh *mesh ) {}
+
+
+/*ARGSUSED*/ void GLAPIENTRY __gl_noBeginData( GLenum type,
+					     void *polygonData ) {}
+/*ARGSUSED*/ void GLAPIENTRY __gl_noEdgeFlagData( GLboolean boundaryEdge,
+				       void *polygonData ) {}
+/*ARGSUSED*/ void GLAPIENTRY __gl_noVertexData( void *data,
+					      void *polygonData ) {}
+/*ARGSUSED*/ void GLAPIENTRY __gl_noEndData( void *polygonData ) {}
+/*ARGSUSED*/ void GLAPIENTRY __gl_noErrorData( GLenum errnum,
+					     void *polygonData ) {}
+/*ARGSUSED*/ void GLAPIENTRY __gl_noCombineData( GLdouble coords[3],
+					       void *data[4],
+					       GLfloat weight[4],
+					       void **outData,
+					       void *polygonData ) {}
+
+/* Half-edges are allocated in pairs (see mesh.c) */
+typedef struct { GLUhalfEdge e, eSym; } EdgePair;
+
+#undef	MAX
+#define MAX(a,b)	((a) > (b) ? (a) : (b))
+#define MAX_FAST_ALLOC	(MAX(sizeof(EdgePair), \
+                         MAX(sizeof(GLUvertex),sizeof(GLUface))))
+
+
+GLUtesselator * GLAPIENTRY
+gluNewTess( void )
+{
+  GLUtesselator *tess;
+
+  /* Only initialize fields which can be changed by the api.  Other fields
+   * are initialized where they are used.
+   */
+
+  if (memInit( MAX_FAST_ALLOC ) == 0) {
+     return 0;			/* out of memory */
+  }
+  tess = (GLUtesselator *)memAlloc( sizeof( GLUtesselator ));
+  if (tess == NULL) {
+     return 0;			/* out of memory */
+  }
+
+  tess->state = T_DORMANT;
+
+  tess->normal[0] = 0;
+  tess->normal[1] = 0;
+  tess->normal[2] = 0;
+
+  tess->relTolerance = GLU_TESS_DEFAULT_TOLERANCE;
+  tess->windingRule = GLU_TESS_WINDING_ODD;
+  tess->flagBoundary = FALSE;
+  tess->boundaryOnly = FALSE;
+
+  tess->callBegin = &noBegin;
+  tess->callEdgeFlag = &noEdgeFlag;
+  tess->callVertex = &noVertex;
+  tess->callEnd = &noEnd;
+
+  tess->callError = &noError;
+  tess->callCombine = &noCombine;
+  tess->callMesh = &noMesh;
+
+  tess->callBeginData= &__gl_noBeginData;
+  tess->callEdgeFlagData= &__gl_noEdgeFlagData;
+  tess->callVertexData= &__gl_noVertexData;
+  tess->callEndData= &__gl_noEndData;
+  tess->callErrorData= &__gl_noErrorData;
+  tess->callCombineData= &__gl_noCombineData;
+
+  tess->polygonData= NULL;
+
+  return tess;
+}
+
+static void MakeDormant( GLUtesselator *tess )
+{
+  /* Return the tessellator to its original dormant state. */
+
+  if( tess->mesh != NULL ) {
+    __gl_meshDeleteMesh( tess->mesh );
+  }
+  tess->state = T_DORMANT;
+  tess->lastEdge = NULL;
+  tess->mesh = NULL;
+}
+
+#define RequireState( tess, s )   if( tess->state != s ) GotoState(tess,s)
+
+static void GotoState( GLUtesselator *tess, enum TessState newState )
+{
+  while( tess->state != newState ) {
+    /* We change the current state one level at a time, to get to
+     * the desired state.
+     */
+    if( tess->state < newState ) {
+      switch( tess->state ) {
+      case T_DORMANT:
+	CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_POLYGON );
+	gluTessBeginPolygon( tess, NULL );
+	break;
+      case T_IN_POLYGON:
+	CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_CONTOUR );
+	gluTessBeginContour( tess );
+	break;
+      default:
+	 ;
+      }
+    } else {
+      switch( tess->state ) {
+      case T_IN_CONTOUR:
+	CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_CONTOUR );
+	gluTessEndContour( tess );
+	break;
+      case T_IN_POLYGON:
+	CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_POLYGON );
+	/* gluTessEndPolygon( tess ) is too much work! */
+	MakeDormant( tess );
+	break;
+      default:
+	 ;
+      }
+    }
+  }
+}
+
+
+void GLAPIENTRY
+gluDeleteTess( GLUtesselator *tess )
+{
+  RequireState( tess, T_DORMANT );
+  memFree( tess );
+}
+
+
+void GLAPIENTRY
+gluTessProperty( GLUtesselator *tess, GLenum which, GLdouble value )
+{
+  GLenum windingRule;
+
+  switch( which ) {
+  case GLU_TESS_TOLERANCE:
+    if( value < 0.0 || value > 1.0 ) break;
+    tess->relTolerance = value;
+    return;
+
+  case GLU_TESS_WINDING_RULE:
+    windingRule = (GLenum) value;
+    if( windingRule != value ) break;	/* not an integer */
+
+    switch( windingRule ) {
+    case GLU_TESS_WINDING_ODD:
+    case GLU_TESS_WINDING_NONZERO:
+    case GLU_TESS_WINDING_POSITIVE:
+    case GLU_TESS_WINDING_NEGATIVE:
+    case GLU_TESS_WINDING_ABS_GEQ_TWO:
+      tess->windingRule = windingRule;
+      return;
+    default:
+      break;
+    }
+
+  case GLU_TESS_BOUNDARY_ONLY:
+    tess->boundaryOnly = (value != 0);
+    return;
+
+  default:
+    CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
+    return;
+  }
+  CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_VALUE );
+}
+
+/* Returns tessellator property */
+void GLAPIENTRY
+gluGetTessProperty( GLUtesselator *tess, GLenum which, GLdouble *value )
+{
+   switch (which) {
+   case GLU_TESS_TOLERANCE:
+      /* tolerance should be in range [0..1] */
+      assert(0.0 <= tess->relTolerance && tess->relTolerance <= 1.0);
+      *value= tess->relTolerance;
+      break;
+   case GLU_TESS_WINDING_RULE:
+      assert(tess->windingRule == GLU_TESS_WINDING_ODD ||
+	     tess->windingRule == GLU_TESS_WINDING_NONZERO ||
+	     tess->windingRule == GLU_TESS_WINDING_POSITIVE ||
+	     tess->windingRule == GLU_TESS_WINDING_NEGATIVE ||
+	     tess->windingRule == GLU_TESS_WINDING_ABS_GEQ_TWO);
+      *value= tess->windingRule;
+      break;
+   case GLU_TESS_BOUNDARY_ONLY:
+      assert(tess->boundaryOnly == TRUE || tess->boundaryOnly == FALSE);
+      *value= tess->boundaryOnly;
+      break;
+   default:
+      *value= 0.0;
+      CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
+      break;
+   }
+} /* gluGetTessProperty() */
+
+void GLAPIENTRY
+gluTessNormal( GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z )
+{
+  tess->normal[0] = x;
+  tess->normal[1] = y;
+  tess->normal[2] = z;
+}
+
+void GLAPIENTRY
+gluTessCallback( GLUtesselator *tess, GLenum which, _GLUfuncptr fn)
+{
+  switch( which ) {
+  case GLU_TESS_BEGIN:
+    tess->callBegin = (fn == NULL) ? &noBegin : (void (GLAPIENTRY *)(GLenum)) fn;
+    return;
+  case GLU_TESS_BEGIN_DATA:
+    tess->callBeginData = (fn == NULL) ?
+	&__gl_noBeginData : (void (GLAPIENTRY *)(GLenum, void *)) fn;
+    return;
+  case GLU_TESS_EDGE_FLAG:
+    tess->callEdgeFlag = (fn == NULL) ? &noEdgeFlag :
+					(void (GLAPIENTRY *)(GLboolean)) fn;
+    /* If the client wants boundary edges to be flagged,
+     * we render everything as separate triangles (no strips or fans).
+     */
+    tess->flagBoundary = (fn != NULL);
+    return;
+  case GLU_TESS_EDGE_FLAG_DATA:
+    tess->callEdgeFlagData= (fn == NULL) ?
+	&__gl_noEdgeFlagData : (void (GLAPIENTRY *)(GLboolean, void *)) fn;
+    /* If the client wants boundary edges to be flagged,
+     * we render everything as separate triangles (no strips or fans).
+     */
+    tess->flagBoundary = (fn != NULL);
+    return;
+  case GLU_TESS_VERTEX:
+    tess->callVertex = (fn == NULL) ? &noVertex :
+				      (void (GLAPIENTRY *)(void *)) fn;
+    return;
+  case GLU_TESS_VERTEX_DATA:
+    tess->callVertexData = (fn == NULL) ?
+	&__gl_noVertexData : (void (GLAPIENTRY *)(void *, void *)) fn;
+    return;
+  case GLU_TESS_END:
+    tess->callEnd = (fn == NULL) ? &noEnd : (void (GLAPIENTRY *)(void)) fn;
+    return;
+  case GLU_TESS_END_DATA:
+    tess->callEndData = (fn == NULL) ? &__gl_noEndData :
+				       (void (GLAPIENTRY *)(void *)) fn;
+    return;
+  case GLU_TESS_ERROR:
+    tess->callError = (fn == NULL) ? &noError : (void (GLAPIENTRY *)(GLenum)) fn;
+    return;
+  case GLU_TESS_ERROR_DATA:
+    tess->callErrorData = (fn == NULL) ?
+	&__gl_noErrorData : (void (GLAPIENTRY *)(GLenum, void *)) fn;
+    return;
+  case GLU_TESS_COMBINE:
+    tess->callCombine = (fn == NULL) ? &noCombine :
+	(void (GLAPIENTRY *)(GLdouble [3],void *[4], GLfloat [4], void ** )) fn;
+    return;
+  case GLU_TESS_COMBINE_DATA:
+    tess->callCombineData = (fn == NULL) ? &__gl_noCombineData :
+					   (void (GLAPIENTRY *)(GLdouble [3],
+						     void *[4],
+						     GLfloat [4],
+						     void **,
+						     void *)) fn;
+    return;
+  case GLU_TESS_MESH:
+    tess->callMesh = (fn == NULL) ? &noMesh : (void (GLAPIENTRY *)(GLUmesh *)) fn;
+    return;
+  default:
+    CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM );
+    return;
+  }
+}
+
+static int AddVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
+{
+  GLUhalfEdge *e;
+
+  e = tess->lastEdge;
+  if( e == NULL ) {
+    /* Make a self-loop (one vertex, one edge). */
+
+    e = __gl_meshMakeEdge( tess->mesh );
+    if (e == NULL) return 0;
+    if ( !__gl_meshSplice( e, e->Sym ) ) return 0;
+  } else {
+    /* Create a new vertex and edge which immediately follow e
+     * in the ordering around the left face.
+     */
+    if (__gl_meshSplitEdge( e ) == NULL) return 0;
+    e = e->Lnext;
+  }
+
+  /* The new vertex is now e->Org. */
+  e->Org->data = data;
+  e->Org->coords[0] = coords[0];
+  e->Org->coords[1] = coords[1];
+  e->Org->coords[2] = coords[2];
+
+  /* The winding of an edge says how the winding number changes as we
+   * cross from the edge''s right face to its left face.  We add the
+   * vertices in such an order that a CCW contour will add +1 to
+   * the winding number of the region inside the contour.
+   */
+  e->winding = 1;
+  e->Sym->winding = -1;
+
+  tess->lastEdge = e;
+
+  return 1;
+}
+
+
+static void CacheVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
+{
+  CachedVertex *v = &tess->cache[tess->cacheCount];
+
+  v->data = data;
+  v->coords[0] = coords[0];
+  v->coords[1] = coords[1];
+  v->coords[2] = coords[2];
+  ++tess->cacheCount;
+}
+
+
+static int EmptyCache( GLUtesselator *tess )
+{
+  CachedVertex *v = tess->cache;
+  CachedVertex *vLast;
+
+  tess->mesh = __gl_meshNewMesh();
+  if (tess->mesh == NULL) return 0;
+
+  for( vLast = v + tess->cacheCount; v < vLast; ++v ) {
+    if ( !AddVertex( tess, v->coords, v->data ) ) return 0;
+  }
+  tess->cacheCount = 0;
+  tess->emptyCache = FALSE;
+
+  return 1;
+}
+
+
+void GLAPIENTRY
+gluTessVertex( GLUtesselator *tess, GLdouble coords[3], void *data )
+{
+  int i, tooLarge = FALSE;
+  GLdouble x, clamped[3];
+
+  RequireState( tess, T_IN_CONTOUR );
+
+  if( tess->emptyCache ) {
+    if ( !EmptyCache( tess ) ) {
+       CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
+       return;
+    }
+    tess->lastEdge = NULL;
+  }
+  for( i = 0; i < 3; ++i ) {
+    x = coords[i];
+    if( x < - GLU_TESS_MAX_COORD ) {
+      x = - GLU_TESS_MAX_COORD;
+      tooLarge = TRUE;
+    }
+    if( x > GLU_TESS_MAX_COORD ) {
+      x = GLU_TESS_MAX_COORD;
+      tooLarge = TRUE;
+    }
+    clamped[i] = x;
+  }
+  if( tooLarge ) {
+    CALL_ERROR_OR_ERROR_DATA( GLU_TESS_COORD_TOO_LARGE );
+  }
+
+  if( tess->mesh == NULL ) {
+    if( tess->cacheCount < TESS_MAX_CACHE ) {
+      CacheVertex( tess, clamped, data );
+      return;
+    }
+    if ( !EmptyCache( tess ) ) {
+       CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
+       return;
+    }
+  }
+  if ( !AddVertex( tess, clamped, data ) ) {
+       CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
+  }
+}
+
+
+void GLAPIENTRY
+gluTessBeginPolygon( GLUtesselator *tess, void *data )
+{
+  RequireState( tess, T_DORMANT );
+
+  tess->state = T_IN_POLYGON;
+  tess->cacheCount = 0;
+  tess->emptyCache = FALSE;
+  tess->mesh = NULL;
+
+  tess->polygonData= data;
+}
+
+
+void GLAPIENTRY
+gluTessBeginContour( GLUtesselator *tess )
+{
+  RequireState( tess, T_IN_POLYGON );
+
+  tess->state = T_IN_CONTOUR;
+  tess->lastEdge = NULL;
+  if( tess->cacheCount > 0 ) {
+    /* Just set a flag so we don't get confused by empty contours
+     * -- these can be generated accidentally with the obsolete
+     * NextContour() interface.
+     */
+    tess->emptyCache = TRUE;
+  }
+}
+
+
+void GLAPIENTRY
+gluTessEndContour( GLUtesselator *tess )
+{
+  RequireState( tess, T_IN_CONTOUR );
+  tess->state = T_IN_POLYGON;
+}
+
+void GLAPIENTRY
+gluTessEndPolygon( GLUtesselator *tess )
+{
+  GLUmesh *mesh;
+
+  if (setjmp(tess->env) != 0) { 
+     /* come back here if out of memory */
+     CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY );
+     return;
+  }
+
+  RequireState( tess, T_IN_POLYGON );
+  tess->state = T_DORMANT;
+
+  if( tess->mesh == NULL ) {
+    if( ! tess->flagBoundary && tess->callMesh == &noMesh ) {
+
+      /* Try some special code to make the easy cases go quickly
+       * (eg. convex polygons).  This code does NOT handle multiple contours,
+       * intersections, edge flags, and of course it does not generate
+       * an explicit mesh either.
+       */
+      if( __gl_renderCache( tess )) {
+	tess->polygonData= NULL;
+	return;
+      }
+    }
+    if ( !EmptyCache( tess ) ) longjmp(tess->env,1); /* could've used a label*/
+  }
+
+  /* Determine the polygon normal and project vertices onto the plane
+   * of the polygon.
+   */
+  __gl_projectPolygon( tess );
+
+  /* __gl_computeInterior( tess ) computes the planar arrangement specified
+   * by the given contours, and further subdivides this arrangement
+   * into regions.  Each region is marked "inside" if it belongs
+   * to the polygon, according to the rule given by tess->windingRule.
+   * Each interior region is guaranteed be monotone.
+   */
+  if ( !__gl_computeInterior( tess ) ) {
+     longjmp(tess->env,1);	/* could've used a label */
+  }
+
+  mesh = tess->mesh;
+  if( ! tess->fatalError ) {
+    int rc = 1;
+
+    /* If the user wants only the boundary contours, we throw away all edges
+     * except those which separate the interior from the exterior.
+     * Otherwise we tessellate all the regions marked "inside".
+     */
+    if( tess->boundaryOnly ) {
+      rc = __gl_meshSetWindingNumber( mesh, 1, TRUE );
+    } else {
+      rc = __gl_meshTessellateInterior( mesh );
+    }
+    if (rc == 0) longjmp(tess->env,1);	/* could've used a label */
+
+    __gl_meshCheckMesh( mesh );
+
+    if( tess->callBegin != &noBegin || tess->callEnd != &noEnd
+       || tess->callVertex != &noVertex || tess->callEdgeFlag != &noEdgeFlag
+       || tess->callBeginData != &__gl_noBeginData
+       || tess->callEndData != &__gl_noEndData
+       || tess->callVertexData != &__gl_noVertexData
+       || tess->callEdgeFlagData != &__gl_noEdgeFlagData )
+    {
+      if( tess->boundaryOnly ) {
+	__gl_renderBoundary( tess, mesh );  /* output boundary contours */
+      } else {
+	__gl_renderMesh( tess, mesh );	   /* output strips and fans */
+      }
+    }
+    if( tess->callMesh != &noMesh ) {
+
+      /* Throw away the exterior faces, so that all faces are interior.
+       * This way the user doesn't have to check the "inside" flag,
+       * and we don't need to even reveal its existence.  It also leaves
+       * the freedom for an implementation to not generate the exterior
+       * faces in the first place.
+       */
+      __gl_meshDiscardExterior( mesh );
+      (*tess->callMesh)( mesh );		/* user wants the mesh itself */
+      tess->mesh = NULL;
+      tess->polygonData= NULL;
+      return;
+    }
+  }
+  __gl_meshDeleteMesh( mesh );
+  tess->polygonData= NULL;
+  tess->mesh = NULL;
+}
+
+
+/*XXXblythe unused function*/
+#if 0
+void GLAPIENTRY
+gluDeleteMesh( GLUmesh *mesh )
+{
+  __gl_meshDeleteMesh( mesh );
+}
+#endif
+
+
+
+/*******************************************************/
+
+/* Obsolete calls -- for backward compatibility */
+
+void GLAPIENTRY
+gluBeginPolygon( GLUtesselator *tess )
+{
+  gluTessBeginPolygon( tess, NULL );
+  gluTessBeginContour( tess );
+}
+
+
+/*ARGSUSED*/
+void GLAPIENTRY
+gluNextContour( GLUtesselator *tess, GLenum type )
+{
+  gluTessEndContour( tess );
+  gluTessBeginContour( tess );
+}
+
+
+void GLAPIENTRY
+gluEndPolygon( GLUtesselator *tess )
+{
+  gluTessEndContour( tess );
+  gluTessEndPolygon( tess );
+}

+ 165 - 0
3rdparty/tessellate/tess.h

@@ -0,0 +1,165 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __tess_h_
+#define __tess_h_
+
+#include "glu.h"
+#include <setjmp.h>
+#include "mesh.h"
+#include "dict.h"
+#include "priorityq.h"
+
+/* The begin/end calls must be properly nested.  We keep track of
+ * the current state to enforce the ordering.
+ */
+enum TessState { T_DORMANT, T_IN_POLYGON, T_IN_CONTOUR };
+
+/* We cache vertex data for single-contour polygons so that we can
+ * try a quick-and-dirty decomposition first.
+ */
+#define TESS_MAX_CACHE	100
+
+typedef struct CachedVertex {
+  GLdouble	coords[3];
+  void		*data;
+} CachedVertex;
+
+struct GLUtesselator {
+
+  /*** state needed for collecting the input data ***/
+
+  enum TessState state;		/* what begin/end calls have we seen? */
+
+  GLUhalfEdge	*lastEdge;	/* lastEdge->Org is the most recent vertex */
+  GLUmesh	*mesh;		/* stores the input contours, and eventually
+                                   the tessellation itself */
+
+  void		(GLAPIENTRY *callError)( GLenum errnum );
+
+  /*** state needed for projecting onto the sweep plane ***/
+
+  GLdouble	normal[3];	/* user-specified normal (if provided) */
+  GLdouble	sUnit[3];	/* unit vector in s-direction (debugging) */
+  GLdouble	tUnit[3];	/* unit vector in t-direction (debugging) */
+
+  /*** state needed for the line sweep ***/
+
+  GLdouble	relTolerance;	/* tolerance for merging features */
+  GLenum	windingRule;	/* rule for determining polygon interior */
+  GLboolean	fatalError;	/* fatal error: needed combine callback */
+
+  Dict		*dict;		/* edge dictionary for sweep line */
+  PriorityQ	*pq;		/* priority queue of vertex events */
+  GLUvertex	*event;		/* current sweep event being processed */
+
+  void		(GLAPIENTRY *callCombine)( GLdouble coords[3], void *data[4],
+			        GLfloat weight[4], void **outData );
+
+  /*** state needed for rendering callbacks (see render.c) ***/
+
+  GLboolean	flagBoundary;	/* mark boundary edges (use EdgeFlag) */
+  GLboolean	boundaryOnly;	/* Extract contours, not triangles */
+  GLUface	*lonelyTriList;
+    /* list of triangles which could not be rendered as strips or fans */
+
+  void		(GLAPIENTRY *callBegin)( GLenum type );
+  void		(GLAPIENTRY *callEdgeFlag)( GLboolean boundaryEdge );
+  void		(GLAPIENTRY *callVertex)( void *data );
+  void		(GLAPIENTRY *callEnd)( void );
+  void		(GLAPIENTRY *callMesh)( GLUmesh *mesh );
+
+
+  /*** state needed to cache single-contour polygons for renderCache() */
+
+  GLboolean	emptyCache;		/* empty cache on next vertex() call */
+  int		cacheCount;		/* number of cached vertices */
+  CachedVertex	cache[TESS_MAX_CACHE];	/* the vertex data */
+
+  /*** rendering callbacks that also pass polygon data  ***/ 
+  void		(GLAPIENTRY *callBeginData)( GLenum type, void *polygonData );
+  void		(GLAPIENTRY *callEdgeFlagData)( GLboolean boundaryEdge, 
+				     void *polygonData );
+  void		(GLAPIENTRY *callVertexData)( void *data, void *polygonData );
+  void		(GLAPIENTRY *callEndData)( void *polygonData );
+  void		(GLAPIENTRY *callErrorData)( GLenum errnum, void *polygonData );
+  void		(GLAPIENTRY *callCombineData)( GLdouble coords[3], void *data[4],
+				    GLfloat weight[4], void **outData,
+				    void *polygonData );
+
+  jmp_buf env;			/* place to jump to when memAllocs fail */
+
+  void *polygonData;		/* client data for current polygon */
+};
+
+void GLAPIENTRY __gl_noBeginData( GLenum type, void *polygonData );
+void GLAPIENTRY __gl_noEdgeFlagData( GLboolean boundaryEdge, void *polygonData );
+void GLAPIENTRY __gl_noVertexData( void *data, void *polygonData );
+void GLAPIENTRY __gl_noEndData( void *polygonData );
+void GLAPIENTRY __gl_noErrorData( GLenum errnum, void *polygonData );
+void GLAPIENTRY __gl_noCombineData( GLdouble coords[3], void *data[4],
+			 GLfloat weight[4], void **outData,
+			 void *polygonData );
+
+#define CALL_BEGIN_OR_BEGIN_DATA(a) \
+   if (tess->callBeginData != &__gl_noBeginData) \
+      (*tess->callBeginData)((a),tess->polygonData); \
+   else (*tess->callBegin)((a));
+
+#define CALL_VERTEX_OR_VERTEX_DATA(a) \
+   if (tess->callVertexData != &__gl_noVertexData) \
+      (*tess->callVertexData)((a),tess->polygonData); \
+   else (*tess->callVertex)((a));
+
+#define CALL_EDGE_FLAG_OR_EDGE_FLAG_DATA(a) \
+   if (tess->callEdgeFlagData != &__gl_noEdgeFlagData) \
+      (*tess->callEdgeFlagData)((a),tess->polygonData); \
+   else (*tess->callEdgeFlag)((a));
+
+#define CALL_END_OR_END_DATA() \
+   if (tess->callEndData != &__gl_noEndData) \
+      (*tess->callEndData)(tess->polygonData); \
+   else (*tess->callEnd)();
+
+#define CALL_COMBINE_OR_COMBINE_DATA(a,b,c,d) \
+   if (tess->callCombineData != &__gl_noCombineData) \
+      (*tess->callCombineData)((a),(b),(c),(d),tess->polygonData); \
+   else (*tess->callCombine)((a),(b),(c),(d));
+
+#define CALL_ERROR_OR_ERROR_DATA(a) \
+   if (tess->callErrorData != &__gl_noErrorData) \
+      (*tess->callErrorData)((a),tess->polygonData); \
+   else (*tess->callError)((a));
+
+#endif

+ 231 - 0
3rdparty/tessellate/tessellate.c

@@ -0,0 +1,231 @@
+#include "glu.h"
+#include "tess.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+/******************************************************************************/
+
+typedef struct Triangle {
+    int v[3];
+    struct Triangle *prev;
+} Triangle;
+
+typedef struct Vertex {
+    double pt[3];
+    int index;
+    struct Vertex *prev;
+} Vertex;
+
+typedef struct TessContext {
+    Triangle *latest_t;
+    int n_tris;
+    
+    Vertex *v_prev;
+    Vertex *v_prevprev;
+    Vertex *latest_v;
+    GLenum current_mode;
+    int odd_even_strip;
+
+    void (*vertex_cb)(Vertex *, struct TessContext *);
+} TessContext;
+
+void skip_vertex(Vertex *v, TessContext *ctx);
+
+/******************************************************************************/
+
+TessContext *new_tess_context()
+{
+    TessContext *result = (TessContext *)malloc(sizeof (struct TessContext));
+    result->latest_t = NULL;
+    result->latest_v = NULL;
+    result->n_tris = 0;
+    result->v_prev = NULL;
+    result->v_prevprev = NULL;
+    result->v_prev = NULL;
+    result->v_prev = NULL;
+    result->vertex_cb = &skip_vertex;
+    result->odd_even_strip = 0;
+    return result;
+}
+
+void destroy_tess_context(TessContext *ctx)
+{
+    free(ctx);
+}
+
+Vertex *new_vertex(TessContext *ctx, double x, double y)
+{
+    Vertex *result = (Vertex *)malloc(sizeof(Vertex));
+    result->prev = ctx->latest_v;
+    result->pt[0] = x;
+    result->pt[1] = y;
+    result->pt[2] = 0;
+
+    if (ctx->latest_v == NULL) {
+        result->index = 0;
+    } else {
+        result->index = ctx->latest_v->index+1;
+    }
+    return ctx->latest_v = result;
+}
+
+
+Triangle *new_triangle(TessContext *ctx, int v1, int v2, int v3)
+{
+    Triangle *result = (Triangle *)malloc(sizeof(Triangle));
+    result->prev = ctx->latest_t;
+    result->v[0] = v1;
+    result->v[1] = v2;
+    result->v[2] = v3;
+    ctx->n_tris++;
+    return ctx->latest_t = result;
+}
+
+/******************************************************************************/
+
+void skip_vertex(Vertex *v, TessContext *ctx) {};
+
+void fan_vertex(Vertex *v, TessContext *ctx) {
+    if (ctx->v_prevprev == NULL) {
+        ctx->v_prevprev = v;
+        return;
+    }
+    if (ctx->v_prev == NULL) {
+        ctx->v_prev = v;
+        return;
+    }
+    new_triangle(ctx, ctx->v_prevprev->index, ctx->v_prev->index, v->index);
+    ctx->v_prev = v;
+}
+
+void strip_vertex(Vertex *v, TessContext *ctx)
+{
+    if (ctx->v_prev == NULL) {
+        ctx->v_prev = v;
+        return;
+    }
+    if (ctx->v_prevprev == NULL) {
+        ctx->v_prevprev = v;
+        return;
+    }
+    if (ctx->odd_even_strip) {
+        new_triangle(ctx, ctx->v_prevprev->index, ctx->v_prev->index, v->index);
+    } else {
+        new_triangle(ctx, ctx->v_prev->index, ctx->v_prevprev->index, v->index);
+    }
+    ctx->odd_even_strip = !ctx->odd_even_strip;
+
+    ctx->v_prev = ctx->v_prevprev;
+    ctx->v_prevprev = v;
+}
+
+void triangle_vertex(Vertex *v, TessContext *ctx) {
+    if (ctx->v_prevprev == NULL) {
+        ctx->v_prevprev = v;
+        return;
+    }
+    if (ctx->v_prev == NULL) {
+        ctx->v_prev = v;
+        return;
+    }
+    new_triangle(ctx, ctx->v_prevprev->index, ctx->v_prev->index, v->index);
+    ctx->v_prev = ctx->v_prevprev = NULL;
+}
+
+void vertex(void *vertex_data, void *poly_data)
+{
+    Vertex *ptr = (Vertex *)vertex_data;
+    TessContext *ctx = (TessContext *)poly_data;
+    ctx->vertex_cb(ptr, ctx);
+}
+
+void begin(GLenum which, void *poly_data)
+{
+    TessContext *ctx = (TessContext *)poly_data;
+    ctx->v_prev = ctx->v_prevprev = NULL;
+    ctx->odd_even_strip = 0;
+    switch (which) {
+    case GL_TRIANGLES: ctx->vertex_cb = &triangle_vertex; break;
+    case GL_TRIANGLE_STRIP: ctx->vertex_cb = &strip_vertex; break;
+    case GL_TRIANGLE_FAN: ctx->vertex_cb = &fan_vertex; break;
+    default:
+        fprintf(stderr, "ERROR, can't handle %d\n", (int)which);
+        ctx->vertex_cb = &skip_vertex;
+    }
+}
+
+void combine(const GLdouble newVertex[3],
+             const void *neighborVertex[4],
+             const GLfloat neighborWeight[4], void **outData, void *polyData)
+{
+    TessContext *ctx = (TessContext *)polyData;
+    Vertex *result = new_vertex(ctx, newVertex[0], newVertex[1]);
+    *outData = result;
+}
+
+void write_output(TessContext *ctx, double **coordinates_out, int **tris_out, int *vc, int *tc)
+{
+    int n_verts = 1 + ctx->latest_v->index;
+    *vc = n_verts;
+    int n_tris_copy = ctx->n_tris;
+    *tc = ctx->n_tris;
+    *coordinates_out = malloc(n_verts * sizeof(double) * 2);
+    *tris_out = (ctx->n_tris ? malloc(ctx->n_tris * sizeof(int) * 3) : NULL);
+
+    while (ctx->latest_v) {
+        (*coordinates_out)[2*ctx->latest_v->index]   = ctx->latest_v->pt[0];
+        (*coordinates_out)[2*ctx->latest_v->index+1] = ctx->latest_v->pt[1];
+        Vertex *prev = ctx->latest_v->prev;
+        free(ctx->latest_v);
+        ctx->latest_v = prev;
+    }
+
+    while (ctx->latest_t) {
+        (*tris_out)[3*(n_tris_copy-1)]   = ctx->latest_t->v[0];
+        (*tris_out)[3*(n_tris_copy-1)+1] = ctx->latest_t->v[1];
+        (*tris_out)[3*(n_tris_copy-1)+2] = ctx->latest_t->v[2];
+        Triangle *prev = ctx->latest_t->prev;
+        free(ctx->latest_t);
+        ctx->latest_t = prev;
+        n_tris_copy--;
+    }
+}
+
+void tessellate
+    (double **verts,
+     int *nverts,
+     int **tris,
+     int *ntris,
+     const double **contoursbegin, 
+     const double **contoursend)
+{
+    const double *contourbegin, *contourend;
+    Vertex *current_vertex;
+    GLUtesselator *tess;
+    TessContext *ctx;
+
+    tess = gluNewTess();
+    ctx = new_tess_context();
+
+    gluTessCallback(tess, GLU_TESS_VERTEX_DATA,  (GLvoid (*) ()) &vertex);
+    gluTessCallback(tess, GLU_TESS_BEGIN_DATA,   (GLvoid (*) ()) &begin);
+    gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (GLvoid (*) ()) &combine);
+
+    gluTessBeginPolygon(tess, ctx);
+    do {
+        contourbegin = *contoursbegin++;
+        contourend = *contoursbegin;
+        gluTessBeginContour(tess);
+        while (contourbegin != contourend) {
+            current_vertex = new_vertex(ctx, contourbegin[0], contourbegin[1]);
+            contourbegin += 2;
+            gluTessVertex(tess, current_vertex->pt, current_vertex);
+        }
+        gluTessEndContour(tess);
+    } while (contoursbegin != (contoursend - 1));
+    gluTessEndPolygon(tess);
+
+    write_output(ctx, verts, tris, nverts, ntris);
+    destroy_tess_context(ctx);
+    gluDeleteTess(tess);
+}

+ 13 - 0
3rdparty/tessellate/tessellate.h

@@ -0,0 +1,13 @@
+typedef struct Vertex {
+    double pt[3];
+    int index;
+    struct Vertex *prev;
+} Vertex;
+
+void tessellate
+    (double **verts,
+     int *nverts,
+     int **tris,
+     int *ntris,
+     const double **contoursbegin, 
+     const double **contoursend);

+ 19 - 0
3rdparty/tessellate/tessellate.pro

@@ -0,0 +1,19 @@
+TEMPLATE = lib
+
+TARGET = tessellate
+
+CONFIG = staticlib
+
+QMAKE_CFLAGS += -fPIC
+
+SOURCES = dict.c \
+  geom.c \
+  memalloc.c \
+  mesh.c \
+  normal.c \
+  priorityq.c \
+  render.c \
+  sweep.c \
+  tess.c \
+  tessellate.c \
+  tessmono.c

+ 201 - 0
3rdparty/tessellate/tessmono.c

@@ -0,0 +1,201 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#include "gluos.h"
+#include <stdlib.h>
+#include "geom.h"
+#include "mesh.h"
+#include "tessmono.h"
+#include <assert.h>
+
+#define AddWinding(eDst,eSrc)	(eDst->winding += eSrc->winding, \
+				 eDst->Sym->winding += eSrc->Sym->winding)
+
+/* __gl_meshTessellateMonoRegion( face ) tessellates a monotone region
+ * (what else would it do??)  The region must consist of a single
+ * loop of half-edges (see mesh.h) oriented CCW.  "Monotone" in this
+ * case means that any vertical line intersects the interior of the
+ * region in a single interval.  
+ *
+ * Tessellation consists of adding interior edges (actually pairs of
+ * half-edges), to split the region into non-overlapping triangles.
+ *
+ * The basic idea is explained in Preparata and Shamos (which I don''t
+ * have handy right now), although their implementation is more
+ * complicated than this one.  The are two edge chains, an upper chain
+ * and a lower chain.  We process all vertices from both chains in order,
+ * from right to left.
+ *
+ * The algorithm ensures that the following invariant holds after each
+ * vertex is processed: the untessellated region consists of two
+ * chains, where one chain (say the upper) is a single edge, and
+ * the other chain is concave.  The left vertex of the single edge
+ * is always to the left of all vertices in the concave chain.
+ *
+ * Each step consists of adding the rightmost unprocessed vertex to one
+ * of the two chains, and forming a fan of triangles from the rightmost
+ * of two chain endpoints.  Determining whether we can add each triangle
+ * to the fan is a simple orientation test.  By making the fan as large
+ * as possible, we restore the invariant (check it yourself).
+ */
+int __gl_meshTessellateMonoRegion( GLUface *face )
+{
+  GLUhalfEdge *up, *lo;
+
+  /* All edges are oriented CCW around the boundary of the region.
+   * First, find the half-edge whose origin vertex is rightmost.
+   * Since the sweep goes from left to right, face->anEdge should
+   * be close to the edge we want.
+   */
+  up = face->anEdge;
+  assert( up->Lnext != up && up->Lnext->Lnext != up );
+
+  for( ; VertLeq( up->Dst, up->Org ); up = up->Lprev )
+    ;
+  for( ; VertLeq( up->Org, up->Dst ); up = up->Lnext )
+    ;
+  lo = up->Lprev;
+
+  while( up->Lnext != lo ) {
+    if( VertLeq( up->Dst, lo->Org )) {
+      /* up->Dst is on the left.  It is safe to form triangles from lo->Org.
+       * The EdgeGoesLeft test guarantees progress even when some triangles
+       * are CW, given that the upper and lower chains are truly monotone.
+       */
+      while( lo->Lnext != up && (EdgeGoesLeft( lo->Lnext )
+	     || EdgeSign( lo->Org, lo->Dst, lo->Lnext->Dst ) <= 0 )) {
+	GLUhalfEdge *tempHalfEdge= __gl_meshConnect( lo->Lnext, lo );
+	if (tempHalfEdge == NULL) return 0;
+	lo = tempHalfEdge->Sym;
+      }
+      lo = lo->Lprev;
+    } else {
+      /* lo->Org is on the left.  We can make CCW triangles from up->Dst. */
+      while( lo->Lnext != up && (EdgeGoesRight( up->Lprev )
+	     || EdgeSign( up->Dst, up->Org, up->Lprev->Org ) >= 0 )) {
+	GLUhalfEdge *tempHalfEdge= __gl_meshConnect( up, up->Lprev );
+	if (tempHalfEdge == NULL) return 0;
+	up = tempHalfEdge->Sym;
+      }
+      up = up->Lnext;
+    }
+  }
+
+  /* Now lo->Org == up->Dst == the leftmost vertex.  The remaining region
+   * can be tessellated in a fan from this leftmost vertex.
+   */
+  assert( lo->Lnext != up );
+  while( lo->Lnext->Lnext != up ) {
+    GLUhalfEdge *tempHalfEdge= __gl_meshConnect( lo->Lnext, lo );
+    if (tempHalfEdge == NULL) return 0;
+    lo = tempHalfEdge->Sym;
+  }
+
+  return 1;
+}
+
+
+/* __gl_meshTessellateInterior( mesh ) tessellates each region of
+ * the mesh which is marked "inside" the polygon.  Each such region
+ * must be monotone.
+ */
+int __gl_meshTessellateInterior( GLUmesh *mesh )
+{
+  GLUface *f, *next;
+
+  /*LINTED*/
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) {
+    /* Make sure we don''t try to tessellate the new triangles. */
+    next = f->next;
+    if( f->inside ) {
+      if ( !__gl_meshTessellateMonoRegion( f ) ) return 0;
+    }
+  }
+
+  return 1;
+}
+
+
+/* __gl_meshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces
+ * which are not marked "inside" the polygon.  Since further mesh operations
+ * on NULL faces are not allowed, the main purpose is to clean up the
+ * mesh so that exterior loops are not represented in the data structure.
+ */
+void __gl_meshDiscardExterior( GLUmesh *mesh )
+{
+  GLUface *f, *next;
+
+  /*LINTED*/
+  for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) {
+    /* Since f will be destroyed, save its next pointer. */
+    next = f->next;
+    if( ! f->inside ) {
+      __gl_meshZapFace( f );
+    }
+  }
+}
+
+#define MARKED_FOR_DELETION	0x7fffffff
+
+/* __gl_meshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the
+ * winding numbers on all edges so that regions marked "inside" the
+ * polygon have a winding number of "value", and regions outside
+ * have a winding number of 0.
+ *
+ * If keepOnlyBoundary is TRUE, it also deletes all edges which do not
+ * separate an interior region from an exterior one.
+ */
+int __gl_meshSetWindingNumber( GLUmesh *mesh, int value,
+			        GLboolean keepOnlyBoundary )
+{
+  GLUhalfEdge *e, *eNext;
+
+  for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) {
+    eNext = e->next;
+    if( e->Rface->inside != e->Lface->inside ) {
+
+      /* This is a boundary edge (one side is interior, one is exterior). */
+      e->winding = (e->Lface->inside) ? value : -value;
+    } else {
+
+      /* Both regions are interior, or both are exterior. */
+      if( ! keepOnlyBoundary ) {
+	e->winding = 0;
+      } else {
+	if ( !__gl_meshDelete( e ) ) return 0;
+      }
+    }
+  }
+  return 1;
+}

+ 71 - 0
3rdparty/tessellate/tessmono.h

@@ -0,0 +1,71 @@
+/*
+ * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
+ * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice including the dates of first publication and
+ * either this permission notice or a reference to
+ * http://oss.sgi.com/projects/FreeB/
+ * shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Except as contained in this notice, the name of Silicon Graphics, Inc.
+ * shall not be used in advertising or otherwise to promote the sale, use or
+ * other dealings in this Software without prior written authorization from
+ * Silicon Graphics, Inc.
+ */
+/*
+** Author: Eric Veach, July 1994.
+**
+*/
+
+#ifndef __tessmono_h_
+#define __tessmono_h_
+
+/* __gl_meshTessellateMonoRegion( face ) tessellates a monotone region
+ * (what else would it do??)  The region must consist of a single
+ * loop of half-edges (see mesh.h) oriented CCW.  "Monotone" in this
+ * case means that any vertical line intersects the interior of the
+ * region in a single interval.  
+ *
+ * Tessellation consists of adding interior edges (actually pairs of
+ * half-edges), to split the region into non-overlapping triangles.
+ *
+ * __gl_meshTessellateInterior( mesh ) tessellates each region of
+ * the mesh which is marked "inside" the polygon.  Each such region
+ * must be monotone.
+ *
+ * __gl_meshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces
+ * which are not marked "inside" the polygon.  Since further mesh operations
+ * on NULL faces are not allowed, the main purpose is to clean up the
+ * mesh so that exterior loops are not represented in the data structure.
+ *
+ * __gl_meshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the
+ * winding numbers on all edges so that regions marked "inside" the
+ * polygon have a winding number of "value", and regions outside
+ * have a winding number of 0.
+ *
+ * If keepOnlyBoundary is TRUE, it also deletes all edges which do not
+ * separate an interior region from an exterior one.
+ */
+
+int __gl_meshTessellateMonoRegion( GLUface *face );
+int __gl_meshTessellateInterior( GLUmesh *mesh );
+void __gl_meshDiscardExterior( GLUmesh *mesh );
+int __gl_meshSetWindingNumber( GLUmesh *mesh, int value,
+			        GLboolean keepOnlyBoundary );
+
+#endif

+ 302 - 0
CMakeLists.txt

@@ -0,0 +1,302 @@
+#############################################################
+# CMake settings
+cmake_minimum_required(VERSION 3.5)
+cmake_policy(SET CMP0071 NEW)
+cmake_policy(SET CMP0074 NEW)
+cmake_policy(SET CMP0077 NEW)
+cmake_policy(SET CMP0087 NEW)
+
+set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/src/qml/imports CACHE PATH "QML import path for Qt Creator to detect custom modules properly")
+
+set(CMAKE_COLOR_MAKEFILE ON)
+set(APP_NAME "QField" CACHE STRING "Application Name")
+set(APK_VERSION_CODE "1" CACHE STRING "Apk Version Code (Example: 1)")
+set(APP_VERSION "" CACHE STRING "Application Version (Example: 1.0.0)")
+set(APP_VERSION_STR "local - dev" CACHE STRING "Application Version Name (Example: 1.0.0 - Homerun)")
+set(APP_PACKAGE_NAME "qfield" CACHE STRING "Package name suffix. E.g. qfield --> ch.opengis.qfield")
+
+string(REGEX REPLACE "v" "" CLEAN_APP_VERSION "${APP_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
+
+include(GetGitRevisionDescription)
+
+set(VCPKG_APPLOCAL_DEPS ON)
+set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON)
+
+project(${APP_NAME}
+  VERSION ${CLEAN_APP_VERSION}
+)
+
+
+GET_GIT_HEAD_REVISION(GIT_REFSPEC GIT_REV)
+
+add_definitions(-DAPP_NAME="${APP_NAME}")
+add_definitions(-DAPP_VERSION="${APP_VERSION}")
+add_definitions(-DAPP_VERSION_STR="${APP_VERSION_STR}")
+add_definitions(-DGIT_REV="${GIT_REV}")
+
+set(DEFAULT_BIN_SUBDIR     bin)
+set(DEFAULT_CGIBIN_SUBDIR  bin)
+set(DEFAULT_LIB_SUBDIR     lib${LIB_SUFFIX})
+set(DEFAULT_INCLUDE_SUBDIR include/qfield)
+
+set(QFIELD_BIN_SUBDIR     ${DEFAULT_BIN_SUBDIR}     CACHE STRING "Subdirectory where executables will be installed")
+set(QFIELD_LIB_SUBDIR     ${DEFAULT_LIB_SUBDIR}     CACHE STRING "Subdirectory where libraries will be installed")
+set(QFIELD_INCLUDE_SUBDIR ${DEFAULT_INCLUDE_SUBDIR} CACHE STRING "Subdirectory where header files will be installed")
+
+set(WITH_VCPKG OFF CACHE BOOL "Build with vcpkg packaging layout")
+set(USE_MAC_BUNDLING NOT ${WITH_VCPKG} CACHE BOOL "Use mac bundling")
+
+set(RELATIVE_PREFIX_PATH ${WITH_VCPKG} CACHE BOOL "Use a prefix path relative to the application itself rather than hard coding the path while compiling")
+if(RELATIVE_PREFIX_PATH)
+  add_definitions(-DRELATIVE_PREFIX_PATH)
+endif()
+
+mark_as_advanced (QFIELD_INCLUDE_SUBDIR QFIELD_BIN_SUBDIR QFIELD_LIB_SUBDIR WITH_VCPKG USE_MAC_BUNDLING)
+set(QFIELD_BIN_DIR ${QFIELD_BIN_SUBDIR})
+set(QFIELD_LIB_DIR ${QFIELD_LIB_SUBDIR})
+set(QFIELD_INCLUDE_DIR ${QFIELD_INCLUDE_SUBDIR})
+set(QT_ANDROID_APPLICATION_BINARY "qfield")
+
+# set the default locations where the targets (executables, libraries) will land when compiled
+# this is to allow running QField directly from the build directory.
+if (ANDROID)
+  set(QFIELD_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/android-build)
+else()
+  set(QFIELD_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output)
+endif()
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${QFIELD_OUTPUT_DIRECTORY}/${QFIELD_BIN_SUBDIR}/$<0:>)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QFIELD_OUTPUT_DIRECTORY}/${QFIELD_LIB_SUBDIR}/$<0:>)
+
+install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources DESTINATION share/qfield)
+
+set(WITH_SPIX FALSE CACHE STRING "Compile with Spix for testing")
+
+if (WITH_SPIX)
+  find_package(Spix)
+endif ()
+
+if (ANDROID)
+  # This is necessary in order for CMake to be able to detect libraries in OSGeo4A
+  list(APPEND CMAKE_FIND_ROOT_PATH /)
+
+  add_definitions(-DQGIS_INSTALL_DIR="") # TODO: do we need osgeo4a/[lib]/files here? see qgis.pri
+
+  # Android SDK stuff
+  set(ANDROID_TARGET_PLATFORM CACHE INT "Target Android platform SDK version")
+  set(ANDROID_PLATFORM 21 CACHE INT "Minimum Android platform SDK version")
+  set(ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/android)
+
+  # Paths relevant to OSGeo4A
+  set(OSGEO4A_STAGE_DIR NONE CACHE PATH "OSGeo4A stage path (base path without architecture)")
+  set(OSGEO4A_LIB_DIR ${OSGEO4A_STAGE_DIR}/${ANDROID_ABI}/lib)
+
+  # Extra libraries to be copied after building
+  list(APPEND ANDROID_EXTRA_LIBS
+    ${OSGEO4A_LIB_DIR}/libssl_1_1.so
+    ${OSGEO4A_LIB_DIR}/libcrypto_1_1.so
+    ${OSGEO4A_LIB_DIR}/libexpat.so
+    ${OSGEO4A_LIB_DIR}/libgeos.so
+    ${OSGEO4A_LIB_DIR}/libgeos_c.so
+    ${OSGEO4A_LIB_DIR}/libgslcblas.so
+    ${OSGEO4A_LIB_DIR}/libsqlite3.so
+    ${OSGEO4A_LIB_DIR}/libcharset.so
+    ${OSGEO4A_LIB_DIR}/libexiv2.so
+    ${OSGEO4A_LIB_DIR}/libiconv.so
+    ${OSGEO4A_LIB_DIR}/libopenjp2.so
+    ${OSGEO4A_LIB_DIR}/libfreexl.so
+    ${OSGEO4A_LIB_DIR}/libtiff.so
+    ${OSGEO4A_LIB_DIR}/libpng16.so
+    ${OSGEO4A_LIB_DIR}/libgdal.so
+    ${OSGEO4A_LIB_DIR}/libproj.so
+    ${OSGEO4A_LIB_DIR}/libspatialindex.so
+    ${OSGEO4A_LIB_DIR}/libpq.so
+    ${OSGEO4A_LIB_DIR}/libspatialite.so
+    ${OSGEO4A_LIB_DIR}/libgsl.so
+    ${OSGEO4A_LIB_DIR}/libgslcblas.so
+    ${OSGEO4A_LIB_DIR}/libqca-qt5_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libqgis_core_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libqgis_analysis_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libqgis_native_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libqt5keychain_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libzip.so
+    ${OSGEO4A_LIB_DIR}/libzstd.so
+    ${OSGEO4A_LIB_DIR}/libpoppler_${ANDROID_ABI}.so
+    ${OSGEO4A_LIB_DIR}/libfreetype.so
+  )
+
+  find_package(Qt5 COMPONENTS AndroidExtras REQUIRED)
+
+  configure_file(cmake/generated.xml.in ${CMAKE_SOURCE_DIR}/android/res/values/generated.xml @ONLY)
+  configure_file(cmake/AndroidManifest.xml.in ${CMAKE_SOURCE_DIR}/android/AndroidManifest.xml @ONLY)
+
+  include(CreateZip)
+
+  file(GLOB PLUGINS_QGIS_PROVIDERS
+    ${OSGEO4A_LIB_DIR}/*provider*.so
+  )
+  file(GLOB PLUGINS_QGIS_AUTHMETHODS
+    ${OSGEO4A_LIB_DIR}/*authmethod*.so
+  )
+
+  set(SHARE_PATH "${CMAKE_CURRENT_BINARY_DIR}/android-build/assets/share")
+  file(COPY ${PLUGINS_QGIS_PROVIDERS} DESTINATION "${SHARE_PATH}/plugins/")
+  file(COPY ${PLUGINS_QGIS_AUTHMETHODS} DESTINATION "${SHARE_PATH}/plugins/")
+  file(COPY "${OSGEO4A_LIB_DIR}/qca-qt5/crypto/" DESTINATION "${SHARE_PATH}/plugins/crypto/")
+  file(COPY "${OSGEO4A_STAGE_DIR}/${ANDROID_ABI}/share/proj/" DESTINATION "${SHARE_PATH}/proj/")
+  file(COPY "${OSGEO4A_STAGE_DIR}/${ANDROID_ABI}/files/share/svg/" DESTINATION "${SHARE_PATH}/qgis/svg/")
+  file(COPY "${OSGEO4A_STAGE_DIR}/${ANDROID_ABI}/files/share/resources/" DESTINATION "${SHARE_PATH}/qgis/resources/")
+
+  # Strip libs to save space
+  file(GLOB_RECURSE PLUGINS_LIBS
+    ${SHARE_PATH}/plugins/*.so
+  )
+  foreach(PLUGINS_LIB IN LISTS PLUGINS_LIBS)
+    if(ANDROID_ABI STREQUAL "x86")
+      EXECUTE_PROCESS(COMMAND "${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-strip" "${PLUGINS_LIB}")
+    elseif(ANDROID_ABI STREQUAL "x86_64")
+      EXECUTE_PROCESS(COMMAND "${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-strip" "${PLUGINS_LIB}")
+    elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
+      EXECUTE_PROCESS(COMMAND "${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-android-strip" "${PLUGINS_LIB}")
+    elseif(ANDROID_ABI STREQUAL "arm64-v8a")
+      EXECUTE_PROCESS(COMMAND "${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip" "${PLUGINS_LIB}")
+    endif()
+  endforeach()
+
+  # Remove world map to keep apk size a bit smaller
+  file(REMOVE "${SHARE_PATH}/qgis/resources/data/world_map.gpkg")
+  file(COPY resources/ DESTINATION "${SHARE_PATH}/qfield/")
+
+  # We use GDAL in OSGeo4A instead
+  set(ENV{GDAL_ROOT} ${OSGEO4A_STAGE_DIR}/${ANDROID_ABI})
+
+  # Don't do tests when building for Android
+  set(ENABLE_TESTS FALSE)
+endif()
+
+set(QT_MIN_VERSION 5.14.0)
+find_package(Qt5 COMPONENTS Concurrent Core Qml Gui Xml Positioning Widgets Network Quick Svg OpenGL Sql Sensors WebView Bluetooth Multimedia REQUIRED)
+
+# PrintSupport isn't required, because it doesn't exist for ios
+# qgis will deal with it an define a public 'QT_NO_PRINTER'
+find_package(Qt5 COMPONENTS PrintSupport QUIET)
+
+find_package(QGIS REQUIRED)
+if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
+  find_package(Proj)
+  find_package(GDAL)
+endif()
+find_package(QCA)
+find_package(QtKeychain)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
+  add_definitions(-DQT_NO_PRINTER)
+
+  set(SHARE_PATH "${CMAKE_CURRENT_BINARY_DIR}/share")
+
+  # keep trailing slash for QField to rename the directory (instead of copying in subdir)
+  file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/resources/ DESTINATION ${SHARE_PATH}/qfield)
+  file(COPY ${QGIS_APPLE_RESOURCES_DIR}/resources DESTINATION ${SHARE_PATH}/qgis)
+  file(COPY ${QGIS_APPLE_RESOURCES_DIR}/svg DESTINATION ${SHARE_PATH}/qgis)
+  file(COPY ${QFIELD_PROJ_DIR} DESTINATION ${SHARE_PATH})
+  # Remove world map to keep apk size a bit smaller
+  file(REMOVE "${SHARE_PATH}/qgis/resources/data/world_map.gpkg")
+
+  set(QFIELD_RESOURCE_FILES ${SHARE_PATH})
+endif()
+
+set(ENABLE_TESTS CACHE BOOL "Build unit tests")
+
+if(MSVC)
+  find_package(Qt5 COMPONENTS Charts REQUIRED) # vcpkg doesn't include QtCharts.dll as dep of the qml module otherwise
+  add_definitions(-D_USE_MATH_DEFINES)
+  add_definitions(-DNOMINMAX)
+  add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
+endif()
+
+add_subdirectory(3rdparty/tessellate)
+add_subdirectory(src/core)
+add_subdirectory(src/app)
+
+if (ENABLE_TESTS)
+  find_package(Qt5 COMPONENTS Test QuickTest)
+  enable_testing()
+  add_subdirectory(test)
+endif()
+
+if(WITH_VCPKG)
+  SET(VCPKG_BASE_DIR "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}")
+  if(MSVC)
+    SET(QGIS_PLUGIN_DIR "${VCPKG_BASE_DIR}/tools/qgis/plugins")
+    file(GLOB PROVIDER_LIBS
+      "${QGIS_PLUGIN_DIR}/*provider*.dll"
+    )
+    file(GLOB AUTHMETHODS_LIBS
+      "${QGIS_PLUGIN_DIR}/*authmethod*.dll"
+    )
+    # From QGIS CMakeLists.txt
+    set(QGIS_PLUGIN_INSTALL_PREFIX "plugins")
+  else()
+    SET(QGIS_PLUGIN_DIR "${VCPKG_BASE_DIR}/lib/qgis/plugins")
+    file(GLOB PROVIDER_LIBS
+      "${QGIS_PLUGIN_DIR}/*provider*.so"
+    )
+    file(GLOB AUTHMETHODS_LIBS
+      "${QGIS_PLUGIN_DIR}/*authmethod*.so"
+    )
+    # From QGIS CMakeLists.txt
+    set(QGIS_PLUGIN_INSTALL_PREFIX "lib${LIB_SUFFIX}/qgis/plugins")
+  endif()
+  add_custom_target(deploy)
+  add_custom_command(TARGET deploy
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/output/bin/qgis/plugins"
+  )
+  foreach(LIB ${PROVIDER_LIBS})
+    add_custom_command(TARGET deploy
+                      POST_BUILD
+                      COMMAND ${CMAKE_COMMAND} -E copy_if_different "${LIB}" "${CMAKE_BINARY_DIR}/output/bin/qgis/plugins"
+    )
+    install(FILES "${LIB}" DESTINATION "${QGIS_PLUGIN_INSTALL_PREFIX}")
+  endforeach()
+  foreach(LIB ${AUTHMETHODS_LIBS})
+    add_custom_command(TARGET deploy
+                      POST_BUILD
+                      COMMAND ${CMAKE_COMMAND} -E copy_if_different "${LIB}" "${CMAKE_BINARY_DIR}/output/bin/qgis/plugins"
+    )
+  endforeach()
+  add_custom_command(TARGET deploy
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory "${VCPKG_BASE_DIR}/share/qgis/resources" "${CMAKE_BINARY_DIR}/output/bin/qgis/resources"
+  )
+  add_custom_command(TARGET deploy
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory "${VCPKG_BASE_DIR}/share/proj4" "${CMAKE_BINARY_DIR}/output/bin/proj"
+  )
+  add_custom_command(TARGET deploy
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory "${VCPKG_BASE_DIR}/share/gdal" "${CMAKE_BINARY_DIR}/output/bin/gdal"
+  )
+  install(DIRECTORY "${VCPKG_BASE_DIR}/share/qgis/resources/" DESTINATION "share/qgis/resources")
+  install(DIRECTORY "${VCPKG_BASE_DIR}/share/proj4/" DESTINATION "share/proj")
+  install(DIRECTORY "${VCPKG_BASE_DIR}/share/gdal/" DESTINATION "share/gdal")
+
+  if(EXISTS ${VCPKG_BASE_DIR}/plugins/mediaservice)
+    add_custom_command(TARGET deploy
+      POST_BUILD
+      COMMAND ${CMAKE_COMMAND} -E copy_directory "${VCPKG_BASE_DIR}/plugins/mediaservice" "${CMAKE_BINARY_DIR}/output/bin/plugins/mediaservice"
+    )
+    install(DIRECTORY "${VCPKG_BASE_DIR}/plugins/mediaservice/" DESTINATION "bin/plugins/mediaservice")
+  endif()
+
+  add_dependencies(qfield deploy)
+endif()
+
+if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  install(FILES ${CMAKE_SOURCE_DIR}/images/icons/qfield_logo.svg DESTINATION share/icons RENAME qfield.svg)
+  install(FILES ${CMAKE_SOURCE_DIR}/platform/linux/qfield.desktop DESTINATION share/qfield)
+endif()
+
+include(Package)

+ 340 - 0
LICENSE

@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {description}
+    Copyright (C) {year}  {fullname}
+
+    This program 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 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  {signature of Ty Coon}, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+

+ 1 - 0
RELEASE_NAME

@@ -0,0 +1 @@
+Dev

+ 80 - 0
android/build.gradle

@@ -0,0 +1,80 @@
+buildscript {
+    repositories {
+        jcenter()
+        mavenCentral()
+        google()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        google()
+    }
+
+    gradle.projectsEvaluated {
+        tasks.withType(JavaCompile) {
+            options.compilerArgs << "-Xlint:deprecation"
+        }
+    }
+}
+
+apply plugin: 'com.android.application'
+
+def outputPathName = "some.apk"
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    compile 'com.android.support:support-v4:26.1.0'
+
+}
+
+android {
+    /*******************************************************
+     * The following variables:
+     * - androidBuildToolsVersion,
+     * - androidCompileSdkVersion
+     * - qt5AndroidDir - holds the path to qt android files
+     *                   needed to build any Qt application
+     *                   on Android.
+     *
+     * are defined in gradle.properties file. This file is
+     * updated by QtCreator and androiddeployqt tools.
+     * Changing them manually might break the compilation!
+     *******************************************************/
+
+    compileSdkVersion androidCompileSdkVersion.toInteger()
+
+
+    /*
+    // see https://bugreports.qt.io/browse/QTBUG-69755
+    // and https://forum.qt.io/topic/86570/incorrect-apk-directory/4
+    applicationVariants.all { variant ->
+            variant.outputs.all {
+                outputFileName = "../" + outputFileName
+            }
+         }
+    */
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
+            aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
+            res.srcDirs = [qt5AndroidDir + '/res', 'res']
+            resources.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            assets.srcDirs = ['assets']
+            jniLibs.srcDirs = ['libs']
+       }
+    }
+
+    lintOptions {
+        abortOnError false
+        disable 'MissingTranslation'
+    }
+}

BIN
android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Thu Dec 03 07:11:59 CET 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

+ 164 - 0
android/gradlew

@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
android/gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

BIN
android/res/drawable-hdpi/card.png


BIN
android/res/drawable-hdpi/dataset.png


BIN
android/res/drawable-hdpi/directory.png


BIN
android/res/drawable-hdpi/project.png


BIN
android/res/drawable-hdpi/qfield_logo.png


BIN
android/res/drawable-hdpi/qfield_logo_beta.png


BIN
android/res/drawable-hdpi/qfield_logo_pr.png


BIN
android/res/drawable-hdpi/tablet.png


BIN
android/res/drawable-mdpi/card.png


BIN
android/res/drawable-mdpi/dataset.png


BIN
android/res/drawable-mdpi/directory.png


BIN
android/res/drawable-mdpi/project.png


BIN
android/res/drawable-mdpi/qfield_logo.png


BIN
android/res/drawable-mdpi/qfield_logo_beta.png


BIN
android/res/drawable-mdpi/qfield_logo_pr.png


BIN
android/res/drawable-mdpi/tablet.png


BIN
android/res/drawable-xhdpi/card.png


BIN
android/res/drawable-xhdpi/dataset.png


BIN
android/res/drawable-xhdpi/directory.png


BIN
android/res/drawable-xhdpi/project.png


BIN
android/res/drawable-xhdpi/qfield_logo.png


BIN
android/res/drawable-xhdpi/qfield_logo_beta.png


BIN
android/res/drawable-xhdpi/qfield_logo_pr.png


BIN
android/res/drawable-xhdpi/tablet.png


BIN
android/res/drawable-xxhdpi/card.png


BIN
android/res/drawable-xxhdpi/dataset.png


BIN
android/res/drawable-xxhdpi/directory.png


BIN
android/res/drawable-xxhdpi/project.png


BIN
android/res/drawable-xxhdpi/qfield_logo.png


Some files were not shown because too many files changed in this diff