iMSTK
Interactive Medical Simulation Toolkit
imstkCollisionUtils.h
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #pragma once
8 
9 #include "imstkMath.h"
10 #include "imstkTypes.h"
11 
12 #include <algorithm>
13 #include <array>
14 
15 namespace imstk
16 {
17 namespace CollisionUtils
18 {
25 inline bool
26 isIntersect(const double a, const double b, const double c, const double d)
27 {
28  return ((a <= d && a >= c) || (c <= b && c >= a)) ? true : false;
29 }
30 
34 inline bool
35 testAabbToAabb(const double min1_x, const double max1_x,
36  const double min1_y, const double max1_y,
37  const double min1_z, const double max1_z,
38  const double min2_x, const double max2_x,
39  const double min2_y, const double max2_y,
40  const double min2_z, const double max2_z)
41 {
42  return (isIntersect(min1_x, max1_x, min2_x, max2_x)
43  && isIntersect(min1_y, max1_y, min2_y, max2_y)
44  && isIntersect(min1_z, max1_z, min2_z, max2_z));
45 }
46 
50 inline bool
51 testPointToTriAabb(const double x1, const double y1, const double z1,
52  const double x2, const double y2, const double z2,
53  const double x3, const double y3, const double z3,
54  const double x4, const double y4, const double z4,
55  const double prox1, const double prox2)
56 {
57  const auto min_x = std::min({ x2, x3, x4 });
58  const auto max_x = std::max({ x2, x3, x4 });
59  const auto min_y = std::min({ y2, y3, y4 });
60  const auto max_y = std::max({ y2, y3, y4 });
61  const auto min_z = std::min({ z2, z3, z4 });
62  const auto max_z = std::max({ z2, z3, z4 });
63 
64  return testAabbToAabb(x1 - prox1, x1 + prox1, y1 - prox1, y1 + prox1,
65  z1 - prox1, z1 + prox1, min_x - prox2, max_x + prox2,
66  min_y - prox2, max_y + prox2, min_z - prox2, max_z + prox2);
67 }
68 
78 bool testLineToLineAabb(const double x1, const double y1, const double z1,
79  const double x2, const double y2, const double z2,
80  const double x3, const double y3, const double z3,
81  const double x4, const double y4, const double z4,
82  const double prox1 = VERY_SMALL_EPSILON_D, const double prox2 = VERY_SMALL_EPSILON_D);
83 
93 inline bool
94 testLineToLineAabb(const Vec3d& p1A, const Vec3d& p1B,
95  const Vec3d& p2A, const Vec3d& p2B,
96  const double prox1 = VERY_SMALL_EPSILON_D, const double prox2 = VERY_SMALL_EPSILON_D)
97 {
98  const double* p1Aptr = &p1A[0];
99  const double* p1Bptr = &p1B[0];
100  const double* p2Aptr = &p2A[0];
101  const double* p2Bptr = &p2B[0];
102  return testLineToLineAabb(p1Aptr[0], p1Aptr[1], p1Aptr[2],
103  p1Bptr[0], p1Bptr[1], p1Bptr[2],
104  p2Aptr[0], p2Aptr[1], p2Aptr[2],
105  p2Bptr[0], p2Bptr[1], p2Bptr[2],
106  prox1, prox2);
107 }
108 
117 inline bool
118 testOBBToPoint(
119  const Vec3d& cubePos, const Mat3d& rot, const Vec3d extents,
120  const Vec3d& pt)
121 {
122  // We assume rot is an orthonormal rotation matrix (no shear or scale/normalized)
123  // We then take the diff from the center of the cube and project onto each axes
124  const Vec3d diff = (pt - cubePos);
125  const Vec3d proj = rot.transpose() * diff; // dot product on each axes
126 
127  return
128  (std::abs(proj[0]) < extents[0])
129  && (std::abs(proj[1]) < extents[1])
130  && (std::abs(proj[2]) < extents[2]);
131 }
132 
137 inline bool
138 testOBBToPoint(
139  const Vec3d& cubePos, const Mat3d& rot, const Vec3d extents,
140  const Vec3d& pt,
141  Vec3d& ptContactNormal, Vec3d& cubeContactPt, double& penetrationDepth)
142 {
143  const Vec3d diff = (pt - cubePos);
144  const Vec3d proj = rot.transpose() * diff; // dot product on each axes
145 
146  bool inside[3] =
147  {
148  (std::abs(proj[0]) < extents[0]),
149  (std::abs(proj[1]) < extents[1]),
150  (std::abs(proj[2]) < extents[2])
151  };
152  bool isInsideCube = inside[0] && inside[1] && inside[2];
153 
154  if (isInsideCube)
155  {
156  // If inside, find closest face, use that distance
157  penetrationDepth = std::numeric_limits<double>::max();
158  for (int i = 0; i < 3; i++)
159  {
160  double dist = proj[i];
161  const Vec3d& axes = rot.col(i);
162 
163  if (dist < extents[i] && dist > 0.0)
164  {
165  const double unsignedDistToSide = (extents[i] - dist);
166  if (unsignedDistToSide < penetrationDepth)
167  {
168  cubeContactPt = pt + unsignedDistToSide * axes;
169  penetrationDepth = unsignedDistToSide;
170  ptContactNormal = axes;
171  }
172  }
173  else if (dist > -extents[i] && dist < 0.0)
174  {
175  const double unsignedDistToSide = (extents[i] + dist);
176  if (unsignedDistToSide < penetrationDepth)
177  {
178  cubeContactPt = pt - unsignedDistToSide * axes;
179  penetrationDepth = unsignedDistToSide;
180  ptContactNormal = -axes;
181  }
182  }
183  }
184  }
185  else
186  {
187  // If outside we need to also consider diagonal distance to corners and edges
188  // Compute nearest point
189  cubeContactPt = cubePos;
190  ptContactNormal = Vec3d::Zero();
191  for (int i = 0; i < 3; i++)
192  {
193  double dist = proj[i];
194  const Vec3d& axes = rot.col(i);
195 
196  // If distance farther than the box extents, clamp to the box
197  if (dist >= extents[i])
198  {
199  cubeContactPt += extents[i] * axes;
200  ptContactNormal += axes;
201  }
202  else if (dist <= -extents[i])
203  {
204  cubeContactPt -= extents[i] * axes;
205  ptContactNormal -= axes;
206  }
207  }
208  ptContactNormal.normalize();
209  }
210 
211  return isInsideCube;
212 }
213 
218 inline bool
219 testPlaneToSphere(
220  const Vec3d& planePt, const Vec3d& planeNormal,
221  const Vec3d& spherePos, const double r)
222 {
223  return (spherePos - planePt).dot(planeNormal) < r;
224 }
225 
230 inline bool
231 testPlaneToSphere(
232  const Vec3d& planePt, const Vec3d& planeNormal,
233  const Vec3d& spherePos, const double r,
234  Vec3d& planeContactPt, Vec3d& planeContactNormal,
235  Vec3d& sphereContactPt, Vec3d& sphereContactNormal, double& penetrationDepth)
236 {
237  const double d = (spherePos - planePt).dot(planeNormal);
238 
239  planeContactNormal = -planeNormal;
240  sphereContactNormal = planeNormal;
241  planeContactPt = spherePos - d * planeNormal;
242  sphereContactPt = spherePos - r * planeNormal;
243 
244  penetrationDepth = r - d;
245  return d < r;
246 }
247 
252 inline bool
253 testBidirectionalPlaneToSphere(
254  const Vec3d& planePt, const Vec3d& planeNormal,
255  const Vec3d& spherePos, const double r,
256  Vec3d& planeContactPt, Vec3d& planeContactNormal,
257  Vec3d& sphereContactPt, Vec3d& sphereContactNormal, double& pentrationDepth)
258 {
259  const double d = (spherePos - planePt).dot(planeNormal);
260 
261  planeContactPt = spherePos - d * planeNormal;
262 
263  if (d < 0.0)
264  {
265  planeContactNormal = planeNormal;
266  sphereContactNormal = -planeNormal;
267  sphereContactPt = spherePos + r * planeNormal;
268  }
269  else
270  {
271  planeContactNormal = -planeNormal;
272  sphereContactNormal = planeNormal;
273  sphereContactPt = spherePos - r * planeNormal;
274  }
275 
276  pentrationDepth = r - std::abs(d);
277  return pentrationDepth > 0.0;
278 }
279 
284 inline bool
285 testSphereToSphere(
286  const Vec3d& sphereAPos, const double rA,
287  const Vec3d& sphereBPos, const double rB)
288 {
289  const double rSum = rA + rB;
290  return (sphereBPos - sphereAPos).squaredNorm() < (rSum * rSum);
291 }
292 
297 inline bool
298 testSphereToSphere(
299  const Vec3d& sphereAPos, const double rA,
300  const Vec3d& sphereBPos, const double rB,
301  Vec3d& sphereAContactPt, Vec3d& sphereAContactNormal,
302  Vec3d& sphereBContactPt, Vec3d& sphereBContactNormal,
303  double& depth)
304 {
305  Vec3d dirAtoB = sphereBPos - sphereAPos;
306 
307  const double d = dirAtoB.norm();
308  dirAtoB = dirAtoB / d;
309 
310  sphereAContactPt = sphereAPos + dirAtoB * rA;
311  sphereAContactNormal = -dirAtoB;
312  sphereBContactPt = sphereBPos - dirAtoB * rB;
313  sphereBContactNormal = dirAtoB;
314  depth = (rA + rB) - d;
315 
316  return depth > 0.0;
317 }
318 
323 inline bool
324 testSphereToCylinder(
325  const Vec3d& spherePos, const double rSphere,
326  const Vec3d& cylinderPos, const Vec3d& cylinderAxis, const double rCylinder, const double cylinderLength,
327  Vec3d& sphereContactPt, Vec3d& sphereContactNormal,
328  Vec3d& cylinderContactPt, Vec3d& cylinderContactNormal,
329  double& depth)
330 {
331  const double cylHalfLength = cylinderLength * 0.5;
332 
333  // Compute distance
334  const Vec3d cylToSphere = (spherePos - cylinderPos);
335  const Vec3d n = cylinderAxis;
336 
337  // Get normal distance (along axes)
338  const double distN = n.dot(cylToSphere);
339  const Vec3d distNVec = distN * n;
340 
341  // Get perp distance (orthogonal to axes)
342  const Vec3d distPerpVec = cylToSphere - distNVec; // Remove N component to get perp
343  const double distPerp = distPerpVec.norm();
344  const Vec3d perp = distPerpVec.normalized();
345 
346  // If the center of the sphere is within the length of the cylinder
347  if (std::abs(distN) < cylHalfLength)
348  {
349  // ______
350  // | |_
351  // | (__)
352  // | |
353  //
354 
355  sphereContactPt = spherePos - perp * rSphere;
356  cylinderContactPt = cylinderPos + distNVec + perp * rCylinder;
357 
358  sphereContactNormal = perp;
359  cylinderContactNormal = -perp;
360  depth = (rSphere + rCylinder) - distPerp;
361 
362  return depth < 0.0;
363  }
364  else
365  {
366  // \todo Does not return closest points, returns no points when no collision
367  // slight math change needed (also add SDF function to cylinder)
368  // __ __
369  // _(__)_ ____(__)
370  // | | | |
371  // | | | |
372  //
373 
374  // If any portion of the sphere is within the length of the cylinder
375  if (std::abs(distN) < (cylHalfLength + rSphere))
376  {
377  // If the center of the sphere is within the circle of the caps
378  // __
379  // _(__)_
380  // | |
381  // | |
382  //
383  if (distPerp < rCylinder)
384  {
385  sphereContactPt = spherePos - n * rSphere;
386  cylinderContactPt = cylinderPos + n * cylHalfLength + distPerpVec;
387 
388  sphereContactNormal = n;
389  cylinderContactNormal = -n;
390  depth = (rSphere + cylHalfLength) - std::abs(distN);
391 
392  return true;
393  }
394  // Finally the sphere vs circular cap edge/rim
395  // __
396  // ____(__)
397  // | |
398  // | |
399  //
400  else if (distPerp < (rCylinder + rSphere))
401  {
402  cylinderContactPt = cylinderPos + n * cylHalfLength + perp * rCylinder;
403  const Vec3d diagDiff = spherePos - cylinderContactPt;
404  const double diagDist = diagDiff.norm();
405 
406  sphereContactNormal = diagDiff / diagDist;
407  cylinderContactNormal = -sphereContactNormal;
408 
409  sphereContactPt = spherePos + cylinderContactNormal * rSphere;
410  depth = rSphere - diagDist;
411  return true;
412  }
413  }
414  }
415  return false;
416 }
417 
422 inline bool
423 testCapsuleToPoint(
424  const Vec3d& capsulePos, const Vec3d& capsuleAxis, const double capsuleLength, const double rCapsule,
425  const Vec3d& point)
426 {
427  // Two lines points
428  const Vec3d a = capsulePos + 0.5 * capsuleAxis * capsuleLength;
429  const Vec3d b = 2.0 * capsulePos - a;
430 
431  const Vec3d pa = point - a;
432  const Vec3d ba = b - a;
433  const double h = std::min(std::max(pa.dot(ba) / ba.dot(ba), 0.0), 1.0);
434  const double signedDist = ((pa - ba * h).norm() - rCapsule);
435  return signedDist < 0.0;
436 }
437 
442 inline bool
443 testCapsuleToPoint(
444  const Vec3d& capsulePos, const Vec3d& capsuleAxis, const double capsuleLength, const double rCapsule,
445  const Vec3d& point,
446  Vec3d& capsuleContactPt, Vec3d& capsuleContactNormal, Vec3d& pointContactNormal, double& depth)
447 {
448  // Get position of end points of the capsule
449  // \todo Fix this issue of extra computation in future
450  const Vec3d mid = capsulePos;
451  const Vec3d p1 = mid + 0.5 * capsuleAxis * capsuleLength;
452  const Vec3d p0 = 2.0 * mid - p1;
453  const Vec3d pDiff = p1 - p0;
454  const double pDiffSqrLength = pDiff.dot(pDiff);
455  const double pDotp0 = pDiff.dot(p0);
456 
457  // First, check collision with bounding sphere
458  if ((mid - point).norm() > (rCapsule + capsuleLength * 0.5))
459  {
460  return false;
461  }
462 
463  // Do the actual check
464  const double alpha = (point.dot(pDiff) - pDotp0) / pDiffSqrLength;
465  Vec3d closestPoint;
466  if (alpha > 1.0)
467  {
468  closestPoint = p1;
469  }
470  else if (alpha < 0.0)
471  {
472  closestPoint = p0;
473  }
474  else
475  {
476  closestPoint = p0 + alpha * pDiff;
477  }
478 
479  // If the point is inside the bounding sphere then the closest point
480  // should be inside the capsule
481  const Vec3d diff = (point - closestPoint);
482  const double dist = diff.norm();
483  if (dist < rCapsule)
484  {
485  depth = rCapsule - dist;
486  pointContactNormal = diff.normalized();
487  capsuleContactNormal = -pointContactNormal;
488  capsuleContactPt = closestPoint + pointContactNormal * rCapsule;
489  return true;
490  }
491  return false;
492 }
493 
498 inline bool
499 testCylinderToPoint(
500  const Vec3d& cylinderPos, const Vec3d& cylinderAxis, const double cylinderLength, const double cylinderRadius,
501  const Vec3d& point,
502  Vec3d& cylinderContactPt, Vec3d& cylinderContactNormal, Vec3d& pointContactNormal, double& depth)
503 {
504  // First, check collision with bounding sphere
505  if ((cylinderPos - point).squaredNorm() > (cylinderRadius * cylinderRadius + 0.25 * cylinderLength * cylinderLength))
506  {
507  return false;
508  }
509 
510  // Get position of end points of the cylinder
511  const Vec3d mid = cylinderPos;
512  const Vec3d p1 = mid + 0.5 * cylinderAxis * cylinderLength;
513  const Vec3d p0 = 2.0 * mid - p1;
514  const Vec3d pDiff = p1 - p0;
515  const double pDotp0 = pDiff.dot(p0);
516 
517  // Do the actual check
518  const double alpha = (point.dot(pDiff) - pDotp0) / (cylinderLength * cylinderLength);
519  if ((alpha > 1.0) || (alpha < 0.0))
520  {
521  return false;
522  }
523  else
524  {
525  Vec3d closestPointOnAxis = p0 + alpha * pDiff;
526  const Vec3d diff = (point - closestPointOnAxis);
527  const double dist = diff.norm();
528  if (dist < cylinderRadius)
529  {
530  const double distToEnd = (alpha * pDiff).norm();
531 
532  if (distToEnd < (cylinderRadius - dist))
533  {
534  depth = distToEnd;
535  cylinderContactNormal = pDiff.normalized();
536  pointContactNormal = -cylinderContactNormal;
537  cylinderContactPt = point + pointContactNormal * distToEnd;
538  }
539  else if ((cylinderLength - distToEnd) < (cylinderRadius - dist))
540  {
541  depth = cylinderLength - distToEnd;
542  cylinderContactNormal = -pDiff.normalized();
543  pointContactNormal = -cylinderContactNormal;
544  cylinderContactPt = point + pointContactNormal * (cylinderLength - distToEnd);
545  }
546  else
547  {
548  depth = cylinderRadius - dist;
549  pointContactNormal = diff.normalized();
550  cylinderContactNormal = -pointContactNormal;
551  cylinderContactPt = closestPointOnAxis + pointContactNormal * cylinderRadius;
552  }
553  return true;
554  }
555  return false;
556  }
557 }
558 
563 inline bool
564 testSphereToPoint(
565  const Vec3d& spherePos, const double rSqr, const Vec3d& point)
566 {
567  return (spherePos - point).squaredNorm() < rSqr;
568 }
569 
574 inline bool
575 testSphereToPoint(
576  const Vec3d& spherePos, const double r, const Vec3d& point,
577  Vec3d& sphereContactPt, Vec3d& sphereContactNormal, double& penetrationDepth)
578 {
579  const Vec3d diff = point - spherePos;
580  penetrationDepth = diff.norm();
581  sphereContactNormal = diff / penetrationDepth;
582  sphereContactPt = spherePos + sphereContactNormal * r;
583  const double signedDist = penetrationDepth - r;
584  penetrationDepth = std::abs(signedDist);
585  return signedDist < 0.0;
586 }
587 
592 inline bool
593 testPlaneToPoint(
594  const Vec3d& planePt, const Vec3d& planeNormal,
595  const Vec3d& point)
596 {
597  return (point - planePt).dot(planeNormal) < 0.0;
598 }
599 
604 inline bool
605 testPlaneToPoint(
606  const Vec3d& planePt, const Vec3d& planeNormal,
607  const Vec3d& point,
608  Vec3d& planeContactPt, Vec3d& contactNormal, double& pointPenetrationDepth)
609 {
610  contactNormal = planeNormal;
611  const double d = (point - planePt).dot(planeNormal);
612  planeContactPt = point - pointPenetrationDepth * contactNormal;
613  pointPenetrationDepth = std::abs(d);
614  return d < 0.0;
615 }
616 
620 inline bool
621 testPlaneLine(const Vec3d& p, const Vec3d& q,
622  const Vec3d& planePt, const Vec3d& planeNormal, Vec3d& iPt)
623 {
624  const Vec3d n = q - p;
625  const double denom = n.dot(planeNormal);
626  // Plane and line are parallel
627  if (std::abs(denom) < IMSTK_DOUBLE_EPS)
628  {
629  return false;
630  }
631  // const Vec3d dir = n.normalized();
632  const double t = (planePt - p).dot(planeNormal) / denom;
633  iPt = p + t * n;
634  return true;
635 }
636 
641 static bool
642 testSegmentTriangle(
643  const Vec3d& p, const Vec3d& q,
644  const Vec3d& a, const Vec3d& b, const Vec3d& c,
645  Vec3d& uvw)
646 {
647  const Vec3d n = q - p;
648  const Vec3d planeNormal = (b - a).cross(c - a);
649  const double denom = n.dot(planeNormal);
650  // Plane and line are parallel
651  if (std::abs(denom) < IMSTK_DOUBLE_EPS)
652  {
653  return false;
654  }
655 
656  const double t1 = (a - p).dot(planeNormal);
657  const double t2 = (a - q).dot(planeNormal);
658 
659  // Check if p and q lie on opposites side of the plane
660  if ((t1 < 0.0 && t2 >= 0.0) || (t1 >= 0.0 && t2 < 0.0))
661  {
662  // \todo: Does planeNormal need to be normalized to get valid p + t1 * n, given division by denom
663  //t1 /= denom;
664  //t2 /= denom;
665  uvw = baryCentric(p + t1 / denom * n, a, b, c);
666  // Lastly check if the point on the plane p+t1*n is inside the triangle
667  return (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0);
668  }
669  else
670  {
671  return false;
672  }
673 }
674 
678 static bool
679 testSegmentTriangle(
680  const Vec3d& p, const Vec3d& q,
681  const Vec3d& a, const Vec3d& b, const Vec3d& c)
682 {
683  const Vec3d n = q - p;
684  const Vec3d planeNormal = (b - a).cross(c - a);
685  const double denom = n.dot(planeNormal);
686  // Plane and line are parallel
687  if (std::abs(denom) < IMSTK_DOUBLE_EPS)
688  {
689  return false;
690  }
691 
692  const double t1 = (a - p).dot(planeNormal);
693  const double t2 = (a - q).dot(planeNormal);
694 
695  // Check if p and q lie on opposites side of the plane
696  if ((t1 < 0.0 && t2 >= 0.0) || (t1 >= 0.0 && t2 < 0.0))
697  {
698  // \todo: Does planeNormal need to be normalized to get valid p + t1 * n, given division by denom
699  //t1 /= denom;
700  //t2 /= denom;
701  const Vec3d uvw = baryCentric(p + t1 / denom * n, a, b, c);
702  // Lastly check if the point on the plane p+t1*n is inside the triangle
703  return (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0);
704  }
705  else
706  {
707  return false;
708  }
709 }
710 
717 Vec3d closestPointOnSegment(const Vec3d& point, const Vec3d& x1, const Vec3d& x2, int& caseType);
718 
729 Vec3d closestPointOnTriangle(const Vec3d& p, const Vec3d& a, const Vec3d& b, const Vec3d& c, int& caseType);
730 
735 inline bool
736 testSphereToTriangle(const Vec3d& spherePt, const double sphereRadius,
737  const Vec3d& a, const Vec3d& b, const Vec3d& c,
738  Vec3d& sphereContactPt, Vec3d& sphereContactNormal, double& penetrationDepth)
739 {
740  int type = 0;
741  const Vec3d closestPtOnTriangle = closestPointOnTriangle(spherePt, a, b, c, type);
742 
743  sphereContactNormal = (spherePt - closestPtOnTriangle);
744  const double dist = sphereContactNormal.norm();
745  penetrationDepth = sphereRadius - dist;
746  sphereContactNormal /= dist;
747  sphereContactPt = spherePt - sphereContactNormal * sphereRadius;
748  return dist < sphereRadius;
749 }
750 
757 inline int
758 testSphereToTriangle(const Vec3d& spherePt, const double sphereRadius,
759  const Vec3i& tri, const Vec3d& a, const Vec3d& b, const Vec3d& c,
760  Vec3d& triangleContactPt,
761  Vec2i& edgeContact,
762  int& pointContact)
763 {
764  int caseType = 0;
765  triangleContactPt = closestPointOnTriangle(spherePt, a, b, c, caseType);
766  const Vec3d diff = spherePt - triangleContactPt;
767  const double dist = diff.norm();
768 
769  // If intersecting
770  if (dist <= sphereRadius)
771  {
772  switch (caseType)
773  {
774  case 0:
775  pointContact = tri[0];
776  return 3;
777  case 1:
778  pointContact = tri[1];
779  return 3;
780  case 2:
781  pointContact = tri[2];
782  return 3;
783  case 3:
784  edgeContact = { tri[0], tri[1] };
785  return 1;
786  case 4:
787  edgeContact = { tri[1], tri[2] };
788  return 1;
789  case 5:
790  edgeContact = { tri[2], tri[0] };
791  return 1;
792  case 6:
793  return 2;
794  default:
795  return 0;
796  break;
797  }
798  ;
799  }
800  else
801  {
802  return 0;
803  }
804 }
805 
812 inline int
813 testSphereToTriangle(const Vec3d& spherePt, const double sphereRadius,
814  const Vec3d& a, const Vec3d& b, const Vec3d& c,
815  Vec3d& triangleContactPt)
816 {
817  int caseType = 0;
818  triangleContactPt = closestPointOnTriangle(spherePt, a, b, c, caseType);
819  const double dist = (spherePt - triangleContactPt).squaredNorm();
820 
821  // If intersecting
822  if (dist <= sphereRadius * sphereRadius)
823  {
824  return 1;
825  }
826  return 0;
827 }
828 
832 inline bool
833 testPointToTetrahedron(const std::array<Vec3d, 4>& inputTetVerts, const Vec3d& p)
834 {
835  const Vec4d bCoord = baryCentric(p, inputTetVerts[0], inputTetVerts[1], inputTetVerts[2], inputTetVerts[3]);
836 
837  constexpr const double eps = IMSTK_DOUBLE_EPS;
838  if (bCoord[0] >= -eps
839  && bCoord[1] >= -eps
840  && bCoord[2] >= -eps
841  && bCoord[3] >= -eps)
842  {
843  return true;
844  }
845 
846  return false;
847 }
848 
853 inline bool
854 testTetToSegment(
855  const std::array<Vec3d, 4>& inputTetVerts,
856  const Vec3d& x1, const Vec3d& x2)
857 {
858  static int faces[4][3] = { { 0, 1, 2 }, { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 } };
859  for (int i = 0; i < 4; i++)
860  {
861  if (testSegmentTriangle(x1, x2, inputTetVerts[faces[i][0]], inputTetVerts[faces[i][1]], inputTetVerts[faces[i][2]]))
862  {
863  return true;
864  }
865  }
866 
867  // If either point lies inside the tetrahedron (handles completely inside case)
868  if (testPointToTetrahedron(inputTetVerts, x1) || testPointToTetrahedron(inputTetVerts, x2))
869  {
870  return true;
871  }
872 
873  return false;
874 }
875 
881 inline bool
882 testTetToSegment(
883  const std::array<Vec3d, 4>& inputTetVerts,
884  const Vec3d& x1, const Vec3d& x2,
885  int& intersectionFace0, int& intersectionFace1,
886  Vec3d& iPt0, Vec3d& iPt1)
887 {
888  static int faces[4][3] = { { 0, 1, 2 }, { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 } };
889  bool firstFound = false;
890  for (int i = 0; i < 4; i++)
891  {
892  const Vec3d& a = inputTetVerts[faces[i][0]];
893  const Vec3d& b = inputTetVerts[faces[i][1]];
894  const Vec3d& c = inputTetVerts[faces[i][2]];
895  Vec3d uvw;
896  if (testSegmentTriangle(x1, x2, a, b, c, uvw))
897  {
898  if (firstFound)
899  {
900  intersectionFace1 = i;
901  iPt1 = uvw[0] * a + uvw[1] * b + uvw[2] * c;
902  return true;
903  }
904  else
905  {
906  intersectionFace0 = i;
907  iPt0 = uvw[0] * a + uvw[1] * b + uvw[2] * c;
908 
909  // There could still be one intersection face, keep searching
910  firstFound = true;
911  }
912  }
913  }
914  return firstFound;
915 }
916 
926 inline bool
927 testRayToObb(const Vec3d& rayOrigin, const Vec3d& rayDir,
928  const Mat4d& worldToBox, Vec3d extents,
929  Vec2d& iPt)
930 {
931  // convert from world to box space
932  const Vec3d rd = (worldToBox * Vec4d(rayDir[0], rayDir[1], rayDir[2], 0.0)).head<3>();
933  const Vec3d ro = (worldToBox * Vec4d(rayOrigin[0], rayOrigin[1], rayOrigin[2], 1.0)).head<3>();
934 
935  // ray-box intersection in box space
936  const Vec3d m = Vec3d(1.0, 1.0, 1.0).cwiseQuotient(rd);
937  const Vec3d s = Vec3d((rd[0] < 0.0) ? 1.0 : -1.0,
938  (rd[1] < 0.0) ? 1.0 : -1.0,
939  (rd[2] < 0.0) ? 1.0 : -1.0);
940  const Vec3d t1 = m.cwiseProduct(-ro + s.cwiseProduct(extents));
941  const Vec3d t2 = m.cwiseProduct(-ro - s.cwiseProduct(extents));
942 
943  const double tN = std::max(std::max(t1[0], t1[1]), t1[2]);
944  const double tF = std::min(std::min(t2[0], t2[1]), t2[2]);
945 
946  // Does not enter
947  if (tN > tF || tF < 0.0)
948  {
949  return false;
950  }
951  iPt = Vec2d(tN, tF); // Parameterized along the ray
952 
953  return true;
954 }
955 
960 inline bool
961 testRayToSphere(const Vec3d& rayOrigin, const Vec3d& rayDir,
962  const Vec3d& spherePos, const double radius,
963  Vec3d& iPt)
964 {
965  const Vec3d m = rayOrigin - spherePos;
966  double b = m.dot(rayDir);
967  double c = m.dot(m) - radius * radius;
968 
969  // Exit if r’s origin outside s (c > 0) and r pointing away from s (b > 0)
970  if (c > 0.0 && b > 0.0)
971  {
972  return false;
973  }
974  double discr = b * b - c;
975  // A negative discriminant corresponds to ray missing sphere
976  if (discr < 0.0)
977  {
978  return false;
979  }
980  // Ray now found to intersect sphere, compute smallest t value of intersection
981  // If t is negative, ray started inside sphere so clamp t to zero
982  double t = std::max(0.0, -b - std::sqrt(discr));
983  iPt = rayOrigin + t * rayDir;
984  return true;
985 }
986 
990 inline bool
991 testRayToPlane(const Vec3d& rayOrigin, const Vec3d& rayDir,
992  const Vec3d& planePos, const Vec3d& planeNormal,
993  Vec3d& iPt)
994 {
995  const double denom = rayDir.dot(planeNormal);
996  // Plane and ray are parallel
997  if (std::abs(denom) < IMSTK_DOUBLE_EPS)
998  {
999  return false;
1000  }
1001  const double t = (planePos - rayOrigin).dot(planeNormal) / denom;
1002  // Ray points out from plane
1003  if (t <= 0.0)
1004  {
1005  return false;
1006  }
1007  iPt = rayOrigin + t * rayDir;
1008  return true;
1009 }
1010 
1014 double pointSegmentClosestDistance(const Vec3d& point, const Vec3d& x1, const Vec3d& x2);
1015 
1019 double pointTriangleClosestDistance(const Vec3d& point, const Vec3d& x1, const Vec3d& x2, const Vec3d& x3);
1020 
1028 int triangleToTriangle(
1029  const Vec3i& tri_a, const Vec3i& tri_b,
1030  const Vec3d& p0_a, const Vec3d& p1_a, const Vec3d& p2_a,
1031  const Vec3d& p0_b, const Vec3d& p1_b, const Vec3d& p2_b,
1032  std::pair<Vec2i, Vec2i>& edgeContact,
1033  std::pair<int, Vec3i>& vertexTriangleContact,
1034  std::pair<Vec3i, int>& triangleVertexContact);
1035 
1047 inline int
1048 edgeToEdgeClosestPoints(
1049  const Vec3d& a0, const Vec3d& a1,
1050  const Vec3d& b0, const Vec3d& b1,
1051  Vec3d& ptA, Vec3d& ptB)
1052 {
1053  double s = 0.0;
1054  double t = 0.0;
1055  int caseType = 0;
1056 
1057  const Vec3d d1 = a1 - a0; // Direction vector of segment S1
1058  const Vec3d d2 = b1 - b0; // Direction vector of segment S2
1059  const Vec3d r = a0 - b0;
1060  const double a = d1.dot(d1); // Squared length of segment S1, always nonnegative
1061  const double e = d2.dot(d2); // Squared length of segment S2, always nonnegative
1062  const double f = d2.dot(r);
1063 
1064  // Check if either or both segments degenerate into points
1065  if (a <= IMSTK_DOUBLE_EPS && e <= IMSTK_DOUBLE_EPS)
1066  {
1067  // Both segments degenerate into points
1068  s = t = 0.0;
1069  ptA = a0;
1070  ptB = b0;
1071  caseType = 1;
1072  return caseType;
1073  }
1074  if (a <= IMSTK_DOUBLE_EPS)
1075  {
1076  // First segment degenerates into a point
1077  s = 0.0;
1078  t = f / e; // s = 0 => t = (b*s + f) / e = f / e
1079  t = std::min(std::max(t, 0.0), 1.0);
1080  caseType = 1;
1081  }
1082  else
1083  {
1084  const double c = d1.dot(r);
1085  if (e <= IMSTK_DOUBLE_EPS)
1086  {
1087  // Second segment degenerates into a point
1088  t = 0.0;
1089  s = std::min(std::max(-c / a, 0.0), 1.0); // t = 0 => s = (b*t - c) / a = -c / a
1090  caseType = 1;
1091  }
1092  else
1093  {
1094  // The general nondegenerate case starts here
1095  const double b = d1.dot(d2);
1096  const double denom = a * e - b * b; // Always nonnegative
1097  // If segments not parallel, compute closest point on L1 to L2 and
1098  // clamp to segment S1. Else pick arbitrary s (here 0)
1099  if (denom != 0.0)
1100  {
1101  s = std::min(std::max((b * f - c * e) / denom, 0.0), 1.0);
1102  }
1103  else
1104  {
1105  s = 0.0;
1106  caseType = 1;
1107  }
1108  // Compute point on L2 closest to S1(s) using
1109  // t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e
1110  t = (b * s + f) / e;
1111  // If t in [0,1] done. Else clamp t, recompute s for the new value
1112  // of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a
1113  // and clamp s to [0, 1]
1114  if (t < 0.0)
1115  {
1116  t = 0.0;
1117  s = std::min(std::max(-c / a, 0.0), 1.0);
1118  caseType = 1;
1119  }
1120  else if (t > 1.0)
1121  {
1122  t = 1.0;
1123  s = std::min(std::max((b - c) / a, 0.0), 1.0);
1124  caseType = 1;
1125  }
1126  }
1127  }
1128  ptA = a0 + d1 * s;
1129  ptB = b0 + d2 * t;
1130 
1131  return caseType;
1132 }
1133 } // namespace CollisionUtils
1134 } // namespace imstk
Compound Geometry.