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