From a6dc34541500fe5509a9b590fb5e5a895bac99ba Mon Sep 17 00:00:00 2001 From: Teschnique Date: Sun, 16 Mar 2025 01:17:33 -0700 Subject: [PATCH] Fix tangent baking for curves where the derivative evaluates to 0 due to collinear control points. --- scene/resources/curve.cpp | 44 +++++++++++++++------ tests/scene/test_path_follow_3d.h | 66 ++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index de68737b8c9..ca9b247f271 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -881,12 +881,22 @@ void Curve2D::_bake_segment2d_even_length(RBMap &r_bake, real_t 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) { // Handle corner cases. - if (Math::is_zero_approx(p_t - 0.0f) && p_control_1.is_equal_approx(p_begin)) { - return (p_end - p_begin).normalized(); - } - - if (Math::is_zero_approx(p_t - 1.0f) && p_control_2.is_equal_approx(p_end)) { - return (p_end - p_begin).normalized(); + if (Math::is_zero_approx(p_t - 0.0f)) { + if (p_control_1.is_equal_approx(p_begin)) { + if (p_control_1.is_equal_approx(p_control_2)) { + return (p_end - p_begin).normalized(); + } else { + return (p_control_2 - p_begin).normalized(); + } + } + } else if (Math::is_zero_approx(p_t - 1.0f)) { + if (p_control_2.is_equal_approx(p_end)) { + if (p_control_2.is_equal_approx(p_control_1)) { + return (p_end - p_begin).normalized(); + } else { + return (p_end - p_control_1).normalized(); + } + } } 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 &r_bake, real_t 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) { // Handle corner cases. - if (Math::is_zero_approx(p_t - 0.0f) && p_control_1.is_equal_approx(p_begin)) { - return (p_end - p_begin).normalized(); - } - - if (Math::is_zero_approx(p_t - 1.0f) && p_control_2.is_equal_approx(p_end)) { - return (p_end - p_begin).normalized(); + if (Math::is_zero_approx(p_t - 0.0f)) { + if (p_control_1.is_equal_approx(p_begin)) { + if (p_control_1.is_equal_approx(p_control_2)) { + return (p_end - p_begin).normalized(); + } else { + return (p_control_2 - p_begin).normalized(); + } + } + } else if (Math::is_zero_approx(p_t - 1.0f)) { + if (p_control_2.is_equal_approx(p_end)) { + if (p_control_2.is_equal_approx(p_control_1)) { + return (p_end - p_begin).normalized(); + } else { + return (p_end - p_control_1).normalized(); + } + } } return p_begin.bezier_derivative(p_control_1, p_control_2, p_end, p_t).normalized(); diff --git a/tests/scene/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h index 367b60b43d4..8c39f9367b6 100644 --- a/tests/scene/test_path_follow_3d.h +++ b/tests/scene/test_path_follow_3d.h @@ -45,7 +45,8 @@ bool is_equal_approx(const Vector3 &p_a, const Vector3 &p_b) { } TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") { - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); @@ -89,7 +90,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") { } TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") { - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); @@ -133,7 +135,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") { } TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") { - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); @@ -157,7 +160,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") { } TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") { - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); Path3D *path = memnew(Path3D); @@ -194,7 +198,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") { } TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") { - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); Path3D *path = memnew(Path3D); @@ -232,7 +237,8 @@ TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") { TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") { const real_t dist_cube_100 = 100 * Math::sqrt(3.0); - Ref curve = memnew(Curve3D); + Ref curve; + curve.instantiate(); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(200, 100, -100)); @@ -283,4 +289,52 @@ TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") { memdelete(path); } + +TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector with degenerate curves") { + Ref curve; + curve.instantiate(); + curve->add_point(Vector3(0, 0, 1), Vector3(), Vector3(1, 0, 0)); + curve->add_point(Vector3(1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0)); + curve->add_point(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(-1, 0, 0)); + curve->add_point(Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0)); + curve->add_point(Vector3(0, 0, 1), Vector3(-1, 0, 0), Vector3()); + Path3D *path = memnew(Path3D); + path->set_curve(curve); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); + + path_follow_3d->set_loop(false); + path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED); + + path_follow_3d->set_progress_ratio(0.00); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.25); + CHECK(is_equal_approx(Vector3(0, 0, 1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.50); + CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.75); + CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(1.00); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.125); + CHECK(is_equal_approx(Vector3(-0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.375); + CHECK(is_equal_approx(Vector3(0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.625); + CHECK(is_equal_approx(Vector3(0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress_ratio(0.875); + CHECK(is_equal_approx(Vector3(-0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2))); + + memdelete(path); +} + } // namespace TestPathFollow3D