-
Notifications
You must be signed in to change notification settings - Fork 3
/
mQuickHull.cpp
428 lines (371 loc) · 13.2 KB
/
mQuickHull.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
#include "mQuickHull.h"
#include <maya/MGlobal.h>
#include <maya/MVectorArray.h>
#include <maya/MBoundingBox.h>
#include <iostream>
using namespace std;
#define HULL_TOLERANCE 1e-10
// Perpendicular Distance Above/Below Face
static double distanceOutsideFace(const MPoint &faceCenter, const MVector &faceNormal, const MPoint &pt) {
return (MVector(pt - faceCenter) * faceNormal);
}
// Add Triangle to List
static void addTriangle(const int p0, const int p1, const int p2, const MPointArray &points, MIntArray &triangleVertices, MVectorArray &triangleNormals) {
triangleVertices.append(p0);
triangleVertices.append(p1);
triangleVertices.append(p2);
MVector v01 = points[p1] - points[p0];
MVector v02 = points[p2] - points[p0];
triangleNormals.append((v01 ^ v02).normal());
}
// Remove Triangles from List (Copy Triangles Not Being Removed)
static void removeTriangles(const MIntArray &remove, MIntArray &triangleVertices, MVectorArray &triangleNormals) {
MIntArray vList;
MVectorArray nList;
for(unsigned t = 0; 3*t < triangleVertices.length(); t++) {
bool removeTri = false;
for(unsigned r = 0; r < remove.length(); r++) {
if(remove[r] == t) {
removeTri = true;
break;
}
}
if(removeTri == false) {
vList.append(triangleVertices[3*t+0]);
vList.append(triangleVertices[3*t+1]);
vList.append(triangleVertices[3*t+2]);
nList.append(triangleNormals[t]);
}
}
triangleVertices = vList;
triangleNormals = nList;
}
static bool triangleContainsEdge(const MIntArray &triangleVertices, int tri, int e0, int e1, int &other_vertex) {
int v0 = triangleVertices[3*tri+0];
int v1 = triangleVertices[3*tri+1];
int v2 = triangleVertices[3*tri+2];
if(e0 == v0) {
if(e1 == v1) {
other_vertex = v2;
return true;
} else if(e1 == v2) {
other_vertex = v1;
return true;
}
} else if(e0 == v1) {
if(e1 == v0) {
other_vertex = v2;
return true;
} else if(e1 == v2) {
other_vertex = v0;
return true;
}
} else if(e0 == v2) {
if(e1 == v0) {
other_vertex = v1;
return true;
} else if(e1 == v1) {
other_vertex = v0;
return true;
}
}
return false;
}
// Returns List of Triangle's Neighbour (Sharing 1 Edge with Triangle)
static MIntArray getTriangleNeighbours(const MIntArray &triangleVertices, int tri) {
tri *= 3;
int e0 = triangleVertices[tri+0];
int e1 = triangleVertices[tri+1];
int e2 = triangleVertices[tri+2];
MIntArray neighbours;
for(unsigned t = 0; t < triangleVertices.length(); t+=3) {
if(t != tri) {
int count = 0;
count += (e0 == triangleVertices[t+0]) || (e0 == triangleVertices[t+1]) || (e0 == triangleVertices[t+2]);
count += (e1 == triangleVertices[t+0]) || (e1 == triangleVertices[t+1]) || (e1 == triangleVertices[t+2]);
count += (e2 == triangleVertices[t+0]) || (e2 == triangleVertices[t+1]) || (e2 == triangleVertices[t+2]);
if(count == 2) {
neighbours.append(t / 3);
}
}
}
return neighbours;
}
// Determines The 'Ridge Edges' That Mark The Boundary of the Visible Face Set (To Be Replaced) & the Vertex from that edge being lost
static void getRidgeEdge(const MPointArray &points, const MIntArray &triangleVertices, const MVectorArray &triangleNormals, const MIntArray &visibleFaceSet, MIntArray &edges) {
MStatus status;
// Ridge Edges: Edge IDs That Are Used Exactly Once by visibleFaceSet
for(unsigned i = 0; i < visibleFaceSet.length(); i++) {
unsigned tri = 3*visibleFaceSet[i];
int e0 = triangleVertices[tri+0];
int e1 = triangleVertices[tri+1];
int e2 = triangleVertices[tri+2];
int count0 = 0, count1 = 0, count2 = 0;
int other_vertex0, other_vertex1, other_vertex2;
for(unsigned j = 0; j < visibleFaceSet.length(); j++) {
if(triangleContainsEdge(triangleVertices, visibleFaceSet[j], e0, e1, other_vertex0)) {
count0++;
}
if(triangleContainsEdge(triangleVertices, visibleFaceSet[j], e1, e2, other_vertex1)) {
count1++;
}
if(triangleContainsEdge(triangleVertices, visibleFaceSet[j], e2, e0, other_vertex2)) {
count2++;
}
}
if(count0 == 1) {
edges.append(e0);
edges.append(e1);
edges.append(other_vertex0);
}
if(count1 == 1) {
edges.append(e1);
edges.append(e2);
edges.append(other_vertex1);
}
if(count2 == 1) {
edges.append(e2);
edges.append(e0);
edges.append(other_vertex2);
}
}
}
// Recursively Builds List of Neighbouring Faces That All Have Specified Point Above It
static void getNeighbouringOutsideFaces(const MPointArray &points, const MIntArray &triangleVertices, const MVectorArray &triangleNormals, MIntArray &visibleFaceSet, const MPoint &pt, int tri) {
MPoint faceCenter = points[triangleVertices[3*tri]];
MVector faceNormal = triangleNormals[tri];
double len = distanceOutsideFace(faceCenter, faceNormal, pt);
if(len > HULL_TOLERANCE) {
for(unsigned f = 0; f < visibleFaceSet.length(); f++) {
if(visibleFaceSet[f] == tri) {
return;
}
}
visibleFaceSet.append(tri);
MIntArray neighbours = getTriangleNeighbours(triangleVertices, tri);
for(unsigned n = 0; n < neighbours.length(); n++) {
getNeighbouringOutsideFaces(points, triangleVertices, triangleNormals, visibleFaceSet, pt, neighbours[n]);
}
}
}
// Expands Hull to Include Point Furthest Above Any Hull Mesh Face
static MStatus expandHull(const MPointArray &points, MIntArray &unassignedPoints, MIntArray &triangleVertices, MVectorArray &triangleNormals) {
MStatus status;
// Determine Point Highest Above Any Face
int maxFace = -1;
int maxPoint = -1;
double maxDistance = HULL_TOLERANCE;
for(unsigned i = 0; i < unassignedPoints.length(); i++) {
unsigned p = unassignedPoints[i];
for(unsigned t = 0; 3*t < triangleVertices.length(); t++) {
MPoint faceCenter = points[triangleVertices[3*t]];
MVector faceNormal = triangleNormals[t];
double len = distanceOutsideFace(faceCenter, faceNormal, points[p]);
if(len > maxDistance) {
maxFace = t;
maxPoint = p;
maxDistance = len;
}
}
}
// All Points Inside Hull ?
if(maxPoint < 0) {
return MStatus::kFailure;
}
// Build Visible Face Set of Faces that Point is Above
MIntArray visibleFaceSet;
getNeighbouringOutsideFaces(points, triangleVertices, triangleNormals, visibleFaceSet, points[maxPoint], maxFace);
// Determine 'Ridge' Edges Around Visible Set
MIntArray edges;
getRidgeEdge(points, triangleVertices, triangleNormals, visibleFaceSet, edges);
// Remove Old Faces
removeTriangles(visibleFaceSet, triangleVertices, triangleNormals);
// Build New triangleVertices using Ridge Edges
for(unsigned r = 0; 3*r < edges.length(); r++) {
int p0 = edges[3*r];
int p1 = edges[3*r+1];
int oldp2 = edges[3*r+2];
int p2 = maxPoint;
MPoint pt0 = points[p0];
MPoint pt1 = points[p1];
MPoint pt2 = points[p2];
MVector shift = points[p2] - points[oldp2];
MVector vec01(pt1 - pt0);
MVector vec02(pt2 - pt0);
MVector vx = (vec01 ^ vec02).normal();
if(vx*shift > 0.0) {
addTriangle(p0, p1, p2, points, triangleVertices, triangleNormals);
} else {
addTriangle(p0, p2, p1, points, triangleVertices, triangleNormals);
}
}
// Assign Points Now Inside Mesh
for(unsigned i = 0; i < unassignedPoints.length(); ) {
unsigned p = unassignedPoints[i];
if(p == maxPoint) {
unassignedPoints.remove(i);
} else {
bool outside = false;
for(unsigned t = 0; 3*t < triangleVertices.length(); t++) {
MPoint faceCenter = points[triangleVertices[3*t]];
MVector faceNormal = triangleNormals[t];
if(distanceOutsideFace(faceCenter, faceNormal, points[p]) > HULL_TOLERANCE) {
outside = true;
break;
}
}
if(outside) {
i++;
} else {
unassignedPoints.remove(i);
}
}
}
return MStatus::kSuccess;
}
// Determine Point Cloud Bounding Box (inc. Low/High X/Y/Z Point Indexes)
static MBoundingBox rangePointCloud(const MPointArray &points, unsigned &loX, unsigned &hiX, unsigned &loY, unsigned &hiY, unsigned &loZ, unsigned &hiZ) {
MBoundingBox bbox;
bbox.expand(points[0]);
loX = hiX = loY = hiY = loZ = hiZ = 0;
for(unsigned p = 1; p < points.length(); p++) {
MPoint pt = points[p];
bbox.expand(pt);
loX = (pt.x < points[loX].x) ? p : loX;
hiX = (pt.x > points[hiX].x) ? p : hiX;
loY = (pt.y < points[loY].y) ? p : loY;
hiY = (pt.y > points[hiY].y) ? p : hiY;
loZ = (pt.z < points[loZ].z) ? p : loZ;
hiZ = (pt.z > points[hiZ].z) ? p : hiZ;
}
return bbox;
}
// Build Initial Simplex Hull (Tetrahedron) of Most Spread Out Points
static MStatus simplexHull(const MPointArray &points, MIntArray &unassignedPoints, MIntArray &triangleVertices, MVectorArray &triangleNormals) {
MStatus status;
unsigned p0, p1, p2, p3;
// Determine Bounding Box of Points
unsigned minX, maxX, minY, maxY, minZ, maxZ;
MBoundingBox pointRange = rangePointCloud(points, minX, maxX, minY, maxY, minZ, maxZ);
// Which Dimension has Greatest Spread? Set That As Simplex Vertices 0 & 1
if(pointRange.width() > pointRange.height()) {
if(pointRange.width() > pointRange.depth()) {
p0 = minX; p1 = maxX;
} else {
p0 = minZ; p1 = maxZ;
}
} else {
if(pointRange.height() > pointRange.depth()) {
p0 = minY; p1 = maxY;
} else {
p0 = minZ; p1 = maxZ;
}
}
// Check We Have A Decent Setup Line
if(MVector(points[p1] - points[p0]).length() < HULL_TOLERANCE) {
status = MStatus::kFailure;
status.perror("Insufficient Range of Points for Hull Creation");
return status;
}
// Find Vertex 2: Furthest from p0 -> p1 Line
MVector dir01(points[p1] - points[p0]);
dir01.normalize();
int p2candidate = -1;
double p2distance = HULL_TOLERANCE;
for(unsigned i = 0; i < points.length(); i++) {
if((i != p0) && (i != p1)) {
double u = dir01*MVector(points[i] - points[p0]);
double distance = MVector(points[i] - (points[p0]+u*dir01)).length();
if(distance > p2distance) {
p2candidate = i;
p2distance = distance;
}
}
}
if(p2candidate < 0) {
status = MStatus::kFailure;
status.perror("Insufficient Range of Points for Hull Creation (Colinear?)");
return status;
}
p2 = p2candidate;
// Find Vertex 3: Furthest from Plane of p0 -> p1 & p0 -> p2 Lines (via Cross Product to Normal)
MVector dir02(points[p2] - points[p0]);
dir02.normalize();
MVector dirx = (dir01 ^ dir02).normal();
int p3candidate = -1;
double p3distance = HULL_TOLERANCE;
for(unsigned i = 0; i < points.length(); i++) {
if((i != p0) && (i != p1) && (i != p2)) {
double distance = dirx*MVector(points[i] - points[p0]);
if(distance > p3distance) {
p3candidate = i;
p3distance = distance;
}
}
}
if(p3candidate < 0) {
status = MStatus::kFailure;
status.perror("Insufficient Range of Points for Hull Creation (Coplanar?)");
return status;
}
p3 = p3candidate;
// Setup Simplex Mesh
if(dirx * (points[p3] - points[p0]) < 0.0) {
addTriangle(p0, p1, p2, points, triangleVertices, triangleNormals);
addTriangle(p3, p1, p0, points, triangleVertices, triangleNormals);
addTriangle(p3, p2, p1, points, triangleVertices, triangleNormals);
addTriangle(p3, p0, p2, points, triangleVertices, triangleNormals);
} else {
addTriangle(p0, p2, p1, points, triangleVertices, triangleNormals);
addTriangle(p3, p0, p1, points, triangleVertices, triangleNormals);
addTriangle(p3, p1, p2, points, triangleVertices, triangleNormals);
addTriangle(p3, p2, p0, points, triangleVertices, triangleNormals);
}
// Assign Points Now Inside Mesh
for(unsigned i = 0; i < unassignedPoints.length(); ) {
unsigned p = unassignedPoints[i];
if((p == p0) || (p == p1) || (p == p2) || (p == p3)) {
unassignedPoints.remove(i);
} else {
bool outside = false;
for(unsigned t = 0; 3*t < triangleVertices.length(); t++) {
MPoint faceCenter = points[triangleVertices[3*t]];
MVector faceNormal = triangleNormals[t];
if(distanceOutsideFace(faceCenter, faceNormal, points[p]) > HULL_TOLERANCE) {
outside = true;
break;
}
}
if(outside) {
i++;
} else {
unassignedPoints.remove(i);
}
}
}
return MStatus::kSuccess;
}
// Builds Convex Hull to Wrap Points Cloud. Returns Triangle Mesh (VertexIDs: [Size = 3 * TriangleCount])
MStatus buildConvexHull(const MPointArray &points, MIntArray &triangleMesh) {
MStatus status;
if(points.length() < 4) {
status = MStatus::kFailure;
status.perror(MString("Insufficient Points for Hull Creation: ") + points.length());
return status;
}
// Unassigned - List of VertexIDs Not On Hull & Not Inside Hull
MIntArray unassignedPoints(points.length());
for(unsigned p = 0; p < unassignedPoints.length(); p++) {
unassignedPoints[p] = p;
}
// TriangleNormals - Triangle Normals [Size = TriangleCount]
MVectorArray triangleNormals;
// Build Simplex
status = simplexHull(points, unassignedPoints, triangleMesh, triangleNormals);
CHECK_MSTATUS_AND_RETURN_IT(status);
// Loop & Expand Hull Until All Points Inside (within Tolerance)
do {
status = expandHull(points, unassignedPoints, triangleMesh, triangleNormals);
} while(status);
return MStatus::kSuccess;
}