Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add support for forced orientation (CW/CCW)

  • Loading branch information...
commit bfc78aa6219b0d5ede009e33a6c6ac7a61c3c635 1 parent 6bf2fc9
Remi Gillig authored
2  LibTessDotNet/Sources/MeshUtils.cs
@@ -108,7 +108,7 @@ public class Vertex
108 108 internal Vec3 _coords;
109 109 internal float _s, _t;
110 110 internal PQHandle _pqHandle;
111   - internal int _n, _idx;
  111 + internal int _n;
112 112 internal object _data;
113 113 }
114 114
1  LibTessDotNet/Sources/Sweep.cs
@@ -360,7 +360,6 @@ private void VertexWeights(MeshUtils.Vertex isect, MeshUtils.Vertex org, MeshUti
360 360 private void GetIntersectData(MeshUtils.Vertex isect, MeshUtils.Vertex orgUp, MeshUtils.Vertex dstUp, MeshUtils.Vertex orgLo, MeshUtils.Vertex dstLo)
361 361 {
362 362 isect._coords = Vec3.Zero;
363   - isect._idx = MeshUtils.Undef;
364 363 float w0, w1, w2, w3;
365 364 VertexWeights(isect, orgUp, dstUp, out w0, out w1);
366 365 VertexWeights(isect, orgLo, dstLo, out w2, out w3);
94 LibTessDotNet/Sources/Tess.cs
@@ -51,6 +51,13 @@ public enum ElementType
51 51 BoundaryContours
52 52 }
53 53
  54 + public enum ContourOrientation
  55 + {
  56 + Original,
  57 + Clockwise,
  58 + CounterClockwise
  59 + }
  60 +
54 61 public struct ContourVertex
55 62 {
56 63 public Vec3 Position;
@@ -75,10 +82,8 @@ public partial class Tess
75 82 private MeshUtils.Vertex _event;
76 83
77 84 private CombineCallback _combineCallback;
78   - private int _vertexIndexCounter;
79 85
80 86 private ContourVertex[] _vertices;
81   - private int[] _vertexIndices;
82 87 private int _vertexCount;
83 88 private int[] _elements;
84 89 private int _elementCount;
@@ -89,7 +94,6 @@ public partial class Tess
89 94 public float SUnitY = 1.0f;
90 95
91 96 public ContourVertex[] Vertices { get { return _vertices; } }
92   - public int[] VertexIndices { get { return _vertexIndices; } }
93 97 public int VertexCount { get { return _vertexCount; } }
94 98
95 99 public int[] Elements { get { return _elements; } }
@@ -103,10 +107,7 @@ public Tess()
103 107 _windingRule = WindingRule.EvenOdd;
104 108 _mesh = null;
105 109
106   - _vertexIndexCounter = 0;
107   -
108 110 _vertices = null;
109   - _vertexIndices = null;
110 111 _vertexCount = 0;
111 112 _elements = null;
112 113 _elementCount = 0;
@@ -474,7 +475,6 @@ private void OutputPolymesh(ElementType elementType, int polySize)
474 475
475 476 _vertexCount = maxVertexCount;
476 477 _vertices = new ContourVertex[_vertexCount];
477   - _vertexIndices = new int[_vertexCount];
478 478
479 479 // Output vertices.
480 480 for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
@@ -485,8 +485,6 @@ private void OutputPolymesh(ElementType elementType, int polySize)
485 485 int n = v._n;
486 486 _vertices[v._n].Position = v._coords;
487 487 _vertices[v._n].Data = v._data;
488   - // Store vertex index.
489   - _vertexIndices[v._n] = v._idx;
490 488 }
491 489 }
492 490
@@ -556,11 +554,9 @@ private void OutputContours()
556 554
557 555 _elements = new int[_elementCount * 2];
558 556 _vertices = new ContourVertex[_vertexCount];
559   - _vertexIndices = new int[_vertexCount];
560 557
561 558 int vertIndex = 0;
562 559 int elementIndex = 0;
563   - int vertIndsIndex = 0;
564 560
565 561 startVert = 0;
566 562
@@ -570,15 +566,12 @@ private void OutputContours()
570 566
571 567 vertCount = 0;
572 568 start = edge = f._anEdge;
573   - do
574   - {
  569 + do {
575 570 _vertices[vertIndex++].Position = edge._Org._coords;
576 571 _vertices[vertIndex++].Data = edge._Org._data;
577   - _vertexIndices[vertIndsIndex++] = edge._Org._idx;
578 572 ++vertCount;
579 573 edge = edge._Lnext;
580   - }
581   - while (edge != start);
  574 + } while (edge != start);
