1 /*************************************************************************
2 * *
3 * Vega FEM Simulation Library Version 3.1 *
4 * *
5 * "distance field" library , Copyright (C) 2007 CMU, 2016 USC *
6 * All rights reserved. *
7 * *
8 * Code authors: Hongyi Xu, Jernej Barbic *
9 * http://www.jernejbarbic.com/code *
10 * *
11 * Research: Jernej Barbic, Hongyi Xu, Doug L. James *
12 * *
13 * Funding: National Science Foundation, Link Foundation, *
14 * Zumberge Research and Innovation Fund at USC *
15 * *
16 * This library is free software; you can redistribute it and/or *
17 * modify it under the terms of the BSD-style license that is *
18 * included with this library in the file LICENSE.txt *
19 * *
20 * This library is distributed in the hope that it will be useful, *
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file *
23 * LICENSE.TXT for more details. *
24 * *
25 *************************************************************************/
26
27 /*
28 This class computes a signed distance field from polygon soup geometry, as described in:
29
30 Hongyi Xu, Jernej Barbic:
31 Signed Distance Fields for Polygon Soup Meshes, Graphics Interface 2014, Montreal, Canada
32 */
33
34 #include <stdio.h>
35 #include "signedDistanceFieldFromPolygonSoup.h"
36 #include "marchingCubes.h"
37 using namespace::std;
38
SignedDistanceFieldFromPolygonSoup(ObjMesh * objMesh_,double expansionRatio_,bool useCubicBox_,Vec3d * bbmin_,Vec3d * bbmax_)39 SignedDistanceFieldFromPolygonSoup::SignedDistanceFieldFromPolygonSoup(ObjMesh * objMesh_, double expansionRatio_, bool useCubicBox_,
40 Vec3d * bbmin_, Vec3d * bbmax_):
41 objMesh(objMesh_), expansionRatio(expansionRatio_), useCubicBox(useCubicBox_)
42 {
43 if(bbmin_ != NULL)
44 bbmin = *bbmin_;
45 else
46 bbmin = Vec3d(0.0);
47
48 if(bbmax_ != NULL)
49 bbmax = *bbmax_;
50 else
51 bbmax = Vec3d(1.0);
52
53
54 if(bbmin_ == NULL || bbmax_ == NULL)
55 autoBoundingBox = true;
56 else
57 autoBoundingBox = false;
58 }
59
60 // construct TriangleWithCollisionInfoAndPseudoNormals for each triangle in the mesh
ConstructPseudoNormalComponents(ObjMesh * isoMesh,vector<TriangleWithCollisionInfoAndPseudoNormals * > * triangleList)61 bool SignedDistanceFieldFromPolygonSoup::ConstructPseudoNormalComponents(ObjMesh* isoMesh, vector<TriangleWithCollisionInfoAndPseudoNormals*>* triangleList)
62 {
63 printf("*************** Computing PseudoNormals *****************\n");
64 ObjMesh* pseudoNormalObjMesh = isoMesh;
65 int numZeroAreaFaces = pseudoNormalObjMesh->removeZeroAreaFaces();
66 printf("Encountered and removed %d faces with zero surface area.\n", numZeroAreaFaces);
67
68 //printf("here:%d %d\n", removedFaces[0].first, removedFaces[0].second);
69
70 pseudoNormalObjMesh->computePseudoNormals();
71 pseudoNormalObjMesh->computeEdgePseudoNormals();
72
73 Vec3d pseudoNormals[7];
74 for (unsigned int i=0; i<pseudoNormalObjMesh->getNumGroups(); ++i)
75 {
76 triangleList[i].clear();
77 for (unsigned int j=0; j<(pseudoNormalObjMesh->getGroupHandle(i))->getNumFaces(); ++j)
78 {
79 unsigned int index0 = pseudoNormalObjMesh->getVertexIndex(i, j, 0);
80 unsigned int index1 = pseudoNormalObjMesh->getVertexIndex(i, j, 1);
81 unsigned int index2 = pseudoNormalObjMesh->getVertexIndex(i, j, 2);
82
83 pseudoNormals[0] = pseudoNormalObjMesh->getPseudoNormal(index0);
84 pseudoNormals[1] = pseudoNormalObjMesh->getPseudoNormal(index1);
85 pseudoNormals[2] = pseudoNormalObjMesh->getPseudoNormal(index2);
86
87 if (pseudoNormalObjMesh->getEdgePseudoNormal(index0, index1, &pseudoNormals[3]) != 0)
88 {
89 printf("Error: encountered an edge without a pseudonormal. Degenerate face? Vertices: %d %d\n", index0, index1);
90 return false;
91 }
92
93 if (pseudoNormalObjMesh->getEdgePseudoNormal(index1, index2, &pseudoNormals[4]) != 0)
94 {
95 printf("Error: encountered an edge without a pseudonormal. Degenerate face? Vertices: %d %d\n", index1, index2);
96 return false;
97 }
98
99 if (pseudoNormalObjMesh->getEdgePseudoNormal(index2, index0, &pseudoNormals[5]) != 0)
100 {
101 printf("Error: encountered an edge without a pseudonormal. Degenerate face? Vertices: %d %d.\n", index2, index0);
102 return false;
103 }
104
105 // face pseudo normal
106 Vec3d p0 = pseudoNormalObjMesh->getPosition(index0);
107 Vec3d p1 = pseudoNormalObjMesh->getPosition(index1);
108 Vec3d p2 = pseudoNormalObjMesh->getPosition(index2);
109
110 pseudoNormals[6] = norm(cross(p1-p0,p2-p0));
111
112 for(int normali=0; normali < 7; normali++)
113 {
114 if (ObjMesh::isNaN(pseudoNormals[normali][0]) ||
115 ObjMesh::isNaN(pseudoNormals[normali][1]) ||
116 ObjMesh::isNaN(pseudoNormals[normali][2]))
117 {
118 printf("Error: pseudonormal nan encountered: %lf %lf %lf\n", pseudoNormals[normali][0], pseudoNormals[normali][1], pseudoNormals[normali][2]);
119 printf("Group: %d | Triangle: %d\n", i, j);
120 printf("vtx0: %d | vtx1: %d | vtx2: %d\n",index0, index1, index2);
121 return false;
122 }
123 }
124 TriangleWithCollisionInfoAndPseudoNormals * triangle = new TriangleWithCollisionInfoAndPseudoNormals(p0, p1, p2, pseudoNormals);
125 triangleList[i].push_back(triangle);
126 }
127 }
128
129 return true;
130 }
131
132 // check whether comp1 is strictly inside comp2 using bounding box
CheckInAndOutViaBoundingBox(vector<Vec3d> & bmin,vector<Vec3d> & bmax,int compIndex1,int compIndex2)133 bool SignedDistanceFieldFromPolygonSoup::CheckInAndOutViaBoundingBox(vector<Vec3d> & bmin, vector<Vec3d> & bmax, int compIndex1, int compIndex2)
134 {
135 if (bmin[compIndex1][0]>=bmin[compIndex2][0] && bmin[compIndex1][1]>=bmin[compIndex2][1] && bmin[compIndex1][2] >= bmin[compIndex2][2]&& bmax[compIndex1][0]<=bmax[compIndex2][0] && bmax[compIndex1][1]<=bmax[compIndex2][1] && bmax[compIndex1][2] <= bmax[compIndex2][2])
136 return true;
137
138 return false;
139 }
140
141 // check whether comp2's normal points toward opposite direction as comp1's position
142 // this is to prevent the case that comp1's bounding box is inside comp2's, but comp1 is outside comp2
CheckInAndOutViaNormalTest(vector<TriangleWithCollisionInfoAndPseudoNormals * > * triangleList,int compIndex1,int compIndex2)143 bool SignedDistanceFieldFromPolygonSoup::CheckInAndOutViaNormalTest(vector<TriangleWithCollisionInfoAndPseudoNormals*>* triangleList, int compIndex1, int compIndex2)
144 {
145 if (triangleList[compIndex1].size() == 0)
146 return true;
147
148 Vec3d testPoint = triangleList[compIndex1][0]->first();
149
150 int closestFeature = -1;
151 int closestTriangle = -1;
152 //int indexClosestTriangle = -1;
153 double closestDistance2 = DBL_MAX;
154
155 for (size_t i=0; i<triangleList[compIndex2].size(); ++i)
156 {
157 int closestLocalFeature = -1;
158 double d2 = triangleList[compIndex2][i]->distanceToPoint2(testPoint, &closestLocalFeature);
159 if( d2 < closestDistance2)
160 {
161 closestDistance2 = d2;
162 closestFeature = closestLocalFeature;
163 closestTriangle = (int)i;
164 }
165 }
166
167 Vec3d pseudoNormal = triangleList[compIndex2][closestTriangle]->pseudoNormal(closestFeature);
168 Vec3d pseudoClosestPosition = triangleList[compIndex2][closestTriangle]->pseudoClosestPosition(closestFeature);
169 Vec3d pointVec = testPoint - pseudoClosestPosition;
170
171 if(dot(pseudoNormal,pointVec) < 0)
172 // if (pseudoNormal[0]*pointVec[0]+pseudoNormal[1]*pointVec[1]+pseudoNormal[2]*pointVec[2]<0)
173 return true;
174
175 return false;
176 }
177
RemoveInteriorComponents(ObjMesh * isoMesh)178 ObjMesh * SignedDistanceFieldFromPolygonSoup::RemoveInteriorComponents(ObjMesh * isoMesh)
179 {
180 int numGroups = (int) isoMesh->getNumGroups();
181
182 if(numGroups == 1)
183 return isoMesh;
184
185 //building bounding box for each group;
186 vector<Vec3d> bmin(numGroups);
187 vector<Vec3d> bmax(numGroups);
188
189 vector<std::pair<int, int> >groupFaceList;
190
191 for (int i=0; i<numGroups; ++i)
192 {
193 const ObjMesh::Group * group = isoMesh->getGroupHandle(i);
194
195 bmin[i][0] = bmin[i][1] = bmin[i][2] = DBL_MAX;
196 bmax[i][0] = bmax[i][1] = bmax[i][2] = -DBL_MAX;
197
198 for (unsigned int j=0; j<group->getNumFaces(); ++j)
199 {
200 const ObjMesh::Face * face = group->getFaceHandle(j);
201
202 groupFaceList.push_back(std::make_pair(i, j));
203 for (unsigned int v=0; v<face->getNumVertices(); ++v)
204 {
205 const ObjMesh::Vertex * vertex = face->getVertexHandle(v);
206 Vec3d pos = isoMesh->getPosition(vertex->getPositionIndex());
207
208 if(pos[0]<bmin[i][0])
209 bmin[i][0] = pos[0];
210 else if(pos[0]>bmax[i][0])
211 bmax[i][0] = pos[0];
212
213 if(pos[1]<bmin[i][1])
214 bmin[i][1] = pos[1];
215 else if(pos[1]>bmax[i][1])
216 bmax[i][1] = pos[1];
217
218 if(pos[2]<bmin[i][2])
219 bmin[i][2] = pos[2];
220 else if(pos[2]>bmax[i][2])
221 bmax[i][2] = pos[2];
222 }
223 }
224 }
225
226 ObjMesh * cloneMesh = isoMesh->clone(groupFaceList, 0);
227
228 // judge the position relation
229 vector<double> bbVolume(numGroups);
230
231 for(int i = 0; i < numGroups; ++i)
232 {
233 bbVolume[i] = (bmax[i][0] - bmin[i][0]) * (bmax[i][1]- bmin[i][1]) * (bmax[i][2]- bmin[i][2]);
234 }
235
236 vector<int> componentOrder(numGroups);
237 vector<int> tag(numGroups);
238
239
240 for(int i=0; i<numGroups; i++)
241 tag[i] = numGroups;
242
243 // sort each group according to bounding box volume
244 // tag[groudp id] = rank in bounding box size (0: smallest)
245 for(int i=0; i<numGroups; ++i)
246 {
247 double minValue = DBL_MAX;
248 int minIndex = -1;
249 for(int j=0; j<numGroups; ++j)
250 {
251 if(tag[j]>i && bbVolume[j]<minValue)
252 {
253 minValue = bbVolume[j];
254 minIndex = j;
255 }
256 }
257 tag[minIndex] = i;
258 }
259
260 for(int i=0; i<numGroups; ++i)
261 {
262 componentOrder[tag[i]] = i;
263 }
264
265 vector<int> removedList;
266
267 //order the bbVolume
268 vector<TriangleWithCollisionInfoAndPseudoNormals*>* triangleList = new vector<TriangleWithCollisionInfoAndPseudoNormals*>[numGroups];
269 vector<std::pair<int, int> >removedFaces;
270 if (ConstructPseudoNormalComponents(isoMesh, triangleList) == false)
271 {
272 for(int i=0; i<numGroups; ++i)
273 {
274 for(size_t j = 0; j < triangleList[i].size(); j++)
275 delete triangleList[i][j];
276 }
277 delete [] triangleList;
278
279 delete isoMesh;
280 return NULL;
281 }
282
283 for(int i = 0; i < numGroups-1; ++i)
284 {
285 int compIndex = componentOrder[i];
286 for(int j = numGroups-1; j > i; --j)
287 {
288 int compIndex2 = componentOrder[j];
289 if(!CheckInAndOutViaBoundingBox(bmin, bmax, compIndex, compIndex2))
290 continue;
291 if(CheckInAndOutViaNormalTest(triangleList, compIndex, compIndex2))
292 {
293 printf("removing group %d from group %d\n", compIndex, compIndex2);
294 removedList.push_back(compIndex);
295 break;
296 }
297 }
298 }
299
300 printf("Removed %d interior groups: ", (int)removedList.size());
301
302 //Add back the zero area face
303 delete isoMesh;
304 isoMesh = cloneMesh;
305 // sort the list because we have to remove groups in a descending order
306 // otherwise ObjMesh::removeGroup will modify mesh group index order
307 std::sort(removedList.begin(), removedList.end());
308
309 for(int i=(int) removedList.size() - 1; i>=0; --i)
310 {
311 printf("%d ", removedList[i]);
312 isoMesh->removeGroup(removedList[i]);
313 }
314 printf("\n");
315
316 for(int i=0; i<numGroups; ++i)
317 {
318 for(size_t j = 0; j < triangleList[i].size(); j++)
319 delete triangleList[i][j];
320 }
321 delete [] triangleList;
322
323 return isoMesh;
324 }
325
ComputeIsosurface(DistanceFieldBase * distanceField,double sigma)326 ObjMesh * SignedDistanceFieldFromPolygonSoup::ComputeIsosurface(DistanceFieldBase * distanceField, double sigma)
327 {
328 printf("Computing the isosurface using marching cubes. Iso-level: %f .\n", sigma);
329 ObjMesh * marchingCubesResult = MarchingCubes::compute(distanceField, sigma);
330 printf("Finishing marching cubes...\n");
331
332 printf("Removing interior components...\n");
333 ObjMesh * outputMesh = marchingCubesResult->splitIntoConnectedComponents(0);
334 printf("Detected %d groups in the isoMesh.\n", (int)outputMesh->getNumGroups());
335 outputMesh = RemoveInteriorComponents(outputMesh);
336
337 delete(marchingCubesResult);
338
339 return outputMesh;
340 }
341
setBoundingBox(DistanceFieldBase * field,int resolutionX,int resolutionY,int resolutionZ)342 bool SignedDistanceFieldFromPolygonSoup::setBoundingBox(DistanceFieldBase* field, int resolutionX, int resolutionY, int resolutionZ)
343 {
344 if (autoBoundingBox)
345 {
346 if (useCubicBox)
347 printf("Forcing a cube-sized bounding box.\n");
348 field->setAutomaticBoundingBox(useCubicBox, expansionRatio);
349 field->computeBoundingBox(objMesh, resolutionX, resolutionY, resolutionZ);
350 }
351 else
352 {
353 if ((bbmin[0] >= bbmax[0]) || (bbmin[1] >= bbmax[1]) || (bbmin[2] >= bbmax[2]))
354 {
355 printf("Invalid bounding box.\n");
356 return false;
357 }
358 field->setBoundingBox(bbmin,bbmax);
359 }
360 return true;
361 }
362
ComputeDistanceField(int resolutionX,int resolutionY,int resolutionZ,double sigma,int subtractSigma,int numThreads,bool computeVoronoiDiagram,int maxTriCount,int maxDepth,int closestPointFlag,const char * precomputedUnsignedFieldFilename)363 DistanceField * SignedDistanceFieldFromPolygonSoup::ComputeDistanceField(int resolutionX, int resolutionY, int resolutionZ,
364 double sigma, int subtractSigma, int numThreads, bool computeVoronoiDiagram, int maxTriCount, int maxDepth,
365 int closestPointFlag, const char* precomputedUnsignedFieldFilename)
366 {
367 DistanceField * field = NULL;
368
369 if (!closestPointFlag)
370 {
371 if (numThreads == 0)
372 field = new DistanceField();
373 else
374 field = new DistanceFieldMT(numThreads);
375 }
376 else
377 field = new ClosestPointField();
378
379 field->enableVoronoiDiagramComputation(computeVoronoiDiagram);
380
381 bool ret = setBoundingBox(field, resolutionX, resolutionY, resolutionZ);
382 if (ret == false)
383 {
384 delete field;
385 return NULL;
386 }
387
388 int code = 0;
389
390 if (precomputedUnsignedFieldFilename == NULL)
391 {
392 printf("********* Computing unsigned distance field (%d x %d x %d) *************\n", resolutionX, resolutionY, resolutionZ); fflush(NULL);
393 code = field->computeUnsignedField(objMesh, resolutionX, resolutionY, resolutionZ, maxTriCount, maxDepth);
394
395 if(code !=0)
396 {
397 printf("Error computing unsigned distance field.\n");
398 delete field;
399 return NULL;
400 }
401 }
402 else
403 field->load(precomputedUnsignedFieldFilename);
404
405 ObjMesh * isoMesh = ComputeIsosurface(field, sigma);
406 printf("********* Recomputing signed distance field (%d x %d x %d) *************\n", resolutionX, resolutionY, resolutionZ); fflush(NULL);
407 field->offsetDistanceField(-sigma);
408 code = field->computeFloodFillSignedField(isoMesh, resolutionX, resolutionY, resolutionZ, maxTriCount, maxDepth);
409 delete isoMesh;
410
411 if (code != 0)
412 {
413 printf("Error recomputing signed distance field.\n");
414 delete field;
415 return NULL;
416 }
417
418 if (subtractSigma)
419 field->offsetDistanceField(sigma);
420
421 return field;
422 }
423
ComputeDistanceFieldNarrowBand(int resolutionX,int resolutionY,int resolutionZ,double bandwidth,double sigma,int subtractSigma,int maxTriCount,int maxDepth,const char * precomputedUnsignedFieldFilename)424 DistanceFieldNarrowBand * SignedDistanceFieldFromPolygonSoup::ComputeDistanceFieldNarrowBand(int resolutionX, int resolutionY, int resolutionZ, double bandwidth,
425 double sigma, int subtractSigma, int maxTriCount, int maxDepth, const char * precomputedUnsignedFieldFilename)
426 {
427 DistanceFieldNarrowBand * field = new DistanceFieldNarrowBand();
428
429 bool ret = setBoundingBox(field, resolutionX, resolutionY, resolutionZ);
430 if (ret == false)
431 {
432 delete field;
433 return NULL;
434 }
435
436 int code = 0;
437
438 if (sigma >= bandwidth)
439 {
440 printf("Error: sigma is not smaller than the offset.\n");
441 delete field;
442 return NULL;
443 }
444
445 if (precomputedUnsignedFieldFilename == NULL)
446 {
447 printf("********* Computing unsigned distance field (%d x %d x %d) *************\n", resolutionX, resolutionY, resolutionZ); fflush(NULL);
448 code = field->computeUnsignedField(objMesh, resolutionX, resolutionY, resolutionZ, (float) bandwidth, maxTriCount, maxDepth);
449
450 if (code !=0)
451 {
452 printf("Error computing unsigned distance field.\n");
453 delete field;
454 return NULL;
455 }
456
457 //printf("Computation completed. Performing sanity check...\n");
458 //field->sanityCheck();
459 }
460 else
461 {
462 code = field->load(precomputedUnsignedFieldFilename);
463 if(code != 0)
464 {
465 printf("Failed to load precomputed unsigned distance field.\n");
466 delete field;
467 return NULL;
468 }
469 }
470
471 ObjMesh * isoMesh = ComputeIsosurface(field, sigma);
472 printf("********* Computing signed distance field (%d x %d x %d) *************\n", resolutionX, resolutionY, resolutionZ); fflush(NULL);
473 field->offsetDistanceField(-sigma); // shift the computed signed distance field
474 code = field->computeInteriorSignedField(isoMesh, resolutionX, resolutionY, resolutionZ, (float) (bandwidth + sigma), maxTriCount, maxDepth);
475 delete isoMesh;
476
477 if (code != 0)
478 {
479 printf("Error recomputing signed distance field.\n");
480 delete field;
481 return NULL;
482 }
483
484 if (subtractSigma)
485 field->offsetDistanceField(sigma);
486
487 return field;
488 }
489
490