iMSTK
Interactive Medical Simulation Toolkit
imstkPbdObjectController.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 "imstkPbdObjectController.h"
8 #include "imstkDeviceClient.h"
9 #include "imstkLogger.h"
10 #include "imstkPbdObject.h"
11 
12 #include <Eigen/Eigenvalues>
13 
14 namespace imstk
15 {
16 void
17 PbdObjectController::setControlledObject(std::shared_ptr<SceneObject> obj)
18 {
19  m_pbdObject = std::dynamic_pointer_cast<PbdObject>(obj);
20  CHECK(m_pbdObject != nullptr) << "Controlled object must be a PbdObject";
21  CHECK(m_pbdObject->getPbdBody()->bodyType == PbdBody::Type::RIGID)
22  << "PbdObjectController can only operate on pbd rigid bodies";
23  SceneObjectController::setControlledObject(obj);
24 }
25 
26 void
27 PbdObjectController::update(const double& dt)
28 {
29  if (m_deviceClient == nullptr)
30  {
31  return;
32  }
33  if (!updateTrackingData(dt))
34  {
35  LOG(WARNING) << "warning: could not update tracking info.";
36  return;
37  }
38 
39  if (m_pbdObject == nullptr)
40  {
41  return;
42  }
43 
44  // Implementation partially from otaduy lin's paper eq14
45  // "A Modular Haptic Rendering Algorithm for Stable and Transparent 6 - DOF Manipulation"
46  if (m_deviceClient->getTrackingEnabled() && m_useSpring)
47  {
48  // Move the virtual tool to the physical tool position on the first call
49  if (m_firstRun)
50  {
51  m_firstRun = false;
52  // Zero out external force/torque
53  m_pbdObject->getPbdBody()->externalForce = Vec3d(0.0, 0.0, 0.0);
54  m_pbdObject->getPbdBody()->externalTorque = Vec3d(0.0, 0.0, 0.0);
55  // Directly set position/rotation
56  (*m_pbdObject->getPbdBody()->vertices)[0] = getPosition();
57  (*m_pbdObject->getPbdBody()->orientations)[0] = getOrientation();
58 
59  m_inversionParams[0] = (m_invertFlags& InvertFlag::transX) ? -1.0 : 1.0;
60  m_inversionParams[1] = (m_invertFlags& InvertFlag::transY) ? -1.0 : 1.0;
61  m_inversionParams[2] = (m_invertFlags& InvertFlag::transZ) ? -1.0 : 1.0;
62 
63  return;
64  }
65 
66  const Vec3d& currPos = (*m_pbdObject->getPbdBody()->vertices)[0];
67  const Quatd& currOrientation = (*m_pbdObject->getPbdBody()->orientations)[0];
68  const Vec3d& currVelocity = (*m_pbdObject->getPbdBody()->velocities)[0];
69  const Vec3d& currAngularVelocity = (*m_pbdObject->getPbdBody()->angularVelocities)[0];
70  Vec3d& currForce = m_pbdObject->getPbdBody()->externalForce;
71  Vec3d& currTorque = m_pbdObject->getPbdBody()->externalTorque;
72 
73  const Vec3d& devicePos = getPosition();
74  const Quatd& deviceOrientation = getOrientation();
75  const Vec3d hapticOffsetLocal = currOrientation._transformVector(m_hapticOffset);
76 
77  // If using critical damping automatically compute kd
79  {
80  const double mass = (*m_pbdObject->getPbdBody()->masses)[0];
81  const double linearKs = m_linearKs.maxCoeff();
82 
83  const double fudge = 1.00;
84  m_linearKd = 2.0 * std::sqrt(mass * linearKs) * fudge;
85 
86  const Mat3d inertia = (*m_pbdObject->getPbdBody()->inertias)[0];
87  // Currently kd is not a 3d vector though it could be.
88  // So here we make an approximation. Either:
89  // - Use one colums eigenvalue (maxCoeff)
90  // - cbrt(eigenvalue0*eigenvalue1*eigenvalue2). (det)
91  // Both may behave weird on anistropic inertia tensors
92  //const double inertiaScale = inertia.eigenvalues().real().maxCoeff();
93  const double inertiaScale = std::cbrt(inertia.determinant());
94  const double angularKs = m_angularKs.maxCoeff();
95  m_angularKd = 2.0 * std::sqrt(inertiaScale * angularKs) * fudge;
96  }
97 
98  // If kd > 2 * sqrt(mass * ks); The system is overdamped (may be intentional)
99  // If kd < 2 * sqrt(mass * ks); The system is underdamped (never intended)
100 
101  // Uses non-relative velocity
102  {
103  // Compute force
104  m_fS = m_linearKs.cwiseProduct(devicePos - currPos - hapticOffsetLocal);
105  m_fD = m_linearKd * (-currVelocity - currAngularVelocity.cross(hapticOffsetLocal));
106  Vec3d force = m_fS + m_fD;
107 
108  // Computer torque
109  const Quatd dq = deviceOrientation * currOrientation.inverse();
110  const Rotd angleAxes = Rotd(dq);
111  m_tS = hapticOffsetLocal.cross(force) + m_angularKs.cwiseProduct(angleAxes.axis() * angleAxes.angle());
112  m_tD = m_angularKd * -currAngularVelocity;
113  Vec3d torque = m_tS + m_tD;
114 
115  currForce += force;
116  currTorque += torque;
117  }
118 
119  // Uses relative velocity
120  //{
121  // const Vec3d& deviceVelocity = getVelocity();
122  // const Vec3d& deviceAngularVelocity = getAngularVelocity();
123  //
124  // // Compute force
125  // m_fS = m_linearKs.cwiseProduct(devicePos - currPos - deviceOffset);
126  // m_fD = m_linearKd * (deviceVelocity - currVelocity - currAngularVelocity.cross(deviceOffset));
127  // Vec3d force = m_fS + m_fD;
128  //
129  // // Compute torque
130  // const Quatd dq = deviceOrientation * currOrientation.inverse();
131  // const Rotd angleAxes = Rotd(dq);
132  // m_tS = deviceOffset.cross(force) + m_angularKs.cwiseProduct(angleAxes.axis() * angleAxes.angle());
133  // m_tD = m_angularKd * (deviceAngularVelocity - currAngularVelocity);
134  // Vec3d torque = m_tS + m_tD;
135  //
136  // currForce += force;
137  // currTorque += torque;
138  //}
139  }
140  else
141  {
142  // Zero out external force/torque
143  m_pbdObject->getPbdBody()->externalForce = Vec3d(0.0, 0.0, 0.0);
144  m_pbdObject->getPbdBody()->externalTorque = Vec3d(0.0, 0.0, 0.0);
145  // Directly set position/rotation
146  (*m_pbdObject->getPbdBody()->vertices)[0] = getPosition();
147  (*m_pbdObject->getPbdBody()->orientations)[0] = getOrientation();
148  }
149 
150  applyForces();
151 }
152 
153 void
155 {
156  // Apply force back to device
157  if (m_pbdObject != nullptr && m_useSpring)
158  {
159  // Get Device Force but also apply inversion if it was set up
160  // for this device, NOTE that we are not inverting the torque
161  // here
162  const Vec3d force = -getDeviceForce().cwiseProduct(m_inversionParams);
163 
164  if (m_forceSmoothening)
165  {
166  m_forces.push_back(force);
167  m_forceSum += force;
168  if (static_cast<int>(m_forces.size()) > m_smoothingKernelSize)
169  {
170  m_forceSum -= m_forces.front();
171  m_forces.pop_front();
172  }
173  const Vec3d avgForce = m_forceSum / m_forces.size();
174 
175  // Render only the spring force (not the other forces the body has)
176  m_deviceClient->setForce(avgForce);
177  }
178  else
179  {
180  // Render only the spring force (not the other forces the body has)
181  m_deviceClient->setForce(force);
182  }
183  }
184 }
185 } // namespace imstk
bool m_useSpring
If off, pos & orientation directly set.
virtual bool updateTrackingData(const double dt)
Update tracking data.
void applyForces() override
Apply forces to the haptic device.
Vec3d m_angularKs
Spring coefficient, rotational.
Compound Geometry.
unsigned char m_invertFlags
Invert flags to be masked with DeviceTracker::InvertFlag.
double m_angularKd
Damping coefficient, rotational.
void update(const double &dt) override
Update controlled scene object using latest tracking information.
Vec3d getDeviceForce() const
Return the device applied force (scaled)
const Quatd & getOrientation() const
Set/Get the orientation of the tracker.
double m_linearKd
Damping coefficient, linear.
bool m_useCriticalDamping
If on, kd is automatically computed.
const Vec3d & getPosition() const
Set/Get the position of the tracker.
Vec3d m_inversionParams
Inversion parameters for each axis.
Vec3d m_linearKs
Spring coefficient, linear.