iMSTK
Interactive Medical Simulation Toolkit
imstkClosedSurfaceMeshToCapsuleCD.cpp
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 #include "imstkClosedSurfaceMeshToCapsuleCD.h"
8 #include "imstkCapsule.h"
9 #include "imstkCollisionUtils.h"
10 #include "imstkParallelUtils.h"
11 #include "imstkSurfaceMesh.h"
12 
13 namespace imstk
14 {
15 ClosedSurfaceMeshToCapsuleCD::ClosedSurfaceMeshToCapsuleCD()
16 {
17  setRequiredInputType<SurfaceMesh>(0);
18  setRequiredInputType<Capsule>(1);
19 }
20 
21 void
23  std::shared_ptr<Geometry> geomA,
24  std::shared_ptr<Geometry> geomB,
25  std::vector<CollisionElement>& elementsA,
26  std::vector<CollisionElement>& elementsB)
27 {
28  std::shared_ptr<SurfaceMesh> surfMesh = std::dynamic_pointer_cast<SurfaceMesh>(geomA);
29  std::shared_ptr<Capsule> capsule = std::dynamic_pointer_cast<Capsule>(geomB);
30 
31  const Vec3d& capsulePos = capsule->getPosition();
32  const double capsuleRadius = capsule->getRadius();
33  const double capsuleLength = capsule->getLength();
34  const Quatd& capsuleOrientation = capsule->getOrientation();
35 
36  // PosA and PosB are the end points of the line used to represent the cylindrical section of the capsule
37  const Vec3d& capsulePosA = capsulePos - 0.5 * capsuleLength * capsuleOrientation.toRotationMatrix().col(1);
38  const Vec3d& capsulePosB = capsulePos + (capsulePos - capsulePosA);
39 
40  std::shared_ptr<VecDataArray<int, 3>> indicesPtr = surfMesh->getCells();
41  const VecDataArray<int, 3>& indices = *indicesPtr;
42  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = surfMesh->getVertexPositions();
43  const VecDataArray<double, 3>& vertices = *verticesPtr;
44 
45  // \todo: Doesn't remove duplicate contacts (shared edges), refer to SurfaceMeshCD for easy method to do so
47  ParallelUtils::parallelFor(indices.size(), [&](int cellId)
48  {
49  const Vec3i& cell = indices[cellId];
50  const Vec3d& x1 = vertices[cell[0]];
51  const Vec3d& x2 = vertices[cell[1]];
52  const Vec3d& x3 = vertices[cell[2]];
53 
54  // Use signed distance value to filter triangles
55  double minSDV = std::numeric_limits<double>::max();
56  for (int vertId = 0; vertId < 3; vertId++)
57  {
58  auto SDV = capsule->getFunctionValue(vertices[cell[vertId]]);
59  minSDV = std::min(minSDV, SDV);
60  }
61  // Only generate CD if capsule is sufficiently close to triangle
62  if (minSDV <= capsuleRadius * 8.0)
63  {
64  // Choose the closest point on capsule axis to create a virtual sphere for CD
65  int unusedCaseType = 0;
66  const Vec3d trianglePointA = CollisionUtils::closestPointOnTriangle(capsulePosA, x1, x2, x3, unusedCaseType);
67  const Vec3d trianglePointB = CollisionUtils::closestPointOnTriangle(capsulePosB, x1, x2, x3, unusedCaseType);
68 
69  const auto segmentPointA = CollisionUtils::closestPointOnSegment(trianglePointA, capsulePosA, capsulePosB, unusedCaseType);
70  const auto segmentPointB = CollisionUtils::closestPointOnSegment(trianglePointB, capsulePosA, capsulePosB, unusedCaseType);
71 
72  const auto distanceA = (segmentPointA - trianglePointA).squaredNorm();
73  const auto distanceB = (segmentPointB - trianglePointB).squaredNorm();
74 
75  const double sphereRadius = capsuleRadius;
76  Vec3d spherePos(0, 0, 0);
77 
78  if (distanceA < distanceB)
79  {
80  spherePos = segmentPointA;
81  }
82  else if (distanceA > distanceB)
83  {
84  spherePos = segmentPointB;
85  }
86  else // parallel
87  {
88  spherePos = (segmentPointA + segmentPointB) / 2.0;
89  }
90 
91  // Create possible contact points
92  // These are set by testSphereToTriangle depending on
93  // what geometry is collided
94  Vec3d triangleContactPt;
95 
96  int caseType = CollisionUtils::testSphereToTriangle(
97  spherePos, sphereRadius,
98  x1, x2, x3,
99  triangleContactPt);
100 
101  // Test if capsule centerline intersects with surface
102  Vec3d uvw;
103  const bool inserted = CollisionUtils::testSegmentTriangle(
104  capsulePosA, capsulePosB,
105  x1, x2, x3,
106  uvw);
107 
108  // If capsule segment is inside of triangle, find nearest segment tip and
109  // create constraint to move the capsule out using that position + radius
110  if (inserted)
111  {
112  caseType = 2;
113  }
114 
115  // Contact with triangle face
116  if (caseType == 1)
117  {
118  CellIndexElement elemA;
119  elemA.ids[0] = cell[0];
120  elemA.ids[1] = cell[1];
121  elemA.ids[2] = cell[2];
122  elemA.idCount = 3;
123  elemA.parentId = cellId;
124  elemA.cellType = IMSTK_TRIANGLE;
125 
126  Vec3d contactNormal = (spherePos - triangleContactPt);
127  const double dist = contactNormal.norm();
128  const double penetrationDepth = sphereRadius - dist;
129  contactNormal /= dist;
130 
131  PointDirectionElement elemB;
132  elemB.dir = contactNormal; // Direction to resolve sphere
133  elemB.pt = spherePos - sphereRadius * contactNormal; // Contact point on sphere
134  elemB.penetrationDepth = penetrationDepth;
135 
136  lock.lock();
137  elementsA.push_back(elemA);
138  elementsB.push_back(elemB);
139  lock.unlock();
140  }
141  // Capsule body intersecting triangle
142  else if (caseType == 2)
143  {
144  CellIndexElement elemA;
145  elemA.ids[0] = cell[0];
146  elemA.ids[1] = cell[1];
147  elemA.ids[2] = cell[2];
148  elemA.idCount = 3;
149  elemA.parentId = cellId;
150  elemA.cellType = IMSTK_TRIANGLE;
151 
152  Vec3d contactNormal = (spherePos - triangleContactPt);
153  const double dist = contactNormal.norm();
154  double penetrationDepth = sphereRadius - dist;
155  contactNormal /= dist;
156 
157  contactNormal = (x2 - x1).cross(x3 - x1).normalized();
158 
159  penetrationDepth = sphereRadius * 2.0;
160 
161  PointDirectionElement elemB;
162  elemB.dir = contactNormal; // Direction to resolve sphere
163  elemB.pt = spherePos - sphereRadius * contactNormal; // Contact point on sphere
164  elemB.penetrationDepth = penetrationDepth;
165 
166  lock.lock();
167  elementsA.push_back(elemA);
168  elementsB.push_back(elemB);
169  lock.unlock();
170  }
171  }
172  }, surfMesh->getNumTriangles() > 200);
173 }
174 } // namespace imstk
The SpinLock class.
Definition: imstkSpinLock.h:20
void unlock()
End a thread-safe region.
Definition: imstkSpinLock.h:53
Vec3d getPosition(DataType type=DataType::PostTransform)
Get the local or global position (post transformed)
Compound Geometry.
void computeCollisionDataAB(std::shared_ptr< Geometry > geomA, std::shared_ptr< Geometry > geomB, std::vector< CollisionElement > &elementsA, std::vector< CollisionElement > &elementsB) override
Compute collision data for AB simultaneously.
void lock()
Start a thread-safe region, where only one thread can execute at a time until a call to the unlock fu...
Definition: imstkSpinLock.h:45
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
Represents a cell by a single cell id OR by N vertex ids. Which case can be determined by the idCount...
Capsule geometry, default configuration is centered at origin with length running up and down the y a...
Definition: imstkCapsule.h:21
Direclty gives a point-direction contact as its collision data.