Skip to content

Commit a6dc345

Browse files
committed
Fix tangent baking for curves where the derivative evaluates to 0 due to
collinear control points.
1 parent 0e2d152 commit a6dc345

File tree

2 files changed

+92
-18
lines changed

2 files changed

+92
-18
lines changed

scene/resources/curve.cpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -881,12 +881,22 @@ void Curve2D::_bake_segment2d_even_length(RBMap<real_t, Vector2> &r_bake, real_t
881881

882882
Vector2 Curve2D::_calculate_tangent(const Vector2 &p_begin, const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) {
883883
// Handle corner cases.
884-
if (Math::is_zero_approx(p_t - 0.0f) && p_control_1.is_equal_approx(p_begin)) {
885-
return (p_end - p_begin).normalized();
886-
}
887-
888-
if (Math::is_zero_approx(p_t - 1.0f) && p_control_2.is_equal_approx(p_end)) {
889-
return (p_end - p_begin).normalized();
884+
if (Math::is_zero_approx(p_t - 0.0f)) {
885+
if (p_control_1.is_equal_approx(p_begin)) {
886+
if (p_control_1.is_equal_approx(p_control_2)) {
887+
return (p_end - p_begin).normalized();
888+
} else {
889+
return (p_control_2 - p_begin).normalized();
890+
}
891+
}
892+
} else if (Math::is_zero_approx(p_t - 1.0f)) {
893+
if (p_control_2.is_equal_approx(p_end)) {
894+
if (p_control_2.is_equal_approx(p_control_1)) {
895+
return (p_end - p_begin).normalized();
896+
} else {
897+
return (p_end - p_control_1).normalized();
898+
}
899+
}
890900
}
891901

892902
return p_begin.bezier_derivative(p_control_1, p_control_2, p_end, p_t).normalized();
@@ -1620,12 +1630,22 @@ void Curve3D::_bake_segment3d_even_length(RBMap<real_t, Vector3> &r_bake, real_t
16201630

16211631
Vector3 Curve3D::_calculate_tangent(const Vector3 &p_begin, const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) {
16221632
// Handle corner cases.
1623-
if (Math::is_zero_approx(p_t - 0.0f) && p_control_1.is_equal_approx(p_begin)) {
1624-
return (p_end - p_begin).normalized();
1625-
}
1626-
1627-
if (Math::is_zero_approx(p_t - 1.0f) && p_control_2.is_equal_approx(p_end)) {
1628-
return (p_end - p_begin).normalized();
1633+
if (Math::is_zero_approx(p_t - 0.0f)) {
1634+
if (p_control_1.is_equal_approx(p_begin)) {
1635+
if (p_control_1.is_equal_approx(p_control_2)) {
1636+
return (p_end - p_begin).normalized();
1637+
} else {
1638+
return (p_control_2 - p_begin).normalized();
1639+
}
1640+
}
1641+
} else if (Math::is_zero_approx(p_t - 1.0f)) {
1642+
if (p_control_2.is_equal_approx(p_end)) {
1643+
if (p_control_2.is_equal_approx(p_control_1)) {
1644+
return (p_end - p_begin).normalized();
1645+
} else {
1646+
return (p_end - p_control_1).normalized();
1647+
}
1648+
}
16291649
}
16301650

16311651
return p_begin.bezier_derivative(p_control_1, p_control_2, p_end, p_t).normalized();

tests/scene/test_path_follow_3d.h

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ bool is_equal_approx(const Vector3 &p_a, const Vector3 &p_b) {
4545
}
4646

4747
TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
48-
Ref<Curve3D> curve = memnew(Curve3D);
48+
Ref<Curve3D> curve;
49+
curve.instantiate();
4950
curve->add_point(Vector3(0, 0, 0));
5051
curve->add_point(Vector3(100, 0, 0));
5152
curve->add_point(Vector3(100, 100, 0));
@@ -89,7 +90,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
8990
}
9091

9192
TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
92-
Ref<Curve3D> curve = memnew(Curve3D);
93+
Ref<Curve3D> curve;
94+
curve.instantiate();
9395
curve->add_point(Vector3(0, 0, 0));
9496
curve->add_point(Vector3(100, 0, 0));
9597
curve->add_point(Vector3(100, 100, 0));
@@ -133,7 +135,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
133135
}
134136

135137
TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
136-
Ref<Curve3D> curve = memnew(Curve3D);
138+
Ref<Curve3D> curve;
139+
curve.instantiate();
137140
curve->add_point(Vector3(0, 0, 0));
138141
curve->add_point(Vector3(100, 0, 0));
139142
curve->add_point(Vector3(100, 100, 0));
@@ -157,7 +160,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
157160
}
158161

159162
TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") {
160-
Ref<Curve3D> curve = memnew(Curve3D);
163+
Ref<Curve3D> curve;
164+
curve.instantiate();
161165
curve->add_point(Vector3(0, 0, 0));
162166
curve->add_point(Vector3(100, 0, 0));
163167
Path3D *path = memnew(Path3D);
@@ -194,7 +198,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") {
194198
}
195199

196200
TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") {
197-
Ref<Curve3D> curve = memnew(Curve3D);
201+
Ref<Curve3D> curve;
202+
curve.instantiate();
198203
curve->add_point(Vector3(0, 0, 0));
199204
curve->add_point(Vector3(100, 0, 0));
200205
Path3D *path = memnew(Path3D);
@@ -232,7 +237,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") {
232237

233238
TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
234239
const real_t dist_cube_100 = 100 * Math::sqrt(3.0);
235-
Ref<Curve3D> curve = memnew(Curve3D);
240+
Ref<Curve3D> curve;
241+
curve.instantiate();
236242
curve->add_point(Vector3(0, 0, 0));
237243
curve->add_point(Vector3(100, 0, 0));
238244
curve->add_point(Vector3(200, 100, -100));
@@ -283,4 +289,52 @@ TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
283289

284290
memdelete(path);
285291
}
292+
293+
TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector with degenerate curves") {
294+
Ref<Curve3D> curve;
295+
curve.instantiate();
296+
curve->add_point(Vector3(0, 0, 1), Vector3(), Vector3(1, 0, 0));
297+
curve->add_point(Vector3(1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0));
298+
curve->add_point(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(-1, 0, 0));
299+
curve->add_point(Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0));
300+
curve->add_point(Vector3(0, 0, 1), Vector3(-1, 0, 0), Vector3());
301+
Path3D *path = memnew(Path3D);
302+
path->set_curve(curve);
303+
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
304+
path->add_child(path_follow_3d);
305+
SceneTree::get_singleton()->get_root()->add_child(path);
306+
307+
path_follow_3d->set_loop(false);
308+
path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED);
309+
310+
path_follow_3d->set_progress_ratio(0.00);
311+
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
312+
313+
path_follow_3d->set_progress_ratio(0.25);
314+
CHECK(is_equal_approx(Vector3(0, 0, 1), path_follow_3d->get_transform().get_basis().get_column(2)));
315+
316+
path_follow_3d->set_progress_ratio(0.50);
317+
CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
318+
319+
path_follow_3d->set_progress_ratio(0.75);
320+
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
321+
322+
path_follow_3d->set_progress_ratio(1.00);
323+
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
324+
325+
path_follow_3d->set_progress_ratio(0.125);
326+
CHECK(is_equal_approx(Vector3(-0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
327+
328+
path_follow_3d->set_progress_ratio(0.375);
329+
CHECK(is_equal_approx(Vector3(0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
330+
331+
path_follow_3d->set_progress_ratio(0.625);
332+
CHECK(is_equal_approx(Vector3(0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
333+
334+
path_follow_3d->set_progress_ratio(0.875);
335+
CHECK(is_equal_approx(Vector3(-0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
336+
337+
memdelete(path);
338+
}
339+
286340
} // namespace TestPathFollow3D

0 commit comments

Comments
 (0)