Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Constraints/PathConstraintPathHermite.h>7#include <Jolt/Core/Profiler.h>8#include <Jolt/ObjectStream/TypeDeclarations.h>9#include <Jolt/Core/StreamIn.h>10#include <Jolt/Core/StreamOut.h>1112JPH_NAMESPACE_BEGIN1314JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PathConstraintPathHermite::Point)15{16JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mPosition)17JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mTangent)18JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mNormal)19}2021JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintPathHermite)22{23JPH_ADD_BASE_CLASS(PathConstraintPathHermite, PathConstraintPath)2425JPH_ADD_ATTRIBUTE(PathConstraintPathHermite, mPoints)26}2728// Calculate position and tangent for a Cubic Hermite Spline segment29static inline void sCalculatePositionAndTangent(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inT, Vec3 &outPosition, Vec3 &outTangent)30{31// Calculate factors for Cubic Hermite Spline32// See: https://en.wikipedia.org/wiki/Cubic_Hermite_spline33float t2 = inT * inT;34float t3 = inT * t2;35float h00 = 2.0f * t3 - 3.0f * t2 + 1.0f;36float h10 = t3 - 2.0f * t2 + inT;37float h01 = -2.0f * t3 + 3.0f * t2;38float h11 = t3 - t2;3940// Calculate d/dt for factors to calculate the tangent41float ddt_h00 = 6.0f * (t2 - inT);42float ddt_h10 = 3.0f * t2 - 4.0f * inT + 1.0f;43float ddt_h01 = -ddt_h00;44float ddt_h11 = 3.0f * t2 - 2.0f * inT;4546outPosition = h00 * inP1 + h10 * inM1 + h01 * inP2 + h11 * inM2;47outTangent = ddt_h00 * inP1 + ddt_h10 * inM1 + ddt_h01 * inP2 + ddt_h11 * inM2;48}4950// Calculate the closest point to the origin for a Cubic Hermite Spline segment51// This is used to get an estimate for the interval in which the closest point can be found,52// the interval [0, 1] is too big for Newton Raphson to work on because it is solving a 5th degree polynomial which may53// have multiple local minima that are not the root. This happens especially when the path is straight (tangents aligned with inP2 - inP1).54// Based on the bisection method: https://en.wikipedia.org/wiki/Bisection_method55static inline void sCalculateClosestPointThroughBisection(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float &outTMin, float &outTMax)56{57outTMin = 0.0f;58outTMax = 1.0f;5960// To get the closest point of the curve to the origin we need to solve:61// d/dt P(t) . P(t) = 0 for t, where P(t) is the point on the curve segment62// Using d/dt (a(t) . b(t)) = d/dt a(t) . b(t) + a(t) . d/dt b(t)63// See: https://proofwiki.org/wiki/Derivative_of_Dot_Product_of_Vector-Valued_Functions64// d/dt P(t) . P(t) = 2 P(t) d/dt P(t) = 2 P(t) . Tangent(t)6566// Calculate the derivative at t = 0, we know P(0) = inP1 and Tangent(0) = inM167float ddt_min = inP1.Dot(inM1); // Leaving out factor 2, we're only interested in the root68if (abs(ddt_min) < 1.0e-6f)69{70// Derivative is near zero, we found our root71outTMax = 0.0f;72return;73}74bool ddt_min_negative = ddt_min < 0.0f;7576// Calculate derivative at t = 1, we know P(1) = inP2 and Tangent(1) = inM277float ddt_max = inP2.Dot(inM2);78if (abs(ddt_max) < 1.0e-6f)79{80// Derivative is near zero, we found our root81outTMin = 1.0f;82return;83}84bool ddt_max_negative = ddt_max < 0.0f;8586// If the signs of the derivative are not different, this algorithm can't find the root87if (ddt_min_negative == ddt_max_negative)88return;8990// With 4 iterations we'll get a result accurate to 1 / 2^4 = 0.062591for (int iteration = 0; iteration < 4; ++iteration)92{93float t_mid = 0.5f * (outTMin + outTMax);94Vec3 position, tangent;95sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t_mid, position, tangent);96float ddt_mid = position.Dot(tangent);97if (abs(ddt_mid) < 1.0e-6f)98{99// Derivative is near zero, we found our root100outTMin = outTMax = t_mid;101return;102}103bool ddt_mid_negative = ddt_mid < 0.0f;104105// Update the search interval so that the signs of the derivative at both ends of the interval are still different106if (ddt_mid_negative == ddt_min_negative)107outTMin = t_mid;108else109outTMax = t_mid;110}111}112113// Calculate the closest point to the origin for a Cubic Hermite Spline segment114// Only considers the range t e [inTMin, inTMax] and will stop as soon as the closest point falls outside of that range115static inline float sCalculateClosestPointThroughNewtonRaphson(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inTMin, float inTMax, float &outDistanceSq)116{117// This is the closest position on the curve to the origin that we found118Vec3 position;119120// Calculate the size of the interval121float interval = inTMax - inTMin;122123// Start in the middle of the interval124float t = 0.5f * (inTMin + inTMax);125126// Do max 10 iterations to prevent taking too much CPU time127for (int iteration = 0; iteration < 10; ++iteration)128{129// Calculate derivative at t, see comment at sCalculateClosestPointThroughBisection for derivation of the equations130Vec3 tangent;131sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t, position, tangent);132float ddt = position.Dot(tangent); // Leaving out factor 2, we're only interested in the root133134// Calculate derivative of ddt: d^2/dt P(t) . P(t) = d/dt (2 P(t) . Tangent(t))135// = 2 (d/dt P(t)) . Tangent(t) + P(t) . d/dt Tangent(t)) = 2 (Tangent(t) . Tangent(t) + P(t) . d/dt Tangent(t))136float d2dt_h00 = 12.0f * t - 6.0f;137float d2dt_h10 = 6.0f * t - 4.0f;138float d2dt_h01 = -d2dt_h00;139float d2dt_h11 = 6.0f * t - 2.0f;140Vec3 ddt_tangent = d2dt_h00 * inP1 + d2dt_h10 * inM1 + d2dt_h01 * inP2 + d2dt_h11 * inM2;141float d2dt = tangent.Dot(tangent) + position.Dot(ddt_tangent); // Leaving out factor 2, because we left it out above too142143// If d2dt is zero, the curve is flat and there are multiple t's for which we are closest to the origin, stop now144if (d2dt == 0.0f)145break;146147// Do a Newton Raphson step148// See: https://en.wikipedia.org/wiki/Newton%27s_method149// Clamp against [-interval, interval] to avoid overshooting too much, we're not interested outside the interval150float delta = Clamp(-ddt / d2dt, -interval, interval);151152// If we're stepping away further from t e [inTMin, inTMax] stop now153if ((t > inTMax && delta > 0.0f) || (t < inTMin && delta < 0.0f))154break;155156// If we've converged, stop now157t += delta;158if (abs(delta) < 1.0e-4f)159break;160}161162// Calculate the distance squared for the origin to the curve163outDistanceSq = position.LengthSq();164return t;165}166167void PathConstraintPathHermite::GetIndexAndT(float inFraction, int &outIndex, float &outT) const168{169int num_points = int(mPoints.size());170171// Start by truncating the fraction to get the index and storing the remainder in t172int index = int(trunc(inFraction));173float t = inFraction - float(index);174175if (IsLooping())176{177JPH_ASSERT(!mPoints.front().mPosition.IsClose(mPoints.back().mPosition), "A looping path should have a different first and last point!");178179// Make sure index is positive by adding a multiple of num_points180if (index < 0)181index += (-index / num_points + 1) * num_points;182183// Index needs to be modulo num_points184index = index % num_points;185}186else187{188// Clamp against range of points189if (index < 0)190{191index = 0;192t = 0.0f;193}194else if (index >= num_points - 1)195{196index = num_points - 2;197t = 1.0f;198}199}200201outIndex = index;202outT = t;203}204205float PathConstraintPathHermite::GetClosestPoint(Vec3Arg inPosition, float inFractionHint) const206{207JPH_PROFILE_FUNCTION();208209int num_points = int(mPoints.size());210211// Start with last point on the path, in the non-looping case we won't be visiting this point212float best_dist_sq = (mPoints[num_points - 1].mPosition - inPosition).LengthSq();213float best_t = float(num_points - 1);214215// Loop over all points216for (int i = 0, max_i = IsLooping()? num_points : num_points - 1; i < max_i; ++i)217{218const Point &p1 = mPoints[i];219const Point &p2 = mPoints[(i + 1) % num_points];220221// Make the curve relative to inPosition222Vec3 p1_pos = p1.mPosition - inPosition;223Vec3 p2_pos = p2.mPosition - inPosition;224225// Get distance to p1226float dist_sq = p1_pos.LengthSq();227if (dist_sq < best_dist_sq)228{229best_t = float(i);230best_dist_sq = dist_sq;231}232233// First find an interval for the closest point so that we can start doing Newton Raphson steps234float t_min, t_max;235sCalculateClosestPointThroughBisection(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max);236237if (t_min == t_max)238{239// If the function above returned no interval then it found the root already and we can just calculate the distance240Vec3 position, tangent;241sCalculatePositionAndTangent(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, position, tangent);242dist_sq = position.LengthSq();243if (dist_sq < best_dist_sq)244{245best_t = float(i) + t_min;246best_dist_sq = dist_sq;247}248}249else250{251// Get closest distance along curve segment252float t = sCalculateClosestPointThroughNewtonRaphson(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max, dist_sq);253if (t >= 0.0f && t <= 1.0f && dist_sq < best_dist_sq)254{255best_t = float(i) + t;256best_dist_sq = dist_sq;257}258}259}260261return best_t;262}263264void PathConstraintPathHermite::GetPointOnPath(float inFraction, Vec3 &outPathPosition, Vec3 &outPathTangent, Vec3 &outPathNormal, Vec3 &outPathBinormal) const265{266JPH_PROFILE_FUNCTION();267268// Determine which hermite spline segment we need269int index;270float t;271GetIndexAndT(inFraction, index, t);272273// Get the points on the segment274const Point &p1 = mPoints[index];275const Point &p2 = mPoints[(index + 1) % int(mPoints.size())];276277// Calculate the position and tangent on the path278Vec3 tangent;279sCalculatePositionAndTangent(p1.mPosition, p1.mTangent, p2.mPosition, p2.mTangent, t, outPathPosition, tangent);280outPathTangent = tangent.Normalized();281282// Just linearly interpolate the normal283Vec3 normal = (1.0f - t) * p1.mNormal + t * p2.mNormal;284285// Calculate binormal286outPathBinormal = normal.Cross(outPathTangent).Normalized();287288// Recalculate normal so it is perpendicular to both (linear interpolation will cause it not to be)289outPathNormal = outPathTangent.Cross(outPathBinormal);290JPH_ASSERT(outPathNormal.IsNormalized());291}292293void PathConstraintPathHermite::SaveBinaryState(StreamOut &inStream) const294{295PathConstraintPath::SaveBinaryState(inStream);296297inStream.Write(mPoints);298}299300void PathConstraintPathHermite::RestoreBinaryState(StreamIn &inStream)301{302PathConstraintPath::RestoreBinaryState(inStream);303304inStream.Read(mPoints);305}306307JPH_NAMESPACE_END308309310