iMSTK
Interactive Medical Simulation Toolkit
imstkPbdModel.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 "imstkDataTracker.h"
8 #include "imstkPbdModel.h"
9 #include "imstkGraph.h"
10 #include "imstkLineMesh.h"
11 #include "imstkLogger.h"
12 #include "imstkParallelUtils.h"
13 #include "imstkPbdConstraintFunctor.h"
14 #include "imstkPbdModelConfig.h"
15 #include "imstkPbdSolver.h"
16 #include "imstkTaskGraph.h"
17 
18 namespace imstk
19 {
20 PbdModel::PbdModel() : AbstractDynamicalModel(DynamicalModelType::PositionBasedDynamics),
21  m_config(std::make_shared<PbdModelConfig>()),
22  m_constraints(std::make_shared<PbdConstraintContainer>())
23 {
24  // Add a virtual particle buffer, cleared every frame
25  addBody();
26  // Add a virtual particle buffer, persistent
27  addBody();
28 
29  // Create a virtual particles buffer for particles that need to be quickly added/removed
30  // such as during collision
31  m_state.m_bodies[0] = std::make_shared<PbdBody>(0);
32  m_state.m_bodies[0]->bodyType = PbdBody::Type::DEFORMABLE_ORIENTED;
33  m_state.m_bodies[0]->prevVertices = std::make_shared<VecDataArray<double, 3>>();
34  m_state.m_bodies[0]->vertices = std::make_shared<VecDataArray<double, 3>>();
35  m_state.m_bodies[0]->prevOrientations = std::make_shared<StdVectorOfQuatd>();
36  m_state.m_bodies[0]->orientations = std::make_shared<StdVectorOfQuatd>();
37  m_state.m_bodies[0]->velocities = std::make_shared<VecDataArray<double, 3>>();
38  m_state.m_bodies[0]->angularVelocities = std::make_shared<VecDataArray<double, 3>>();
39  m_state.m_bodies[0]->masses = std::make_shared<DataArray<double>>();
40  m_state.m_bodies[0]->invMasses = std::make_shared<DataArray<double>>();
41  m_state.m_bodies[0]->inertias = std::make_shared<StdVectorOfMat3d>();
42  m_state.m_bodies[0]->invInertias = std::make_shared<StdVectorOfMat3d>();
43 
44  // The second virtual particle buffer is for persistant virtual particles
45  m_state.m_bodies[1] = std::make_shared<PbdBody>(1);
46  m_state.m_bodies[1]->bodyType = PbdBody::Type::DEFORMABLE_ORIENTED;
47  m_state.m_bodies[1]->prevVertices = std::make_shared<VecDataArray<double, 3>>();
48  m_state.m_bodies[1]->vertices = std::make_shared<VecDataArray<double, 3>>();
49  m_state.m_bodies[1]->prevOrientations = std::make_shared<StdVectorOfQuatd>();
50  m_state.m_bodies[1]->orientations = std::make_shared<StdVectorOfQuatd>();
51  m_state.m_bodies[1]->velocities = std::make_shared<VecDataArray<double, 3>>();
52  m_state.m_bodies[1]->angularVelocities = std::make_shared<VecDataArray<double, 3>>();
53  m_state.m_bodies[1]->masses = std::make_shared<DataArray<double>>();
54  m_state.m_bodies[1]->invMasses = std::make_shared<DataArray<double>>();
55  m_state.m_bodies[1]->inertias = std::make_shared<StdVectorOfMat3d>();
56  m_state.m_bodies[1]->invInertias = std::make_shared<StdVectorOfMat3d>();
57 
58  m_validGeometryTypes = {
59  "PointSet",
60  "LineMesh",
61  "SurfaceMesh",
62  "TetrahedralMesh",
63  "HexahedralMesh"
64  };
65 
66  // Setup PBD compute nodes
67  m_integrationPositionNode = m_taskGraph->addFunction("PbdModel_IntegratePosition",
68  [&]() { integratePosition(); });
69  m_solveConstraintsNode = m_taskGraph->addFunction("PbdModel_SolveConstraints",
70  [&]() { solveConstraints(); });
71  m_updateVelocityNode = m_taskGraph->addFunction("PbdModel_UpdateVelocity",
72  [&]() { updateVelocity(); });
73 }
74 
75 void
77 {
78  m_state.deepCopy(m_initialState);
79 
80  // Set previous particle positions, orientations to current to avoid a jump
81  for (auto bodyIter = m_state.m_bodies.begin();
82  bodyIter != m_state.m_bodies.end(); bodyIter++)
83  {
84  PbdBody& body = **bodyIter;
85  for (int i = 0; i < body.prevVertices->size(); i++)
86  {
87  (*body.prevVertices)[i] = (*body.vertices)[i];
88  }
89  if (body.getOriented())
90  {
91  for (int i = 0; i < body.prevOrientations->size(); i++)
92  {
93  (*body.prevOrientations)[i] = (*body.orientations)[i];
94  }
95  }
96  }
97 }
98 
99 void
100 PbdModel::configure(std::shared_ptr<PbdModelConfig> config)
101 {
102  m_config = config;
103 }
104 
105 std::shared_ptr<PbdBody>
107 {
108  m_state.m_bodies.push_back(std::make_shared<PbdBody>(m_iterKey));
109  m_modified = true;
110  m_iterKey++;
111  return m_state.m_bodies.back();
112 }
113 
114 void
115 PbdModel::removeBody(std::shared_ptr<PbdBody> body)
116 {
117  auto iter = std::find(m_state.m_bodies.begin(), m_state.m_bodies.end(), body);
118  CHECK(iter != m_state.m_bodies.end()) << "removeBody called but could not find PbdyBody in PbdState";
119  m_state.m_bodies.erase(iter);
120  m_modified = true;
121 }
122 
123 std::shared_ptr<imstk::PbdBody>
124 PbdModel::getBody(size_t index) const
125 {
126  if (index < m_state.m_bodies.size())
127  {
128  return m_state.m_bodies[index];
129  }
130  else
131  {
132  LOG(WARNING) << "PbdModel::getBody: index out of range";
133  return nullptr;
134  }
135 }
136 
139  const Vec3d& pos, const Quatd& orientation,
140  const double mass, const Mat3d inertia,
141  const Vec3d& velocity, const Vec3d& angularVelocity,
142  const bool persist)
143 {
144  const int virtualBufferId = static_cast<int>(persist);
145 
146  m_state.m_bodies[virtualBufferId]->prevVertices->push_back(pos);
147  m_state.m_bodies[virtualBufferId]->vertices->push_back(pos);
148  m_state.m_bodies[virtualBufferId]->prevOrientations->push_back(orientation);
149  m_state.m_bodies[virtualBufferId]->orientations->push_back(orientation);
150  m_state.m_bodies[virtualBufferId]->velocities->push_back(velocity);
151  m_state.m_bodies[virtualBufferId]->angularVelocities->push_back(angularVelocity);
152  m_state.m_bodies[virtualBufferId]->masses->push_back(mass);
153  m_state.m_bodies[virtualBufferId]->invMasses->push_back((mass == 0.0) ? 0.0 : 1.0 / mass);
154  m_state.m_bodies[virtualBufferId]->inertias->push_back(inertia);
155  Mat3d invInertia = Mat3d::Zero();
156  if (inertia.determinant() == 0.0)
157  {
158  LOG(FATAL) << "Tried to add virtual particle with non-invertible inertia";
159  return { -1, -1 };
160  }
161  invInertia = inertia.inverse();
162  m_state.m_bodies[virtualBufferId]->invInertias->push_back(invInertia);
163  return { virtualBufferId, m_state.m_bodies[virtualBufferId]->vertices->size() - 1 };
164 }
165 
168  const Vec3d& pos, const double mass,
169  const Vec3d& velocity,
170  const bool persist)
171 {
172  return addVirtualParticle(pos, Quatd::Identity(),
173  mass, Mat3d::Identity(),
174  velocity, Vec3d::Zero(), persist);
175 }
176 
177 void
179 {
180  CHECK(m_state.m_bodies.size() != 0 || m_state.m_bodies[0] != nullptr) << "Missing virtual/dummy body";
181  resizeBodyParticles(*m_state.m_bodies[0], 0);
182 }
183 
184 std::shared_ptr<PbdModelConfig>
186 {
187  CHECK(m_config != nullptr) << "Cannot PbdModel::getConfig, config is nullptr";
188  return m_config;
189 }
190 
191 bool
193 {
194  // Store a copy of the initial state
195  m_initialState.deepCopy(m_state);
196 
197  // Initialize constraints
198  {
199  m_config->computeElasticConstants();
200 
201  // Run all the functors to generate the constraints
202  for (const auto& functorVec : m_config->m_functors)
203  {
204  for (const auto& functor : functorVec.second)
205  {
206  (*functor)(*m_constraints);
207  }
208  }
209 
210  // Partition constraints for parallel computation
211  if (m_config->m_doPartitioning)
212  {
213  m_constraints->partitionConstraints(static_cast<int>(m_partitionThreshold));
214  }
215  else
216  {
217  m_constraints->clearPartitions();
218  }
219  }
220 
221  // Setup the default pbd solver if none exists
222  if (m_pbdSolver == nullptr)
223  {
224  m_pbdSolver = std::make_shared<PbdSolver>();
225  }
226 
227  if (m_config->m_dataTracker)
228  {
229  m_pbdSolver->m_dataTracker = m_config->m_dataTracker;
230  m_config->m_dataTracker->configureProbe(DataTracker::Physics::SolverTime_ms, DataTracker::ePhysics::SolverTime_ms);
231  m_config->m_dataTracker->configureProbe(DataTracker::Physics::NumConstraints, DataTracker::ePhysics::NumConstraints);
232  m_config->m_dataTracker->configureProbe(DataTracker::Physics::AverageC, DataTracker::ePhysics::AverageC);
233  }
234 
235  return true;
236 }
237 
238 void
239 PbdModel::initGraphEdges(std::shared_ptr<TaskNode> source, std::shared_ptr<TaskNode> sink)
240 {
241  // Setup graph connectivity
242  m_taskGraph->addEdge(source, m_integrationPositionNode);
243  m_taskGraph->addEdge(m_integrationPositionNode, m_solveConstraintsNode);
244  m_taskGraph->addEdge(m_solveConstraintsNode, m_updateVelocityNode);
245  m_taskGraph->addEdge(m_updateVelocityNode, sink);
246 }
247 
248 void
249 PbdModel::addConstraints(std::shared_ptr<std::unordered_set<size_t>> vertices, const int bodyId)
250 {
251  for (const auto& functorVec : m_config->m_functors)
252  {
253  for (const auto& functor : functorVec.second)
254  {
255  if (auto bodyFunctor = std::dynamic_pointer_cast<PbdBodyConstraintFunctor>(functor))
256  {
257  if (bodyFunctor->m_bodyIndex == bodyId)
258  {
259  bodyFunctor->addConstraints(*m_constraints, vertices);
260  }
261  }
262  }
263  }
264 }
265 
266 void
267 PbdModel::setTimeStep(const double timeStep)
268 {
269  m_config->m_dt = timeStep;
270 }
271 
272 double
274 {
275  return m_config->m_dt;
276 }
277 
278 void
280 {
281  // resize 0 virtual particles (avoids reallocation)
282  clearVirtualParticles();
283  int bodyCount = m_state.m_bodies.size() - 2;
284 
285  // There are two virtual particles buffer, skip the first two
286  ParallelUtils::parallelFor(bodyCount,
287  [&](const int i) {
288  integratePosition(*m_state.m_bodies[i + 2]);
289  });
290 }
291 
292 void
294 {
295  VecDataArray<double, 3>& pos = *body.vertices;
296  VecDataArray<double, 3>& prevPos = *body.prevVertices;
297  VecDataArray<double, 3>& vel = *body.velocities;
298  const DataArray<double>& invMasses = *body.invMasses;
299 
300  // Check all the arrays are the same
301  const int numParticles = pos.size();
302  CHECK(numParticles == prevPos.size()) << "PbdModel data corrupt";
303  CHECK(numParticles == vel.size()) << "PbdModel data corrupt";
304  CHECK(numParticles == invMasses.size()) << "PbdModel data corrupt";
305 
306  const double dt = m_config->m_dt;
307  const double linearVelocityDamp = 1.0 - m_config->getLinearDamping(body.bodyHandle);
308  ParallelUtils::parallelFor(numParticles,
309  [&](const int i)
310  {
311  if (std::abs(invMasses[i]) > 0.0)
312  {
313  const Vec3d accel = static_cast<double>(body.bodyGravity) * m_config->m_gravity + body.externalForce * invMasses[i];
314  vel[i] += accel * dt;
315  vel[i] *= linearVelocityDamp;
316 
317  // Cap velocity to increase stability
318  vel[i] = vel[i].cwiseMax(-m_velocityThreshold).cwiseMin(m_velocityThreshold);
319 
320  prevPos[i] = pos[i];
321  pos[i] += vel[i] * dt;
322  }
323  }, numParticles > 50); // Only run parallel when more than 50 pts
324 
325  // If using oriented particles update those too
326  if (body.getOriented())
327  {
328  StdVectorOfQuatd& orientations = *body.orientations;
329  StdVectorOfQuatd& prevOrientations = *body.prevOrientations;
330  VecDataArray<double, 3>& angularVelocities = *body.angularVelocities;
331  const StdVectorOfMat3d& inertias = *body.inertias;
332  const StdVectorOfMat3d& invInertias = *body.invInertias;
333 
334  // Check all the arrays are the same
335  CHECK(numParticles == orientations.size()) << "PbdModel data corrupt";
336  CHECK(numParticles == prevOrientations.size()) << "PbdModel data corrupt";
337  CHECK(numParticles == angularVelocities.size()) << "PbdModel data corrupt";
338  CHECK(numParticles == invInertias.size()) << "PbdModel data corrupt";
339 
340  const double angularVelocityDamp = 1.0 - m_config->getAngularDamping(body.bodyHandle);
341  ParallelUtils::parallelFor(numParticles,
342  [&](const int i)
343  {
344  if (!invInertias[i].isZero())
345  {
346  Vec3d& w = angularVelocities[i];
347  const Vec3d accel = invInertias[i] *
348  (body.externalTorque - (w.cross(inertias[i] * w)));
349  w += dt * accel;
350  w *= angularVelocityDamp;
351  prevOrientations[i] = orientations[i];
352 
353  // Cap angular velocity to increase stability (uses same value as linear in rad/sec)
354  w.cwiseMax(-m_velocityThreshold).cwiseMin(m_velocityThreshold);
355 
356  // Limit on rotation
357  double scale = dt;
358  const double phi = w.norm();
359  if (phi * scale > 0.5)
360  {
361  scale = 0.5 / phi;
362  }
363  const Quatd dq = Quatd(0.0,
364  w[0] * scale,
365  w[1] * scale,
366  w[2] * scale) * orientations[i];
367  orientations[i].coeffs() += dq.coeffs() * 0.5;
368  orientations[i].normalize();
369  }
370  }, numParticles > 50); // Only run parallel when more than 50 pts
371  }
372 }
373 
374 void
376 {
377  int bodyCount = m_state.m_bodies.size() - 2;
378  ParallelUtils::parallelFor(bodyCount,
379  [&](const int i) {
380  updateVelocity(*m_state.m_bodies[i + 2]);
381  });
382 
383  // Correctly velocities for friction and restitution
384  // Unfortunately the constraint would be clear after a solve
385  for (const auto& colConstraintList : m_pbdSolver->getConstraintLists())
386  {
387  for (auto& colConstraint : *colConstraintList)
388  {
389  colConstraint->correctVelocity(m_state, m_config->m_dt);
390  }
391  }
392  m_pbdSolver->clearConstraintLists();
393 }
394 
395 void
397 {
398  if (m_config->m_dt > 0.0)
399  {
400  const VecDataArray<double, 3>& pos = *body.vertices;
401  const VecDataArray<double, 3>& prevPos = *body.prevVertices;
402  VecDataArray<double, 3>& vel = *body.velocities;
403  const DataArray<double>& invMasses = *body.invMasses;
404 
405  // Check all the arrays are the same
406  const int numParticles = pos.size();
407  CHECK(numParticles == prevPos.size()) << "PbdModel data corrupt";
408  CHECK(numParticles == vel.size()) << "PbdModel data corrupt";
409  CHECK(numParticles == invMasses.size()) << "PbdModel data corrupt";
410 
411  const double invDt = 1.0 / m_config->m_dt;
412  ParallelUtils::parallelFor(numParticles,
413  [&](const int i)
414  {
415  if (std::abs(invMasses[i]) > 0.0)
416  {
417  vel[i] = (pos[i] - prevPos[i]) * invDt;
418  }
419  }, numParticles > 50);
420 
421  if (body.getOriented())
422  {
423  const StdVectorOfQuatd& orientations = *body.orientations;
424  const StdVectorOfQuatd& prevOrientations = *body.prevOrientations;
425  VecDataArray<double, 3>& angularVelocities = *body.angularVelocities;
426  const StdVectorOfMat3d& invInertias = *body.invInertias;
427 
428  // Check all the arrays are the same
429  CHECK(numParticles == orientations.size()) << "PbdModel data corrupt";
430  CHECK(numParticles == prevOrientations.size()) << "PbdModel data corrupt";
431  CHECK(numParticles == angularVelocities.size()) << "PbdModel data corrupt";
432  CHECK(numParticles == invInertias.size()) << "PbdModel data corrupt";
433 
434  ParallelUtils::parallelFor(numParticles,
435  [&](const int i)
436  {
437  if (!invInertias[i].isZero())
438  {
439  const Quatd dq = orientations[i] * prevOrientations[i].inverse();
440  //const Quatd dq = prevOrientations[i] * orientations[i].inverse();
441  const Vec3d angularVel = 2.0 * Vec3d(dq.x(), dq.y(), dq.z()) * invDt;
442  angularVelocities[i] = dq.w() >= 0.0 ? angularVel : -angularVel;
443  }
444  }, numParticles > 50);
445  }
446  }
447 
448  body.externalForce = Vec3d::Zero();
449  body.externalTorque = Vec3d::Zero();
450 }
451 
452 void
454 {
455  m_pbdSolver->setPbdBodies(&m_state);
456  m_pbdSolver->setConstraints(getConstraints());
457  m_pbdSolver->setTimeStep(m_config->m_dt);
458  m_pbdSolver->setIterations(m_config->m_iterations);
459  m_pbdSolver->setSolverType(m_config->m_solverType);
460  m_pbdSolver->solve();
461 }
462 
463 void
464 PbdModel::resizeBodyParticles(PbdBody& body, const int particleCount)
465 {
466  body.prevVertices->resize(particleCount);
467  body.vertices->resize(particleCount);
468  body.velocities->resize(particleCount);
469  body.masses->resize(particleCount);
470  body.invMasses->resize(particleCount);
471  if (body.getOriented())
472  {
473  body.prevOrientations->resize(particleCount);
474  body.orientations->resize(particleCount);
475  body.angularVelocities->resize(particleCount);
476  body.inertias->resize(particleCount);
477  body.invInertias->resize(particleCount);
478  }
479 }
480 } // namespace imstk
Represents a pbd body in the model. This is a data only object. It does no function. PbdBody can be of different types. The types effect what properties it has.
Definition: imstkPbdBody.h:53
void configure(std::shared_ptr< PbdModelConfig > params)
Set simulation parameters.
int size() const
Get number of values/tuples.
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...
int bodyHandle
Id in the system.
Definition: imstkPbdBody.h:186
void addConstraints(std::shared_ptr< std::unordered_set< size_t >> vertices, const int bodyId)
Add/generate constraints for given set of vertices on the body, useful for topology changes...
void setTimeStep(const double timeStep) override
Set the time step size.
Compound Geometry.
void clearVirtualParticles()
Resize 0 the virtual particles.
void updateVelocity()
Time integrate the velocity.
DynamicalModelType
Type of the time dependent mathematical model.
bool initialize() override
Initialize the PBD model.
void resizeBodyParticles(PbdBody &body, const int particleCount)
Resize the amount of particles for a body.
bool bodyGravity
Switch to activate/deactivate gravity for this Pbd Body. 1 is gravity on, 0 is gravity off...
Definition: imstkPbdBody.h:211
PbdParticleId addVirtualParticle(const Vec3d &pos, const Quatd &orientation, const double mass, const Mat3d inertia, const Vec3d &velocity=Vec3d::Zero(), const Vec3d &angularVelocity=Vec3d::Zero(), const bool persist=false)
Add a particle to a virtual pool/buffer of particles for quick removal/insertion The persist flag ind...
std::shared_ptr< PbdBody > getBody(size_t index) const
void integratePosition()
Time integrate the position.
void solveConstraints()
Solve the internal constraints.
void resetToInitialState() override
Reset the current state to the initial state.
std::shared_ptr< PbdModelConfig > getConfig() const
Get the simulation parameters.
double getTimeStep() const override
Returns the time step size.
bool getOriented() const
The body should have orientations if its DEFORMABLE_ORIENTED or RIGID.
Definition: imstkPbdBody.h:100
std::shared_ptr< PbdBody > addBody()
Add/remove PbdBody.
void initGraphEdges()
Initializes the edges of the graph.