iMSTK
Interactive Medical Simulation Toolkit
imstkPointPicker.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 "imstkPointPicker.h"
8 #include "imstkCollisionUtils.h"
9 #include "imstkLineMesh.h"
10 #include "imstkOrientedBox.h"
11 #include "imstkPlane.h"
12 #include "imstkSphere.h"
13 #include "imstkSurfaceMesh.h"
14 #include "imstkTetrahedralMesh.h"
15 #include "imstkVecDataArray.h"
16 
17 #include <set>
18 
19 namespace imstk
20 {
21 void
23 {
24  // Use for sorting
25  // \todo: Could also parameterize by distance to avoid recomputation
26  // if dealing with many intersections
27  auto pred = [&](const PickData& a, const PickData& b)
28  {
29  const double sqrDistA = (a.pickPoint - m_rayStart).squaredNorm();
30  const double sqrDistB = (b.pickPoint - m_rayStart).squaredNorm();
31  return sqrDistA < sqrDistB;
32  };
33  std::set<PickData, decltype(pred)> resultSet(pred);
34 
35  std::shared_ptr<Geometry> geomToPick = getInput(0);
36  geomToPick->updatePostTransformData();
37  if (auto surfMeshToPick = std::dynamic_pointer_cast<SurfaceMesh>(geomToPick))
38  {
39  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = surfMeshToPick->getVertexPositions();
40  const VecDataArray<double, 3>& vertices = *verticesPtr;
41  std::shared_ptr<VecDataArray<int, 3>> indicesPtr = surfMeshToPick->getCells();
42  const VecDataArray<int, 3>& indices = *indicesPtr;
43 
44  // Brute force
45  // For every cell
46  for (int i = 0; i < indices.size(); i++)
47  {
48  const Vec3i& cell = indices[i];
49  const Vec3d& a = vertices[cell[0]];
50  const Vec3d& b = vertices[cell[1]];
51  const Vec3d& c = vertices[cell[2]];
52  const Vec3d n = (b - a).cross(c - a).normalized();
53  Vec3d iPt = Vec3d::Zero();
54  if (CollisionUtils::testRayToPlane(m_rayStart, m_rayDir, vertices[cell[0]], n, iPt))
55  {
56  const Vec3d uvw = baryCentric(iPt, a, b, c);
57  if (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0) // Check if within triangle
58  {
59  resultSet.insert({ { i }, 1, IMSTK_TRIANGLE, iPt });
60  }
61  }
62  }
63  }
64  else if (auto tetMeshToPick = std::dynamic_pointer_cast<TetrahedralMesh>(geomToPick))
65  {
66  // Current implementation just based off the triangle faces
67  static int faces[4][3] = { { 0, 1, 2 }, { 1, 2, 3 }, { 0, 2, 3 }, { 0, 1, 3 } };
68 
69  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = tetMeshToPick->getVertexPositions();
70  const VecDataArray<double, 3>& vertices = *verticesPtr;
71  std::shared_ptr<VecDataArray<int, 4>> indicesPtr = tetMeshToPick->getCells();
72  const VecDataArray<int, 4>& indices = *indicesPtr;
73 
74  // For every tet
75  for (int i = 0; i < indices.size(); i++)
76  {
77  const Vec4i& tet = indices[i];
78 
79  // For every face
80  for (int j = 0; j < 4; j++)
81  {
82  // Find intersection point and add constraints
83  const Vec3d& a = vertices[tet[faces[j][0]]];
84  const Vec3d& b = vertices[tet[faces[j][1]]];
85  const Vec3d& c = vertices[tet[faces[j][2]]];
86 
87  Vec3d iPt;
88  if (CollisionUtils::testRayToPlane(m_rayStart, m_rayDir, a,
89  (b - a).cross(c - a).normalized(), iPt))
90  {
91  const Vec3d uvw = baryCentric(iPt, a, b, c);
92  if (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0) // Check if within triangle
93  {
94  resultSet.insert({ { i }, 1, IMSTK_TETRAHEDRON, iPt });
95  }
96  }
97  }
98  }
99  }
100  else if (auto lineMeshToPick = std::dynamic_pointer_cast<LineMesh>(geomToPick))
101  {
102  // Requires a thickness
103  LOG(FATAL) << "LineMesh picking not implemented yet";
104  }
105  else if (auto sphereToPick = std::dynamic_pointer_cast<Sphere>(geomToPick))
106  {
107  Vec3d iPt = Vec3d::Zero();
108  if (CollisionUtils::testRayToSphere(m_rayStart, m_rayDir,
109  sphereToPick->getPosition(), sphereToPick->getRadius(), iPt))
110  {
111  resultSet.insert({ { 0 }, 1, IMSTK_VERTEX, iPt });
112  // \todo: Exit point
113  }
114  }
115  else if (auto planeToPick = std::dynamic_pointer_cast<Plane>(geomToPick))
116  {
117  Vec3d iPt = Vec3d::Zero();
118  if (CollisionUtils::testRayToPlane(m_rayStart, m_rayDir,
119  planeToPick->getPosition(), planeToPick->getNormal(), iPt))
120  {
121  resultSet.insert({ { 0 }, 1, IMSTK_VERTEX, iPt });
122  }
123  }
124  //else if (auto capsuleToPick = std::dynamic_pointer_cast<Capsule>(geomToPick))
125  //{
126  //}
127  else if (auto obbToPick = std::dynamic_pointer_cast<OrientedBox>(geomToPick))
128  {
129  Mat4d worldToBox = mat4dTranslate(obbToPick->getPosition()) *
130  mat4dRotation(obbToPick->getOrientation());
131  Vec2d t = Vec2d::Zero(); // Entry and exit t
132  if (CollisionUtils::testRayToObb(m_rayStart, m_rayDir,
133  worldToBox.inverse(), obbToPick->getExtents(), t))
134  {
135  resultSet.insert({ { 0 }, 1, IMSTK_VERTEX, m_rayStart + m_rayDir * t[0] });
136  resultSet.insert({ { 1 }, 1, IMSTK_VERTEX, m_rayStart + m_rayDir * t[1] });
137  }
138  }
139  else if (auto implicitGeom = std::dynamic_pointer_cast<ImplicitGeometry>(geomToPick))
140  {
141  // Implicit primitives such as capsule should get caught here if the
142  // above analytic solution is not provided. SDFs as well
143  // Only works with bounded geometries
144 
145  // Find the intersection point on the oriented box
146  Vec3d min, max;
147  implicitGeom->computeBoundingBox(min, max);
148  const Vec3d center = (min + max) * 0.5;
149  const Vec3d extents = (max - min) * 0.5; // Half the size
150  const double size = extents.norm() * 2.0;
151  const double stepLength = size / 50.0;
152 
153  const Mat4d boxTransform = mat4dTranslate(center);
154  Vec2d tPt = Vec2d::Zero(); // Entry and exit t
155  if (CollisionUtils::testRayToObb(m_rayStart, m_rayDir, boxTransform.inverse(), extents, tPt))
156  {
157  // If it hit, start iterating from this point on the box in the implicit geometry
158  Vec3d iPt = m_rayStart + m_rayDir * tPt[0];
159 
160  // For implicit geometry this isn't always signed distance
161  double currDist = IMSTK_DOUBLE_MAX;
162  Vec3d currPt = iPt;
163  double prevDist = IMSTK_DOUBLE_MAX;
164  Vec3d prevPt = iPt;
165  for (int i = 0; i < 50; i++)
166  {
167  // Push back state
168  prevDist = currDist;
169  prevPt = currPt;
170 
171  // Compute new pt
172  const double t = static_cast<double>(i) / 50.0;
173  currPt = iPt + t * m_rayDir * stepLength;
174  currDist = implicitGeom->getFunctionValue(currPt);
175 
176  // If the sign changed
177  if (std::signbit(currDist) != std::signbit(prevDist))
178  {
179  // Use midpoint of results
180  iPt = (currPt + prevPt) * 0.5;
181  resultSet.insert({ { 0 }, 1, IMSTK_VERTEX, iPt });
182  }
183  }
184  }
185  }
186  else
187  {
188  LOG(FATAL) << "Tried to point pick with an unsupported geometry " << geomToPick->getTypeName();
189  return;
190  }
191 
192  // Only select the first hit that is within max distance
193  const double maxSqrDist = m_maxDist * m_maxDist;
194  const bool useMaxDist = (m_maxDist != -1.0);
195  if (m_useFirstHit)
196  {
197  // Start at max, unless you get under this it won't select
198  double minSqrDist = useMaxDist ? maxSqrDist : IMSTK_DOUBLE_MAX;
199  PickData results;
200  bool resultsFound = false;
201  for (const auto& pickData : resultSet)
202  {
203  // Possibly parameterize all by t and use that here instead
204  const double sqrDist = (pickData.pickPoint - m_rayStart).squaredNorm();
205  if (sqrDist <= minSqrDist)
206  {
207  results = pickData;
208  resultsFound = true;
209  minSqrDist = sqrDist;
210  }
211  }
212 
213  if (resultsFound)
214  {
215  m_results.resize(1);
216  m_results[0] = results;
217  }
218  }
219  else
220  {
221  m_results = std::vector<PickData>();
222  for (auto pickData : resultSet)
223  {
224  const double sqrDist = (pickData.pickPoint - m_rayStart).squaredNorm();
225  if (!useMaxDist || sqrDist <= maxSqrDist)
226  {
227  m_results.push_back(pickData);
228  }
229  }
230  }
231 }
232 } // namespace imstk
std::shared_ptr< Geometry > getInput(size_t port=0) const
Returns input geometry given port, returns nullptr if doesn&#39;t exist.
void requestUpdate() override
Users can implement this for the logic to be run.
Compound Geometry.
Vec3d pickPoint
Some pickings may produce specific points on an element.
PickData provides ids to indicate what was picked These may be optionally used to indicate the select...