Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/PathConstraintPathHermite.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Constraints/PathConstraintPathHermite.h>
8
#include <Jolt/Core/Profiler.h>
9
#include <Jolt/ObjectStream/TypeDeclarations.h>
10
#include <Jolt/Core/StreamIn.h>
11
#include <Jolt/Core/StreamOut.h>
12
13
JPH_NAMESPACE_BEGIN
14
15
JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PathConstraintPathHermite::Point)
16
{
17
JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mPosition)
18
JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mTangent)
19
JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mNormal)
20
}
21
22
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintPathHermite)
23
{
24
JPH_ADD_BASE_CLASS(PathConstraintPathHermite, PathConstraintPath)
25
26
JPH_ADD_ATTRIBUTE(PathConstraintPathHermite, mPoints)
27
}
28
29
// Calculate position and tangent for a Cubic Hermite Spline segment
30
static inline void sCalculatePositionAndTangent(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inT, Vec3 &outPosition, Vec3 &outTangent)
31
{
32
// Calculate factors for Cubic Hermite Spline
33
// See: https://en.wikipedia.org/wiki/Cubic_Hermite_spline
34
float t2 = inT * inT;
35
float t3 = inT * t2;
36
float h00 = 2.0f * t3 - 3.0f * t2 + 1.0f;
37
float h10 = t3 - 2.0f * t2 + inT;
38
float h01 = -2.0f * t3 + 3.0f * t2;
39
float h11 = t3 - t2;
40
41
// Calculate d/dt for factors to calculate the tangent
42
float ddt_h00 = 6.0f * (t2 - inT);
43
float ddt_h10 = 3.0f * t2 - 4.0f * inT + 1.0f;
44
float ddt_h01 = -ddt_h00;
45
float ddt_h11 = 3.0f * t2 - 2.0f * inT;
46
47
outPosition = h00 * inP1 + h10 * inM1 + h01 * inP2 + h11 * inM2;
48
outTangent = ddt_h00 * inP1 + ddt_h10 * inM1 + ddt_h01 * inP2 + ddt_h11 * inM2;
49
}
50
51
// Calculate the closest point to the origin for a Cubic Hermite Spline segment
52
// This is used to get an estimate for the interval in which the closest point can be found,
53
// the interval [0, 1] is too big for Newton Raphson to work on because it is solving a 5th degree polynomial which may
54
// have multiple local minima that are not the root. This happens especially when the path is straight (tangents aligned with inP2 - inP1).
55
// Based on the bisection method: https://en.wikipedia.org/wiki/Bisection_method
56
static inline void sCalculateClosestPointThroughBisection(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float &outTMin, float &outTMax)
57
{
58
outTMin = 0.0f;
59
outTMax = 1.0f;
60
61
// To get the closest point of the curve to the origin we need to solve:
62
// d/dt P(t) . P(t) = 0 for t, where P(t) is the point on the curve segment
63
// Using d/dt (a(t) . b(t)) = d/dt a(t) . b(t) + a(t) . d/dt b(t)
64
// See: https://proofwiki.org/wiki/Derivative_of_Dot_Product_of_Vector-Valued_Functions
65
// d/dt P(t) . P(t) = 2 P(t) d/dt P(t) = 2 P(t) . Tangent(t)
66
67
// Calculate the derivative at t = 0, we know P(0) = inP1 and Tangent(0) = inM1
68
float ddt_min = inP1.Dot(inM1); // Leaving out factor 2, we're only interested in the root
69
if (abs(ddt_min) < 1.0e-6f)
70
{
71
// Derivative is near zero, we found our root
72
outTMax = 0.0f;
73
return;
74
}
75
bool ddt_min_negative = ddt_min < 0.0f;
76
77
// Calculate derivative at t = 1, we know P(1) = inP2 and Tangent(1) = inM2
78
float ddt_max = inP2.Dot(inM2);
79
if (abs(ddt_max) < 1.0e-6f)
80
{
81
// Derivative is near zero, we found our root
82
outTMin = 1.0f;
83
return;
84
}
85
bool ddt_max_negative = ddt_max < 0.0f;
86
87
// If the signs of the derivative are not different, this algorithm can't find the root
88
if (ddt_min_negative == ddt_max_negative)
89
return;
90
91
// With 4 iterations we'll get a result accurate to 1 / 2^4 = 0.0625
92
for (int iteration = 0; iteration < 4; ++iteration)
93
{
94
float t_mid = 0.5f * (outTMin + outTMax);
95
Vec3 position, tangent;
96
sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t_mid, position, tangent);
97
float ddt_mid = position.Dot(tangent);
98
if (abs(ddt_mid) < 1.0e-6f)
99
{
100
// Derivative is near zero, we found our root
101
outTMin = outTMax = t_mid;
102
return;
103
}
104
bool ddt_mid_negative = ddt_mid < 0.0f;
105
106
// Update the search interval so that the signs of the derivative at both ends of the interval are still different
107
if (ddt_mid_negative == ddt_min_negative)
108
outTMin = t_mid;
109
else
110
outTMax = t_mid;
111
}
112
}
113
114
// Calculate the closest point to the origin for a Cubic Hermite Spline segment
115
// Only considers the range t e [inTMin, inTMax] and will stop as soon as the closest point falls outside of that range
116
static inline float sCalculateClosestPointThroughNewtonRaphson(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inTMin, float inTMax, float &outDistanceSq)
117
{
118
// This is the closest position on the curve to the origin that we found
119
Vec3 position;
120
121
// Calculate the size of the interval
122
float interval = inTMax - inTMin;
123
124
// Start in the middle of the interval
125
float t = 0.5f * (inTMin + inTMax);
126
127
// Do max 10 iterations to prevent taking too much CPU time
128
for (int iteration = 0; iteration < 10; ++iteration)
129
{
130
// Calculate derivative at t, see comment at sCalculateClosestPointThroughBisection for derivation of the equations
131
Vec3 tangent;
132
sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t, position, tangent);
133
float ddt = position.Dot(tangent); // Leaving out factor 2, we're only interested in the root
134
135
// Calculate derivative of ddt: d^2/dt P(t) . P(t) = d/dt (2 P(t) . Tangent(t))
136
// = 2 (d/dt P(t)) . Tangent(t) + P(t) . d/dt Tangent(t)) = 2 (Tangent(t) . Tangent(t) + P(t) . d/dt Tangent(t))
137
float d2dt_h00 = 12.0f * t - 6.0f;
138
float d2dt_h10 = 6.0f * t - 4.0f;
139
float d2dt_h01 = -d2dt_h00;
140
float d2dt_h11 = 6.0f * t - 2.0f;
141
Vec3 ddt_tangent = d2dt_h00 * inP1 + d2dt_h10 * inM1 + d2dt_h01 * inP2 + d2dt_h11 * inM2;
142
float d2dt = tangent.Dot(tangent) + position.Dot(ddt_tangent); // Leaving out factor 2, because we left it out above too
143
144
// If d2dt is zero, the curve is flat and there are multiple t's for which we are closest to the origin, stop now
145
if (d2dt == 0.0f)
146
break;
147
148
// Do a Newton Raphson step
149
// See: https://en.wikipedia.org/wiki/Newton%27s_method
150
// Clamp against [-interval, interval] to avoid overshooting too much, we're not interested outside the interval
151
float delta = Clamp(-ddt / d2dt, -interval, interval);
152
153
// If we're stepping away further from t e [inTMin, inTMax] stop now
154
if ((t > inTMax && delta > 0.0f) || (t < inTMin && delta < 0.0f))
155
break;
156
157
// If we've converged, stop now
158
t += delta;
159
if (abs(delta) < 1.0e-4f)
160
break;
161
}
162
163
// Calculate the distance squared for the origin to the curve
164
outDistanceSq = position.LengthSq();
165
return t;
166
}
167
168
void PathConstraintPathHermite::GetIndexAndT(float inFraction, int &outIndex, float &outT) const
169
{
170
int num_points = int(mPoints.size());
171
172
// Start by truncating the fraction to get the index and storing the remainder in t
173
int index = int(trunc(inFraction));
174
float t = inFraction - float(index);
175
176
if (IsLooping())
177
{
178
JPH_ASSERT(!mPoints.front().mPosition.IsClose(mPoints.back().mPosition), "A looping path should have a different first and last point!");
179
180
// Make sure index is positive by adding a multiple of num_points
181
if (index < 0)
182
index += (-index / num_points + 1) * num_points;
183
184
// Index needs to be modulo num_points
185
index = index % num_points;
186
}
187
else
188
{
189
// Clamp against range of points
190
if (index < 0)
191
{
192
index = 0;
193
t = 0.0f;
194
}
195
else if (index >= num_points - 1)
196
{
197
index = num_points - 2;
198
t = 1.0f;
199
}
200
}
201
202
outIndex = index;
203
outT = t;
204
}
205
206
float PathConstraintPathHermite::GetClosestPoint(Vec3Arg inPosition, float inFractionHint) const
207
{
208
JPH_PROFILE_FUNCTION();
209
210
int num_points = int(mPoints.size());
211
212
// Start with last point on the path, in the non-looping case we won't be visiting this point
213
float best_dist_sq = (mPoints[num_points - 1].mPosition - inPosition).LengthSq();
214
float best_t = float(num_points - 1);
215
216
// Loop over all points
217
for (int i = 0, max_i = IsLooping()? num_points : num_points - 1; i < max_i; ++i)
218
{
219
const Point &p1 = mPoints[i];
220
const Point &p2 = mPoints[(i + 1) % num_points];
221
222
// Make the curve relative to inPosition
223
Vec3 p1_pos = p1.mPosition - inPosition;
224
Vec3 p2_pos = p2.mPosition - inPosition;
225
226
// Get distance to p1
227
float dist_sq = p1_pos.LengthSq();
228
if (dist_sq < best_dist_sq)
229
{
230
best_t = float(i);
231
best_dist_sq = dist_sq;
232
}
233
234
// First find an interval for the closest point so that we can start doing Newton Raphson steps
235
float t_min, t_max;
236
sCalculateClosestPointThroughBisection(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max);
237
238
if (t_min == t_max)
239
{
240
// If the function above returned no interval then it found the root already and we can just calculate the distance
241
Vec3 position, tangent;
242
sCalculatePositionAndTangent(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, position, tangent);
243
dist_sq = position.LengthSq();
244
if (dist_sq < best_dist_sq)
245
{
246
best_t = float(i) + t_min;
247
best_dist_sq = dist_sq;
248
}
249
}
250
else
251
{
252
// Get closest distance along curve segment
253
float t = sCalculateClosestPointThroughNewtonRaphson(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max, dist_sq);
254
if (t >= 0.0f && t <= 1.0f && dist_sq < best_dist_sq)
255
{
256
best_t = float(i) + t;
257
best_dist_sq = dist_sq;
258
}
259
}
260
}
261
262
return best_t;
263
}
264
265
void PathConstraintPathHermite::GetPointOnPath(float inFraction, Vec3 &outPathPosition, Vec3 &outPathTangent, Vec3 &outPathNormal, Vec3 &outPathBinormal) const
266
{
267
JPH_PROFILE_FUNCTION();
268
269
// Determine which hermite spline segment we need
270
int index;
271
float t;
272
GetIndexAndT(inFraction, index, t);
273
274
// Get the points on the segment
275
const Point &p1 = mPoints[index];
276
const Point &p2 = mPoints[(index + 1) % int(mPoints.size())];
277
278
// Calculate the position and tangent on the path
279
Vec3 tangent;
280
sCalculatePositionAndTangent(p1.mPosition, p1.mTangent, p2.mPosition, p2.mTangent, t, outPathPosition, tangent);
281
outPathTangent = tangent.Normalized();
282
283
// Just linearly interpolate the normal
284
Vec3 normal = (1.0f - t) * p1.mNormal + t * p2.mNormal;
285
286
// Calculate binormal
287
outPathBinormal = normal.Cross(outPathTangent).Normalized();
288
289
// Recalculate normal so it is perpendicular to both (linear interpolation will cause it not to be)
290
outPathNormal = outPathTangent.Cross(outPathBinormal);
291
JPH_ASSERT(outPathNormal.IsNormalized());
292
}
293
294
void PathConstraintPathHermite::SaveBinaryState(StreamOut &inStream) const
295
{
296
PathConstraintPath::SaveBinaryState(inStream);
297
298
inStream.Write(mPoints);
299
}
300
301
void PathConstraintPathHermite::RestoreBinaryState(StreamIn &inStream)
302
{
303
PathConstraintPath::RestoreBinaryState(inStream);
304
305
inStream.Read(mPoints);
306
}
307
308
JPH_NAMESPACE_END
309
310