iMSTK
Interactive Medical Simulation Toolkit
imstkSurfaceMeshToCapsuleCD.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 "imstkSurfaceMeshToCapsuleCD.h"
8 #include "imstkCapsule.h"
9 #include "imstkCollisionUtils.h"
10 #include "imstkParallelUtils.h"
11 #include "imstkSurfaceMesh.h"
12 
13 namespace imstk
14 {
15 SurfaceMeshToCapsuleCD::SurfaceMeshToCapsuleCD()
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  const Vec3d& capsulePosA = capsulePos - 0.5 * capsuleLength * capsuleOrientation.toRotationMatrix().col(1);
36  const Vec3d& capsulePosB = capsulePos + (capsulePos - capsulePosA);
37 
38  std::shared_ptr<VecDataArray<int, 3>> indicesPtr = surfMesh->getCells();
39  const VecDataArray<int, 3>& indices = *indicesPtr;
40  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = surfMesh->getVertexPositions();
41  const VecDataArray<double, 3>& vertices = *verticesPtr;
42 
43  Eigen::AlignedBox3d box1, box2;
44  Vec3d lower, upper;
45  geomA->computeBoundingBox(lower, upper);
46  box1.extend(lower);
47  box1.extend(upper);
48  geomB->computeBoundingBox(lower, upper);
49  box2.extend(lower);
50  box2.extend(upper);
51 
52  if (!box1.intersects(box2))
53  {
54  return;
55  }
56 
57  // \todo: Doesn't remove duplicate contacts (shared edges), refer to SurfaceMeshCD for easy method to do so
59  ParallelUtils::parallelFor(indices.size(), [&](int i)
60  {
61  const Vec3i& cell = indices[i];
62  const Vec3d& x1 = vertices[cell[0]];
63  const Vec3d& x2 = vertices[cell[1]];
64  const Vec3d& x3 = vertices[cell[2]];
65 
66  // Choose the closest point on capsule axis to create a virtual sphere for CD
67  int unusedCaseType = 0;
68  const Vec3d trianglePointA = CollisionUtils::closestPointOnTriangle(capsulePosA, x1, x2, x3, unusedCaseType);
69  const Vec3d trianglePointB = CollisionUtils::closestPointOnTriangle(capsulePosB, x1, x2, x3, unusedCaseType);
70 
71  const auto segmentPointA = CollisionUtils::closestPointOnSegment(trianglePointA, capsulePosA, capsulePosB, unusedCaseType);
72  const auto segmentPointB = CollisionUtils::closestPointOnSegment(trianglePointB, capsulePosA, capsulePosB, unusedCaseType);
73 
74  const auto distanceA = (segmentPointA - trianglePointA).squaredNorm();
75  const auto distanceB = (segmentPointB - trianglePointB).squaredNorm();
76 
77  const double sphereRadius = capsuleRadius;
78  Vec3d spherePos(0, 0, 0);
79 
80  Vec3d nearestTip;
81  Vec3d triTipProjection;
82 
83  if (distanceA < distanceB)
84  {
85  spherePos = segmentPointA;
86  }
87  else if (distanceA > distanceB)
88  {
89  spherePos = segmentPointB;
90  }
91  else // parallel
92  {
93  spherePos = (segmentPointA + segmentPointB) / 2.0; // switch to intersection point?
94  }
95 
96  // This approach does a built in sphere sweep
97  // \todo: Spatial accelerators need to be abstracted
98  const Vec3d centroid = (x1 + x2 + x3) / 3.0;
99 
100  // Find the maximal point from centroid for radius
101  const double rSqr1 = (centroid - x1).squaredNorm();
102  const double rSqr2 = (centroid - x2).squaredNorm();
103  const double rSqr3 = (centroid - x3).squaredNorm();
104  const double triangleBoundingRadius = std::sqrt(std::max(rSqr1, std::max(rSqr2, rSqr3)));
105 
106  const double distSqr = (centroid - spherePos).squaredNorm();
107  const double rSum = triangleBoundingRadius + sphereRadius;
108  if (distSqr <= rSum * rSum)
109  {
110  // Create possible contact points
111  // These are set by testSphereToTriangle depending on
112  // what geometry is collided
113  Vec3d triangleContactPt;
114  Vec2i edgeContact;
115  int pointContact;
116 
117  int caseType = CollisionUtils::testSphereToTriangle(
118  spherePos, sphereRadius,
119  cell, x1, x2, x3,
120  triangleContactPt,
121  edgeContact, pointContact);
122 
123  // Test if capsule centerline intersects with surface
124  Vec3d uvw;
125  const bool inserted = CollisionUtils::testSegmentTriangle(
126  capsulePosA, capsulePosB,
127  x1, x2, x3,
128  uvw);
129 
130  // If capsule segment is inside of triangle, find nearest segment tip and
131  // create constraint to move the capsule out using that position + radius
132  if (inserted)
133  {
134  caseType = 4;
135 
136  auto intersectionPt = uvw[0] * x1 + uvw[1] * x2 + uvw[2] * x3;
137 
138  auto capsuleSegTipDistA = (capsulePosA - intersectionPt).squaredNorm();
139  auto capsuleSegTipDistB = (capsulePosB - intersectionPt).squaredNorm();
140 
141  if (capsuleSegTipDistA <= capsuleSegTipDistB)
142  {
143  nearestTip = capsulePosA; // Note: this is the wrong place to do this, the distance is not the distance you think it is.
144  triTipProjection = trianglePointA;
145  }
146  else
147  {
148  nearestTip = capsulePosB;
149  triTipProjection = trianglePointB;
150  }
151  }
152 
153  // Contact with triangle edge
154  if (caseType == 1)
155  {
156  CellIndexElement elemA;
157  elemA.ids[0] = edgeContact[0];
158  elemA.ids[1] = edgeContact[1];
159  elemA.idCount = 2;
160  elemA.parentId = i; // Triangle id
161  elemA.cellType = IMSTK_EDGE;
162 
163  Vec3d contactNormal = (spherePos - triangleContactPt);
164  const double dist = contactNormal.norm();
165  const double penetrationDepth = sphereRadius - dist;
166  contactNormal /= dist;
167 
168  PointDirectionElement elemB;
169  elemB.dir = contactNormal; // Direction to resolve sphere
170  elemB.pt = spherePos - sphereRadius * contactNormal; // Contact point on sphere
171  elemB.penetrationDepth = penetrationDepth;
172 
173  lock.lock();
174  elementsA.push_back(elemA);
175  elementsB.push_back(elemB);
176  lock.unlock();
177  }
178  // Contact with triangle face
179  else if (caseType == 2)
180  {
181  CellIndexElement elemA;
182  elemA.ids[0] = cell[0];
183  elemA.ids[1] = cell[1];
184  elemA.ids[2] = cell[2];
185  elemA.idCount = 3;
186  elemA.parentId = i; // Triangle id
187  elemA.cellType = IMSTK_TRIANGLE;
188 
189  Vec3d contactNormal = (spherePos - triangleContactPt);
190  const double dist = contactNormal.norm();
191  const double penetrationDepth = sphereRadius - dist;
192  contactNormal /= dist;
193 
194  PointDirectionElement elemB;
195  elemB.dir = contactNormal; // Direction to resolve sphere
196  elemB.pt = spherePos - sphereRadius * contactNormal; // Contact point on sphere
197  elemB.penetrationDepth = penetrationDepth;
198 
199  lock.lock();
200  elementsA.push_back(elemA);
201  elementsB.push_back(elemB);
202  lock.unlock();
203  }
204  // Contact with trianlge vertex
205  else if (caseType == 3)
206  {
207  Vec3d contactNormal = (spherePos - triangleContactPt);
208  const double dist = contactNormal.norm();
209  const double penetrationDepth = sphereRadius - dist;
210  contactNormal /= dist;
211 
212  // Point contact
214  elemA.ptIndex = pointContact; // Point on triangle
215  elemA.dir = -contactNormal; // Direction to resolve point on triangle
216  elemA.penetrationDepth = penetrationDepth;
217 
218  PointDirectionElement elemB;
219  elemB.pt = spherePos - sphereRadius * contactNormal; // Contact point on sphere
220  elemB.dir = contactNormal; // Direction to resolve point
221  elemB.penetrationDepth = penetrationDepth;
222 
223  lock.lock();
224  elementsA.push_back(elemA);
225  elementsB.push_back(elemB);
226  lock.unlock();
227  }
228  // Capsule body intersecting triangle
229  else if (caseType == 4)
230  {
231  CellIndexElement elemA;
232  elemA.ids[0] = cell[0];
233  elemA.ids[1] = cell[1];
234  elemA.ids[2] = cell[2];
235  elemA.idCount = 3;
236  elemA.parentId = i; // Triangle id
237  elemA.cellType = IMSTK_TRIANGLE;
238 
239  // Use triangle normal
240  Vec3d contactNormal = (x2 - x1).cross(x3 - x1).normalized();
241  Vec3d penetrationVec = (triTipProjection - nearestTip);
242 
243  Vec3d projectionToNormal = penetrationVec.dot(contactNormal) * contactNormal;
244  const double dist = projectionToNormal.norm();
245  const double penetrationDepth = dist + sphereRadius;// sphere radius is capsule radius
246 
247  PointDirectionElement elemB;
248  elemB.dir = contactNormal; // Direction to resolve capsule
249  elemB.pt = spherePos - contactNormal * penetrationDepth;
250  elemB.penetrationDepth = penetrationDepth;
251 
252  lock.lock();
253  elementsA.push_back(elemA);
254  elementsB.push_back(elemB);
255  lock.unlock();
256  }
257  }
258  }, surfMesh->getNumTriangles() > 200);
259  // Tipping point for payoff is around 200 triangles, the benchmark is very simple
260  // with regard to triangle distribution and tests
261  // 200 triangles was determined experimentally see SurfaceMeshCDBenchmark
262 }
263 } // 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...
Direclty gives a point-direction contact as its collision data, point given by index.
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.