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