iMSTK
Interactive Medical Simulation Toolkit
VRLapToolControlExample.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 "imstkOpenVRDeviceClient.h"
16 #include "imstkPbdContactConstraint.h"
17 #include "imstkPbdModel.h"
18 #include "imstkPbdModelConfig.h"
19 #include "imstkPbdObject.h"
20 #include "imstkPbdObjectCollision.h"
21 #include "imstkPbdObjectController.h"
22 #include "imstkPbdObjectGrasping.h"
23 #include "imstkPlane.h"
24 #include "imstkPortHoleInteraction.h"
25 #include "imstkRenderMaterial.h"
26 #include "imstkScene.h"
27 #include "imstkSceneControlText.h"
28 #include "imstkSceneManager.h"
29 #include "imstkSimulationManager.h"
30 #include "imstkSimulationUtils.h"
31 #include "imstkSphere.h"
32 #include "imstkVisualModel.h"
33 #include "imstkVTKOpenVRViewer.h"
34 #include "imstkVTKViewer.h"
35 #include "VRCameraControl.h"
36 
37 using namespace imstk;
38 
42 std::shared_ptr<PbdObject>
43 makeLapToolObj(const std::string& name,
44  std::shared_ptr<PbdModel> model)
45 {
46  auto lapTool = std::make_shared<PbdObject>(name);
47 
48  const double capsuleLength = 0.5;
49  auto toolGeom = std::make_shared<Capsule>(
50  Vec3d(0.0, 0.0, capsuleLength * 0.5 - 0.005), // Position
51  0.002, // Radius
52  capsuleLength, // Length
53  Quatd(Rotd(PI_2, Vec3d(1.0, 0.0, 0.0)))); // Orientation
54 
55  const double lapToolHeadLength = 0.01;
56  auto graspCapsule = std::make_shared<Capsule>(
57  Vec3d(0.0, 0.0, lapToolHeadLength * 0.5), // Position
58  0.004, // Radius
59  lapToolHeadLength, // Length
60  Quatd::FromTwoVectors(Vec3d(0.0, 1.0, 0.0), Vec3d(0.0, 0.0, 1.0))); // Orientation
61 
62  auto lapToolVisualGeom = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/LapTool/laptool_all_in_one.obj");
63 
64  lapTool->setDynamicalModel(model);
65  lapTool->setPhysicsGeometry(toolGeom);
66  lapTool->setCollidingGeometry(toolGeom);
67  lapTool->setVisualGeometry(lapToolVisualGeom);
68  lapTool->setPhysicsToVisualMap(std::make_shared<IsometricMap>(toolGeom, lapToolVisualGeom));
69 
70  // Add grasp capsule for visualization
71  auto graspVisualModel = std::make_shared<VisualModel>();
72  graspVisualModel->setGeometry(graspCapsule);
73  graspVisualModel->getRenderMaterial()->setIsDynamicMesh(false);
74  graspVisualModel->setIsVisible(false);
75  lapTool->addVisualModel(graspVisualModel);
76 
77  std::shared_ptr<RenderMaterial> material = lapTool->getVisualModel(0)->getRenderMaterial();
78  material->setIsDynamicMesh(false);
79  material->setMetalness(1.0);
80  material->setRoughness(0.2);
81  material->setShadingModel(RenderMaterial::ShadingModel::PBR);
82 
83  lapTool->getPbdBody()->setRigid(
84  Vec3d(0.0, 0.0, capsuleLength * 0.5) + Vec3d(0.0, 0.1, -1.0),
85  5.0,
86  Quatd::Identity(),
87  Mat3d::Identity() * 0.08);
88 
89  // \todo: The grasp capsule and its map can't be placed as components yet.
90  // For now grasp capsule is placed as a VisualModel and the map updated in this lambda
91  auto graspCapsuleMap = std::make_shared<IsometricMap>(toolGeom, graspCapsule);
92  auto graspCapsuleUpdate = lapTool->addComponent<LambdaBehaviour>("graspCapsuleUpdate");
93  graspCapsuleUpdate->setUpdate([ = ](const double&)
94  {
95  graspCapsuleMap->update();
96  });
97 
98  return lapTool;
99 }
100 
104 std::shared_ptr<PbdObject>
105 makeHandObj(const std::string& name,
106  std::shared_ptr<PbdModel> model)
107 {
108  auto handSphereObj = std::make_shared<PbdObject>(name);
109 
110  auto sphere = std::make_shared<Sphere>(Vec3d(0.0, 0.0, 0.0), 0.02);
111 
112  handSphereObj->setDynamicalModel(model);
113  handSphereObj->setPhysicsGeometry(sphere);
114  handSphereObj->setCollidingGeometry(sphere);
115  handSphereObj->setVisualGeometry(sphere);
116 
117  std::shared_ptr<RenderMaterial> material = handSphereObj->getVisualModel(0)->getRenderMaterial();
118  material->setIsDynamicMesh(false);
119  material->setMetalness(0.0);
120  material->setRoughness(1.0);
121  material->setColor(Color::Green);
122  material->setShadingModel(RenderMaterial::ShadingModel::PBR);
123 
124  handSphereObj->getPbdBody()->setRigid(
125  Vec3d(0.0, 0.1, -1.0),
126  5.0,
127  Quatd::Identity(),
128  Mat3d::Identity() * 0.08);
129 
130  auto controller = handSphereObj->addComponent<PbdObjectController>();
131  controller->setControlledObject(handSphereObj);
132  controller->setLinearKs(10000.0);
133  controller->setAngularKs(10.0);
134  controller->setForceScaling(0.01);
135  controller->setSmoothingKernelSize(15);
136  controller->setUseForceSmoothening(true);
137 
138  return handSphereObj;
139 }
140 
144 static std::shared_ptr<PbdObject>
145 makePbdString(
146  const std::string& name,
147  const Vec3d& pos, const Vec3d& dir, const int numVerts,
148  const double stringLength,
149  std::shared_ptr<PbdObject> needleObj)
150 {
151  auto stringObj = std::make_shared<PbdObject>(name);
152 
153  // Setup the Geometry
154  std::shared_ptr<LineMesh> stringMesh =
155  GeometryUtils::toLineGrid(pos, dir, stringLength, numVerts);
156 
157  // Setup the VisualModel
158  auto material = std::make_shared<RenderMaterial>();
159  material->setBackFaceCulling(false);
160  material->setColor(Color::Red);
161  material->setLineWidth(2.0);
162  material->setPointSize(6.0);
163  material->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
164 
165  // Setup the Object
166  stringObj->setVisualGeometry(stringMesh);
167  stringObj->getVisualModel(0)->setRenderMaterial(material);
168  stringObj->setPhysicsGeometry(stringMesh);
169  stringObj->setCollidingGeometry(stringMesh);
170  std::shared_ptr<PbdModel> model = needleObj->getPbdModel();
171  stringObj->setDynamicalModel(model);
172  //stringObj->getPbdBody()->fixedNodeIds = { 0, 1, 19, 20 };
173  stringObj->getPbdBody()->uniformMassValue = 0.02;
174 
175  const int bodyHandle = stringObj->getPbdBody()->bodyHandle;
176  model->getConfig()->enableConstraint(PbdModelConfig::ConstraintGenType::Distance, 1000.0,
177  bodyHandle);
178  model->getConfig()->enableBendConstraint(0.1, 1, true, bodyHandle);
179  //model->getConfig()->enableBendConstraint(0.1, 2, true, bodyHandle);
180 
181  // Add a component to update the suture thread to be on the needle
182  auto needleLineMesh = std::dynamic_pointer_cast<LineMesh>(needleObj->getPhysicsGeometry());
183 
184  // Add an attachment constraint for two-way between the string and needle
185  // This is important to be able to pull the needle by the string
186  model->getConfig()->addPbdConstraintFunctor([ = ](PbdConstraintContainer& container)
187  {
188  const Vec3d endOfNeedle = (*needleLineMesh->getVertexPositions())[0];
189  auto attachmentConstraint = std::make_shared<PbdBodyToBodyDistanceConstraint>();
190  attachmentConstraint->initConstraint(model->getBodies(),
191  { needleObj->getPbdBody()->bodyHandle, 0 },
192  endOfNeedle,
193  { stringObj->getPbdBody()->bodyHandle, 0 }, // Start of string
194  0.0, // Rest length
195  0.0000001);
196  container.addConstraint(attachmentConstraint);
197  });
198 
199  return stringObj;
200 }
201 
208 int
209 main()
210 {
211  // Write log to stdout and file
213 
214  auto scene = std::make_shared<Scene>("VRLapToolControl");
215 
216  auto model = std::make_shared<PbdModel>();
217  model->getConfig()->m_gravity = Vec3d::Zero();
218  model->getConfig()->m_dt = 0.001;
219  model->getConfig()->m_doPartitioning = false;
220 
221  auto bodyObject = std::make_shared<CollidingObject>("body");
222  {
223  auto surfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/human/full_body/body.obj");
224  auto bodyPlane = std::make_shared<Plane>(Vec3d(0.0, -0.04, -1.0), Vec3d(0.0, 1.0, 0.0));
225  bodyObject->setCollidingGeometry(bodyPlane);
226  bodyObject->setVisualGeometry(surfMesh);
227  bodyObject->getVisualModel(0)->getRenderMaterial()->setShadingModel(
229  std::shared_ptr<RenderMaterial> material =
230  bodyObject->getVisualModel(0)->getRenderMaterial();
231  material->setRoughness(0.8);
232  material->setMetalness(0.1);
233  material->setOpacity(0.5);
234  }
235  scene->addSceneObject(bodyObject);
236 
237  // Add the hands
238  std::shared_ptr<PbdObject> leftHandObj = makeHandObj("leftHand", model);
239  scene->addSceneObject(leftHandObj);
240  std::shared_ptr<PbdObject> rightHandObj = makeHandObj("leftHand", model);
241  scene->addSceneObject(rightHandObj);
242 
243  // Add the rigid lap tools
244  std::shared_ptr<PbdObject> leftToolObj = makeLapToolObj("leftLapTool", model);
245  scene->addSceneObject(leftToolObj);
246  std::shared_ptr<PbdObject> rightToolObj = makeLapToolObj("rightLapTool", model);
247  scene->addSceneObject(rightToolObj);
248 
249  // Add a rigid needle
250  auto needleObj = std::make_shared<PbdObject>();
251  {
252  auto needleMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture.stl");
253  auto needleLineMesh = MeshIO::read<LineMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Needles/c6_suture_hull.vtk");
254  // Transform so center of mass is in center of the needle
255  needleMesh->translate(Vec3d(0.0, -0.0047, -0.0087), Geometry::TransformType::ApplyToData);
256  needleLineMesh->translate(Vec3d(0.0, -0.0047, -0.0087), Geometry::TransformType::ApplyToData);
257  needleObj->setVisualGeometry(needleMesh);
258  needleObj->setCollidingGeometry(needleLineMesh);
259  needleObj->setPhysicsGeometry(needleLineMesh);
260  needleObj->setPhysicsToVisualMap(std::make_shared<IsometricMap>(needleLineMesh, needleMesh));
261  needleObj->setDynamicalModel(model);
262  needleObj->getPbdBody()->setRigid(
263  Vec3d(0.02, 0.0, -1.26),
264  1.0,
265  Quatd::Identity(),
266  Mat3d::Identity() * 0.01);
267  needleObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Orange);
268  }
269  scene->addSceneObject(needleObj);
270 
271  // Add deformable suture thread
272  auto sutureThreadObj = makePbdString("sutureThread",
273  Vec3d(0.02, 0.0, -1.26), Vec3d(0.0, 0.0, 1.0), 50, 0.2, needleObj);
274  scene->addSceneObject(sutureThreadObj);
275 
276  // Add tool-on-tool collision
277  auto lapToolCollision = std::make_shared<PbdObjectCollision>(leftToolObj, rightToolObj);
278  lapToolCollision->setRigidBodyCompliance(0.00001);
279  scene->addInteraction(lapToolCollision);
280 
281  // Add thread-on-tool collisions
282  auto threadCollision0 = std::make_shared<PbdObjectCollision>(leftToolObj, sutureThreadObj);
283  threadCollision0->setRigidBodyCompliance(0.0001);
284  threadCollision0->setUseCorrectVelocity(false);
285  //threadCollision0->setFriction(0.1);
286  scene->addInteraction(threadCollision0);
287  auto threadCollision1 = std::make_shared<PbdObjectCollision>(rightToolObj, sutureThreadObj);
288  threadCollision1->setRigidBodyCompliance(0.0001);
289  threadCollision1->setUseCorrectVelocity(false);
290  //threadCollision1->setFriction(0.1);
291  scene->addInteraction(threadCollision1);
292 
293  // Add left grasping
294  auto leftToolGrasping = std::make_shared<PbdObjectGrasping>(leftToolObj, leftHandObj);
295  leftToolGrasping->setCompliance(0.00001);
296  scene->addInteraction(leftToolGrasping);
297  auto leftNeedleGrasping = std::make_shared<PbdObjectGrasping>(needleObj, leftToolObj);
298  leftNeedleGrasping->setCompliance(0.00001);
299  scene->addInteraction(leftNeedleGrasping);
300  auto leftThreadGrasping = std::make_shared<PbdObjectGrasping>(sutureThreadObj, leftToolObj);
301  leftThreadGrasping->setCompliance(0.00001);
302  scene->addInteraction(leftThreadGrasping);
303 
304  // Add right grasping
305  auto rightToolGrasping = std::make_shared<PbdObjectGrasping>(rightToolObj, rightHandObj);
306  rightToolGrasping->setCompliance(0.00001);
307  scene->addInteraction(rightToolGrasping);
308  auto rightNeedleGrasping = std::make_shared<PbdObjectGrasping>(needleObj, rightToolObj);
309  rightNeedleGrasping->setCompliance(0.00001);
310  scene->addInteraction(rightNeedleGrasping);
311  auto rightThreadGrasping = std::make_shared<PbdObjectGrasping>(sutureThreadObj, rightToolObj);
312  rightThreadGrasping->setCompliance(0.00001);
313  scene->addInteraction(rightThreadGrasping);
314 
315  // Add thread-thread self collision
316  auto threadOnThreadCollision = std::make_shared<PbdObjectCollision>(sutureThreadObj, sutureThreadObj);
317  threadOnThreadCollision->setDeformableStiffnessA(0.05);
318  threadOnThreadCollision->setDeformableStiffnessB(0.05);
319  scene->addInteraction(threadOnThreadCollision);
320 
321  // Light
322  auto light = std::make_shared<DirectionalLight>();
323  light->setIntensity(1.0);
324  scene->addLight("light", light);
325 
326  // Add port holes
327  auto portHoleInteraction = rightToolObj->addComponent<PortHoleInteraction>();
328  portHoleInteraction->setTool(rightToolObj);
329  portHoleInteraction->setPortHoleLocation(Vec3d(0.015, 0.092, -1.117));
330  auto sphere = std::make_shared<Sphere>(Vec3d(0.015, 0.092, -1.117), 0.01);
331  auto rightPortVisuals = rightToolObj->addComponent<VisualModel>();
332  rightPortVisuals->setGeometry(sphere);
333  portHoleInteraction->setToolGeometry(rightToolObj->getCollidingGeometry());
334  portHoleInteraction->setCompliance(0.000001);
335 
336  auto portHoleInteraction2 = leftToolObj->addComponent<PortHoleInteraction>();
337  portHoleInteraction2->setTool(leftToolObj);
338  portHoleInteraction2->setPortHoleLocation(Vec3d(-0.065, 0.078, -1.127));
339  auto sphere2 = std::make_shared<Sphere>(Vec3d(-0.065, 0.078, -1.127), 0.01);
340  auto leftPortVisuals = leftToolObj->addComponent<VisualModel>();
341  leftPortVisuals->setGeometry(sphere2);
342  portHoleInteraction2->setToolGeometry(leftToolObj->getCollidingGeometry());
343  portHoleInteraction2->setCompliance(0.000001);
344 
345  // Run the simulation
346  {
347  // Setup a viewer to render in its own thread
348  auto viewer = std::make_shared<VTKOpenVRViewer>();
349  viewer->setActiveScene(scene);
350  viewer->setDebugAxesLength(0.1, 0.1, 0.1);
351 
352  // Setup a scene manager to advance the scene
353  auto sceneManager = std::make_shared<SceneManager>();
354  sceneManager->setActiveScene(scene);
355 
356  auto driver = std::make_shared<SimulationManager>();
357  driver->addModule(viewer);
358  driver->addModule(sceneManager);
359  driver->setDesiredDt(0.001);
360 
361  auto leftController = leftHandObj->getComponent<PbdObjectController>();
362  std::shared_ptr<OpenVRDeviceClient> leftDeviceClient = viewer->getVRDeviceClient(OPENVR_LEFT_CONTROLLER);
363  leftController->setDevice(leftDeviceClient);
364 
365  auto rightController = rightHandObj->getComponent<PbdObjectController>();
366  std::shared_ptr<OpenVRDeviceClient> rightDeviceClient = viewer->getVRDeviceClient(OPENVR_RIGHT_CONTROLLER);
367  rightController->setDevice(rightDeviceClient);
368 
369  connect<ButtonEvent>(rightDeviceClient, &DeviceClient::buttonStateChanged,
370  [&](ButtonEvent* e)
371  {
372  // Right trigger
373  if (e->m_button == 7)
374  {
375  if (e->m_buttonState == BUTTON_PRESSED)
376  {
377  if (rightToolGrasping->hasConstraints())
378  {
379  rightToolGrasping->endGrasp();
380  }
381  else
382  {
383  rightToolGrasping->beginCellGrasp(
384  std::dynamic_pointer_cast<Sphere>(rightHandObj->getCollidingGeometry()));
385  }
386  }
387  }
388  // Right grip
389  else if (e->m_button == 5)
390  {
391  viewer->setRenderingMode(Renderer::Mode::Debug);
392  if (e->m_buttonState == BUTTON_PRESSED)
393  {
394  // Use a slighty larger capsule at the tip
395  auto graspCapsule = std::dynamic_pointer_cast<Capsule>(rightToolObj->getVisualModel(1)->getGeometry());
396  rightNeedleGrasping->beginCellGrasp(graspCapsule);
397  rightThreadGrasping->beginCellGrasp(graspCapsule);
398  }
399  else if (e->m_buttonState == BUTTON_RELEASED)
400  {
401  rightNeedleGrasping->endGrasp();
402  rightThreadGrasping->endGrasp();
403  }
404  }
405  });
406  connect<ButtonEvent>(leftDeviceClient, &DeviceClient::buttonStateChanged,
407  [&](ButtonEvent* e)
408  {
409  // Left trigger
410  if (e->m_button == 6)
411  {
412  if (e->m_buttonState == BUTTON_PRESSED)
413  {
414  if (leftToolGrasping->hasConstraints())
415  {
416  leftToolGrasping->endGrasp();
417  }
418  else
419  {
420  leftToolGrasping->beginCellGrasp(
421  std::dynamic_pointer_cast<Sphere>(leftHandObj->getCollidingGeometry()));
422  }
423  }
424  }
425  // Left grip
426  if (e->m_button == 4)
427  {
428  if (e->m_buttonState == BUTTON_PRESSED)
429  {
430  // Use a slighty larger capsule at the tip
431  auto graspCapsule = std::dynamic_pointer_cast<Capsule>(leftToolObj->getVisualModel(1)->getGeometry());
432  leftNeedleGrasping->beginCellGrasp(graspCapsule);
433  leftThreadGrasping->beginCellGrasp(graspCapsule);
434  }
435  else if (e->m_buttonState == BUTTON_RELEASED)
436  {
437  leftNeedleGrasping->endGrasp();
438  leftThreadGrasping->endGrasp();
439  }
440  }
441  });
442 
443  // Add default mouse and keyboard controls to the viewer
444  auto controls = std::make_shared<Entity>();
445  auto camControl = controls->addComponent<VRCameraControl>();
446  camControl->setRotateDevice(viewer->getVRDeviceClient(OPENVR_RIGHT_CONTROLLER));
447  camControl->setTranslateDevice(viewer->getVRDeviceClient(OPENVR_LEFT_CONTROLLER));
448  camControl->setTranslateSpeedScale(1.0);
449  camControl->setRotateSpeedScale(1.0);
450  camControl->setCamera(scene->getActiveCamera());
451  scene->addSceneObject(controls);
452 
453  connect<Event>(sceneManager, &SceneManager::preUpdate,
454  [&](Event*)
455  {
456  model->getConfig()->m_dt = sceneManager->getDt();
457  });
458 
459  driver->start();
460  }
461 
462  return 0;
463 }
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.
std::shared_ptr< LineMesh > toLineGrid(const Vec3d &start, const Vec3d &dir, const double length, const int dim)
Creates a set of connected lines.
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...
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.
Defines a control scheme to move the camera with joysticks. Relative to world space with Y up...
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.