iMSTK
Interactive Medical Simulation Toolkit
TwoDevicesExample.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 "imstkKeyboardDeviceClient.h"
12 #include "imstkMeshIO.h"
13 #include "imstkMouseDeviceClient.h"
14 #include "imstkMouseSceneControl.h"
15 #include "imstkObjectControllerGhost.h"
16 #include "imstkOrientedBox.h"
17 #include "imstkPbdModel.h"
18 #include "imstkPbdModelConfig.h"
19 #include "imstkPbdObject.h"
20 #include "imstkPbdObjectCollision.h"
21 #include "imstkPbdObjectController.h"
22 #include "imstkPlane.h"
23 #include "imstkPointwiseMap.h"
24 #include "imstkRenderMaterial.h"
25 #include "imstkScene.h"
26 #include "imstkSceneManager.h"
27 #include "imstkSimulationManager.h"
28 #include "imstkVisualModel.h"
29 
30 #include "imstkPbdDistanceConstraint.h"
31 #include "imstkPbdRigidObjectGrasping.h"
32 
33 #include "imstkCapsule.h"
34 
35 #ifdef iMSTK_USE_RENDERING_VTK
36 #include "imstkKeyboardSceneControl.h"
37 #include "imstkSimulationUtils.h"
38 #include "imstkVTKViewer.h"
39 #include "imstkVTKRenderer.h"
40 #endif
41 
42 using namespace imstk;
43 
47 std::shared_ptr<PbdObject>
48 makeOrgan(const std::string& name, std::shared_ptr<PbdModel> model)
49 {
50  // Setup the Geometry
51  //NOTE: Replace with path to stomach
52  // auto tissueMesh = MeshIO::read<TetrahedralMesh>(iMSTK_DATA_ROOT "/Organs/Stomach/stomach.msh");
53  auto tissueMesh = MeshIO::read<TetrahedralMesh>("./stomach.msh");
54  const Vec3d center = tissueMesh->getCenter();
55  tissueMesh->translate(-center, Geometry::TransformType::ApplyToData);
56  tissueMesh->scale(1.0, Geometry::TransformType::ApplyToData);
57  tissueMesh->rotate(Vec3d(0.0, 0.0, 1.0), 30.0 / 180.0 * 3.14, Geometry::TransformType::ApplyToData);
58 
59  const Vec3d shift = { 0.0, 0.01, 0.0 }; // use this to offset organ posiiton
60  tissueMesh->translate(shift, Geometry::TransformType::ApplyToData);
61 
62  auto surfMesh = tissueMesh->extractSurfaceMesh();
63 
64  // Setup the material
65  auto material = std::make_shared<RenderMaterial>();
66  material->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);
67  material->setBackFaceCulling(false);
68  material->setOpacity(0.5);
69 
70  // Add a visual model to render the tet mesh
71  auto visualModel = std::make_shared<VisualModel>();
72  visualModel->setGeometry(surfMesh);
73  visualModel->setRenderMaterial(material);
74 
75  // Setup the Object
76  auto tissueObj = std::make_shared<PbdObject>(name);
77  tissueObj->addVisualModel(visualModel);
78  //tissueObj->addVisualModel(labelModel);
79  tissueObj->setPhysicsGeometry(tissueMesh);
80  tissueObj->setCollidingGeometry(surfMesh);
81  tissueObj->setDynamicalModel(model);
82 
83  tissueObj->setPhysicsToCollidingMap(std::make_shared<PointwiseMap>(tissueMesh, surfMesh));
84 
85  // Gallblader is about 60g
86  // NOTE: Replace with parameters used in hernia
87  tissueObj->getPbdBody()->uniformMassValue = 0.6 / tissueMesh->getNumVertices();
88 
89  model->getConfig()->m_femParams->m_YoungModulus = 108000.0;
90  model->getConfig()->m_femParams->m_PoissonRatio = 0.4;
91  model->getConfig()->enableFemConstraint(PbdFemConstraint::MaterialType::NeoHookean);
92  model->getConfig()->setBodyDamping(tissueObj->getPbdBody()->bodyHandle, 0.01);
93 
94  // Define box to set up boundary conditions
95  // NOTE: Move this box to constrain point on the stomach
96  Vec3d boxPos = { 0.0, 0.0, 0.1 }; // center of box
97  Vec3d boxSize = { 0.1, 0.1, 0.15 }; // edge length of box
98 
99  // Fix the borders using constraints if point is within the defined box
100  std::shared_ptr<VecDataArray<double, 3>> vertices = tissueMesh->getVertexPositions();
101  for (int i = 0; i < tissueMesh->getNumVertices(); i++)
102  {
103  const Vec3d& pos = (*vertices)[i];
104  if (pos[0] < boxPos[0] + (boxSize[0] / 2.0) && pos[0] > boxPos[0] - (boxSize[0] / 2.0)
105  && pos[1] < boxPos[1] + (boxSize[1] / 2.0) && pos[1] > boxPos[1] - (boxSize[1] / 2.0)
106  && pos[2] < boxPos[2] + (boxSize[2] / 2.0) && pos[2] > boxPos[2] - (boxSize[2] / 2.0))
107  {
108  auto newPt = model->addVirtualParticle(pos, 0, Vec3d::Zero(), true);
109 
110  PbdParticleId vertex = { tissueObj->getPbdBody()->bodyHandle, i };
111  auto constraint = std::make_shared<PbdDistanceConstraint>();
112  constraint->initConstraint(0, newPt, vertex, 10.0);
113 
114  model->getConstraints()->addConstraint(constraint);
115  }
116  }
117 
118  LOG(INFO) << "Per particle mass: " << tissueObj->getPbdBody()->uniformMassValue;
119 
120  tissueObj->initialize();
121 
122  return tissueObj;
123 }
124 
125 std::shared_ptr<PbdObject>
126 makeTool(std::shared_ptr<DeviceClient> deviceClient)
127 {
128  // The visual geometry is the scissor mesh read in from file
129  auto rbdObj = std::make_shared<PbdObject>();
130  auto model = std::make_shared<PbdModel>();
131  model->getConfig()->m_dt = 0.001;
132  model->getConfig()->m_gravity = Vec3d::Zero();
133  rbdObj->setDynamicalModel(model);
134  rbdObj->getPbdBody()->setRigid(
135  Vec3d(0.0, 0.05, 0.0), // Position
136  7.0, // Mass
137  Quatd::Identity(), // Orientation
138  Mat3d::Identity() * 100000000.0); // Inertia
139 
140  auto surfMesh = MeshIO::read<SurfaceMesh>(iMSTK_DATA_ROOT "/Surgical Instruments/Scissors/Metzenbaum Scissors/Metz_Scissors.stl");
141  rbdObj->setCollidingGeometry(surfMesh);
142  rbdObj->setVisualGeometry(surfMesh);
143  rbdObj->setPhysicsGeometry(surfMesh);
144 
145  std::shared_ptr<RenderMaterial> mat = rbdObj->getVisualModel(0)->getRenderMaterial();
146  mat->setShadingModel(RenderMaterial::ShadingModel::PBR);
147  mat->setRoughness(0.5);
148  mat->setMetalness(1.0);
149  mat->setIsDynamicMesh(false);
150 
151  // Add a component for controlling via another device
152  auto controller = rbdObj->addComponent<PbdObjectController>();
153  controller->setControlledObject(rbdObj);
154  controller->setDevice(deviceClient);
155  controller->setTranslationOffset(Vec3d(0.0, 0.05, 0.0));
156  controller->setLinearKs(50000.0);
157  controller->setAngularKs(1000000000000.0);
158  controller->setTranslationScaling(1.0);
159  controller->setForceScaling(0.005);
160  controller->setSmoothingKernelSize(10);
161  controller->setUseForceSmoothening(true);
162  controller->setUseCritDamping(true);
163 
164  // Add extra component to tool for the ghost
165  auto controllerGhost = rbdObj->addComponent<ObjectControllerGhost>();
166  controllerGhost->setController(controller);
167  return rbdObj;
168 }
169 
173 static std::shared_ptr<PbdObject>
174 makeCapsuleToolObj(std::shared_ptr<PbdModel> model, std::shared_ptr<DeviceClient> deviceClient, double shiftX)
175 {
176  double radius = 0.005;
177  double length = 0.2;
178  double mass = 0.02;
179 
180  auto toolGeometry = std::make_shared<Capsule>();
181  // auto toolGeometry = std::make_shared<Sphere>();
182  toolGeometry->setRadius(radius);
183  toolGeometry->setLength(length);
184  toolGeometry->setPosition(Vec3d(shiftX, 0.0, 0.0));
185  toolGeometry->setOrientation(Quatd(0.707, 0.707, 0.0, 0.0));
186 
187  LOG(INFO) << "Tool Radius = " << radius;
188  LOG(INFO) << "Tool mass = " << mass;
189 
190  auto toolObj = std::make_shared<PbdObject>("Tool");
191 
192  // Create the object
193  toolObj->setVisualGeometry(toolGeometry);
194  toolObj->setPhysicsGeometry(toolGeometry);
195  toolObj->setCollidingGeometry(toolGeometry);
196  toolObj->setDynamicalModel(model);
197  toolObj->getPbdBody()->setRigid(
198  Vec3d(0.04, 0.0, 0.0),
199  mass,
200  Quatd::Identity(),
201  Mat3d::Identity() * 1.0);
202 
203  toolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(1.0);
204 
205  // Add a component for controlling via another device
206  auto controller = toolObj->addComponent<PbdObjectController>();
207  controller->setControlledObject(toolObj);
208  controller->setDevice(deviceClient);
209  controller->setHapticOffset(Vec3d(0.0, 0.0, -0.1));
210  controller->setTranslationScaling(1.0);
211  controller->setLinearKs(1000.0);
212  controller->setAngularKs(10000.0);
213  controller->setUseCritDamping(true);
214  controller->setForceScaling(1.0);
215  controller->setSmoothingKernelSize(15);
216  controller->setUseForceSmoothening(true);
217 
218  // Add extra component to tool for the ghost
219  auto controllerGhost = toolObj->addComponent<ObjectControllerGhost>();
220  controllerGhost->setController(controller);
221 
222  return toolObj;
223 }
224 
230 int
231 main(int argc, char* argv[])
232 {
233  int deviceCount = 2;
234  if (argc > 1)
235  {
236  std::string arg(argv[1]);
237  std::cout << "Device count: " << arg << std::endl;
238  deviceCount = std::min(2, std::max(0, std::stoi(arg)));
239  }
240 
241  // Setup logger (write to file and stdout)
243 
244  // Setup default haptics manager
245  std::shared_ptr<DeviceManager> hapticManager = DeviceManagerFactory::makeDeviceManager();
246  std::vector<std::string> deviceName = { "Right Device", "Left Device" };
247  std::vector<std::shared_ptr<DeviceClient>> deviceClients;
248 
249  auto pbdModel = std::make_shared<PbdModel>();
250  std::shared_ptr<PbdModelConfig> pbdParams = pbdModel->getConfig();
251  pbdParams->m_gravity = Vec3d(0.0, -1.0, 0.0);
252  pbdParams->m_dt = 0.002;
253  pbdParams->m_iterations = 1;
254  pbdParams->m_linearDampingCoeff = 0.03;
255 
256  for (int i = 0; i < deviceCount; i++)
257  {
258  deviceClients.push_back(hapticManager->makeDeviceClient(deviceName[i]));
259  }
260 
261  // Scene
262  auto scene = std::make_shared<Scene>("VirtualCoupling");
263  scene->getActiveCamera()->setPosition(Vec3d(0.0, 0.2, 0.35));
264  scene->getActiveCamera()->setFocalPoint(Vec3d(0.0, 0.0, 0.0));
265  scene->getActiveCamera()->setViewUp(Vec3d(0.0, 1.0, 0.0));
266 
267  std::shared_ptr<CollidingObject> obstacleObjs[] =
268  {
269  std::make_shared<CollidingObject>("Plane"),
270  // std::make_shared<CollidingObject>("Cube")
271  };
272 
273  // Create a plane and cube for collision with scissors
274  auto plane = std::make_shared<Plane>();
275  plane->setWidth(0.4);
276  obstacleObjs[0]->setVisualGeometry(plane);
277  obstacleObjs[0]->setCollidingGeometry(plane);
278 
279  // Read in organ mesh and set up as PBD deformable
280  std::shared_ptr<PbdObject> stomach = makeOrgan("Stomach", pbdModel);
281  scene->addSceneObject(stomach);
282 
283  for (auto obj : obstacleObjs)
284  {
285  obj->getVisualModel(0)->getRenderMaterial()->setIsDynamicMesh(false);
286  scene->addSceneObject(obj);
287  }
288 
289  std::vector<std::shared_ptr<PbdObject>> toolObjs;
290 
291  for (int i = 0; i < deviceCount; i++)
292  {
293  // auto tool = makeTool(client);
294  auto tool = makeCapsuleToolObj(pbdModel, deviceClients[i], 0.1 + ((double)i / 10.0));
295  scene->addSceneObject(tool);
296 
297  toolObjs.push_back(tool);
298  }
299 
300  // Add collision between the tools and the floor
301  //for (auto client : deviceClients)
302  //{
303  // // auto tool = makeTool(client);
304  // auto tool = makeCapsuleToolObj(pbdModel, client, 0.1);
305  // scene->addSceneObject(tool);
306 
307  // toolObjs.push_back(tool);
308  //}
309 
310  // Add collision between tools and organ
311  for (auto tool : toolObjs)
312  {
313  scene->addInteraction(std::make_shared<PbdObjectCollision>(stomach, tool));
314  }
315 
316  // Add collision between stomach and floor
317  for (auto obj : obstacleObjs)
318  {
319  scene->addInteraction(std::make_shared<PbdObjectCollision>(stomach, obj));
320  }
321 
322  std::vector<std::shared_ptr<PbdObjectGrasping>> grasping;
323 
324  for (auto tool : toolObjs)
325  {
326  auto grasp = std::make_shared<PbdObjectGrasping>(stomach, tool);
327  grasping.push_back(grasp);
328  scene->addInteraction(grasp);
329  }
330 
331  // Light
332  auto light = std::make_shared<DirectionalLight>();
333  light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
334  light->setIntensity(1.0);
335  scene->addLight("light0", light);
336 
337  // Run the simulation
338  {
339  // Setup a scene manager to advance the scene
340  auto sceneManager = std::make_shared<SceneManager>();
341  sceneManager->setActiveScene(scene);
342  sceneManager->setPaused(true); // Start paused
343 
344  auto driver = std::make_shared<SimulationManager>();
345  driver->addModule(hapticManager);
346 #ifdef iMSTK_USE_RENDERING_VTK
347  // Setup a viewer to render
348  auto viewer = std::make_shared<VTKViewer>();
349  viewer->setActiveScene(scene);
350  viewer->setDebugAxesLength(0.1, 0.1, 0.1);
351 
352  driver->addModule(viewer);
353 #endif
354  driver->addModule(sceneManager);
355  driver->setDesiredDt(0.001);
356 
357  connect<Event>(sceneManager, &SceneManager::preUpdate, [&](Event*)
358  {
359  for (auto tool : toolObjs)
360  {
361  tool->getPbdModel()->getConfig()->m_dt = driver->getDt();
362  }
363  });
364 
365  // grasping: NOTE: For some reason I cant set up the ButtonEvents connects in a loop. If you want both devices to grasp then copy paste and set [0] to [1]
366  connect<ButtonEvent>(deviceClients[0], &DeviceClient::buttonStateChanged,
367  [&](ButtonEvent* e)
368  {
369  if (e->m_buttonState == BUTTON_PRESSED)
370  {
371  if (e->m_button == 1)
372  {
373  // Use a slightly larger capsule since collision prevents intersection
374  auto capsule = std::dynamic_pointer_cast<Capsule>(toolObjs[0]->getCollidingGeometry());
375  auto dilatedCapsule = std::make_shared<Capsule>(*capsule);
376  dilatedCapsule->setRadius(capsule->getRadius() * 1.1);
377  grasping[0]->beginVertexGrasp(std::dynamic_pointer_cast<Capsule>(dilatedCapsule));
378  }
379  }
380  else if (e->m_buttonState == BUTTON_RELEASED)
381  {
382  if (e->m_button == 1)
383  {
384  grasping[0]->endGrasp();
385  }
386  }
387  });
388 
389  // Add mouse and keyboard controls to the viewer
390 #ifdef iMSTK_USE_RENDERING_VTK
391  // Add default mouse and keyboard controls to the viewer
392  std::shared_ptr<Entity> mouseAndKeyControls =
393  SimulationUtils::createDefaultSceneControl(driver);
394  scene->addSceneObject(mouseAndKeyControls);
395 #endif
396 
397  driver->start();
398  }
399 
400  return 0;
401 }
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...
Compound Geometry.
static std::shared_ptr< DeviceManager > makeDeviceManager()
Attempts to create a new DeviceManager by whichever is default If multiple haptic managers are built ...
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.