iMSTK
Interactive Medical Simulation Toolkit
imstkPbdFemTetConstraint.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 "imstkPbdFemTetConstraint.h"
8 
9 namespace imstk
10 {
11 bool
13  const Vec3d& p0, const Vec3d& p1, const Vec3d& p2, const Vec3d& p3,
14  const PbdParticleId& pIdx0, const PbdParticleId& pIdx1,
15  const PbdParticleId& pIdx2, const PbdParticleId& pIdx3,
17 {
18  m_particles[0] = pIdx0;
19  m_particles[1] = pIdx1;
20  m_particles[2] = pIdx2;
21  m_particles[3] = pIdx3;
22 
23  m_initialElementVolume = tetVolume(p0, p1, p2, p3);
24  m_config = config;
25  m_compliance = 1.0 / (config.m_lambda + 2 * config.m_mu);
26 
27  Mat3d m;
28  m.col(0) = p0 - p3;
29  m.col(1) = p1 - p3;
30  m.col(2) = p2 - p3;
31 
32  const double det = m.determinant();
33  if (fabs(det) > 1.0e-16)
34  {
35  m_invRestMat = m.inverse();
36  return true;
37  }
38 
39  return false;
40 }
41 
42 bool
44  double& c, std::vector<Vec3d>& dcdx)
45 {
46  const Vec3d& p0 = bodies.getPosition(m_particles[0]);
47  const Vec3d& p1 = bodies.getPosition(m_particles[1]);
48  const Vec3d& p2 = bodies.getPosition(m_particles[2]);
49  const Vec3d& p3 = bodies.getPosition(m_particles[3]);
50 
51  Mat3d m;
52  m.col(0) = p0 - p3;
53  m.col(1) = p1 - p3;
54  m.col(2) = p2 - p3;
55 
56  // deformation gradient (F)
57  Mat3d defgrad = m * m_invRestMat;
58 
59  // SVD matrices
60  Mat3d U = Mat3d::Identity();
61  Mat3d Fhat = Mat3d::Identity();
62  Mat3d VT = Mat3d::Identity();
63 
64  Mat3d F = defgrad;
65 
66  // If inverted, handle if flag set to true
67  if (m_handleInversions && defgrad.determinant() <= 1E-8)
68  {
69  handleInversions(defgrad, U, Fhat, VT);
70  F = Fhat; // diagonalized deformation gradient
71  }
72 
73  // First Piola-Kirchhoff tensor
74  Mat3d P;
75  // energy constraint
76  double C = 0;
77 
78  const double mu = m_config.m_mu;
79  const double lambda = m_config.m_lambda;
80 
81  switch (m_material)
82  {
83  // P(F) = F*(2*mu*E + lambda*tr(E)*I)
84  // E = (F^T*F - I)/2
85  case MaterialType::StVK:
86  {
87  Mat3d I = Mat3d::Identity();
88  Mat3d E = 0.5 * (F.transpose() * F - I);
89 
90  P = F * (2.0 * mu * E + lambda * E.trace() * I);
91 
92  // C here is strain energy (Often denoted as W in literature)
93  // for the StVK mondel W = mu[tr(E^{T}E)] + 0.5*lambda*(tr(E))^2
94  // C = mu * ((E.transpose() * E).trace()) + 0.5 * lambda * (E.trace() * E.trace());
95  C = 0.5 * lambda * (E.trace() * E.trace()) + mu * (E * E).trace();
96 
97  break;
98  }
99  // P(F) = (2*mu*(F-R) + lambda*(J-1)*J*F^-T
100  case MaterialType::Corotation:
101  {
102  Eigen::JacobiSVD<Mat3d> svd(F, Eigen::ComputeFullU | Eigen::ComputeFullV);
103  Mat3d R = svd.matrixU() * svd.matrixV().adjoint();
104  Vec3d Sigma(svd.singularValues());
105  Mat3d invFT = svd.matrixU();
106  invFT.col(0) /= Sigma(0);
107  invFT.col(1) /= Sigma(1);
108  invFT.col(2) /= Sigma(2);
109  invFT *= svd.matrixV().adjoint();
110  double J = Sigma(0) * Sigma(1) * Sigma(2);
111  Mat3d FR = F - R;
112 
113  P = 2 * mu * FR + lambda * (J - 1) * J * invFT;
114 
115  C = FR(0, 0) * FR(0, 0) + FR(0, 1) * FR(0, 1) + FR(0, 2) * FR(0, 2)
116  + FR(1, 0) * FR(1, 0) + FR(1, 1) * FR(1, 1) + FR(1, 2) * FR(1, 2)
117  + FR(2, 0) * FR(2, 0) + FR(2, 1) * FR(2, 1) + FR(2, 2) * FR(2, 2);
118  C = mu * C + 0.5 * lambda * (J - 1) * (J - 1);
119 
120  break;
121  }
122  // P(F) = mu*(F - mu*F^-T) + 0.5*lambda*log^{2}(J)F^-T;
123  // C = 0.5*mu*(I1 - log(I3) - 3) + (lambda/8)*log^{2}(I3)
124  case MaterialType::NeoHookean:
125  {
126  // First invariant
127  double I1 = (F * F.transpose()).trace();
128 
129  // Third invariant
130  double I3 = (F.transpose() * F).determinant();
131  auto logI3 = log(I3);
132 
133  auto F_invT = F.inverse().transpose();
134 
135  P = mu * (F - F_invT) + 0.5 * lambda * logI3 * F_invT;
136 
137  C = 0.5 * mu * (I1 - logI3 - 3.0) + 0.125 * lambda * (logI3 * logI3);
138 
139  break;
140  }
141  // e = 0.5*(F*F^{T} - I)
142  // P = 2*mu*e + lambda*tr(e)*I
143  case MaterialType::Linear:
144  {
145  Mat3d I = Mat3d::Identity();
146 
147  Mat3d e = 0.5 * (F * F.transpose() - I);
148 
149  P = 2.0 * mu * e + lambda * e.trace() * I;
150  C = mu * (e * e).trace() + 0.5 * lambda * e.trace() * e.trace();
151 
152  break;
153  }
154  default:
155  break;
156  }
157 
158  // Rotate P back here. P = U\hat{P}V^{T}
159  P = U * P * VT;
160 
161  Mat3d gradC = m_initialElementVolume * P * m_invRestMat.transpose();
162  c = C;
164  dcdx[0] = gradC.col(0);
165  dcdx[1] = gradC.col(1);
166  dcdx[2] = gradC.col(2);
167  dcdx[3] = -dcdx[0] - dcdx[1] - dcdx[2];
168 
169  return true;
170 }
171 
172 void
174  Mat3d& F,
175  Mat3d& U,
176  Mat3d& Fhat,
177  Mat3d& VT) const
178 {
179  // Compute SVD of F and return U and VT. Modify to handle inversions. F = U\hat{F} V^{T}
180  Eigen::JacobiSVD<Mat3d> svd(F, Eigen::ComputeFullU | Eigen::ComputeFullV);
181 
182  // Save \hat{F}
183  Vec3d sigma(svd.singularValues());
184 
185  // Store as matrix
186  for (int i = 0; i < 3; i++)
187  {
188  Fhat(i, i) = sigma(i);
189  }
190 
191  // Save U and V
192  U = svd.matrixU();
193  Mat3d V = svd.matrixV();
194 
195  // Verify that V is a pure rotations
196  const double detV = V.determinant();
197 
198  // If detV is negative, then V includes a reflection.
199  // Switch to reflection by multiplying one column by -1
200  if (detV < 0.0)
201  {
202  double minLambda = IMSTK_DOUBLE_MAX;
203  int column = 0;
204  for (int i = 0; i < 3; i++)
205  {
206  if (Fhat(i, i) < minLambda)
207  {
208  column = i;
209  minLambda = Fhat(i, i);
210  }
211  }
212 
213  V.col(column) *= -1.0;
214  }
215 
216  VT = V.transpose();
217 
218  // Check for small singular values
219  int count = 0; // number of small singlar values
220  int position = 0; // position of small singular values
221 
222  for (int i = 0; i < 3; i++)
223  {
224  if (fabs(Fhat(i, i)) < 1E-4)
225  {
226  position = i;
227  count++;
228  }
229  }
230 
231  if (count > 0)
232  {
233  // If more than one singular value is small the element has collapsed
234  // to a line or point. To fix set U to identity.
235  if (count > 1)
236  {
237  U.setIdentity();
238  }
239  else
240  {
241  U = F * V;
242 
243  for (int i = 0; i < 3; i++)
244  {
245  if (i != position)
246  {
247  for (int j = 0; j < 3; j++)
248  {
249  U(j, i) *= 1.0 / Fhat(i, i);
250  }
251  }
252  }
253 
254  // Replace column of U associated with small singular value with
255  // new basis orthogonal to other columns of U
256  Eigen::Matrix<double, 3, 1, 2> v[2];
257  int index = 0;
258  for (int i = 0; i < 3; i++)
259  {
260  if (i != position)
261  {
262  v[index++] = Eigen::Matrix<double, 3, 1, 2>(U(0, i), U(1, i), U(2, i));
263  }
264  }
265 
266  Vec3d vec = v[0].cross(v[1]).normalized();
267  U.col(position) = vec;
268  }
269  }
270  else // No modificaitons required: U = FV\hat{F}^{-1}
271  {
272  U = F * V * Fhat.inverse();
273  }
274 
275  // If detU is negative, then U includes a reflection.
276  const double detU = U.determinant();
277 
278  if (detU < 0.0)
279  {
280  int positionU = 0;
281  double minLambda = IMSTK_DOUBLE_MAX;
282  for (int i = 0; i < 3; i++)
283  {
284  if (Fhat(i, i) < minLambda)
285  {
286  positionU = i;
287  minLambda = Fhat(i, i);
288  }
289  }
290 
291  // Invert values of smallest singular value and associated column of U
292  // This "pushes" the node nearest the uninverted state towards the uninverted state
293  Fhat(positionU, positionU) *= -1.0;
294  for (int i = 0; i < 3; i++)
295  {
296  U(i, positionU) *= -1.0;
297  }
298  }
299 
300  // Clamp small singular values of Fhat
301  const double clamp = 0.577;
302  for (int i = 0; i < 3; i++)
303  {
304  if (Fhat(i, i) < clamp)
305  {
306  Fhat(i, i) = clamp;
307  }
308  }
309 } // end handle tet inversion
310 }; // namespace imstk
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...
std::vector< PbdParticleId > m_particles
body, particle index
MaterialType m_material
Material type.
Compound Geometry.
double m_initialElementVolume
Volume of the element.
double m_compliance
used in xPBD, inverse of Stiffness
void handleInversions(Mat3d &F, Mat3d &U, Mat3d &Fhat, Mat3d &VT) const
Handle inverted tets with the method described by Irving et. al. in "Invertible Finite Elements For R...
double m_lambda
Lame constant, if constraint type is Fem.
double m_mu
Lame constant, if constraint type is Fem.
bool initConstraint(const Vec3d &p0, const Vec3d &p1, const Vec3d &p2, const Vec3d &p3, const PbdParticleId &pIdx0, const PbdParticleId &pIdx1, const PbdParticleId &pIdx2, const PbdParticleId &pIdx3, PbdFemConstraintConfig config)
Initialize the constraint.
Provides interface for accessing particles from a 2d array of PbdBody,Particles.
Definition: imstkPbdBody.h:229
Either mu/lambda used, may be computed from youngs modulus and poissons ratio.
bool computeValueAndGradient(PbdState &bodies, double &c, std::vector< Vec3d > &dcdx) override
Compute value and gradient of constraint function.