iMSTK
Interactive Medical Simulation Toolkit
PBDHapticGraspingExample.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 "imstkControllerForceText.h"
10 #include "imstkDirectionalLight.h"
11 #include "imstkGeometryUtilities.h"
12 #include "imstkKeyboardDeviceClient.h"
13 #include "imstkKeyboardSceneControl.h"
14 #include "imstkMouseDeviceClient.h"
15 #include "imstkMouseSceneControl.h"
16 #include "imstkObjectControllerGhost.h"
17 #include "imstkPbdModel.h"
18 #include "imstkPbdModelConfig.h"
19 #include "imstkPbdObject.h"
20 #include "imstkPbdObjectCollision.h"
21 #include "imstkPbdObjectController.h"
22 #include "imstkPbdRigidObjectGrasping.h"
23 #include "imstkPointwiseMap.h"
24 #include "imstkRenderMaterial.h"
25 #include "imstkScene.h"
26 #include "imstkSceneManager.h"
27 #include "imstkSimulationManager.h"
28 #include "imstkSimulationUtils.h"
29 #include "imstkVisualModel.h"
30 #include "imstkVTKViewer.h"
31 #include "imstkMeshIO.h"
32 
33 #include "imstkImageData.h"
34 
35 #ifdef iMSTK_USE_HAPTICS
36 #include "imstkDeviceManager.h"
37 #include "imstkDeviceManagerFactory.h"
38 #else
39 #include "imstkDummyClient.h"
40 #endif
41 
42 using namespace imstk;
43 
47 std::shared_ptr<PbdObject>
48 makeGallBladder(const std::string& name, std::shared_ptr<PbdModel> model)
49 {
50  // Setup the Geometry
51  auto tissueMesh = MeshIO::read<TetrahedralMesh>(iMSTK_DATA_ROOT "/Organs/Gallblader/gallblader.msh");
52  const Vec3d center = tissueMesh->getCenter();
53  tissueMesh->translate(-center, Geometry::TransformType::ApplyToData);
54  tissueMesh->scale(1.0, Geometry::TransformType::ApplyToData);
55  tissueMesh->rotate(Vec3d(0.0, 0.0, 1.0), 30.0 / 180.0 * 3.14, Geometry::TransformType::ApplyToData);
56 
57  const Vec3d shift = { -0.0, 0.0, 0.0 };
58  tissueMesh->translate(shift, Geometry::TransformType::ApplyToData);
59 
60  auto surfMesh = tissueMesh->extractSurfaceMesh();
61 
62  // Setup the material
63  auto material = std::make_shared<RenderMaterial>();
64  material->setDisplayMode(RenderMaterial::DisplayMode::WireframeSurface);
65  material->setBackFaceCulling(false);
66  material->setOpacity(0.5);
67 
68  // Add a visual model to render the tet mesh
69  auto visualModel = std::make_shared<VisualModel>();
70  visualModel->setGeometry(surfMesh);
71  visualModel->setRenderMaterial(material);
72 
73  // Setup the Object
74  auto tissueObj = std::make_shared<PbdObject>(name);
75  tissueObj->addVisualModel(visualModel);
76  //tissueObj->addVisualModel(labelModel);
77  tissueObj->setPhysicsGeometry(tissueMesh);
78  tissueObj->setCollidingGeometry(surfMesh);
79  tissueObj->setDynamicalModel(model);
80 
81  tissueObj->setPhysicsToCollidingMap(std::make_shared<PointwiseMap>(tissueMesh, surfMesh));
82 
83  // Gallblader is about 60g
84  tissueObj->getPbdBody()->uniformMassValue = 0.6 / tissueMesh->getNumVertices();
85 
86  model->getConfig()->m_femParams->m_YoungModulus = 108000.0;
87  model->getConfig()->m_femParams->m_PoissonRatio = 0.4;
88  model->getConfig()->enableFemConstraint(PbdFemConstraint::MaterialType::NeoHookean);
89  model->getConfig()->setBodyDamping(tissueObj->getPbdBody()->bodyHandle, 0.01);
90 
91  // tissueObj->getPbdBody()->fixedNodeIds = { 57, 131, 132 }; // { 72, , 131, 132 };
92 
93  // Fix the borders
94  std::shared_ptr<VecDataArray<double, 3>> vertices = tissueMesh->getVertexPositions();
95  for (int i = 0; i < tissueMesh->getNumVertices(); i++)
96  {
97  const Vec3d& pos = (*vertices)[i];
98  if (pos[1] >= 0.016)
99  {
100  tissueObj->getPbdBody()->fixedNodeIds.push_back(i);
101  }
102  }
103 
104  LOG(INFO) << "Per particle mass: " << tissueObj->getPbdBody()->uniformMassValue;
105 
106  tissueObj->initialize();
107 
108  return tissueObj;
109 }
110 
114 static std::shared_ptr<PbdObject>
115 makePbdObjCube(
116  const std::string& name,
117  std::shared_ptr<PbdModel> model,
118  const Vec3d& size,
119  const Vec3i& dim,
120  const Vec3d& center)
121 {
122  auto prismObj = std::make_shared<PbdObject>(name);
123 
124  // Setup the Geometry
125  std::shared_ptr<TetrahedralMesh> prismMesh = GeometryUtils::toTetGrid(center, size, dim);
126  std::shared_ptr<SurfaceMesh> surfMesh = prismMesh->extractSurfaceMesh();
127 
128  // Setup the Object
129  prismObj->setPhysicsGeometry(prismMesh);
130  prismObj->setCollidingGeometry(surfMesh);
131  prismObj->setVisualGeometry(surfMesh);
132  prismObj->getVisualModel(0)->getRenderMaterial()->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
133  prismObj->setDynamicalModel(model);
134  // prismObj->getPbdBody()->uniformMassValue = 0.05;
135  prismObj->getPbdBody()->uniformMassValue = 0.003; // 0.06 / surfMesh->getNumVertices();
136 
137  prismObj->setPhysicsToCollidingMap(std::make_shared<PointwiseMap>(prismMesh, surfMesh));
138 
139  //model->getConfig()->enableConstraint(PbdModelConfig::ConstraintGenType::Distance,
140  // 1e4,
141  // prismObj->getPbdBody()->bodyHandle);
142  //model->getConfig()->enableConstraint(PbdModelConfig::ConstraintGenType::Dihedral,
143  // 0.1,
144  // prismObj->getPbdBody()->bodyHandle);
145 
146  //prismObj->initialize();
147  model->getConfig()->m_femParams->m_YoungModulus = 6000.0;
148  model->getConfig()->m_femParams->m_PoissonRatio = 0.4;
149  model->getConfig()->enableFemConstraint(PbdFemConstraint::MaterialType::NeoHookean);
150  model->getConfig()->setBodyDamping(prismObj->getPbdBody()->bodyHandle, 0.001);
151 
152  std::shared_ptr<VecDataArray<int, 3>> indicesPtr = surfMesh->getCells();
153  const VecDataArray<int, 3>& indices = *indicesPtr;
154  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = surfMesh->getVertexPositions();
155  const VecDataArray<double, 3>& verts = *verticesPtr;
156 
157  double avgArea = 0.0;
158  for (int i = 0; i < surfMesh->getNumCells(); i++)
159  {
160  const Vec3i& cell = indices[i];
161  const Vec3d& p0 = verts[cell[0]];
162  const Vec3d& p1 = verts[cell[1]];
163  const Vec3d& p2 = verts[cell[2]];
164  avgArea += 0.5 * (p1 - p0).cross(p2 - p0).norm();
165  }
166 
167  avgArea /= surfMesh->getNumCells();
168  LOG(INFO) << "Average Cell Area = " << avgArea;
169  LOG(INFO) << "Cell Characteristic Length = " << sqrt(avgArea);
170  LOG(INFO) << "Per node mass = " << prismObj->getPbdBody()->uniformMassValue;
171 
172  // Fix the borders
173  std::shared_ptr<VecDataArray<double, 3>> vertices = prismMesh->getVertexPositions();
174  for (int i = 0; i < prismMesh->getNumVertices(); i++)
175  {
176  const Vec3d& pos = (*vertices)[i];
177  if (pos[1] <= center[1] - size[1] * 0.5)
178  {
179  prismObj->getPbdBody()->fixedNodeIds.push_back(i);
180  }
181  }
182 
183  return prismObj;
184 }
185 
189 static std::shared_ptr<PbdObject>
190 makeTissueObj(const std::string& name,
191  std::shared_ptr<PbdModel> model,
192  const double width,
193  const double height,
194  const int rowCount,
195  const int colCount)
196 {
197  // Setup the Geometry
198  std::shared_ptr<SurfaceMesh> surfMesh =
199  GeometryUtils::toTriangleGrid(Vec3d::Zero(),
200  Vec2d(width, height), Vec2i(rowCount, colCount));
201 
202  // Setup the Object
203  auto pbdObject = std::make_shared<PbdObject>(name);
204 
205  pbdObject->setVisualGeometry(surfMesh);
206  pbdObject->getVisualModel(0)->getRenderMaterial()->setDisplayMode(RenderMaterial::DisplayMode::Wireframe);
207  pbdObject->setPhysicsGeometry(surfMesh);
208  pbdObject->setCollidingGeometry(surfMesh);
209  pbdObject->setDynamicalModel(model);
210  pbdObject->getPbdBody()->uniformMassValue = 0.003; // 0.06 / surfMesh->getNumVertices();
211  for (int x = 0; x < rowCount; x++)
212  {
213  for (int y = 0; y < colCount; y++)
214  {
215  if (x == 0 || y == 0 || y == colCount - 1) //|| x == rowCount - 1
216  {
217  pbdObject->getPbdBody()->fixedNodeIds.push_back(x * colCount + y);
218  }
219  }
220  }
221 
222  std::shared_ptr<VecDataArray<int, 3>> indicesPtr = surfMesh->getCells();
223  const VecDataArray<int, 3>& indices = *indicesPtr;
224  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = surfMesh->getVertexPositions();
225  const VecDataArray<double, 3>& vertices = *verticesPtr;
226 
227  double avgArea = 0.0;
228  for (int i = 0; i < surfMesh->getNumCells(); i++)
229  {
230  const Vec3i& cell = indices[i];
231  const Vec3d& p0 = vertices[cell[0]];
232  const Vec3d& p1 = vertices[cell[1]];
233  const Vec3d& p2 = vertices[cell[2]];
234  avgArea += 0.5 * (p1 - p0).cross(p2 - p0).norm();
235  }
236 
237  avgArea /= surfMesh->getNumCells();
238  LOG(INFO) << "Average Cell Area = " << avgArea;
239  LOG(INFO) << "Cell Characteristic Length = " << sqrt(avgArea);
240  LOG(INFO) << "Per node mass = " << pbdObject->getPbdBody()->uniformMassValue;
241 
242  return pbdObject;
243 }
244 
248 static std::shared_ptr<PbdObject>
249 makeCapsuleToolObj(std::shared_ptr<PbdModel> model)
250 {
251  double radius = 0.005;
252  double length = 0.2;
253  double mass = 0.02;
254 
255  auto toolGeometry = std::make_shared<Capsule>();
256  // auto toolGeometry = std::make_shared<Sphere>();
257  toolGeometry->setRadius(radius);
258  toolGeometry->setLength(length);
259  toolGeometry->setPosition(Vec3d(0.0, 0.0, 0.0));
260  toolGeometry->setOrientation(Quatd(0.707, 0.707, 0.0, 0.0));
261 
262  LOG(INFO) << "Tool Radius = " << radius;
263  LOG(INFO) << "Tool mass = " << mass;
264 
265  auto toolObj = std::make_shared<PbdObject>("Tool");
266 
267  // Create the object
268  toolObj->setVisualGeometry(toolGeometry);
269  toolObj->setPhysicsGeometry(toolGeometry);
270  toolObj->setCollidingGeometry(toolGeometry);
271  toolObj->setDynamicalModel(model);
272  toolObj->getPbdBody()->setRigid(
273  Vec3d(0.04, 0.0, 0.0),
274  mass,
275  Quatd::Identity(),
276  Mat3d::Identity() * 1.0);
277 
278  toolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(1.0);
279 
280  // Add a component for controlling via another device
281  auto controller = toolObj->addComponent<PbdObjectController>();
282  controller->setControlledObject(toolObj);
283  controller->setHapticOffset(Vec3d(0.0, 0.0, -0.1));
284  controller->setTranslationScaling(1.0);
285  controller->setLinearKs(1000.0);
286  controller->setAngularKs(10000.0);
287  controller->setUseCritDamping(true);
288  controller->setForceScaling(1.0);
289  controller->setSmoothingKernelSize(15);
290  controller->setUseForceSmoothening(true);
291 
292  // Add extra component to tool for the ghost
293  auto controllerGhost = toolObj->addComponent<ObjectControllerGhost>();
294  controllerGhost->setController(controller);
295 
296  return toolObj;
297 }
298 
303 int
304 main()
305 {
306  // Setup logger (write to file and stdout)
308 
309  // Setup the scene
310  auto scene = std::make_shared<Scene>("PbdHapticGrasping");
311  scene->getActiveCamera()->setPosition(0.00610397, 0.131126, 0.281497);
312  scene->getActiveCamera()->setFocalPoint(0.0, 0.0, 0.0);
313  scene->getActiveCamera()->setViewUp(0.00251247, 0.90946, -0.415783);
314 
315  auto pbdModel = std::make_shared<PbdModel>();
316  std::shared_ptr<PbdModelConfig> pbdParams = pbdModel->getConfig();
317  pbdParams->m_gravity = Vec3d(0.0, 0.0, 0.0);
318  pbdParams->m_dt = 0.002;
319  pbdParams->m_iterations = 2;
320  pbdParams->m_linearDampingCoeff = 0.03;
321  /* pbdParams->enableConstraint(PbdModelConfig::ConstraintGenType::Distance, 1.0e2);
322  pbdParams->enableConstraint(PbdModelConfig::ConstraintGenType::Dihedral, 1.0e1);*/
323 
324  // Setup a gallbladder
325  //std::shared_ptr<PbdObject> pbdObj = makeGallBladder("Gallbladder", pbdModel);
326  //scene->addSceneObject(pbdObj);
327 
328  Vec3d size = Vec3d(0.10, 0.08, 0.10);
329  Vec3i dim = Vec3i(18, 4, 18);
330  Vec3d center = Vec3d(0.0, -0.05, 0.0);
331  std::shared_ptr<PbdObject> pbdObj = makePbdObjCube("Cube", pbdModel, size, dim, center);
332  scene->addSceneObject(pbdObj);
333 
334  // int resolution = 35;
335  //std::shared_ptr<PbdObject> pbdObj = makeTissueObj("Plane", pbdModel, 0.15, 0.15, resolution, resolution);
336  //scene->addSceneObject(pbdObj);
337 
338  // Setup a tool to grasp with
339  std::shared_ptr<PbdObject> toolObj = makeCapsuleToolObj(pbdModel);
340  scene->addSceneObject(toolObj);
341 
342  // Add collision
343  auto pbdToolCollision = std::make_shared<PbdObjectCollision>(pbdObj, toolObj);
344  pbdToolCollision->setRigidBodyCompliance(0.0001); // Helps with smoothness
345  pbdToolCollision->setUseCorrectVelocity(true);
346  scene->addInteraction(pbdToolCollision);
347 
348  // Create new picking with constraints
349  auto toolPicking = std::make_shared<PbdObjectGrasping>(pbdObj, toolObj);
350  toolPicking->setStiffness(0.3);
351  scene->addInteraction(toolPicking);
352 
353  // Light
354  auto light = std::make_shared<DirectionalLight>();
355  light->setFocalPoint(Vec3d(5.0, -8.0, -5.0));
356  light->setIntensity(1.0);
357  scene->addLight("Light", light);
358 
359  // Run the simulation
360  {
361  // Setup a viewer to render
362  auto viewer = std::make_shared<VTKViewer>();
363  viewer->setActiveScene(scene);
364  viewer->setVtkLoggerMode(VTKViewer::VTKLoggerMode::MUTE);
365  viewer->setDebugAxesLength(0.01, 0.01, 0.01);
366 
367  // Setup a scene manager to advance the scene
368  auto sceneManager = std::make_shared<SceneManager>();
369  sceneManager->setActiveScene(scene);
370  sceneManager->pause(); // Start simulation paused
371 
372  auto driver = std::make_shared<SimulationManager>();
373  driver->addModule(viewer);
374  driver->addModule(sceneManager);
375  driver->setDesiredDt(0.002);
376 
377  auto controller = toolObj->getComponent<PbdObjectController>();
378  controller->setPosition(Vec3d(0.0, 0.0, 0.0));
379 
380 #ifdef iMSTK_USE_HAPTICS
381  // Setup default haptics manager
382  std::shared_ptr<DeviceManager> hapticManager = DeviceManagerFactory::makeDeviceManager();
383  if (hapticManager->getTypeName() == "HaplyDeviceManager")
384  {
385  controller->setTranslationOffset(Vec3d(2.0, 0.0, -2.0));
386  }
387  std::shared_ptr<DeviceClient> deviceClient = hapticManager->makeDeviceClient();
388  driver->addModule(hapticManager);
389 
390  connect<ButtonEvent>(deviceClient, &DeviceClient::buttonStateChanged,
391  [&](ButtonEvent* e)
392  {
393  if (e->m_buttonState == BUTTON_PRESSED)
394  {
395  if (e->m_button == 1)
396  {
397  // Use a slightly larger capsule since collision prevents intersection
398  auto capsule = std::dynamic_pointer_cast<Capsule>(toolObj->getCollidingGeometry());
399  auto dilatedCapsule = std::make_shared<Capsule>(*capsule);
400  dilatedCapsule->setRadius(capsule->getRadius() * 1.1);
401  toolPicking->beginVertexGrasp(dilatedCapsule);
402  pbdToolCollision->setEnabled(false);
403  }
404  }
405  else if (e->m_buttonState == BUTTON_RELEASED)
406  {
407  if (e->m_button == 1)
408  {
409  toolPicking->endGrasp();
410  pbdToolCollision->setEnabled(true);
411  }
412  }
413  });
414 #else
415  auto deviceClient = std::make_shared<DummyClient>();
416  connect<Event>(sceneManager, &SceneManager::postUpdate,
417  [&](Event*)
418  {
419  const Vec2d mousePos = viewer->getMouseDevice()->getPos();
420  const Vec3d worldPos = Vec3d(mousePos[0] - 0.5, mousePos[1] - 0.5, 0.0) * 0.1;
421 
422  deviceClient->setPosition(worldPos);
423  });
424 
425  // Add click event and side effects
426  connect<Event>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonPress,
427  [&](Event*)
428  {
429  toolPicking->beginVertexGrasp(std::dynamic_pointer_cast<Capsule>(toolObj->getCollidingGeometry()));
430  pbdToolCollision->setEnabled(false);
431  });
432  connect<Event>(viewer->getMouseDevice(), &MouseDeviceClient::mouseButtonRelease,
433  [&](Event*)
434  {
435  toolPicking->endGrasp();
436  pbdToolCollision->setEnabled(true);
437  });
438 #endif
439 
440  // Alternative grasping by keyboard (in case device doesn't have a button)
441  connect<KeyEvent>(viewer->getKeyboardDevice(), &KeyboardDeviceClient::keyPress,
442  [&](KeyEvent* e)
443  {
444  if (e->m_key == 'g')
445  {
446  auto capsule = std::dynamic_pointer_cast<Capsule>(toolObj->getCollidingGeometry());
447  auto dilatedCapsule = std::make_shared<Capsule>(*capsule);
448  dilatedCapsule->setRadius(capsule->getRadius() * 1.1);
449  toolPicking->beginVertexGrasp(dilatedCapsule);
450  pbdToolCollision->setEnabled(false);
451  }
452  });
453  connect<KeyEvent>(viewer->getKeyboardDevice(), &KeyboardDeviceClient::keyRelease,
454  [&](KeyEvent* e)
455  {
456  if (e->m_key == 'g')
457  {
458  toolPicking->endGrasp();
459  pbdToolCollision->setEnabled(true);
460  }
461  });
462  controller->setDevice(deviceClient);
463 
464  // Add default mouse and keyboard controls to the viewer
465  std::shared_ptr<Entity> mouseAndKeyControls =
466  SimulationUtils::createDefaultSceneControl(driver);
467 
468  // Add something to display controller force
469  auto controllerForceTxt = mouseAndKeyControls->addComponent<ControllerForceText>();
470  controllerForceTxt->setController(controller);
471  controllerForceTxt->setCollision(pbdToolCollision);
472 
473  scene->addSceneObject(mouseAndKeyControls);
474 
475  connect<Event>(sceneManager, &SceneManager::preUpdate, [&](Event*)
476  {
477  // Simulate in real time
478  pbdModel->getConfig()->m_dt = sceneManager->getDt();
479  });
480 
481  driver->start();
482  }
483 
484  return 0;
485 }
std::shared_ptr< SurfaceMesh > toTriangleGrid(const Vec3d &center, const Vec2d &size, const Vec2i &dim, const Quatd orientation=Quatd::Identity(), const double uvScale=1.0)
Produces a triangle grid on a plane given the imstkPlane.
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< TetrahedralMesh > toTetGrid(const Vec3d &center, const Vec3d &size, const Vec3i &divisions, const Quatd orientation=Quatd::Identity())
Produces a tetrahedral grid given the OrientedBox with the given divisions.
static std::shared_ptr< DeviceManager > makeDeviceManager()
Attempts to create a new DeviceManager by whichever is default If multiple haptic managers are built ...
Provides the information of a key event (press, release, & which key)
A behaviour that renders a second copy of the controlled object at a lower opacity in the physical po...
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.
void setController(std::shared_ptr< PbdObjectController > controller)
Get/Set the controller to display the device force of.
Displays virtual coupling force text in the top right.