1 /* -------------------------------------------------------------------------- *
2  *                        Simbody(tm): SimTKmath                              *
3  * -------------------------------------------------------------------------- *
4  * This is part of the SimTK biosimulation toolkit originating from           *
5  * Simbios, the NIH National Center for Physics-Based Simulation of           *
6  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
7  * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody.  *
8  *                                                                            *
9  * Portions copyright (c) 2008-12 Stanford University and the Authors.        *
10  * Authors: Peter Eastman                                                     *
11  * Contributors:                                                              *
12  *                                                                            *
13  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
14  * not use this file except in compliance with the License. You may obtain a  *
15  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
16  *                                                                            *
17  * Unless required by applicable law or agreed to in writing, software        *
18  * distributed under the License is distributed on an "AS IS" BASIS,          *
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
20  * See the License for the specific language governing permissions and        *
21  * limitations under the License.                                             *
22  * -------------------------------------------------------------------------- */
23 
24 #include "SimTKmath.h"
25 
26 #include <set>
27 
28 using std::map;
29 using std::pair;
30 using std::set;
31 
32 namespace SimTK {
33 
34 //==============================================================================
35 //                        COLLISION DETECTION ALGORITHM
36 //==============================================================================
37 
~AlgorithmMap()38 CollisionDetectionAlgorithm::AlgorithmMap::~AlgorithmMap() {
39     // Clean up algorithms to satisfy valgrind
40     for (iterator i = begin(); i != end(); ++i)
41         delete i->second;
42 }
43 
44 CollisionDetectionAlgorithm::AlgorithmMap
45     CollisionDetectionAlgorithm::algorithmMap;
46 
registerStandardAlgorithms()47 static int registerStandardAlgorithms() {
48     CollisionDetectionAlgorithm::registerAlgorithm
49        (ContactGeometry::HalfSpace::classTypeId(),
50         ContactGeometry::Sphere::classTypeId(),
51         new CollisionDetectionAlgorithm::HalfSpaceSphere());
52     CollisionDetectionAlgorithm::registerAlgorithm
53        (ContactGeometry::Sphere::classTypeId(),
54         ContactGeometry::Sphere::classTypeId(),
55         new CollisionDetectionAlgorithm::SphereSphere());
56     CollisionDetectionAlgorithm::registerAlgorithm
57        (ContactGeometry::HalfSpace::classTypeId(),
58         ContactGeometry::Ellipsoid::classTypeId(),
59         new CollisionDetectionAlgorithm::HalfSpaceEllipsoid());
60     CollisionDetectionAlgorithm::registerAlgorithm
61        (ContactGeometry::Ellipsoid::classTypeId(),
62         ContactGeometry::Sphere::classTypeId(),
63         new CollisionDetectionAlgorithm::ConvexConvex());
64     CollisionDetectionAlgorithm::registerAlgorithm
65        (ContactGeometry::Ellipsoid::classTypeId(),
66         ContactGeometry::Ellipsoid::classTypeId(),
67         new CollisionDetectionAlgorithm::ConvexConvex());
68     CollisionDetectionAlgorithm::registerAlgorithm
69        (ContactGeometry::HalfSpace::classTypeId(),
70         ContactGeometry::TriangleMesh::classTypeId(),
71         new CollisionDetectionAlgorithm::HalfSpaceTriangleMesh());
72     CollisionDetectionAlgorithm::registerAlgorithm
73        (ContactGeometry::Sphere::classTypeId(),
74         ContactGeometry::TriangleMesh::classTypeId(),
75         new CollisionDetectionAlgorithm::SphereTriangleMesh());
76     CollisionDetectionAlgorithm::registerAlgorithm
77        (ContactGeometry::TriangleMesh::classTypeId(),
78         ContactGeometry::TriangleMesh::classTypeId(),
79         new CollisionDetectionAlgorithm::TriangleMeshTriangleMesh());
80     return 1;
81 }
82 
83 static int staticInitializer = registerStandardAlgorithms();
84 
registerAlgorithm(ContactGeometryTypeId type1,ContactGeometryTypeId type2,CollisionDetectionAlgorithm * algorithm)85 void CollisionDetectionAlgorithm::registerAlgorithm
86    (ContactGeometryTypeId type1, ContactGeometryTypeId type2,
87     CollisionDetectionAlgorithm* algorithm) {
88     algorithmMap[std::make_pair(type1,type2)] = algorithm;
89 }
90 
91 CollisionDetectionAlgorithm* CollisionDetectionAlgorithm::
getAlgorithm(ContactGeometryTypeId type1,ContactGeometryTypeId type2)92 getAlgorithm(ContactGeometryTypeId type1, ContactGeometryTypeId type2) {
93     AlgorithmMap::const_iterator
94         iter = algorithmMap.find(std::make_pair(type1, type2));
95     if (iter == algorithmMap.end())
96         return NULL;
97     return iter->second;
98 
99 }
100 
101 
102 
103 //==============================================================================
104 //                             HALF SPACE - SPHERE
105 //==============================================================================
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & transform1,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & transform2,Array_<Contact> & contacts) const106 void CollisionDetectionAlgorithm::HalfSpaceSphere::processObjects
107    (ContactSurfaceIndex index1, const ContactGeometry& object1,
108     const Transform& transform1,
109     ContactSurfaceIndex index2, const ContactGeometry& object2,
110     const Transform& transform2,
111     Array_<Contact>& contacts) const
112 {
113     const ContactGeometry::Sphere& sphere =
114         ContactGeometry::Sphere::getAs(object2);
115     // Location of the sphere in the half-space's coordinate frame
116     Vec3 location = (~transform1)*transform2.p();
117     Real r = sphere.getRadius();
118     Real depth = r+location[0];
119     if (depth > 0) {
120         // They are overlapping.
121 
122         Vec3 normal = transform1.R()*Vec3(-1, 0, 0);
123         Vec3 contactLocation =
124             transform1*Vec3(depth/2, location[1], location[2]);
125         contacts.push_back(PointContact(index1, index2, contactLocation,
126                                         normal, r, depth));
127     }
128 }
129 
130 
131 
132 //==============================================================================
133 //                              SPHERE - SPHERE
134 //==============================================================================
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & transform1,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & transform2,Array_<Contact> & contacts) const135 void CollisionDetectionAlgorithm::SphereSphere::processObjects
136    (ContactSurfaceIndex index1, const ContactGeometry& object1,
137     const Transform& transform1,
138     ContactSurfaceIndex index2, const ContactGeometry& object2,
139     const Transform& transform2,
140     Array_<Contact>& contacts) const
141 {
142     const ContactGeometry::Sphere& sphere1 =
143         ContactGeometry::Sphere::getAs(object1);
144     const ContactGeometry::Sphere& sphere2 =
145         ContactGeometry::Sphere::getAs(object2);
146     Vec3 delta = transform2.p()-transform1.p();
147     Real dist = delta.norm();
148     if (dist == 0)
149         return; // No sensible way to deal with this.
150     Real r1 = sphere1.getRadius();
151     Real r2 = sphere2.getRadius();
152     Real depth = r1+r2-dist;
153     if (depth > 0) {
154         // They are overlapping.
155 
156         Real radius = r1*r2/(r1+r2);
157         Vec3 normal = delta/dist;
158         Vec3 location = transform1.p()+(r1-depth/2)*normal;
159         contacts.push_back(PointContact(index1, index2, location, normal, radius, depth));
160     }
161 }
162 
163 
164 
165 //==============================================================================
166 //                            HALF SPACE - ELLIPSOID
167 //==============================================================================
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & transform1,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & transform2,Array_<Contact> & contacts) const168 void CollisionDetectionAlgorithm::HalfSpaceEllipsoid::processObjects
169    (ContactSurfaceIndex index1, const ContactGeometry& object1,
170     const Transform& transform1,
171     ContactSurfaceIndex index2, const ContactGeometry& object2,
172     const Transform& transform2,
173     Array_<Contact>& contacts) const
174 {
175     const ContactGeometry::Ellipsoid& ellipsoid =
176         ContactGeometry::Ellipsoid::getAs(object2);
177 
178     const Vec3& radii = ellipsoid.getRadii();
179     const Vec3 r2(radii[0]*radii[0], radii[1]*radii[1], radii[2]*radii[2]);
180     const Vec3 ri2(1/r2[0], 1/r2[1], 1/r2[2]);
181     // Transform giving half space frame in the ellipsoid's frame.
182     const Transform trans = (~transform1)*transform2;
183     Vec3 normal = ~trans.R()*Vec3(-1, 0, 0);
184     Vec3 location(normal[0]*r2[0], normal[1]*r2[1], normal[2]*r2[2]);
185     location /= -std::sqrt(  normal[0]*location[0]
186                            + normal[1]*location[1]
187                            + normal[2]*location[2]);
188     Real depth = (trans*location)[0];
189     if (depth > 0) {
190         // They are overlapping. We need to calculate the principal radii of
191         // curvature.
192 
193         Vec3 v1 = ~trans.R()*Vec3(0, 1, 0);
194         Vec3 v2 = ~trans.R()*Vec3(0, 0, 1);
195         Real dxx = v1[0]*v1[0]*ri2[0] + v1[1]*v1[1]*ri2[1] + v1[2]*v1[2]*ri2[2];
196         Real dyy = v2[0]*v2[0]*ri2[0] + v2[1]*v2[1]*ri2[1] + v2[2]*v2[2]*ri2[2];
197         Real dxy = v1[0]*v2[0]*ri2[0] + v1[1]*v2[1]*ri2[1] + v1[2]*v2[2]*ri2[2];
198         Vec<2, complex<Real> > eigenvalues;
199         PolynomialRootFinder::findRoots(Vec3(1, -dxx-dyy, dxx*dyy-dxy*dxy),
200                                         eigenvalues);
201         Vec3 contactNormal = transform2.R()*normal;
202         Vec3 contactPoint = transform2*(location+(depth/2)*normal);
203         contacts.push_back(PointContact(index1, index2, contactPoint,
204                                         contactNormal,
205                                         1/std::sqrt(eigenvalues[0].real()),
206                                         1/std::sqrt(eigenvalues[1].real()),
207                                         depth));
208     }
209 }
210 
211 
212 
213 //==============================================================================
214 //                          HALF SPACE - TRIANGLE MESH
215 //==============================================================================
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & X_GH,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & X_GM,Array_<Contact> & contacts) const216 void CollisionDetectionAlgorithm::HalfSpaceTriangleMesh::processObjects
217    (ContactSurfaceIndex index1, const ContactGeometry& object1,
218     const Transform& X_GH,
219     ContactSurfaceIndex index2, const ContactGeometry& object2,
220     const Transform& X_GM,
221     Array_<Contact>& contacts) const
222 {
223     const ContactGeometry::TriangleMesh& mesh =
224         ContactGeometry::TriangleMesh::getAs(object2);
225     // Transform giving mesh (S2) frame in the halfspace (S1) frame.
226     const Transform X_HM = (~X_GH)*X_GM;
227     set<int> insideFaces;
228     Vec3 axisDir = ~X_HM.R()*Vec3(-1, 0, 0);
229     Real xoffset = ~axisDir*(~X_HM*Vec3(0));
230     processBox(mesh, mesh.getOBBTreeNode(), X_HM, axisDir, xoffset, insideFaces);
231     if (insideFaces.size() > 0)
232         contacts.push_back(TriangleMeshContact(index1, index2, X_HM,
233                                                set<int>(), insideFaces));
234 }
235 
processBox(const ContactGeometry::TriangleMesh & mesh,const ContactGeometry::TriangleMesh::OBBTreeNode & node,const Transform & X_HM,const Vec3 & axisDir,Real xoffset,set<int> & insideFaces) const236 void CollisionDetectionAlgorithm::HalfSpaceTriangleMesh::processBox
237    (const ContactGeometry::TriangleMesh&              mesh,
238     const ContactGeometry::TriangleMesh::OBBTreeNode& node,
239     const Transform& X_HM, const Vec3& axisDir, Real xoffset,
240     set<int>& insideFaces) const
241 {   // First check against the node's bounding box.
242 
243     OrientedBoundingBox bounds = node.getBounds();
244     const Vec3 b = bounds.getSize() / 2;
245     Vec3 boxCenter = bounds.getTransform()*b;
246     Real radius = ~b*(~bounds.getTransform().R()*axisDir).abs();
247     Real dist = ~axisDir*boxCenter-xoffset;
248     if (dist > radius)
249         return;
250     if (dist < -radius) {
251         addAllTriangles(node, insideFaces);
252         return;
253     }
254 
255     // If it is not a leaf node, check its children.
256 
257     if (!node.isLeafNode()) {
258         processBox(mesh, node.getFirstChildNode(), X_HM, axisDir, xoffset, insideFaces);
259         processBox(mesh, node.getSecondChildNode(), X_HM, axisDir, xoffset, insideFaces);
260         return;
261     }
262 
263     // Check the triangles.
264 
265     const Array_<int>& triangles = node.getTriangles();
266     const Row3 xdir = X_HM.R().row(0);
267     const Real tx = X_HM.p()[0];
268     for (int i = 0; i < (int) triangles.size(); i++) {
269         if (xdir*mesh.getVertexPosition(mesh.getFaceVertex(triangles[i], 0))+tx > 0)
270             insideFaces.insert(triangles[i]);
271         else if (xdir*mesh.getVertexPosition(mesh.getFaceVertex(triangles[i], 1))+tx > 0)
272             insideFaces.insert(triangles[i]);
273         else if (xdir*mesh.getVertexPosition(mesh.getFaceVertex(triangles[i], 2))+tx > 0)
274             insideFaces.insert(triangles[i]);
275     }
276 }
277 
addAllTriangles(const ContactGeometry::TriangleMesh::OBBTreeNode & node,std::set<int> & insideFaces) const278 void CollisionDetectionAlgorithm::HalfSpaceTriangleMesh::addAllTriangles
279    (const ContactGeometry::TriangleMesh::OBBTreeNode& node,
280     std::set<int>& insideFaces) const
281 {
282     if (node.isLeafNode()) {
283         const Array_<int>& triangles = node.getTriangles();
284         for (int i = 0; i < (int) triangles.size(); i++)
285             insideFaces.insert(triangles[i]);
286     }
287     else {
288         addAllTriangles(node.getFirstChildNode(), insideFaces);
289         addAllTriangles(node.getSecondChildNode(), insideFaces);
290     }
291 }
292 
293 
294 
295 //==============================================================================
296 //                           SPHERE - TRIANGLE MESH
297 //==============================================================================
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & X_GS,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & X_GM,Array_<Contact> & contacts) const298 void CollisionDetectionAlgorithm::SphereTriangleMesh::processObjects
299    (ContactSurfaceIndex index1, const ContactGeometry& object1, // sphere
300     const Transform& X_GS,
301     ContactSurfaceIndex index2, const ContactGeometry& object2, // mesh
302     const Transform& X_GM,
303     Array_<Contact>& contacts) const
304 {
305     const ContactGeometry::Sphere& sphere =
306         ContactGeometry::Sphere::getAs(object1);
307     const ContactGeometry::TriangleMesh& mesh =
308         ContactGeometry::TriangleMesh::getAs(object2);
309 
310     const Transform X_SM = ~X_GS*X_GM;
311     // Want the sphere center measured and expressed in the mesh frame.
312     const Vec3 p_MC = (~X_SM).p();
313     set<int> insideFaces;
314     processBox(p_MC, sphere.getRadius()*sphere.getRadius(),
315                mesh, mesh.getOBBTreeNode(), insideFaces);
316     if (insideFaces.size() > 0)
317         contacts.push_back(TriangleMeshContact(index1, index2, X_SM,
318                                                set<int>(), insideFaces));
319 }
320 
processBox(const Vec3 & center,Real radius2,const ContactGeometry::TriangleMesh & mesh,const ContactGeometry::TriangleMesh::OBBTreeNode & node,set<int> & insideFaces) const321 void CollisionDetectionAlgorithm::SphereTriangleMesh::processBox
322    (const Vec3& center, Real radius2,
323     const ContactGeometry::TriangleMesh& mesh,
324     const ContactGeometry::TriangleMesh::OBBTreeNode& node,
325     set<int>& insideFaces) const
326 {   // First check against the node's bounding box.
327 
328     Vec3 nearestPoint = node.getBounds().findNearestPoint(center);
329     if ((nearestPoint-center).normSqr() > radius2)
330         return;
331 
332     // If it is not a leaf node, check its children.
333 
334     if (!node.isLeafNode()) {
335         processBox(center, radius2, mesh, node.getFirstChildNode(), insideFaces);
336         processBox(center, radius2, mesh, node.getSecondChildNode(), insideFaces);
337         return;
338     }
339 
340     // Check the triangles.
341 
342     const Array_<int>& triangles = node.getTriangles();
343     for (int i = 0; i < (int) triangles.size(); i++) {
344         Vec2 uv;
345         Vec3 nearestPoint = mesh.findNearestPointToFace
346                                             (center, triangles[i], uv);
347         if ((nearestPoint-center).normSqr() < radius2)
348             insideFaces.insert(triangles[i]);
349     }
350 }
351 
352 
353 
354 //==============================================================================
355 //                        TRIANGLE MESH - TRIANGLE MESH
356 //==============================================================================
357 void CollisionDetectionAlgorithm::TriangleMeshTriangleMesh::
processObjects(ContactSurfaceIndex index1,const ContactGeometry & object1,const Transform & X_GM1,ContactSurfaceIndex index2,const ContactGeometry & object2,const Transform & X_GM2,Array_<Contact> & contacts) const358 processObjects
359    (ContactSurfaceIndex index1, const ContactGeometry& object1,
360     const Transform& X_GM1,
361     ContactSurfaceIndex index2, const ContactGeometry& object2,
362     const Transform& X_GM2,
363     Array_<Contact>& contacts) const
364 {
365     const ContactGeometry::TriangleMesh& mesh1 =
366         ContactGeometry::TriangleMesh::getAs(object1);
367     const ContactGeometry::TriangleMesh& mesh2 =
368         ContactGeometry::TriangleMesh::getAs(object2);
369 
370     // Get mesh2's frame measured and expressed in mesh1's frame.
371     const Transform X_M1M2 = (~X_GM1)*X_GM2;
372     set<int> triangles1;
373     set<int> triangles2;
374     OrientedBoundingBox mesh2Bounds = X_M1M2*mesh2.getOBBTreeNode().getBounds();
375     processNodes(mesh1, mesh2, mesh1.getOBBTreeNode(), mesh2.getOBBTreeNode(),
376                  mesh2Bounds, X_M1M2, triangles1, triangles2);
377     if (triangles1.size() == 0)
378         return; // No intersection.
379 
380     // There was an intersection.  We now need to identify every triangle and vertex of each mesh that is inside the other mesh.
381 
382     findInsideTriangles(mesh1, mesh2, ~X_M1M2, triangles1);
383     findInsideTriangles(mesh2, mesh1,  X_M1M2, triangles2);
384     contacts.push_back(TriangleMeshContact(index1, index2, X_M1M2,
385                                            triangles1, triangles2));
386 }
387 
388 void CollisionDetectionAlgorithm::TriangleMeshTriangleMesh::
processNodes(const ContactGeometry::TriangleMesh & mesh1,const ContactGeometry::TriangleMesh & mesh2,const ContactGeometry::TriangleMesh::OBBTreeNode & node1,const ContactGeometry::TriangleMesh::OBBTreeNode & node2,const OrientedBoundingBox & node2Bounds,const Transform & X_M1M2,set<int> & triangles1,set<int> & triangles2) const389 processNodes
390    (const ContactGeometry::TriangleMesh&                mesh1,
391     const ContactGeometry::TriangleMesh&                mesh2,
392     const ContactGeometry::TriangleMesh::OBBTreeNode&   node1,
393     const ContactGeometry::TriangleMesh::OBBTreeNode&   node2,
394     const OrientedBoundingBox&                          node2Bounds,
395     const Transform&                                    X_M1M2,
396     set<int>&                                           triangles1,
397     set<int>&                                           triangles2) const
398 {   // See if the bounding boxes intersect.
399 
400     if (!node1.getBounds().intersectsBox(node2Bounds))
401         return;
402 
403     // If either node is not a leaf node, process the children recursively.
404 
405     if (!node1.isLeafNode()) {
406         if (!node2.isLeafNode()) {
407             OrientedBoundingBox firstChildBounds = X_M1M2*node2.getFirstChildNode().getBounds();
408             OrientedBoundingBox secondChildBounds = X_M1M2*node2.getSecondChildNode().getBounds();
409             processNodes(mesh1, mesh2, node1.getFirstChildNode(), node2.getFirstChildNode(), firstChildBounds, X_M1M2, triangles1, triangles2);
410             processNodes(mesh1, mesh2, node1.getFirstChildNode(), node2.getSecondChildNode(), secondChildBounds, X_M1M2, triangles1, triangles2);
411             processNodes(mesh1, mesh2, node1.getSecondChildNode(), node2.getFirstChildNode(), firstChildBounds, X_M1M2, triangles1, triangles2);
412             processNodes(mesh1, mesh2, node1.getSecondChildNode(), node2.getSecondChildNode(), secondChildBounds, X_M1M2, triangles1, triangles2);
413         }
414         else {
415             processNodes(mesh1, mesh2, node1.getFirstChildNode(), node2, node2Bounds, X_M1M2, triangles1, triangles2);
416             processNodes(mesh1, mesh2, node1.getSecondChildNode(), node2, node2Bounds, X_M1M2, triangles1, triangles2);
417         }
418         return;
419     }
420     else if (!node2.isLeafNode()) {
421         OrientedBoundingBox firstChildBounds = X_M1M2*node2.getFirstChildNode().getBounds();
422         OrientedBoundingBox secondChildBounds = X_M1M2*node2.getSecondChildNode().getBounds();
423         processNodes(mesh1, mesh2, node1, node2.getFirstChildNode(), firstChildBounds, X_M1M2, triangles1, triangles2);
424         processNodes(mesh1, mesh2, node1, node2.getSecondChildNode(), secondChildBounds, X_M1M2, triangles1, triangles2);
425         return;
426     }
427 
428     // These are both leaf nodes, so check triangles for intersections.
429 
430     const Array_<int>& node1triangles = node1.getTriangles();
431     const Array_<int>& node2triangles = node2.getTriangles();
432     for (int i = 0; i < (int) node2triangles.size(); i++) {
433         Vec3 a1 = X_M1M2*mesh2.getVertexPosition(mesh2.getFaceVertex(node2triangles[i], 0));
434         Vec3 a2 = X_M1M2*mesh2.getVertexPosition(mesh2.getFaceVertex(node2triangles[i], 1));
435         Vec3 a3 = X_M1M2*mesh2.getVertexPosition(mesh2.getFaceVertex(node2triangles[i], 2));
436         Geo::Triangle A(a1,a2,a3);
437         for (int j = 0; j < (int) node1triangles.size(); j++) {
438             const Vec3& b1 = mesh1.getVertexPosition(mesh1.getFaceVertex(node1triangles[j], 0));
439             const Vec3& b2 = mesh1.getVertexPosition(mesh1.getFaceVertex(node1triangles[j], 1));
440             const Vec3& b3 = mesh1.getVertexPosition(mesh1.getFaceVertex(node1triangles[j], 2));
441             Geo::Triangle B(b1,b2,b3);
442             if (A.overlapsTriangle(B)) {
443                 // The triangles intersect.
444                 triangles1.insert(node1triangles[j]);
445                 triangles2.insert(node2triangles[i]);
446             }
447         }
448     }
449 }
450 
451 void CollisionDetectionAlgorithm::TriangleMeshTriangleMesh::
findInsideTriangles(const ContactGeometry::TriangleMesh & mesh,const ContactGeometry::TriangleMesh & otherMesh,const Transform & X_OM,set<int> & triangles) const452 findInsideTriangles(const ContactGeometry::TriangleMesh&    mesh,       // M
453                     const ContactGeometry::TriangleMesh&    otherMesh,  // O
454                     const Transform&                        X_OM,
455                     set<int>&                               triangles) const
456 {   // Find which triangles are inside.
457     const int Unknown = UNKNOWN; // work around gcc bug
458     Array_<int> faceType(mesh.getNumFaces(), Unknown);
459     for (set<int>::iterator iter = triangles.begin();
460                             iter != triangles.end(); ++iter)
461         faceType[*iter] = BOUNDARY;
462 
463     for (int i = 0; i < (int) faceType.size(); i++) {
464         if (faceType[i] == UNKNOWN) {
465             // Trace a ray from its center to determine whether it is inside.
466             const Vec3     origin_O    = X_OM    * mesh.findCentroid(i);
467             const UnitVec3 direction_O = X_OM.R()* mesh.getFaceNormal(i);
468             Real distance;
469             int face;
470             Vec2 uv;
471             if (   otherMesh.intersectsRay(origin_O, direction_O, distance,
472                                            face, uv)
473                 && ~direction_O*otherMesh.getFaceNormal(face) > 0)
474             {
475                 faceType[i] = INSIDE;
476                 triangles.insert(i);
477             } else
478                 faceType[i] = OUTSIDE;
479 
480             // Recursively mark adjacent triangles.
481             tagFaces(mesh, faceType, triangles, i, 0);
482         }
483     }
484 }
485 
486 //TODO: the following method uses depth-first recursion to iterate through
487 //unmarked faces. For a large mesh this was observed to produce a stack
488 //overflow in OpenSim. Here we limit the recursion depth; after we get that
489 //deep we'll pop back out and do another expensive intersectsRay() test in
490 //the method above.
491 static const int MaxRecursionDepth = 500;
492 
493 void CollisionDetectionAlgorithm::TriangleMeshTriangleMesh::
tagFaces(const ContactGeometry::TriangleMesh & mesh,Array_<int> & faceType,set<int> & triangles,int index,int depth) const494 tagFaces(const ContactGeometry::TriangleMesh&   mesh,
495          Array_<int>&                           faceType,
496          set<int>&                              triangles,
497          int                                    index,
498          int                                    depth) const
499 {
500     for (int i = 0; i < 3; i++) {
501         int edge = mesh.getFaceEdge(index, i);
502         int face = (mesh.getEdgeFace(edge, 0) == index ? mesh.getEdgeFace(edge, 1) : mesh.getEdgeFace(edge, 0));
503         if (faceType[face] == UNKNOWN) {
504             faceType[face] = faceType[index];
505             if (faceType[index] > 0)
506                 triangles.insert(face);
507             if (depth < MaxRecursionDepth)
508                 tagFaces(mesh, faceType, triangles, face, depth+1);
509         }
510     }
511 }
512 
513 
514 
515 //==============================================================================
516 //                              CONVEX - CONVEX
517 //==============================================================================
518 // This is an implementation based on the Minkowski Portal Refinement method
519 // used by XenoCollide. See G. Snethen, �Xenocollide: Complex collision made
520 // simple,� in Game Programming Gems 7, 2008. MPR is used to obtain an initial
521 // guess for the contact location which is then refined to numerical precision
522 // by using the smooth representation of the colliding objects.
523 
524 Vec3 CollisionDetectionAlgorithm::ConvexConvex::
computeSupport(const ContactGeometry & obj1,const ContactGeometry & obj2,const Transform & transform,UnitVec3 direction)525 computeSupport(const ContactGeometry& obj1,
526                const ContactGeometry& obj2,
527                const Transform& transform, UnitVec3 direction) {
528     return   obj1.calcSupportPoint(direction)
529            - transform*obj2.calcSupportPoint(~transform.R()*-direction);
530 }
531 
processObjects(ContactSurfaceIndex index1,const ContactGeometry & obj1,const Transform & transform1,ContactSurfaceIndex index2,const ContactGeometry & obj2,const Transform & transform2,Array_<Contact> & contacts) const532 void CollisionDetectionAlgorithm::ConvexConvex::processObjects
533    (ContactSurfaceIndex index1, const ContactGeometry& obj1,
534     const Transform& transform1,
535     ContactSurfaceIndex index2, const ContactGeometry& obj2,
536     const Transform& transform2,
537     Array_<Contact>& contacts) const
538 {
539     Transform transform = ~transform1*transform2;
540 
541     // Compute a point that is known to be inside the Minkowski difference, and
542     // a ray directed from that point to the origin.
543 
544     Vec3 v0 = (  computeSupport(obj1, obj2, transform, UnitVec3(1, 0, 0))
545                + computeSupport(obj1, obj2, transform, UnitVec3(-1, 0, 0))) / 2;
546     if (v0 == 0.0) {
547         // This is a pathological case: the two objects are directly on top of
548         // each other with their centers at exactly the same place. Just
549         // return *some* vaguely plausible contact.
550 
551         Vec3 point1 = obj1.calcSupportPoint(UnitVec3(1, 0, 0));
552         Vec3 point2 = obj2.calcSupportPoint(~transform.R()*UnitVec3(-1, 0, 0));
553         addContact(index1, index2, obj1, obj2, transform1, transform2,
554                    transform, point1, point2, contacts);
555         return;
556     }
557 
558     // Select three points that define the initial portal.
559 
560     UnitVec3 dir1 = UnitVec3(-v0);
561     Vec3 v1 = computeSupport(obj1, obj2, transform, dir1);
562     if (~v1*dir1 <= 0.0)
563         return;
564     if (v1%v0 == 0.0) {
565         Vec3 point1 = obj1.calcSupportPoint(dir1);
566         Vec3 point2 = obj2.calcSupportPoint(~transform.R()*-dir1);
567         addContact(index1, index2, obj1, obj2, transform1, transform2,
568                    transform, point1, point2, contacts);
569         return;
570     }
571     UnitVec3 dir2 = UnitVec3(v1%v0);
572     Vec3 v2 = computeSupport(obj1, obj2, transform, dir2);
573     if (~v2*dir2 <= 0.0)
574         return;
575     UnitVec3 dir3 = UnitVec3((v1-v0)%(v2-v0));
576     if (~dir3*v0 > 0) {
577         UnitVec3 swap1 = dir1;
578         Vec3 swap2 = v1;
579         dir1 = dir2;
580         v1 = v2;
581         dir2 = swap1;
582         v2 = swap2;
583         dir3 = -dir3;
584     }
585     Vec3 v3 = computeSupport(obj1, obj2, transform, dir3);
586     if (~v3*dir3 <= 0.0)
587         return;
588     while (true) {
589         if (~v0*(v1%v3) < -1e-14) {
590             dir2 = dir3;
591             v2 = v3;
592         }
593         else if (~v0*(v3%v2) < -1e-14) {
594             dir1 = dir3;
595             v1 = v3;
596         }
597         else
598             break;
599         dir3 = UnitVec3((v1-v0)%(v2-v0));
600         v3 = computeSupport(obj1, obj2, transform, dir3);
601     }
602 
603     // We have a portal that the origin ray passes through. Now we need to
604     // refine it.
605 
606     while (true) {
607         UnitVec3 portalDir = UnitVec3((v2-v1)%(v3-v1));
608         if (~portalDir*v0 > 0)
609             portalDir = -portalDir;
610         Real dist1 = ~portalDir*v1;
611         Vec3 v4 = computeSupport(obj1, obj2, transform, portalDir);
612         Real dist4 = ~portalDir*v4;
613         if (dist1 >= 0.0) {
614             // The origin is inside the portal, so we have an intersection.
615             // Compute the barycentric coordinates of the origin in the outer
616             // face of the portal.
617 
618             Vec3 origin = v0+v0*(~portalDir*(v1-v0)/(~portalDir*v0));
619             Real totalArea = ((v2-v1)%(v3-v1)).norm();
620             Real area1 = ~portalDir*((v2-origin)%(v3-origin));
621             Real area2 = ~portalDir*((v3-origin)%(v1-origin));
622             Real u = area1/totalArea;
623             Real v = area2/totalArea;
624             Real w = 1-u-v;
625 
626             // Compute the contact properties.
627 
628             Vec3 point1 =  u*obj1.calcSupportPoint(dir1)
629                          + v*obj1.calcSupportPoint(dir2)
630                          + w*obj1.calcSupportPoint(dir3);
631             Vec3 point2 =   u*obj2.calcSupportPoint(~transform.R()*-dir1)
632                           + v*obj2.calcSupportPoint(~transform.R()*-dir2)
633                           + w*obj2.calcSupportPoint(~transform.R()*-dir3);
634             addContact(index1, index2, obj1, obj2,
635                        transform1, transform2, transform,
636                        point1, point2, contacts);
637             return;
638         }
639         if (dist4 <= 0.0)
640             return;
641         Vec3 cross = v4%v0;
642         if (~v1*cross > 0.0) {
643             if (~v2*cross > 0.0) {
644                 dir1 = portalDir;
645                 v1 = v4;
646             }
647             else {
648                 dir3 = portalDir;
649                 v3 = v4;
650             }
651         }
652         else {
653             if (~v3*cross > 0.0) {
654                 dir2 = portalDir;
655                 v2 = v4;
656             }
657             else {
658                 dir1 = portalDir;
659                 v1 = v4;
660             }
661         }
662     }
663 }
664 
addContact(ContactSurfaceIndex index1,ContactSurfaceIndex index2,const ContactGeometry & object1,const ContactGeometry & object2,const Transform & transform1,const Transform & transform2,const Transform & transform12,Vec3 point1,Vec3 point2,Array_<Contact> & contacts)665 void CollisionDetectionAlgorithm::ConvexConvex::addContact
666    (ContactSurfaceIndex index1, ContactSurfaceIndex index2,
667     const ContactGeometry& object1,
668     const ContactGeometry& object2,
669     const Transform& transform1, const Transform& transform2,
670     const Transform& transform12,
671     Vec3 point1, Vec3 point2, Array_<Contact>& contacts) {
672     // We have a rough estimate of the contact points. Use Newton iteration to
673     // refine them.
674 
675     Vec6 err = computeErrorVector(object1, object2, point1, point2, transform12);
676     while (err.norm() > Real(1e-12)) {
677         Mat66 J = computeJacobian(object1, object2, point1, point2, transform12);
678         FactorQTZ qtz;
679         qtz.factor(Matrix(J), Real(1e-6));
680         Vector deltaVec(6);
681         qtz.solve(Vector(err), deltaVec);
682         Vec6 delta(&deltaVec[0]);
683 
684         // Line search for safety in case starting guess bad.
685 
686         Real f = 2; // scale back factor
687         Vec3 point1old = point1, point2old = point2;
688         Vec6 errold = err;
689         do {
690             f /= 2;
691             point1 = point1old - f*delta.getSubVec<3>(0);
692             point2 = point2old - f*delta.getSubVec<3>(3);
693             err = computeErrorVector(object1, object2, point1, point2,
694                                      transform12);
695         } while (err.norm() > errold.norm());
696         if (f < 0.1) {
697             // We're clearly outside the region where Newton iteration is going
698             // to work properly. Just project the points onto the surfaces and
699             // then exit.
700 
701             bool inside;
702             UnitVec3 normal;
703             point1 = object1.findNearestPoint(point1, inside, normal);
704             point2 = object2.findNearestPoint(point2, inside, normal);
705             break;
706         }
707     }
708 
709     // Compute the curvature of the two surfaces.
710 
711     Vec2 curvature1, curvature2;
712     Rotation orientation1, orientation2;
713     object1.calcCurvature(point1, curvature1, orientation1);
714     object2.calcCurvature(point2, curvature2, orientation2);
715     Vec2 curvature;
716     UnitVec3 maxDir2(transform12.R()*orientation2(0));
717     ContactGeometry::combineParaboloids(orientation1, curvature1,
718                                         maxDir2, curvature2, curvature);
719 
720     // Record the contact.
721 
722     Vec3 p1 = transform1*point1;
723     Vec3 p2 = transform2*point2;
724     Vec3 position = (p1+p2)/2;
725     UnitVec3 normal(p1-p2);
726     contacts.push_back(PointContact(index1, index2, position, normal,
727                                     1/curvature[0], 1/curvature[1],
728                                     (p1-p2).norm()));
729 }
730 
computeErrorVector(const ContactGeometry & object1,const ContactGeometry & object2,Vec3 pos1,Vec3 pos2,const Transform & transform12)731 Vec6 CollisionDetectionAlgorithm::ConvexConvex::computeErrorVector
732    (const ContactGeometry& object1,
733     const ContactGeometry& object2,
734     Vec3 pos1, Vec3 pos2, const Transform& transform12) {
735     // Compute the function value and normal vector for each object.
736 
737     const Function& f1 = object1.getImplicitFunction();
738     const Function& f2 = object2.getImplicitFunction();
739     Vector x(3);
740     Array_<int> components(1);
741     Vec3 grad1, grad2;
742     Vec3::updAs(&x[0]) = pos1;
743     for (int i = 0; i < 3; i++) {
744         components[0] = i;
745         grad1[i] = f1.calcDerivative(components, x);
746     }
747     Real error1 = f1.calcValue(x);
748     Vec3::updAs(&x[0]) = pos2;
749     for (int i = 0; i < 3; i++) {
750         components[0] = i;
751         grad2[i] = f2.calcDerivative(components, x);
752     }
753     Real error2 = f2.calcValue(x);
754 
755     // Construct a coordinate frame for each object.
756 
757     UnitVec3 n1(-grad1);
758     UnitVec3 n2(-transform12.R()*grad2);
759     UnitVec3 u1(fabs(n1[0]) > 0.5 ? n1%Vec3(0, 1, 0) : n1%Vec3(1, 0, 0));
760     UnitVec3 u2(fabs(n2[0]) > 0.5 ? n2%Vec3(0, 1, 0) : n2%Vec3(1, 0, 0));
761     Vec3 v1 = n1%u1; // Already a unit vector, so we don't need to normalize it.
762     Vec3 v2 = n2%u2;
763 
764     // Compute the error vector. The components indicate, in order, that n1
765     // must be perpendicular to both tangents of object 2, that the separation
766     // vector should be zero or perpendicular to the tangents of object 1, and
767     // that both points should be on their respective surfaces.
768 
769     Vec3 delta = pos1-transform12*pos2;
770     return Vec6(~n1*u2, ~n1*v2, ~delta*u1, ~delta*v1, error1, error2);
771 }
772 
computeJacobian(const ContactGeometry & object1,const ContactGeometry & object2,Vec3 pos1,Vec3 pos2,const Transform & transform12)773 Mat66 CollisionDetectionAlgorithm::ConvexConvex::computeJacobian
774    (const ContactGeometry& object1,
775     const ContactGeometry& object2,
776     Vec3 pos1, Vec3 pos2, const Transform& transform12) {
777     Real dt = Real(1e-7);
778     Vec6 err0 = computeErrorVector(object1, object2, pos1, pos2, transform12);
779     Vec3 d1 = dt*Vec3(1, 0, 0);
780     Vec3 d2 = dt*Vec3(0, 1, 0);
781     Vec3 d3 = dt*Vec3(0, 0, 1);
782     Vec6 err1 = computeErrorVector(object1, object2, pos1+d1, pos2, transform12)
783                 - err0;
784     Vec6 err2 = computeErrorVector(object1, object2, pos1+d2, pos2, transform12)
785                 - err0;
786     Vec6 err3 = computeErrorVector(object1, object2, pos1+d3, pos2, transform12)
787                 - err0;
788     Vec6 err4 = computeErrorVector(object1, object2, pos1, pos2+d1, transform12)
789                 - err0;
790     Vec6 err5 = computeErrorVector(object1, object2, pos1, pos2+d2, transform12)
791                 - err0;
792     Vec6 err6 = computeErrorVector(object1, object2, pos1, pos2+d3, transform12)
793                 - err0;
794     return Mat66(err1, err2, err3, err4, err5, err6) / dt;
795 }
796 
797 } // namespace SimTK
798 
799