Skip to content

Commit 202c420

Browse files
authored
Merge pull request #8 from CptWesley/bowyer-watson#6
Bowyer-Watson Triangulation
2 parents 0806365 + ba3b921 commit 202c420

7 files changed

Lines changed: 261 additions & 34 deletions

File tree

src/ClippingPlane.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ namespace RadiantMapToWavefrontObj
44
{
55
public class ClippingPlane
66
{
7-
public readonly double A, B, C, D;
7+
public readonly double D;
88
public readonly Vector Normal;
99

10+
public double A => Normal.X;
11+
public double B => Normal.Y;
12+
public double C => Normal.Z;
13+
1014
// Constructor for a clipping plane.
1115
public ClippingPlane(Vertex v1, Vertex v2, Vertex v3)
1216
{
1317
Vector vector1 = new Vector(v2.X - v1.X, v2.Y - v1.Y, v2.Z - v1.Z).Unit();
1418
Vector vector2 = new Vector(v3.X - v1.X, v3.Y - v1.Y, v3.Z - v1.Z).Unit();
1519

1620
Normal = Vector.CrossProduct(vector1, vector2).Unit();
17-
A = Normal.X;
18-
B = Normal.Y;
19-
C = Normal.Z; // NB: A,B,C = Normal.X,Y,Z
2021
D = -(A * v2.X + B * v2.Y + C * v2.Z);
2122
}
2223

src/Edge.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+

2+
using System;
3+
4+
namespace RadiantMapToWavefrontObj
5+
{
6+
public struct Edge
7+
{
8+
public Vertex A { get; set; }
9+
public Vertex B { get; set; }
10+
11+
// Constructor for an edge between two vertices.
12+
public Edge(Vertex a, Vertex b)
13+
{
14+
A = a;
15+
B = b;
16+
}
17+
18+
// Returns the vector given by this edge.
19+
public Vector GetVector()
20+
{
21+
return (Vector) B - (Vector) A;
22+
}
23+
24+
// Returns the length of this edge.
25+
public double Length()
26+
{
27+
return GetVector().Length();
28+
}
29+
30+
// Returns the inverse edge of this edge.
31+
public Edge GetInverse()
32+
{
33+
return new Edge(B, A);
34+
}
35+
36+
// Returns a stringified version of this object.
37+
public override string ToString()
38+
{
39+
return "<" + A + ", " + B + ">";
40+
}
41+
42+
// Checks if two edges are equal.
43+
public override bool Equals(object obj)
44+
{
45+
if (obj is Edge)
46+
{
47+
Edge that = (Edge)obj;
48+
if (A == that.A && B == that.B)
49+
return true;
50+
}
51+
return false;
52+
}
53+
54+
// Returns a hascode for the object.
55+
public override int GetHashCode()
56+
{
57+
return A.GetHashCode() + 2 * B.GetHashCode();
58+
}
59+
60+
// Override == operator.
61+
public static bool operator ==(Edge a, Edge b)
62+
{
63+
if (ReferenceEquals(a, null))
64+
{
65+
if (ReferenceEquals(b, null))
66+
return true;
67+
return false;
68+
}
69+
return a.Equals(b);
70+
}
71+
72+
// Override != operator.
73+
public static bool operator !=(Edge a, Edge b)
74+
{
75+
return !(a == b);
76+
}
77+
}
78+
}

src/Face.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-

1+
using System;
2+
23
namespace RadiantMapToWavefrontObj
34
{
45
public class Face
@@ -11,12 +12,21 @@ public Face()
1112
_vertices = new Vertex[3];
1213
}
1314

14-
// Empty contructor that creates a new face with 3 vertices.
15+
// Contructor that creates a new face with 3 vertices.
1516
public Face(Vertex[] vertices)
1617
{
1718
_vertices = vertices;
1819
}
1920

21+
// Contructor that creates a new face with 3 vertices.
22+
public Face(Vertex a, Vertex b, Vertex c)
23+
{
24+
_vertices = new Vertex[3];
25+
_vertices[0] = a;
26+
_vertices[1] = b;
27+
_vertices[2] = c;
28+
}
29+
2030
// Returns the vertices of the face.
2131
public Vertex[] GetVertices()
2232
{
@@ -66,6 +76,34 @@ public Vector GetNormal()
6676
return Vector.CrossProduct(v1, v2) * -1;
6777
}
6878

79+
// Finds the center and radius of a circumcircle of this triangle.
80+
public Tuple<Vertex, double> GetCircumsphere()
81+
{
82+
// Find center.
83+
Vector v0 = (Vector)_vertices[1] - (Vector)_vertices[0];
84+
Vector v1 = (Vector)_vertices[2] - (Vector)_vertices[0];
85+
86+
Vector vx = Vector.CrossProduct(v0, v1);
87+
88+
Vector centerVector = (Vector.CrossProduct(vx, v0) * v1.SquareLength() + Vector.CrossProduct(v1, vx) * v0.SquareLength()) / (2 * vx.SquareLength());
89+
Vertex center = _vertices[0] + centerVector;
90+
91+
// Find radius.
92+
double radius = centerVector.Length();
93+
94+
return new Tuple<Vertex, double>(center, radius);
95+
}
96+
97+
// Finds and returns the edges of this triangle.
98+
public Edge[] GetEdges()
99+
{
100+
Edge[] edges = new Edge[3];
101+
edges[0] = new Edge(_vertices[0], _vertices[1]);
102+
edges[1] = new Edge(_vertices[1], _vertices[2]);
103+
edges[2] = new Edge(_vertices[2], _vertices[0]);
104+
return edges;
105+
}
106+
69107
// Returns a stringified version of the object.
70108
public override string ToString()
71109
{

src/ObjObject.cs

Lines changed: 119 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43

54
namespace RadiantMapToWavefrontObj
65
{
@@ -142,50 +141,142 @@ private static Vertex[] FindIntersections(ClippingPlane[] planes)
142141
private static Face[] CreateFaces(Vertex[] vertices, ClippingPlane[] planes)
143142
{
144143
List<Face> faces = new List<Face>();
145-
double centerOffset = 1e-4;
146144

147145
for (int i = 0; i < planes.Length; ++i)
148146
{
149-
// Create a center point, we need a tiny offset to make sure we don't mess up for perfectly symetrical shapes.
150-
double centerX = centerOffset;
151-
double centerY = centerOffset;
152-
double centerZ = centerOffset;
153-
154-
List<Vertex> verts = planes[i].FindVerticesInPlane(vertices).ToList();
147+
Vertex[] verts = planes[i].FindVerticesInPlane(vertices);
155148

156149
// Abort when there are no vertices anyway. Something went wrong...
157-
if (verts.Count <= 0)
150+
if (verts.Length < 3)
158151
return null;
159152

160-
foreach (Vertex v in verts)
153+
foreach (Face face in BowyerWatson(verts))
154+
{
155+
FixNormal(face, planes[i].Normal);
156+
faces.Add(face);
157+
}
158+
}
159+
160+
return faces.ToArray();
161+
}
162+
163+
// Apply Bowyer-Watson algorithm to triangulate all the points in a plane.
164+
// Pseudo code taken from related wikipedia page and provided on the side in comments.
165+
public static Face[] BowyerWatson(Vertex[] vertices)
166+
{
167+
List<Face> triangles = new List<Face>();
168+
169+
// Add super triangle to list.
170+
Face superTriangle = FindSuperTriangle(vertices);
171+
Vertex[] superVertices = superTriangle.GetVertices();
172+
triangles.Add(superTriangle);
173+
174+
// Add points.
175+
foreach (Vertex v in vertices) // for each point in pointList do
176+
{
177+
List<Face> badTriangles = new List<Face>(); // badTriangles := empty set
178+
foreach (Face triangle in triangles) // for each triangle in triangulation do
161179
{
162-
centerX += v.X;
163-
centerY += v.Y;
164-
centerZ += v.Z;
180+
if (InCircumsphere(v, triangle)) // if point is inside circumcircle of triangle
181+
badTriangles.Add(triangle); // add triangle to badTriangles
165182
}
166183

167-
Vertex center = new Vertex(centerX / verts.Count, centerY / verts.Count, centerZ / verts.Count);
168-
center.SetNormal(planes[i].Normal);
184+
List<Edge> polygon = new List<Edge>(); // polygon := empty set
169185

170-
// Calculate faces based on some hackish algorithm that seems to work so far. Might need replacement later.
171-
// Algorithm: 1. Find vertex closest to the center point.
172-
// 2. Sort the list of vertices based on the distance to the vertex found in 1.
173-
// 3. Add faces in the following manner: {0,1,2}, {1,2,3}, {2,3,4}, etc...
174-
// 4. Check if the face's normal is in the right direction, otherwise: invert it.
175-
if (verts.Count >= 3)
186+
foreach (Face triangle in badTriangles) // for each triangle in badTriangles do
176187
{
177-
verts.Sort((el1, el2) => center.Distance(el1).CompareTo(center.Distance(el2)));
178-
verts.Sort((el1, el2) => verts[0].Distance(el1).CompareTo(verts[0].Distance(el2)));
179-
for (int j = 0; j < verts.Count - 2; ++j)
188+
foreach (Edge edge in triangle.GetEdges()) // for each edge in triangle do
180189
{
181-
Face face = new Face(verts.GetRange(j, 3).ToArray());
182-
FixNormal(face, planes[i].Normal);
183-
faces.Add(face);
190+
bool shared = false;
191+
foreach (Face otherTriangle in badTriangles) // if edge is not shared by any other triangles in badTriangles
192+
{
193+
if (triangle == otherTriangle)
194+
continue;
195+
if (otherTriangle.GetEdges().Contains(edge) || otherTriangle.GetEdges().Contains(edge.GetInverse()))
196+
{
197+
shared = true;
198+
break;
199+
}
200+
}
201+
if (!shared)
202+
polygon.Add(edge); // add edge to polygon
184203
}
185204
}
205+
206+
foreach (Face triangle in badTriangles) // for each triangle in badTriangles do
207+
triangles.Remove(triangle); // remove triangle from triangulation
208+
209+
foreach (Edge e in polygon) // for each edge in polygon do
210+
triangles.Add(new Face(e.A, e.B, v)); // newTri := form a triangle from edge to point + add newTri to triangulation
186211
}
187212

188-
return faces.ToArray();
213+
List<Face> result = new List<Face>();
214+
215+
foreach (Face t in triangles) // for each triangle in triangulation
216+
{
217+
Vertex[] curVertices = t.GetVertices();
218+
if (!curVertices.Contains(superVertices[0]) // if triangle contains a vertex from original super-triangle
219+
&& !curVertices.Contains(superVertices[1])
220+
&& !curVertices.Contains(superVertices[2]))
221+
result.Add(t); // remove triangle from triangulation
222+
}
223+
224+
return result.ToArray(); // return triangulation
225+
}
226+
227+
// Finds the Bowyer-Watson super triangle of a set of vertices.
228+
private static Face FindSuperTriangle(Vertex[] vertices)
229+
{
230+
// Setup super triangle.
231+
double minX, maxX, minY, maxY, minZ, maxZ;
232+
233+
minX = minY = minZ = Double.MaxValue;
234+
maxX = maxY = maxZ = Double.MinValue;
235+
236+
foreach (Vertex v in vertices)
237+
{
238+
if (v.X < minX)
239+
minX = v.X;
240+
if (v.X > maxX)
241+
maxX = v.X;
242+
243+
if (v.Y < minY)
244+
minY = v.Y;
245+
if (v.Y > maxY)
246+
maxY = v.Y;
247+
248+
if (v.Z < minZ)
249+
minZ = v.Z;
250+
if (v.Z > maxZ)
251+
maxZ = v.Z;
252+
}
253+
254+
ClippingPlane plane = new ClippingPlane(vertices[0], vertices[1], vertices[2]);
255+
256+
Vertex a = new Vertex(minX, minY, minZ);
257+
Vertex b = new Vertex(maxX, maxY, maxZ);
258+
259+
Vector ab = (Vector)(b - a);
260+
a -= 10*ab;
261+
b += 10*ab;
262+
263+
Vector triBase = Vector.CrossProduct(ab, plane.Normal).Unit();
264+
265+
double length = ((Vector)(b - a)).Length();
266+
267+
Vertex c = a + triBase * length;
268+
Vertex d = a - triBase * length;
269+
270+
return new Face(b, c, d);
271+
}
272+
273+
// Checks if a point lies in the circumsphere of a face.
274+
private static bool InCircumsphere(Vertex point, Face face)
275+
{
276+
Tuple<Vertex, double> cs = face.GetCircumsphere();
277+
if (point.Distance(cs.Item1) < cs.Item2)
278+
return true;
279+
return false;
189280
}
190281

191282
// Fix normals of faces pointing in the wrong direction.

src/RadiantMapToWavefrontObj.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
<Compile Include="ArrayExtension.cs" />
109109
<Compile Include="Brush.cs" />
110110
<Compile Include="ClippingPlane.cs" />
111+
<Compile Include="Edge.cs" />
111112
<Compile Include="Face.cs" />
112113
<Compile Include="LineSegment.cs" />
113114
<Compile Include="ObjObject.cs" />

src/Vector.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public double Length()
3333
return Math.Sqrt(X*X+Y*Y+Z*Z);
3434
}
3535

36+
// Calculates the squared length of a vector.
37+
public double SquareLength()
38+
{
39+
return X*X + Y*Y + Z*Z;
40+
}
41+
3642
// Returns a unit version of this vector.
3743
public Vector Unit()
3844
{

src/Vertex.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,24 @@ public bool OnPlane(ClippingPlane plane)
5757
return new Vertex(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
5858
}
5959

60+
// Override + operator for vectors.
61+
public static Vertex operator +(Vertex a, Vector b)
62+
{
63+
return new Vertex(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
64+
}
65+
6066
// Override - operator.
6167
public static Vertex operator -(Vertex a, Vertex b)
6268
{
6369
return new Vertex(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
6470
}
6571

72+
// Override - operator for vectors.
73+
public static Vertex operator -(Vertex a, Vector b)
74+
{
75+
return new Vertex(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
76+
}
77+
6678
// Override * operator for two points.
6779
public static Vertex operator *(Vertex a, Vertex b)
6880
{

0 commit comments

Comments
 (0)