582 575
583 576 _elements[elementIndex++] = startVert;
584 577 _elements[elementIndex++] = vertCount;
@@ -587,41 +580,67 @@ private void OutputContours()
587 580 }
588 581 }
589 582
  583 + private float SignedArea(ContourVertex[] vertices)
  584 + {
  585 + float area = 0.0f;
  586 +
  587 + for (int i = 0; i < vertices.Length; i++)
  588 + {
  589 + var v0 = vertices[i];
  590 + var v1 = vertices[(i + 1) % vertices.Length];
  591 +
  592 + area += v0.Position.X * v1.Position.Y;
  593 + area -= v0.Position.Y * v1.Position.X;
  594 + }
  595 +
  596 + return area * 0.5f;
  597 + }
  598 +
590 599 public void AddContour(ContourVertex[] vertices)
591 600 {
  601 + AddContour(vertices, ContourOrientation.Original);
  602 + }
  603 +
  604 + public void AddContour(ContourVertex[] vertices, ContourOrientation forceOrientation)
  605 + {
592 606 if (_mesh == null)
593 607 {
594 608 _mesh = new Mesh();
595 609 }
596 610
  611 + bool reverse = false;
  612 + if (forceOrientation != ContourOrientation.Original)
  613 + {
  614 + float area = SignedArea(vertices);
  615 + reverse = (forceOrientation == ContourOrientation.Clockwise && area < 0.0f) || (forceOrientation == ContourOrientation.CounterClockwise && area > 0.0f);
  616 + }
  617 +
597 618 MeshUtils.Edge e = null;
598 619 for (int i = 0; i < vertices.Length; ++i)
599 620 {
600 621 if (e == null)
601 622 {
602 623 e = _mesh.MakeEdge();
603   - _mesh.Splice(e, e._Sym);
604   - }
605   - else
606   - {
607   - // Create a new vertex and edge which immediately follow e
  624 + _mesh.Splice(e, e._Sym);
  625 + }
  626 + else
  627 + {
  628 + // Create a new vertex and edge which immediately follow e
608 629 // in the ordering around the left face.
609   - _mesh.SplitEdge(e);
610   - e = e._Lnext;
611   - }
612   -
  630 + _mesh.SplitEdge(e);
  631 + e = e._Lnext;
  632 + }
  633 +
  634 + int index = reverse ? vertices.Length - 1 - i : i;
613 635 // The new vertex is now e._Org.
614   - e._Org._coords = vertices[i].Position;
615   - e._Org._data = vertices[i].Data;
616   -
617   - // Store the insertion number so that the vertex can be later recognized.
618   - e._Org._idx = _vertexIndexCounter++;
619   -
620   - // The winding of an edge says how the winding number changes as we
621   - // cross from the edge''s right face to its left face. We add the
622   - // vertices in such an order that a CCW contour will add +1 to
623   - // the winding number of the region inside the contour.
624   - e._winding = 1;
  636 + e._Org._coords = vertices[index].Position;
  637 + e._Org._data = vertices[index].Data;
  638 +
  639 + // The winding of an edge says how the winding number changes as we
  640 + // cross from the edge''s right face to its left face. We add the
  641 + // vertices in such an order that a CCW contour will add +1 to
  642 + // the winding number of the region inside the contour.
  643 + e._winding = 1;
625 644 e._Sym._winding = -1;
626 645 }
627 646 }
@@ -635,9 +654,6 @@ public void Tessellate(WindingRule windingRule, ElementType elementType, int pol
635 654 {
636 655 _vertices = null;
637 656 _elements = null;
638   - _vertexIndices = null;
639   -
640   - _vertexIndexCounter = 0;
641 657
642 658 _windingRule = windingRule;
643 659 _combineCallback = combineCallback;
11 README.md
Source Rendered
@@ -18,7 +18,9 @@ Provide a robust and fast tessellator (polygons with N vertices in the output) f
18 18 - self-intersecting (see "star-intersect" sample)
19 19 - with coincident vertices (see "clipper" sample)
20 20 - advanced winding rules : even/odd, non zero, positive, negative, |winding| >= 2 (see "redbook-winding" sample)
21   -* Custom vertex attributes (eg. UV coordinates) with merging callback
  21 +* Custom input
  22 + - Custom vertex attributes (eg. UV coordinates) with merging callback
  23 + - Force orientation of input contour (clockwise/counterclockwise, eg. for GIS systems, see "force-winding" sample)
22 24 * Choice of output
23 25 - polygons with N vertices (with N >= 3)
24 26 - connected polygons (didn't quite tried this yet, but should work)
@@ -37,7 +39,7 @@ TBD
37 39
38 40 * Better performance timing (eg. multiple loops instead of one)
39 41 * Profile GC allocations
40   -* Comparison with Poly2Tri
  42 +* Comparison with [Poly2Tri](http://code.google.com/p/poly2tri/)
41 43
42 44 ### License
43 45
@@ -46,4 +48,7 @@ More information in LICENSE.txt.
46 48
47 49 ### Links
48 50 * [Reference implementation](http://oss.sgi.com/projects/ogl-sample) - the original SGI reference implementation
49   -* [libtess2](http://code.google.com/p/libtess2/) - Mikko Mononen cleaned up the original GLU tesselator
  51 +* [libtess2](http://code.google.com/p/libtess2/) - Mikko Mononen cleaned up the original GLU tesselator
  52 +* [Poly2Tri](http://code.google.com/p/poly2tri/) - another triangulation library for .NET (other ports also available)
  53 + - Does not support polygons from Clipper, more specifically vertices with same coordinates (coincident)
  54 +* [Clipper](http://www.angusj.com/delphi/clipper.php) - an open source freeware polygon clipping library
29 TessBed/Canvas.cs
@@ -2,6 +2,7 @@
2 2 using System.Drawing;
3 3 using System.Drawing.Drawing2D;
4 4 using System.Windows.Forms;
  5 +using LibTessDotNet;
5 6
6 7 namespace TessBed
7 8 {
@@ -17,6 +18,22 @@ public Canvas()
17 18 SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
18 19 }
19 20
  21 + private float SignedArea(Polygon polygon)
  22 + {
  23 + float area = 0.0f;
  24 +
  25 + for (int i = 0; i < polygon.Count; i++)
  26 + {
  27 + var v0 = polygon[i];
  28 + var v1 = polygon[(i + 1) % polygon.Count];
  29 +
  30 + area += v0.X * v1.Y;
  31 + area -= v0.Y * v1.X;
  32 + }
  33 +
  34 + return area * 0.5f;
  35 + }
  36 +
20 37 protected override void OnPaint(PaintEventArgs pe)
21 38 {
22 39 var g = pe.Graphics;
@@ -60,10 +77,18 @@ protected override void OnPaint(PaintEventArgs pe)
60 77 {
61 78 foreach (var polygon in Input)
62 79 {
  80 + bool reverse = false;
  81 + if (polygon.Orientation != ContourOrientation.Original)
  82 + {
  83 + float area = SignedArea(polygon);
  84 + reverse = (polygon.Orientation == ContourOrientation.Clockwise && area < 0.0f) || (polygon.Orientation == ContourOrientation.CounterClockwise && area > 0.0f);
  85 + }
  86 +
63 87 for (int i = 0; i < polygon.Count; i++)
64 88 {
65   - var p0 = f(polygon[i]);
66   - var p1 = f(polygon[(i + 1) % polygon.Count]);
  89 + int index = reverse ? polygon.Count - 1 - i : i;
  90 + var p0 = f(polygon[(index + (reverse ? 1 : 0)) % polygon.Count]);
  91 + var p1 = f(polygon[(index + (reverse ? 0 : 1)) % polygon.Count]);
67 92
68 93 g.DrawLine(ShowWinding ? penWinding : penContour, p0, p1);
69 94 g.FillEllipse(brushPoint, p0.X - 2.0f, p0.Y - 2.0f, 4.0f, 4.0f);
17 TessBed/Data/force-winding.dat
... ... @@ -0,0 +1,17 @@
  1 +force cw
  2 + 50.0, 50.0
  3 +300.0, 50.0
  4 +300.0, 300.0
  5 + 50.0, 300.0
  6 +
  7 +force ccw
  8 +100.0, 100.0
  9 +200.0, 100.0
  10 +200.0, 250.0
  11 +100.0, 250.0
  12 +
  13 +force ccw
  14 +150.0, 100.0
  15 +250.0, 100.0
  16 +250.0, 250.0
  17 +150.0, 250.0
32 TessBed/DataLoader.cs
@@ -4,6 +4,7 @@
4 4 using System.Globalization;
5 5 using System.IO;
6 6 using System.Reflection;
  7 +using LibTessDotNet;
7 8
8 9 namespace TessBed
9 10 {
@@ -15,6 +16,8 @@ public struct PolygonPoint
15 16
16 17 public class Polygon : List<PolygonPoint>
17 18 {
  19 + public ContourOrientation Orientation = ContourOrientation.Original;
  20 +
18 21 public Polygon()
19 22 {
20 23
@@ -46,8 +49,8 @@ public PolygonSet LoadDat(Stream fileStream)
46 49 var polys = new PolygonSet();
47 50 int lineNum = 0;
48 51 string line;
49   - bool skipLine = false;
50 52 Color currentColor = Color.White;
  53 + ContourOrientation currentOrientation = ContourOrientation.Original;
51 54 using (var stream = new StreamReader(fileStream))
52 55 {
53 56 while ((line = stream.ReadLine()) != null)
@@ -58,7 +61,8 @@ public PolygonSet LoadDat(Stream fileStream)
58 61 {
59 62 if (points.Count > 0)
60 63 {
61   - var p = new Polygon(points);
  64 + var p = new Polygon(points) { Orientation = currentOrientation };
  65 + currentOrientation = ContourOrientation.Original;
62 66 polys.Add(p);
63 67 points.Clear();
64 68 }
@@ -70,20 +74,22 @@ public PolygonSet LoadDat(Stream fileStream)
70 74 {
71 75 continue;
72 76 }
73   - if (!skipLine && line.StartsWith("/*"))
  77 + if (line.StartsWith("force", true, CultureInfo.InvariantCulture))
74 78 {
75   - skipLine = true;
76   - continue;
77   - }
78   - else if (skipLine)
79   - {
80   - if (line.StartsWith("*/"))
  79 + var force = line.Split(new[] { ' ', ',', '\t' }, StringSplitOptions.RemoveEmptyEntries);
  80 + if (force.Length == 2)
81 81 {
82   - skipLine = false;
  82 + if (string.Compare(force[1], "cw", true) == 0)
  83 + {
  84 + currentOrientation = ContourOrientation.Clockwise;
  85 + }
  86 + if (string.Compare(force[1], "ccw", true) == 0)
  87 + {
  88 + currentOrientation = ContourOrientation.CounterClockwise;
  89 + }
83 90 }
84   - continue;
85 91 }
86   - if (line.StartsWith("color", true, CultureInfo.InvariantCulture))
  92 + else if (line.StartsWith("color", true, CultureInfo.InvariantCulture))
87 93 {
88 94 var rgba = line.Split(new[] { ' ', ',', '\t' }, StringSplitOptions.RemoveEmptyEntries);
89 95 int r = 255, g = 255, b = 255, a = 255;
@@ -128,7 +134,7 @@ public PolygonSet LoadDat(Stream fileStream)
128 134
129 135 if (points.Count > 0)
130 136 {
131   - Polygon p = new Polygon(points);
  137 + Polygon p = new Polygon(points) { Orientation = currentOrientation };
132 138 polys.Add(p);
133 139 }
134 140 }
2  TessBed/MainForm.cs
@@ -167,7 +167,7 @@ private void RefreshAsset(string name)
167 167 v[i].Data = poly[i].Color;
168 168 }
169 169 _sw.Start();
170   - _tess.AddContour(v);
  170 + _tess.AddContour(v, poly.Orientation);
171 171 _sw.Stop();
172 172 }
173 173
BIN  TessBed/Misc/screenshot.png
1  TessBed/TessBed.csproj
@@ -84,6 +84,7 @@
84 84 <EmbeddedResource Include="Data\letterE.dat" />
85 85 <EmbeddedResource Include="Data\star-intersect.dat" />
86 86 <EmbeddedResource Include="Data\redbook-winding.dat" />
  87 + <EmbeddedResource Include="Data\force-winding.dat" />
87 88 <None Include="Properties\Settings.settings">
88 89 <Generator>SettingsSingleFileGenerator</Generator>
89 90 <LastGenOutput>Settings.Designer.cs</LastGenOutput>

1 comment on commit bfc78aa

Remi Gillig
Owner

I definitely need to profile the library, on the screens the difference is huge and I don't know why.

Please sign in to comment.
Something went wrong with that request. Please try again.