iMSTK
Interactive Medical Simulation Toolkit
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
pbdDynamicSutureExample.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 "imstkCamera.h"
8 #include "imstkDeviceManager.h"
9 #include "imstkDeviceManagerFactory.h"
10 #include "imstkDirectionalLight.h"
11 #include "imstkGeometryUtilities.h"
12 #include "imstkIsometricMap.h"
13 #include "imstkKeyboardDeviceClient.h"
14 #include "imstkKeyboardSceneControl.h"
15 #include "imstkMeshIO.h"
16 #include "imstkMouseDeviceClient.h"
17 #include "imstkMouseSceneControl.h"
18 #include "imstkNeedle.h"
19 #include "imstkPbdModel.h"
20 #include "imstkPbdModelConfig.h"
21 #include "imstkPbdObject.h"
22 #include "imstkPointwiseMap.h"
23 #include "imstkPuncturable.h"
24 #include "imstkRbdConstraint.h"
25 #include "imstkRenderMaterial.h"
26 #include "imstkScene.h"
27 #include "imstkSceneManager.h"
28 #include "imstkSimulationManager.h"
29 #include "imstkSimulationUtils.h"
30 #include "imstkVisualModel.h"
31 #include "imstkVTKViewer.h"
32 #include "imstkNeedleInteraction.h"
33 #include "imstkPbdObjectController.h"
34 #include "imstkLoggerSynchronous.h"
35 
36 #include "imstkObjectControllerGhost.h"
37 #include "imstkCapsule.h"
38 #include "imstkPbdRigidObjectGrasping.h"
39 
40 #include "imstkPbdDistanceConstraint.h"
41 
42 using namespace imstk;
43 
44 #include <iostream>
45 
46 // Create tissue object to stitch
47 std::shared_ptr<PbdObject>
48 createTissue(std::shared_ptr<PbdModel> model)
49 {
50  // Load a tetrahedral mesh
51  std::shared_ptr<TetrahedralMesh> tetMesh = MeshIO::read<TetrahedralMesh>(iMSTK_DATA_ROOT "Tissues/tissue_hole.vtk");
52  CHECK(tetMesh != nullptr) << "Could not read mesh from file.";
53 
54  std::shared_ptr<SurfaceMesh> surfMesh = tetMesh->extractSurfaceMesh();
55 
56  const int numVerts = tetMesh->getNumVertices();
57 
58  tetMesh->rotate(Vec3d(0.0, 0.0, 1.0), -PI_2, Geometry::TransformType::ApplyToData);
59  tetMesh->rotate(Vec3d(1.0, 0.0, 0.0), -PI_2 / 1.0, Geometry::TransformType::ApplyToData);
60 
61  surfMesh->rotate(Vec3d(0.0, 0.0, 1.0), -PI_2, Geometry::TransformType::ApplyToData);
62  surfMesh->rotate(Vec3d(1.0, 0.0, 0.0), -PI_2 / 1.0, Geometry::TransformType::ApplyToData);
63 
64  tetMesh->scale(0.02, Geometry::TransformType::ApplyToData); // 0.02
65  surfMesh->scale(0.02, Geometry::TransformType::ApplyToData);
66 
67  double squish = 0.5;
68  tetMesh->scale(Vec3d(1.0, squish, 1.0), Geometry::TransformType::ApplyToData);
69  surfMesh->scale(Vec3d(1.0, squish, 1.0), Geometry::TransformType::ApplyToData);
70 
71  surfMesh->computeVertexNormals();
72  surfMesh->computeTrianglesNormals();
73 
74  // Setup the Object
75  auto pbdObject = std::make_shared<PbdObject>("meshHole");
76  pbdObject->setVisualGeometry(surfMesh);
77  pbdObject->getVisualModel(0)->getRenderMaterial()->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);;
78  pbdObject->setPhysicsGeometry(tetMesh);
79  pbdObject->setCollidingGeometry(surfMesh);
80  pbdObject->setPhysicsToCollidingMap(std::make_shared<PointwiseMap>(tetMesh, surfMesh));
81  pbdObject->setDynamicalModel(model);
82  double mass = 2.5;
83  pbdObject->getPbdBody()->uniformMassValue = mass / numVerts; // 0.025
84  std::cout << "Tissue nodal mass = " << mass / numVerts << "\n";
85  pbdObject->addComponent<Puncturable>();
86  pbdObject->initialize();
87 
88  std::cout << "Tissue body index = " << pbdObject->getPbdBody()->bodyHandle << "\n";
89 
90  model->getConfig()->m_femParams->m_YoungModulus = 8000.0; // 8000
91  model->getConfig()->m_femParams->m_PoissonRatio = 0.2;
92  model->getConfig()->enableFemConstraint(PbdFemConstraint::MaterialType::NeoHookean, pbdObject->getPbdBody()->bodyHandle); // StVK
93  model->getConfig()->setBodyDamping(pbdObject->getPbdBody()->bodyHandle, 0.2);
94 
95  // Fix the borders (boundary conditions
96  std::vector<int> fixedNodes;
97  std::cout << "Num Verts = " << numVerts << "\n";
98  for (int i = 0; i < numVerts; i++)
99  {
100  const Vec3d& position = tetMesh->getVertexPosition(i);
101  if (std::fabs(0.0281968 - std::fabs(position[0])) <= 1E-4)
102  {
103  fixedNodes.push_back(i);
104  }
105  }
106 
107  // Fix nodes in place using constraints
108  const int bodyId = pbdObject->getPbdBody()->bodyHandle;
109  for (int nodeId = 0; nodeId < fixedNodes.size(); nodeId++)
110  {
111  const auto& position = tetMesh->getVertexPositions()->at(fixedNodes[nodeId]);
112 
113  const PbdParticleId& p0 = model->addVirtualParticle(position, 0.0, Vec3d::Zero(), true);
114  const PbdParticleId p1 = { bodyId, fixedNodes[nodeId] };
115 
116  auto boundConstraint = std::make_shared<PbdDistanceConstraint>();
117  double restLength = 0.0;
118  double stiffness = 10.0;
119  boundConstraint->initConstraint(restLength, p0, p1, stiffness);
120  model->getConstraints()->addConstraint(boundConstraint);
121  }
122 
123  return pbdObject;
124 }
125 
129 static std::shared_ptr<PbdObject>
130 makeCapsuleToolObj(std::shared_ptr<PbdModel> model)
131 {
132  double radius = 0.002; // Harry radius is 0.005
133  double length = 0.2; // Harry length is 1
134  double mass = 0.01; // 0.1 (kg)
135 
136  auto toolGeometry = std::make_shared<Capsule>();
137  // auto toolGeometry = std::make_shared<Sphere>();
138  toolGeometry->setRadius(radius);
139  toolGeometry->setLength(length);
140  toolGeometry->setPosition(Vec3d(0.0, 0.0, 0.0));
141  toolGeometry->setOrientation(Quatd(0.707, 0.707, 0.0, 0.0));
142 
143  LOG(INFO) << "Tool Radius = " << radius;
144  LOG(INFO) << "Tool mass = " << mass;
145 
146  auto toolObj = std::make_shared<PbdObject>("Tool");
147 
148  // Create the object
149  toolObj->setVisualGeometry(toolGeometry);
150  toolObj->setPhysicsGeometry(toolGeometry);
151  toolObj->setCollidingGeometry(toolGeometry);
152  toolObj->setDynamicalModel(model);
153  toolObj->getPbdBody()->setRigid(
154  Vec3d(0.04, 0.0, 0.0),
155  mass,
156  Quatd::Identity(),
157  Mat3d::Identity() * 1.0);
158 
159  toolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(1.0);
160 
161  // Add a component for controlling via another device
162  auto controller = toolObj->addComponent<PbdObjectController>();
163  controller->setControlledObject(toolObj);
164  controller->setHapticOffset(Vec3d(0.0, 0.0, -0.1));
165  controller->setTranslationScaling(1.0);
166  controller->setLinearKs(50.0);
167  controller->setAngularKs(1000.0);
168  controller->setUseCritDamping(true);
169  controller->setForceScaling(1.0);
170  controller->setSmoothingKernelSize(15);
171  controller->setUseForceSmoothening(true);
172 
173  // Add extra component to tool for the ghost
174  auto controllerGhost = toolObj->addComponent<ObjectControllerGhost>();
175  controllerGhost->setController(controller);
176 
177  return toolObj;
178 }
179 
180 static std::shared_ptr<CollidingObject>
181 makeCollidingCapsule()
182 {
183  double radius = 0.009; // Harry radius is 0.005
184  double length = 0.05; // Harry length is 1
185 
186  auto capsuleGeometry = std::make_shared<Capsule>();
187  // auto capsuleGeometry = std::make_shared<Sphere>();
188  capsuleGeometry->setRadius(radius);
189  capsuleGeometry->setLength(length);
190  capsuleGeometry->setPosition(Vec3d(0.0, 0.0, -0.02));
191  capsuleGeometry->setOrientation(Quatd(0.0, 0.0, 0.0, 0.0));
192 
193  auto capsuleObject = std::make_shared<CollidingObject>("Esophagus");
194 
195  capsuleObject->setCollidingGeometry(capsuleGeometry);
196  capsuleObject->setVisualGeometry(capsuleGeometry);
197 
198  return capsuleObject;
199 }
200 
204 static std::shared_ptr<PbdObject>
205 makePbdString(
206  const std::string& name,
207  const Vec3d& pos, const Vec3d& dir, const int numVerts,
208  const double stringLength,
209  std::shared_ptr<PbdModel> model)
210 {
211  // Setup the Geometry
212  std::shared_ptr<LineMesh> stringMesh =
213  GeometryUtils::toLineGrid(pos, dir, stringLength, numVerts);
214 
215  double segLength = (stringMesh->getVertexPositions()->at(0) - stringMesh->getVertexPositions()->at(1)).norm();
216  std::cout << "Segment length = " << segLength << "\n"; // 0.0016
217  std::cout << "Critical length = " << 0.0016 << "\n";
218 
219  const Vec3d shift = Vec3d(0.0, 0.05, 0.0);
220  stringMesh->translate(shift, Geometry::TransformType::ApplyToData);
221 
222  // Setup the VisualModel
223  auto material = std::make_shared<RenderMaterial>();
224  material->setBackFaceCulling(false);
225  material->setColor(Color::Red);
226  material->setLineWidth(2.0);
227  material->setPointSize(3.0);
228  material->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
229 
230  auto visualModel = std::make_shared<VisualModel>();
231  visualModel->setGeometry(stringMesh);
232  visualModel->setRenderMaterial(material);
233 
234  // Setup the Object
235  auto stringObj = std::make_shared<PbdObject>(name);
236  stringObj->addVisualModel(visualModel);
237  stringObj->setPhysicsGeometry(stringMesh);
238  stringObj->setCollidingGeometry(stringMesh);
239  stringObj->setDynamicalModel(model);
240  //stringObj->getPbdBody()->fixedNodeIds = { 0, 1 };
241  stringObj->getPbdBody()->uniformMassValue = 0.00125;
242  model->getConfig()->enableConstraint(PbdModelConfig::ConstraintGenType::Distance, 1E12, stringObj->getPbdBody()->bodyHandle);
243  model->getConfig()->enableBendConstraint(100, 1, true, stringObj->getPbdBody()->bodyHandle);
244  model->getConfig()->setBodyDamping(stringObj->getPbdBody()->bodyHandle, 0.05);
245 
246  return stringObj;
247 
248  // Harrys thread
249  // 201 verts
250  // total length of 0.75
251  // mass = 0.1
252  // bend constraints stiffness 2 with stride of 2
253 }
254 
255 static std::shared_ptr<PbdObject>
256 makeNeedleObj(std::shared_ptr<PbdModel> model)
257 {
258  auto needleObj = std::make_shared<PbdObject>();
259  auto sutureMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture.stl");
260  auto sutureLineMesh = MeshIO::read<LineMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture_hull.vtk");
261 
262  const Mat4d rot = mat4dRotation(Rotd(-PI_2, Vec3d(0.0, 1.0, 0.0))) *
263  mat4dRotation(Rotd(-0.6, Vec3d(1.0, 0.0, 0.0)));
264 
265  const Vec3d center = sutureLineMesh->getCenter();
266  sutureMesh->translate(-center, Geometry::TransformType::ApplyToData);
267  sutureLineMesh->translate(-center, Geometry::TransformType::ApplyToData);
268 
269  const Vec3d shift = Vec3d(0.0, 0.05, -0.1);
270 
271  sutureMesh->transform(rot, Geometry::TransformType::ApplyToData);
272  sutureMesh->translate(shift, Geometry::TransformType::ApplyToData);
273 
274  sutureLineMesh->transform(rot, Geometry::TransformType::ApplyToData);
275  sutureLineMesh->translate(shift, Geometry::TransformType::ApplyToData);
276 
277  needleObj->setVisualGeometry(sutureMesh);
278  // setVisualGeometry(sutureLineMesh);
279  needleObj->setCollidingGeometry(sutureLineMesh);
280  needleObj->setPhysicsGeometry(sutureLineMesh);
281  needleObj->setPhysicsToVisualMap(std::make_shared<IsometricMap>(sutureLineMesh, sutureMesh));
282 
283  needleObj->getVisualModel(0)->getRenderMaterial()->setColor(Color(0.9, 0.9, 0.9));
284  needleObj->getVisualModel(0)->getRenderMaterial()->setShadingModel(RenderMaterial::ShadingModel::PBR);
285  needleObj->getVisualModel(0)->getRenderMaterial()->setRoughness(0.5);
286  needleObj->getVisualModel(0)->getRenderMaterial()->setMetalness(1.0);
287 
288  needleObj->setDynamicalModel(model);
289  needleObj->getPbdBody()->setRigid(Vec3d(0, 0, 0.1), 0.1, Quatd::Identity(), Mat3d::Identity() * 0.00012);
290  // needle mass 0.1
291  // inertia is 0.00012
292 
293  needleObj->addComponent<Needle>();
294 
295  return needleObj;
296 }
297 
301 int
302 main()
303 {
304  // Setup logger (write to file and stdout)
306  // Logger::instance()->setOutput(std::make_shared<StreamOutput>(std::cout));
307 
308  // Construct the scene
309  auto scene = std::make_shared<Scene>("DynamicSuture");
310 
311  scene->getActiveCamera()->setPosition(0.001, 0.1, 0.145);
312  scene->getActiveCamera()->setFocalPoint(0.000, 0.025, 0.035);
313  scene->getActiveCamera()->setViewUp(0.0, 0.83, -0.55);
314 
315  auto light = std::make_shared<DirectionalLight>();
316  light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
317  light->setIntensity(1.0);
318  scene->addLight("Light", light);
319 
320  // Setup the Model
321  auto pbdModel = std::make_shared<PbdModel>();
322  auto pbdParams = std::make_shared<PbdModelConfig>();
323  pbdParams->m_doPartitioning = false;
324  pbdParams->m_gravity = Vec3d(0.0, 0.0, 0.0);
325  pbdParams->m_dt = 0.001;
326  pbdParams->m_iterations = 5;
327  pbdParams->m_doPartitioning = false;
328  pbdModel->configure(pbdParams);
329 
330  // Mesh with hole for suturing
331  std::shared_ptr<PbdObject> tissueHole = createTissue(pbdModel);
332  scene->addSceneObject(tissueHole);
333 
334  // Capsule for esophagus
335  auto esophagus = makeCollidingCapsule();
336  scene->addSceneObject(esophagus);
337 
338  // Create arced needle
339  std::shared_ptr<PbdObject> needleObj = makeNeedleObj(pbdModel);
340  scene->addSceneObject(needleObj);
341 
342  // Create the thread
343  const double stringLength = 0.15;
344  const int stringVertexCount = 65;
345  std::shared_ptr<PbdObject> sutureThreadObj =
346  makePbdString("SutureThread", Vec3d(0.0, 0.0, 0.018), Vec3d(0.0, 0.0, 1.0),
347  stringVertexCount, stringLength, pbdModel);
348  scene->addSceneObject(sutureThreadObj);
349 
350  // Setup a tool to grasp with
351  std::shared_ptr<PbdObject> toolObj = makeCapsuleToolObj(pbdModel);
352  scene->addSceneObject(toolObj);
353 
354  // Create grasping interactions
355  auto needleGrasp = std::make_shared<PbdObjectGrasping>(needleObj, toolObj);
356  needleGrasp->setStiffness(0.3);
357  scene->addInteraction(needleGrasp);
358 
359  auto threadGrasp = std::make_shared<PbdObjectGrasping>(sutureThreadObj, toolObj);
360  threadGrasp->setStiffness(0.3);
361  scene->addInteraction(threadGrasp);
362 
363  // Add tool to tissue collision
364  auto pbdToolCollision = std::make_shared<PbdObjectCollision>(tissueHole, toolObj);
365  pbdToolCollision->setRigidBodyCompliance(0.0001); // Helps with smoothness
366  pbdToolCollision->setUseCorrectVelocity(true);
367  scene->addInteraction(pbdToolCollision);
368 
369  // Add esophagus to tissue, needle, and tool collision
370  auto esophagusTissueCollision = std::make_shared<PbdObjectCollision>(tissueHole, esophagus);
371  esophagusTissueCollision->setRigidBodyCompliance(0.0001); // Helps with smoothness
372  esophagusTissueCollision->setUseCorrectVelocity(true);
373  scene->addInteraction(esophagusTissueCollision);
374 
375  auto esophagusToolCollision = std::make_shared<PbdObjectCollision>(toolObj, esophagus);
376  esophagusToolCollision->setRigidBodyCompliance(0.0001); // Helps with smoothness
377  esophagusToolCollision->setUseCorrectVelocity(true);
378  scene->addInteraction(esophagusToolCollision);
379 
380  // Add needle constraining behavior between the tissue & arc needle/thread
381  auto sutureInteraction = std::make_shared<NeedleInteraction>(tissueHole, needleObj, sutureThreadObj);
382  scene->addInteraction(sutureInteraction);
383 
384  // Add thread CCD
385  auto interactionCCDThread = std::make_shared<PbdObjectCollision>(sutureThreadObj, sutureThreadObj);
386  // Very important parameter for stability of solver, keep lower than 1.0:
387  interactionCCDThread->setDeformableStiffnessA(0.1);
388  interactionCCDThread->setDeformableStiffnessB(0.1);
389  scene->addInteraction(interactionCCDThread);
390 
391  {
392  // Setup a viewer to render
393  auto viewer = std::make_shared<VTKViewer>();
394  viewer->setActiveScene(scene);
395  viewer->setDebugAxesLength(0.01, 0.01, 0.01);
396 
397  // Setup a scene manager to advance the scene
398  auto sceneManager = std::make_shared<SceneManager>();
399  sceneManager->setActiveScene(scene);
400  sceneManager->pause(); // Start simulation paused
401 
402  // Setup a simulation manager to manage renders & scene updates
403  auto driver = std::make_shared<SimulationManager>();
404  driver->addModule(viewer);
405  driver->addModule(sceneManager);
406  driver->setDesiredDt(0.005);
407 
408 #ifdef iMSTK_USE_HAPTICS
409  // Setup default haptics manager
410  std::shared_ptr<DeviceManager> hapticManager = DeviceManagerFactory::makeDeviceManager();
411  std::shared_ptr<DeviceClient> deviceClient = hapticManager->makeDeviceClient();
412  driver->addModule(hapticManager);
413 
414  auto controller = toolObj->getComponent<PbdObjectController>();
415  controller->setDevice(deviceClient);
416 
417  connect<ButtonEvent>(deviceClient, &DeviceClient::buttonStateChanged,
418  [&](ButtonEvent* e)
419  {
420  if (e->m_buttonState == BUTTON_PRESSED)
421  {
422  if (e->m_button == 1)
423  {
424  // Use a slightly larger capsule since collision prevents intersection
425  auto capsule = std::dynamic_pointer_cast<Capsule>(toolObj->getCollidingGeometry());
426  auto dilatedCapsule = std::make_shared<Capsule>(*capsule);
427  dilatedCapsule->setRadius(capsule->getRadius() * 1.1);
428  needleGrasp->beginCellGrasp(dilatedCapsule);
429  threadGrasp->beginCellGrasp(dilatedCapsule);
430  // pbdToolCollision->setEnabled(false);
431  std::cout << "Grasp\n";
432  }
433  }
434  else if (e->m_buttonState == BUTTON_RELEASED)
435  {
436  if (e->m_button == 1)
437  {
438  needleGrasp->endGrasp();
439  threadGrasp->endGrasp();
440  // pbdToolCollision->setEnabled(true);
441  }
442  }
443  });
444 #else
445  auto deviceClient = std::make_shared<DummyClient>();
446  connect<Event>(sceneManager, &SceneManager::postUpdate,
447  [&](Event*)
448  {
449  const Vec2d mousePos = viewer->getMouseDevice()->getPos();
450  const Vec3d worldPos = Vec3d(mousePos[0] - 0.5, mousePos[1] - 0.5, 0.0) * 0.1;
451 
452  deviceClient->setPosition(worldPos);
453  });
454 
455  // Add click event and side effects
456  connect<Event>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonPress,
457  [&](Event*)
458  {
459  toolPicking->beginVertexGrasp(std::dynamic_pointer_cast<Capsule>(toolObj->getCollidingGeometry()));
460  pbdToolCollision->setEnabled(false);
461  });
462  connect<Event>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonRelease,
463  [&](Event*)
464  {
465  toolPicking->endGrasp();
466  pbdToolCollision->setEnabled(true);
467  });
468 #endif
469 
470  // Update the needle object for real time
471  connect<Event>(sceneManager, &SceneManager::preUpdate,
472  [&](Event*)
473  {
474  sutureThreadObj->getPbdModel()->getConfig()->m_dt = sceneManager->getDt();
475  });
476 
477  // Add default mouse and keyboard controls to the viewer
478  std::shared_ptr<Entity> mouseAndKeyControls =
479  SimulationUtils::createDefaultSceneControl(driver);
480  scene->addSceneObject(mouseAndKeyControls);
481 
482  connect<KeyEvent>(viewer->getKeyboardDevice(), &KeyboardDeviceClient::keyPress,
483  [&](KeyEvent* e)
484  {
485  // Perform stitch
486  if (e->m_key == 's')
487  {
488  sutureInteraction->stitch();
489  }
490  });
491  driver->start();
492  }
493  return 0;
494 }
void initialize()
Initialize the component, called at a later time after all component construction is complete...
Base class for events which contain a type, priority, and data priority defaults to 0 and uses a grea...
std::pair< int, int > PbdParticleId
Index pair that refers to a particle in a PbdState. Index 0 is the body id, Index 1 is the particle i...
This class uses the provided device to control the provided rigid object via virtual coupling...
Place this on an object to make it puncturable by a needle. This allows puncturables to know they&#39;ve ...
Compound Geometry.
static std::shared_ptr< DeviceManager > makeDeviceManager()
Attempts to create a new DeviceManager by whichever is default If multiple haptic managers are built ...
std::shared_ptr< LineMesh > toLineGrid(const Vec3d &start, const Vec3d &dir, const double length, const int dim)
Creates a set of connected lines.
Base for all needles in imstk it supports global puncture state, per object puncture state...
Definition: imstkNeedle.h:20
Provides the information of a key event (press, release, & which key)
Color in RGB space.
Definition: imstkColor.h:24
A behaviour that renders a second copy of the controlled object at a lower opacity in the physical po...
Physically based rendering.
Capsule geometry, default configuration is centered at origin with length running up and down the y a...
Definition: imstkCapsule.h:21
static LoggerG3 & startLogger()
Starts logger with default sinks, use getInstance to create a logger with no sinks.