Program Listing for File XPBD.cpp
↰ Return to documentation for file (PositionBasedDynamics/XPBD.cpp)
#include "XPBD.h"
#include "PositionBasedDynamics.h"
#include "MathFunctions.h"
#include <cfloat>
using namespace PBD;
const Real eps = static_cast<Real>(1e-6);
// XPBD
bool XPBD::solve_DistanceConstraint(
const Vector3r &p0, Real invMass0,
const Vector3r &p1, Real invMass1,
const Real restLength,
const Real stiffness,
const Real dt,
Real& lambda,
Vector3r &corr0, Vector3r &corr1)
{
Real K = invMass0 + invMass1;
Vector3r n = p0 - p1;
Real d = n.norm();
Real C = d - restLength;
if (d > static_cast<Real>(1e-6))
n /= d;
else
{
corr0.setZero();
corr1.setZero();
return true;
}
Real alpha = 0.0;
if (stiffness != 0.0)
{
alpha = static_cast<Real>(1.0) / (stiffness * dt * dt);
K += alpha;
}
Real Kinv = 0.0;
if (fabs(K) > static_cast<Real>(1e-6))
Kinv = static_cast<Real>(1.0) / K;
else
{
corr0.setZero();
corr1.setZero();
return true;
}
const Real delta_lambda = -Kinv * (C + alpha * lambda);
lambda += delta_lambda;
const Vector3r pt = n * delta_lambda;
corr0 = invMass0 * pt;
corr1 = -invMass1 * pt;
return true;
}
// ----------------------------------------------------------------------------------------------
bool XPBD::solve_VolumeConstraint(
const Vector3r& p0, Real invMass0,
const Vector3r& p1, Real invMass1,
const Vector3r& p2, Real invMass2,
const Vector3r& p3, Real invMass3,
const Real restVolume,
const Real stiffness,
const Real dt,
Real& lambda,
Vector3r& corr0, Vector3r& corr1, Vector3r& corr2, Vector3r& corr3)
{
Real volume = static_cast<Real>(1.0 / 6.0) * (p1 - p0).cross(p2 - p0).dot(p3 - p0);
corr0.setZero(); corr1.setZero(); corr2.setZero(); corr3.setZero();
Vector3r grad0 = (p1 - p2).cross(p3 - p2);
Vector3r grad1 = (p2 - p0).cross(p3 - p0);
Vector3r grad2 = (p0 - p1).cross(p3 - p1);
Vector3r grad3 = (p1 - p0).cross(p2 - p0);
Real K =
invMass0 * grad0.squaredNorm() +
invMass1 * grad1.squaredNorm() +
invMass2 * grad2.squaredNorm() +
invMass3 * grad3.squaredNorm();
Real alpha = 0.0;
if (stiffness != 0.0)
{
alpha = static_cast<Real>(1.0) / (stiffness * dt * dt);
K += alpha;
}
if (fabs(K) < eps)
return false;
const Real C = volume - restVolume;
const Real delta_lambda = -(C + alpha * lambda) / K;
lambda += delta_lambda;
corr0 = delta_lambda * invMass0 * grad0;
corr1 = delta_lambda * invMass1 * grad1;
corr2 = delta_lambda * invMass2 * grad2;
corr3 = delta_lambda * invMass3 * grad3;
return true;
}
// ----------------------------------------------------------------------------------------------
bool XPBD::init_IsometricBendingConstraint(
const Vector3r& p0,
const Vector3r& p1,
const Vector3r& p2,
const Vector3r& p3,
Matrix4r& Q)
{
// Compute matrix Q for quadratic bending
const Vector3r* x[4] = { &p2, &p3, &p0, &p1 };
const Vector3r e0 = *x[1] - *x[0];
const Vector3r e1 = *x[2] - *x[0];
const Vector3r e2 = *x[3] - *x[0];
const Vector3r e3 = *x[2] - *x[1];
const Vector3r e4 = *x[3] - *x[1];
const Real c01 = MathFunctions::cotTheta(e0, e1);
const Real c02 = MathFunctions::cotTheta(e0, e2);
const Real c03 = MathFunctions::cotTheta(-e0, e3);
const Real c04 = MathFunctions::cotTheta(-e0, e4);
const Real A0 = static_cast<Real>(0.5) * (e0.cross(e1)).norm();
const Real A1 = static_cast<Real>(0.5) * (e0.cross(e2)).norm();
const Real coef = -3.f / (2.f * (A0 + A1));
const Real K[4] = { c03 + c04, c01 + c02, -c01 - c03, -c02 - c04 };
const Real K2[4] = { coef * K[0], coef * K[1], coef * K[2], coef * K[3] };
for (unsigned char j = 0; j < 4; j++)
{
for (unsigned char k = 0; k < j; k++)
{
Q(j, k) = Q(k, j) = K[j] * K2[k];
}
Q(j, j) = K[j] * K2[j];
}
return true;
}
// ----------------------------------------------------------------------------------------------
bool XPBD::solve_IsometricBendingConstraint(
const Vector3r& p0, Real invMass0,
const Vector3r& p1, Real invMass1,
const Vector3r& p2, Real invMass2,
const Vector3r& p3, Real invMass3,
const Matrix4r& Q,
const Real stiffness,
const Real dt,
Real& lambda,
Vector3r& corr0, Vector3r& corr1, Vector3r& corr2, Vector3r& corr3)
{
const Vector3r* x[4] = { &p2, &p3, &p0, &p1 };
Real invMass[4] = { invMass2, invMass3, invMass0, invMass1 };
Real energy = 0.0;
for (unsigned char k = 0; k < 4; k++)
for (unsigned char j = 0; j < 4; j++)
energy += Q(j, k) * (x[k]->dot(*x[j]));
energy *= 0.5;
Vector3r gradC[4];
gradC[0].setZero();
gradC[1].setZero();
gradC[2].setZero();
gradC[3].setZero();
for (unsigned char k = 0; k < 4; k++)
for (unsigned char j = 0; j < 4; j++)
gradC[j] += Q(j, k) * *x[k];
Real sum_normGradC = 0.0;
for (unsigned int j = 0; j < 4; j++)
{
// compute sum of squared gradient norms
if (invMass[j] != 0.0)
sum_normGradC += invMass[j] * gradC[j].squaredNorm();
}
Real alpha = 0.0;
if (stiffness != 0.0)
{
alpha = static_cast<Real>(1.0) / (stiffness * dt * dt);
sum_normGradC += alpha;
}
// exit early if required
if (fabs(sum_normGradC) > eps)
{
// compute impulse-based scaling factor
const Real delta_lambda = -(energy + alpha * lambda) / sum_normGradC;
lambda += delta_lambda;
corr0 = (delta_lambda * invMass[2]) * gradC[2];
corr1 = (delta_lambda * invMass[3]) * gradC[3];
corr2 = (delta_lambda * invMass[0]) * gradC[0];
corr3 = (delta_lambda * invMass[1]) * gradC[1];
return true;
}
return false;
}
// ----------------------------------------------------------------------------------------------
bool XPBD::solve_FEMTetraConstraint(
const Vector3r& p0, Real invMass0,
const Vector3r& p1, Real invMass1,
const Vector3r& p2, Real invMass2,
const Vector3r& p3, Real invMass3,
const Real restVolume,
const Matrix3r& invRestMat,
const Real youngsModulus,
const Real poissonRatio,
const bool handleInversion,
const Real dt,
Real& multiplier,
Vector3r& corr0, Vector3r& corr1, Vector3r& corr2, Vector3r& corr3)
{
corr0.setZero();
corr1.setZero();
corr2.setZero();
corr3.setZero();
if (youngsModulus <= 0.0)
return true;
if (poissonRatio < 0.0 || poissonRatio > 0.49)
return false;
Vector3r gradU_[4];
Matrix3r epsilon, sigma;
Real volume = (p1 - p0).cross(p2 - p0).dot(p3 - p0) / static_cast<Real>(6.0);
// compute the Lame coefficients mu and lambda divided by Young's modulus E
// since we use Young's modulus as compliance factor alpha = 1/E.
Real mu_ = 1.0 / static_cast<Real>(2.0) / (static_cast<Real>(1.0) + poissonRatio);
Real lambda_ = 1.0 * poissonRatio / (static_cast<Real>(1.0) + poissonRatio) / (static_cast<Real>(1.0) - static_cast<Real>(2.0) * poissonRatio);
// compute value U' which is the potential energy of the elastic solid U divided by Young's modulus E
// U = E * U'
Real U_ = 0.0;
if (!handleInversion || volume > 0.0)
{
PositionBasedDynamics::computeGreenStrainAndPiolaStress(p0, p1, p2, p3, invRestMat, restVolume, mu_, lambda_, epsilon, sigma, U_);
PositionBasedDynamics::computeGradCGreen(restVolume, invRestMat, sigma, gradU_);
}
else
{
PositionBasedDynamics::computeGreenStrainAndPiolaStressInversion(p0, p1, p2, p3, invRestMat, restVolume, mu_, lambda_, epsilon, sigma, U_);
PositionBasedDynamics::computeGradCGreen(restVolume, invRestMat, sigma, gradU_);
}
// By choosing the constraint function as sqrt(2 U'), the potential energy used in XPBD:
// U = 0.5 * alpha^-1 * C^2
// gives us exactly the required potential energy of the elastic solid.
const Real C = sqrt(2.0 * U_);
Real sum_normGradU_ =
invMass0 * gradU_[0].squaredNorm() +
invMass1 * gradU_[1].squaredNorm() +
invMass2 * gradU_[2].squaredNorm() +
invMass3 * gradU_[3].squaredNorm();
Real alpha = static_cast<Real>(1.0) / (youngsModulus * dt * dt);
// Note that grad C = 1/C grad U', by multiplying C^2 to alpha, we now can add the factor C^2 to the nominator
sum_normGradU_ += C * C * alpha;
if (sum_normGradU_ < eps)
return false;
// compute scaling factor
const Real lambda = -C * (C + alpha * multiplier) / sum_normGradU_; // since in the next step we use gradU instead of gradC, we only add the factor C instead of C^2
multiplier += lambda;
corr0 = lambda * invMass0 * gradU_[0];
corr1 = lambda * invMass1 * gradU_[1];
corr2 = lambda * invMass2 * gradU_[2];
corr3 = lambda * invMass3 * gradU_[3];
return true;
}