// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2013 Alec Jacobson // // This Source Code Form is subject to the terms of the Mozilla Public License // v. 2.0. If a copy of the MPL was not distributed with this file, You can #include "kelvinlets.h" #include "PI.h" #include "parallel_for.h" namespace igl { // Performs the deformation of a single point based on the regularized // kelvinlets // // Inputs: // dt delta time used to calculate brush tip displacement // x dim-vector of point to be deformed // x0 dim-vector of brush tip // f dim-vector of brush force (translation) // F dim by dim matrix of brush force matrix (linear) // kp parameters for the kelvinlet brush like brush radius, scale etc // Returns: // X dim-vector of the new point x gets displaced to post deformation template IGL_INLINE auto kelvinlet_evaluator(const Scalar dt, const Eigen::MatrixBase& x, const Eigen::MatrixBase& x0, const Eigen::MatrixBase& f, const Eigen::MatrixBase& F, const igl::KelvinletParams& kp) -> Eigen::Matrix { static constexpr double POISSON_RATIO = 0.5; static constexpr double SHEAR_MODULUS = 1; static constexpr double a = 1 / (4 * igl::PI * SHEAR_MODULUS); static constexpr double b = a / (4 * (1 - POISSON_RATIO)); static constexpr double c = 2 / (3 * a - 2 * b); const auto linearVelocity = f / c / kp.epsilon; const auto originAdjusted = x0 + linearVelocity * dt; const auto r = x - originAdjusted; const auto r_norm_sq = r.squaredNorm(); std::function(const Scalar&)> kelvinlet; switch (kp.brushType) { case igl::BrushType::GRAB: { // Regularized Kelvinlets: Formula (6) kelvinlet = [&r, &f, &r_norm_sq](const Scalar& epsilon) { const auto r_epsilon = sqrt(r_norm_sq + epsilon * epsilon); const auto r_epsilon_3 = r_epsilon * r_epsilon * r_epsilon; auto t1 = ((a - b) / r_epsilon) * f; auto t2 = ((b / r_epsilon_3) * r * r.transpose()) * f; auto t3 = ((a * epsilon * epsilon) / (2 * r_epsilon_3)) * f; return t1 + t2 + t3; }; break; } case igl::BrushType::TWIST: { // Regularized Kelvinlets: Formula (15) kelvinlet = [&r, &F, &r_norm_sq](const Scalar& epsilon) { const auto r_epsilon = sqrt(r_norm_sq + epsilon * epsilon); const auto r_epsilon_3 = r_epsilon * r_epsilon * r_epsilon; return -a * (1 / (r_epsilon_3) + 3 * epsilon * epsilon / (2 * r_epsilon_3 * r_epsilon * r_epsilon)) * F * r; }; break; } case igl::BrushType::SCALE: { // Regularized Kelvinlets: Formula (16) kelvinlet = [&r, &F, &r_norm_sq](const Scalar& epsilon) { static constexpr auto b_compressible = a / 4; // assumes poisson ratio 0 const auto r_epsilon = sqrt(r_norm_sq + epsilon * epsilon); const auto r_epsilon_3 = r_epsilon * r_epsilon * r_epsilon; auto coeff = (2 * b_compressible - a) * (1 / (r_epsilon_3) + 3 * (epsilon * epsilon) / (2 * r_epsilon_3 * r_epsilon * r_epsilon)); return coeff * F * r; }; break; } case igl::BrushType::PINCH: { // Regularized Kelvinlets: Formula (17) kelvinlet = [&r, &F, &r_norm_sq, &kp](const Scalar& epsilon) { const auto r_epsilon = sqrt(r_norm_sq + kp.epsilon * kp.epsilon); const auto r_epsilon_3 = r_epsilon * r_epsilon * r_epsilon; auto t1 = ((2 * b - a) / r_epsilon_3) * F * r; auto t2_coeff = 3 / (2 * r_epsilon * r_epsilon * r_epsilon_3); auto t2 = t2_coeff * (2 * b * (r.transpose().dot(F * r)) * r + a * epsilon * epsilon * epsilon * F * r); return t1 - t2; }; break; } } if (kp.scale == 1) { return kelvinlet(kp.ep[0]); } else if (kp.scale == 2) { // Regularized Kelvinlets: Formula (8) return (kelvinlet(kp.ep[0]) - kelvinlet(kp.ep[1])) * 10; } // Regularized Kelvinlets: Formula (10) return (kp.w[0] * kelvinlet(kp.ep[0]) + kp.w[1] * kelvinlet(kp.ep[1]) + kp.w[2] * kelvinlet(kp.ep[2])) * 20; }; // Implements the Bogacki-Shrampine ODE Solver // https://en.wikipedia.org/wiki/Bogacki%E2%80%93Shampine_method // // It calculates the second and third order approximations which can be used to // estimate the error in the integration step // // Inputs: // t starting time // dt delta time used to calculate brush tip displacement // x dim-vector of point to be deformed // x0 dim-vector of brush tip // f dim-vector of brush force (translation) // F dim by dim matrix of brush force matrix (linear) // kp parameters for the kelvinlet brush like brush radius, scale etc // Outputs: // result dim vector holding the third order approximation result // error The euclidean distance between the second and third order // approximations template IGL_INLINE void integrate(const Scalar t, const Scalar dt, const Eigen::MatrixBase& x, const Eigen::MatrixBase& x0, const Eigen::MatrixBase& f, const Eigen::MatrixBase& F, const igl::KelvinletParams& kp, Eigen::MatrixBase& result, Scalar& error) { constexpr Scalar a1 = 0; constexpr Scalar a2 = 1 / 2.0f; constexpr Scalar a3 = 3 / 4.0f; constexpr Scalar a4 = 1.0f; constexpr Scalar b21 = 1 / 2.0f; constexpr Scalar b31 = 0; constexpr Scalar b32 = 3 / 4.0f; constexpr Scalar b41 = 2 / 9.0f; constexpr Scalar b42 = 1 / 3.0f; constexpr Scalar b43 = 4 / 9.0f; constexpr Scalar c1 = 2 / 9.0f; // third order answer constexpr Scalar c2 = 1 / 3.0f; constexpr Scalar c3 = 4 / 9.0f; constexpr Scalar d1 = 7 / 24.0f; // second order answer constexpr Scalar d2 = 1 / 4.0f; constexpr Scalar d3 = 1 / 3.0f; constexpr Scalar d4 = 1 / 8.0f; auto k1 = dt * kelvinlet_evaluator(t + dt * a1, x, x0, f, F, kp); auto k2 = dt * kelvinlet_evaluator(t + dt * a2, x + k1 * b21, x0, f, F, kp); auto k3 = dt * kelvinlet_evaluator( t + dt * a3, x + k1 * b31 + k2 * b32, x0, f, F, kp); auto k4 = dt * kelvinlet_evaluator( t + dt * a4, x + k1 * b41 + k2 * b42 + k3 * b43, x0, f, F, kp); auto r1 = x + k1 * d1 + k2 * d2 + k3 * d3 + k4 * d4; auto r2 = x + k1 * c1 + k2 * c2 + k3 * c3; result = r2; error = (r2 - r1).norm() / dt; }; template IGL_INLINE void kelvinlets( const Eigen::MatrixBase& V, const Eigen::MatrixBase& x0, const Eigen::MatrixBase& f, const Eigen::MatrixBase& F, const KelvinletParams& params, Eigen::PlainObjectBase& U) { using Scalar = typename DerivedV::Scalar; constexpr auto max_error = 0.001f; constexpr Scalar safety = 0.9; const auto calc_displacement = [&](const int index) { Scalar dt = 0.1; Scalar t = 0; Eigen::Matrix x = V.row(index).transpose(); decltype(x) result; Scalar error; // taking smaller steps seems to prevents weird inside-out artifacts in the // final result. This implementation used an adaptive time step solver to // numerically integrate the ODEs while (t < 1) { dt = std::min(dt, 1 - t); integrate(t, dt, x, x0, f, F, params, result, error); auto new_dt = dt * safety * std::pow(max_error / error, 1 / 3.0); if (error <= max_error || dt <= 0.001) { x = result; t += dt; dt = new_dt; } else { dt = std::max(abs(new_dt - dt) < 0.001 ? dt / 2.f : new_dt, 0.001); } } U.row(index) = x.transpose(); }; const int n = V.rows(); U.resize(n, V.cols()); igl::parallel_for(n, calc_displacement, 1000); } } #ifdef IGL_STATIC_LIBRARY template void igl::kelvinlets, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix>( Eigen::MatrixBase> const&, Eigen::MatrixBase> const&, Eigen::MatrixBase> const&, Eigen::MatrixBase> const&, igl::KelvinletParams const&, Eigen::PlainObjectBase>&); #endif