iMSTK
Interactive Medical Simulation Toolkit
PbdLapToolSuturingExample.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 "imstkCapsule.h"
9 #include "imstkDeviceManager.h"
10 #include "imstkDeviceManagerFactory.h"
11 #include "imstkDirectionalLight.h"
12 #include "imstkGeometryUtilities.h"
13 #include "imstkIsometricMap.h"
14 #include "imstkMeshIO.h"
15 #include "imstkPbdContactConstraint.h"
16 #include "imstkPbdModel.h"
17 #include "imstkPbdModelConfig.h"
18 #include "imstkPbdObject.h"
19 #include "imstkPbdObjectCollision.h"
20 #include "imstkPbdObjectController.h"
21 #include "imstkPbdObjectGrasping.h"
22 #include "imstkPlane.h"
23 #include "imstkPortHoleInteraction.h"
24 #include "imstkRenderMaterial.h"
25 #include "imstkScene.h"
26 #include "imstkSceneControlText.h"
27 #include "imstkSceneManager.h"
28 #include "imstkSimulationManager.h"
29 #include "imstkSimulationUtils.h"
30 #include "imstkSphere.h"
31 #include "imstkVisualModel.h"
32 #include "imstkVTKViewer.h"
33 
34 using namespace imstk;
35 
36 //#define USE_TWO_HAPTIC_DEVICES
37 
38 #ifndef USE_TWO_HAPTIC_DEVICES
39 #include "imstkDummyClient.h"
40 #include "imstkMouseDeviceClient.h"
41 #else
42 #include "imstkDeviceClient.h"
43 #endif
44 
48 std::shared_ptr<PbdObject>
49 makeLapToolObj(const std::string& name,
50  std::shared_ptr<PbdModel> model)
51 {
52  auto lapTool = std::make_shared<PbdObject>(name);
53 
54  const double capsuleLength = 0.3;
55  auto toolGeom = std::make_shared<Capsule>(
56  Vec3d(0.0, 0.0, capsuleLength * 0.5 - 0.005), // Position
57  0.002, // Radius
58  capsuleLength, // Length
59  Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0)))); // Orientation
60 
61  const double lapToolHeadLength = 0.01;
62  auto graspCapsule = std::make_shared<Capsule>(
63  Vec3d(0.0, 0.0, lapToolHeadLength * 0.5), // Position
64  0.004, // Radius
65  lapToolHeadLength, // Length
66  Quatd::FromTwoVectors(Vec3d(0.0, 1.0, 0.0), Vec3d(0.0, 0.0, 1.0))); // Orientation
67 
68  auto lapToolVisualGeom = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/LapTool/laptool_all_in_one.obj");
69 
70  lapTool->setDynamicalModel(model);
71  lapTool->setPhysicsGeometry(toolGeom);
72  lapTool->setCollidingGeometry(toolGeom);
73  lapTool->setVisualGeometry(lapToolVisualGeom);
74  lapTool->setPhysicsToVisualMap(std::make_shared<IsometricMap>(toolGeom, lapToolVisualGeom));
75 
76  // Add grasp capsule for visualization
77  auto graspVisualModel = std::make_shared<VisualModel>();
78  graspVisualModel->setGeometry(graspCapsule);
79  graspVisualModel->getRenderMaterial()->setIsDynamicMesh(false);
80  graspVisualModel->setIsVisible(false);
81  lapTool->addVisualModel(graspVisualModel);
82 
83  std::shared_ptr<RenderMaterial> material = lapTool->getVisualModel(0)->getRenderMaterial();
84  material->setIsDynamicMesh(false);
85  material->setMetalness(1.0);
86  material->setRoughness(0.2);
87  material->setShadingModel(RenderMaterial::ShadingModel::PBR);
88 
89  lapTool->getPbdBody()->setRigid(
90  Vec3d(0.0, 0.0, capsuleLength * 0.5) + Vec3d(0.0, 0.1, -1.0),
91  5.0,
92  Quatd::Identity(),
93  Mat3d::Identity() * 0.08);
94 
95  auto controller = lapTool->addComponent<PbdObjectController>();
96  controller->setControlledObject(lapTool);
97  controller->setLinearKs(10000.0);
98  controller->setAngularKs(10.0);
99  controller->setForceScaling(0.01);
100  controller->setSmoothingKernelSize(15);
101  controller->setUseForceSmoothening(true);
102 
103  // The center of mass of the object is at the tip this allows most force applied
104  // to the tool at the tip upon touch to be translated into linear force. Suitable
105  // for 3dof devices.
106  //
107  // However, the point at which you actually apply force is on the back of the tool,
108  // this is important for the inversion of control in lap tools (right movement at the
109  // back should move the tip left).
110  controller->setHapticOffset(Vec3d(0.0, 0.0, capsuleLength));
111 
112  // \todo: The grasp capsule and its map can't be placed as components yet.
113  // For now grasp capsule is placed as a VisualModel and the map updated in this lambda
114  auto graspCapsuleMap = std::make_shared<IsometricMap>(toolGeom, graspCapsule);
115  auto graspCapsuleUpdate = lapTool->addComponent<LambdaBehaviour>("graspCapsuleUpdate");
116  graspCapsuleUpdate->setUpdate([ = ](const double&)
117  {
118  graspCapsuleMap->update();
119  });
120 
121  return lapTool;
122 }
123 
127 static std::shared_ptr<PbdObject>
128 makePbdString(
129  const std::string& name,
130  const Vec3d& pos, const Vec3d& dir, const int numVerts,
131  const double stringLength,
132  std::shared_ptr<PbdObject> needleObj)
133 {
134  auto stringObj = std::make_shared<PbdObject>(name);
135 
136  // Setup the Geometry
137  std::shared_ptr<LineMesh> stringMesh =
138  GeometryUtils::toLineGrid(pos, dir, stringLength, numVerts);
139 
140  // Setup the VisualModel
141  auto material = std::make_shared<RenderMaterial>();
142  material->setBackFaceCulling(false);
143  material->setColor(Color::Red);
144  material->setLineWidth(2.0);
145  material->setPointSize(6.0);
146  material->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
147 
148  // Setup the Object
149  stringObj->setVisualGeometry(stringMesh);
150  stringObj->getVisualModel(0)->setRenderMaterial(material);
151  stringObj->setPhysicsGeometry(stringMesh);
152  stringObj->setCollidingGeometry(stringMesh);
153  std::shared_ptr<PbdModel> model = needleObj->getPbdModel();
154  stringObj->setDynamicalModel(model);
155  //stringObj->getPbdBody()->fixedNodeIds = { 0, 1, 19, 20 };
156  stringObj->getPbdBody()->uniformMassValue = 0.02;
157 
158  const int bodyHandle = stringObj->getPbdBody()->bodyHandle;
159  model->getConfig()->enableConstraint(PbdModelConfig::ConstraintGenType::Distance, 1000.0,
160  bodyHandle);
161  // It should have a high bend but without plasticity it's very difficult to use
162  //model->getConfig()->enableBendConstraint(100.0, 1, true, bodyHandle);
163  model->getConfig()->enableBendConstraint(1.0, 1, true, bodyHandle);
164  //model->getConfig()->enableBendConstraint(0.1, 2, true, bodyHandle);
165 
166  // Add a component to update the suture thread to be on the needle
167  auto needleLineMesh = std::dynamic_pointer_cast<LineMesh>(needleObj->getPhysicsGeometry());
168 
169  // Add an attachment constraint for two-way between the string and needle
170  // This is important to be able to pull the needle by the string
171  model->getConfig()->addPbdConstraintFunctor([ = ](PbdConstraintContainer& container)
172  {
173  const Vec3d endOfNeedle = (*needleLineMesh->getVertexPositions())[0];
174  auto attachmentConstraint = std::make_shared<PbdBodyToBodyDistanceConstraint>();
175  attachmentConstraint->initConstraint(model->getBodies(),
176  { needleObj->getPbdBody()->bodyHandle, 0 },
177  endOfNeedle,
178  { stringObj->getPbdBody()->bodyHandle, 0 }, // Start of string
179  0.0, // Rest length
180  0.0000001);
181  container.addConstraint(attachmentConstraint);
182  });
183 
184  return stringObj;
185 }
186 
192 int
193 main()
194 {
195  // Write log to stdout and file
197 
198  auto scene = std::make_shared<Scene>("PbdLapToolSuturing");
199  scene->getActiveCamera()->setFocalPoint(0.00100544, 0.0779848, -1.20601);
200  scene->getActiveCamera()->setPosition(-0.000866941, 0.0832288, -1.20377);
201  scene->getActiveCamera()->setViewUp(0.0601552, 0.409407, -0.910367);
202 
203  auto model = std::make_shared<PbdModel>();
204  model->getConfig()->m_gravity = Vec3d::Zero();
205  model->getConfig()->m_dt = 0.001;
206  model->getConfig()->m_doPartitioning = false;
207 
208  auto bodyObject = std::make_shared<CollidingObject>("body");
209  {
210  auto surfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/human/full_body/body.obj");
211  auto bodyPlane = std::make_shared<Plane>(Vec3d(0.0, -0.04, -1.0), Vec3d(0.0, 1.0, 0.0));
212  bodyObject->setCollidingGeometry(bodyPlane);
213  bodyObject->setVisualGeometry(surfMesh);
214  bodyObject->getVisualModel(0)->getRenderMaterial()->setShadingModel(
216  std::shared_ptr<RenderMaterial> material =
217  bodyObject->getVisualModel(0)->getRenderMaterial();
218  material->setRoughness(0.8);
219  material->setMetalness(0.1);
220  material->setOpacity(0.5);
221  }
222  scene->addSceneObject(bodyObject);
223 
224  std::shared_ptr<PbdObject> leftToolObj = makeLapToolObj("leftLapTool", model);
225  scene->addSceneObject(leftToolObj);
226  std::shared_ptr<PbdObject> rightToolObj = makeLapToolObj("rightLapTool", model);
227  scene->addSceneObject(rightToolObj);
228 
229  // Make a pbd rigid body needle
230  auto needleObj = std::make_shared<PbdObject>();
231  {
232  auto needleMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture.stl");
233  auto needleLineMesh = MeshIO::read<LineMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture_hull.vtk");
234  // Transform so center of mass is in center of the needle
235  needleMesh->translate(Vec3d(0.0, -0.0047, -0.0087), Geometry::TransformType::ApplyToData);
236  needleLineMesh->translate(Vec3d(0.0, -0.0047, -0.0087), Geometry::TransformType::ApplyToData);
237  needleObj->setVisualGeometry(needleMesh);
238  needleObj->setCollidingGeometry(needleLineMesh);
239  needleObj->setPhysicsGeometry(needleLineMesh);
240  needleObj->setPhysicsToVisualMap(std::make_shared<IsometricMap>(needleLineMesh, needleMesh));
241  needleObj->setDynamicalModel(model);
242  needleObj->getPbdBody()->setRigid(
243  Vec3d(0.02, 0.0, -1.26),
244  1.0,
245  Quatd::Identity(),
246  Mat3d::Identity() * 0.01);
247  needleObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Orange);
248  }
249  scene->addSceneObject(needleObj);
250 
251  // Make a pbd simulated suture thread
252  auto sutureThreadObj = makePbdString("sutureThread",
253  Vec3d(0.02, 0.0, -1.26), Vec3d(0.0, 0.0, 1.0), 50, 0.2, needleObj);
254  scene->addSceneObject(sutureThreadObj);
255 
256  auto collision = std::make_shared<PbdObjectCollision>(leftToolObj, rightToolObj);
257  collision->setRigidBodyCompliance(0.00001);
258  scene->addInteraction(collision);
259  auto threadCollision0 = std::make_shared<PbdObjectCollision>(leftToolObj, sutureThreadObj);
260  threadCollision0->setRigidBodyCompliance(0.0001);
261  threadCollision0->setUseCorrectVelocity(false);
262  //threadCollision0->setFriction(0.1);
263  scene->addInteraction(threadCollision0);
264  auto threadCollision1 = std::make_shared<PbdObjectCollision>(rightToolObj, sutureThreadObj);
265  threadCollision1->setRigidBodyCompliance(0.0001);
266  threadCollision1->setUseCorrectVelocity(false);
267  //threadCollision1->setFriction(0.1);
268  scene->addInteraction(threadCollision1);
269 
270  auto leftNeedleGrasping = std::make_shared<PbdObjectGrasping>(needleObj, leftToolObj);
271  leftNeedleGrasping->setCompliance(0.00001);
272  scene->addInteraction(leftNeedleGrasping);
273  auto leftThreadGrasping = std::make_shared<PbdObjectGrasping>(sutureThreadObj, leftToolObj);
274  leftThreadGrasping->setCompliance(0.00001);
275  scene->addInteraction(leftThreadGrasping);
276  auto rightNeedleGrasping = std::make_shared<PbdObjectGrasping>(needleObj, rightToolObj);
277  rightNeedleGrasping->setCompliance(0.00001);
278  scene->addInteraction(rightNeedleGrasping);
279  auto rightThreadGrasping = std::make_shared<PbdObjectGrasping>(sutureThreadObj, rightToolObj);
280  rightThreadGrasping->setCompliance(0.00001);
281  scene->addInteraction(rightThreadGrasping);
282 
283  // Add thread-thread self collision
284  auto threadOnThreadCollision = std::make_shared<PbdObjectCollision>(sutureThreadObj, sutureThreadObj);
285  threadOnThreadCollision->setDeformableStiffnessA(0.05);
286  threadOnThreadCollision->setDeformableStiffnessB(0.05);
287  scene->addInteraction(threadOnThreadCollision);
288 
289  // Plane with which to move haptic point of tool on
290  auto mousePlane = std::make_shared<Plane>(Vec3d(0.03, 0.1, -0.95), Vec3d(0.1, 0.0, 1.0));
291  mousePlane->setWidth(0.3);
292 
293  // Light
294  auto light = std::make_shared<DirectionalLight>();
295  light->setIntensity(1.0);
296  scene->addLight("light", light);
297 
298  std::shared_ptr<DeviceManager> hapticManager = DeviceManagerFactory::makeDeviceManager();
299 
300 #ifdef USE_TWO_HAPTIC_DEVICES
301  std::shared_ptr<DeviceClient> leftDeviceClient = hapticManager->makeDeviceClient("Default Device");
302  auto leftController = leftToolObj->getComponent<PbdObjectController>();
303  leftController->setDevice(leftDeviceClient);
304  leftController->setTranslationOffset(Vec3d(0.0, 0.1, -1.0));
305 
306  std::shared_ptr<DeviceClient> rightDeviceClient = hapticManager->makeDeviceClient("Device2");
307  auto rightController = rightToolObj->getComponent<PbdObjectController>();
308  rightController->setDevice(rightDeviceClient);
309  rightController->setTranslationOffset(Vec3d(0.0, 0.1, -1.0));
310 
311  connect<ButtonEvent>(rightDeviceClient, &DeviceClient::buttonStateChanged,
312  [&](ButtonEvent* e)
313  {
314  if (e->m_button == 1)
315  {
316  if (e->m_buttonState == BUTTON_PRESSED)
317  {
318  // Use a slightly larger capsule since collision prevents intersection
319  auto graspCapsule = std::dynamic_pointer_cast<Capsule>(
320  rightToolObj->getVisualModel(1)->getGeometry());
321  rightNeedleGrasping->beginCellGrasp(graspCapsule);
322  rightThreadGrasping->beginCellGrasp(graspCapsule);
323  }
324  else if (e->m_buttonState == BUTTON_RELEASED)
325  {
326  rightNeedleGrasping->endGrasp();
327  rightThreadGrasping->endGrasp();
328  }
329  }
330  });
331 #else
332  std::shared_ptr<DeviceClient> leftDeviceClient = hapticManager->makeDeviceClient(); // Default device
333  auto leftController = leftToolObj->getComponent<PbdObjectController>();
334  leftController->setDevice(leftDeviceClient);
335  leftController->setTranslationOffset(Vec3d(0.0, 0.1, -1.0));
336 
337  auto rightDeviceClient = std::make_shared<DummyClient>();
338  auto rightController = rightToolObj->getComponent<PbdObjectController>();
339  rightController->setDevice(rightDeviceClient);
340 #endif
341  connect<ButtonEvent>(leftDeviceClient, &DeviceClient::buttonStateChanged,
342  [&](ButtonEvent* e)
343  {
344  if (e->m_button == 1)
345  {
346  if (e->m_buttonState == BUTTON_PRESSED)
347  {
348  auto graspCapsule = std::dynamic_pointer_cast<Capsule>(
349  leftToolObj->getVisualModel(1)->getGeometry());
350  leftNeedleGrasping->beginCellGrasp(graspCapsule);
351  leftThreadGrasping->beginCellGrasp(graspCapsule);
352  }
353  else if (e->m_buttonState == BUTTON_RELEASED)
354  {
355  leftNeedleGrasping->endGrasp();
356  leftThreadGrasping->endGrasp();
357  }
358  }
359  });
360 
361  // Add port holes
362  auto portHoleInteraction = rightToolObj->addComponent<PortHoleInteraction>();
363  portHoleInteraction->setTool(rightToolObj);
364  portHoleInteraction->setPortHoleLocation(Vec3d(0.015, 0.092, -1.117));
365  auto sphere = std::make_shared<Sphere>(Vec3d(0.015, 0.092, -1.117), 0.01);
366  auto rightPortVisuals = rightToolObj->addComponent<VisualModel>();
367  rightPortVisuals->setGeometry(sphere);
368  portHoleInteraction->setToolGeometry(rightToolObj->getCollidingGeometry());
369  portHoleInteraction->setCompliance(0.000001);
370 
371  auto portHoleInteraction2 = leftToolObj->addComponent<PortHoleInteraction>();
372  portHoleInteraction2->setTool(leftToolObj);
373  portHoleInteraction2->setPortHoleLocation(Vec3d(-0.065, 0.078, -1.127));
374  auto sphere2 = std::make_shared<Sphere>(Vec3d(-0.065, 0.078, -1.127), 0.01);
375  auto leftPortVisuals = leftToolObj->addComponent<VisualModel>();
376  leftPortVisuals->setGeometry(sphere2);
377  portHoleInteraction2->setToolGeometry(leftToolObj->getCollidingGeometry());
378  portHoleInteraction2->setCompliance(0.000001);
379 
380  // Run the simulation
381  {
382  // Setup a viewer to render in its own thread
383  auto viewer = std::make_shared<VTKViewer>();
384  viewer->setActiveScene(scene);
385  viewer->setDebugAxesLength(0.01, 0.01, 0.01);
386 
387  // Setup a scene manager to advance the scene
388  auto sceneManager = std::make_shared<SceneManager>();
389  sceneManager->setActiveScene(scene);
390  sceneManager->pause();
391 
392  auto driver = std::make_shared<SimulationManager>();
393  driver->addModule(viewer);
394  driver->addModule(sceneManager);
395  driver->addModule(hapticManager);
396  driver->setDesiredDt(0.001);
397  connect<Event>(driver, &SimulationManager::starting,
398  [&](Event*)
399  {
400  sceneManager->setMode(SceneManager::Mode::Debug);
401  viewer->setRenderingMode(Renderer::Mode::Debug);
402  });
403 
404  // Add default mouse and keyboard controls to the viewer
405  std::shared_ptr<Entity> mouseAndKeyControls =
406  SimulationUtils::createDefaultSceneControl(driver);
407  auto instructText = mouseAndKeyControls->getComponent<TextVisualModel>();
408  instructText->setText(instructText->getText() +
409  "\nPress D to Switch to Laprascopic View"
410  "\nPress Haptic Device Button to Grasp");
411  scene->addSceneObject(mouseAndKeyControls);
412 
413 #ifndef USE_TWO_HAPTIC_DEVICES
414  // Process Mouse tool movement & presses
415  double dummyOffset = -0.07;
416  connect<Event>(sceneManager, &SceneManager::postUpdate,
417  [&](Event*)
418  {
419  std::shared_ptr<MouseDeviceClient> mouseDeviceClient = viewer->getMouseDevice();
420  const Vec2d& mousePos = mouseDeviceClient->getPos();
421 
422  auto geom =
423  std::dynamic_pointer_cast<AnalyticalGeometry>(rightToolObj->getPhysicsGeometry());
424 
425  // Use plane definition for dummy movement
426  Vec3d a = Vec3d(0.0, 1.0, 0.0);
427  Vec3d b = a.cross(mousePlane->getNormal()).normalized();
428  a = b.cross(mousePlane->getNormal());
429  const double width = mousePlane->getWidth();
430  rightDeviceClient->setPosition(mousePlane->getPosition() +
431  a * width * (mousePos[1] - 0.5) +
432  b * width * (mousePos[0] - 0.5) +
433  geom->getOrientation().toRotationMatrix().col(1).normalized() *
434  dummyOffset);
435  });
436  connect<MouseEvent>(viewer->getMouseDevice(), &MouseDeviceClient::mouseScroll,
437  [&](MouseEvent* e)
438  {
439  dummyOffset += e->m_scrollDx * 0.01;
440  });
441  connect<MouseEvent>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonPress,
442  [&](MouseEvent* e)
443  {
444  auto graspCapsule = std::dynamic_pointer_cast<Capsule>(
445  rightToolObj->getVisualModel(1)->getGeometry());
446  rightNeedleGrasping->beginCellGrasp(graspCapsule);
447  rightThreadGrasping->beginCellGrasp(graspCapsule);
448  });
449  connect<MouseEvent>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonRelease,
450  [&](MouseEvent* e)
451  {
452  rightNeedleGrasping->endGrasp();
453  rightThreadGrasping->endGrasp();
454  });
455 #endif
456  connect<Event>(sceneManager, &SceneManager::preUpdate,
457  [&](Event*)
458  {
459  model->getConfig()->m_dt = sceneManager->getDt();
460  });
461 
462  driver->start();
463  }
464 
465  return 0;
466 }
Base class for any analytical geometrical representation.
Container for pbd constraints.
Base class for events which contain a type, priority, and data priority defaults to 0 and uses a grea...
This class uses the provided device to control the provided rigid object via virtual coupling...
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.
void setText(const std::string &text)
Text to be plotted.
Base class for all volume mesh types.
Definition: imstkLineMesh.h:18
Defines the behaviour to constrain a PbdObject LineMesh or Capsule to a fixed port hole location...
Provides the information of a mouse event, this includes button presses/releases and scrolling...
A SceneBehaviour that can update via a lambda function.
virtual void addConstraint(std::shared_ptr< PbdConstraint > constraint)
Adds a constraint to the system, thread safe.
Renders text to the screen.
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.
Contains geometric, material, and render information.