diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 7de626e4777..a769cfeabf8 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -127,6 +127,7 @@
Creates a Basis with a rotation such that the forward axis (-Z) points towards the [param target] position.
The up axis (+Y) points as close to the [param up] vector as possible while staying perpendicular to the forward axis. The resulting Basis is orthonormalized. The [param target] and [param up] vectors cannot be zero, and cannot be parallel to each other.
+ If [param use_model_front] is [code]true[/code], the +Z axis (asset front) is treated as forward (implies +X is left) and points toward the [param target] position. By default, the -Z axis (camera forward) is treated as forward (implies +X is right).
diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml
index cad0e056e7b..8247911a343 100644
--- a/doc/classes/Node3D.xml
+++ b/doc/classes/Node3D.xml
@@ -115,10 +115,11 @@
- Rotates the node so that the local forward axis (-Z, [constant Vector3.FORWARD]) points toward the [param target] position. If the [param use_model_front] options is specified, then the model is oriented in reverse, towards the model front axis (+Z, [constant Vector3.MODEL_FRONT]), which is more useful for orienting 3D models.
+ Rotates the node so that the local forward axis (-Z, [constant Vector3.FORWARD]) points toward the [param target] position.
The local up axis (+Y) points as close to the [param up] vector as possible while staying perpendicular to the local forward axis. The resulting transform is orthogonal, and the scale is preserved. Non-uniform scaling may not work correctly.
The [param target] position cannot be the same as the node's position, the [param up] vector cannot be zero, and the direction from the node's position to the [param target] vector cannot be parallel to the [param up] vector.
Operations take place in global space, which means that the node must be in the scene tree.
+ If [param use_model_front] is [code]true[/code], the +Z axis (asset front) is treated as forward (implies +X is left) and points toward the [param target] position. By default, the -Z axis (camera forward) is treated as forward (implies +X is right).
diff --git a/doc/classes/PathFollow2D.xml b/doc/classes/PathFollow2D.xml
index 60118675b47..d958d4c14fc 100644
--- a/doc/classes/PathFollow2D.xml
+++ b/doc/classes/PathFollow2D.xml
@@ -18,9 +18,6 @@
The node's offset along the curve.
-
- How far to look ahead of the curve to calculate the tangent if the node is rotating. E.g. shorter lookaheads will lead to faster rotations.
-
If [code]true[/code], any offset outside the path's length will wrap around, instead of stopping at the ends. Use it for cyclic paths.
diff --git a/doc/classes/PathFollow3D.xml b/doc/classes/PathFollow3D.xml
index 41727a7bd8e..8d4101df0bd 100644
--- a/doc/classes/PathFollow3D.xml
+++ b/doc/classes/PathFollow3D.xml
@@ -43,6 +43,9 @@
If [code]true[/code], the tilt property of [Curve3D] takes effect.
+
+ If [code]true[/code], the node moves on the travel path with orienting the +Z axis as forward. See also [constant Vector3.FORWARD] and [constant Vector3.MODEL_FRONT].
+
The node's offset perpendicular to the curve.
diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml
index d375838a687..c01268779a2 100644
--- a/doc/classes/Transform3D.xml
+++ b/doc/classes/Transform3D.xml
@@ -97,6 +97,7 @@
Returns a copy of the transform rotated such that the forward axis (-Z) points towards the [param target] position.
The up axis (+Y) points as close to the [param up] vector as possible while staying perpendicular to the forward axis. The resulting transform is orthonormalized. The existing rotation, scale, and skew information from the original transform is discarded. The [param target] and [param up] vectors cannot be zero, cannot be parallel to each other, and are defined in global/parent space.
+ If [param use_model_front] is [code]true[/code], the +Z axis (asset front) is treated as forward (implies +X is left) and points toward the [param target] position. By default, the -Z axis (camera forward) is treated as forward (implies +X is right).
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 75cd04bee8a..d49c04445e8 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -274,10 +274,10 @@ void Path3DGizmo::redraw() {
// Fish Bone.
v3p.push_back(p1);
- v3p.push_back(p1 + (side - forward + up * 0.3) * 0.06);
+ v3p.push_back(p1 + (side + forward + up * 0.3) * 0.06);
v3p.push_back(p1);
- v3p.push_back(p1 + (-side - forward + up * 0.3) * 0.06);
+ v3p.push_back(p1 + (-side + forward + up * 0.3) * 0.06);
}
add_lines(v3p, path_material);
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 5036dd30b16..3e6a484e726 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -268,11 +268,11 @@ void PathFollow2D::_notification(int p_what) {
}
}
-void PathFollow2D::set_cubic_interpolation(bool p_enable) {
- cubic = p_enable;
+void PathFollow2D::set_cubic_interpolation_enabled(bool p_enabled) {
+ cubic = p_enabled;
}
-bool PathFollow2D::get_cubic_interpolation() const {
+bool PathFollow2D::is_cubic_interpolation_enabled() const {
return cubic;
}
@@ -312,18 +312,15 @@ void PathFollow2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_progress_ratio", "ratio"), &PathFollow2D::set_progress_ratio);
ClassDB::bind_method(D_METHOD("get_progress_ratio"), &PathFollow2D::get_progress_ratio);
- ClassDB::bind_method(D_METHOD("set_rotates", "enable"), &PathFollow2D::set_rotates);
- ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotating);
+ ClassDB::bind_method(D_METHOD("set_rotates", "enabled"), &PathFollow2D::set_rotation_enabled);
+ ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotation_enabled);
- ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &PathFollow2D::set_cubic_interpolation);
- ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow2D::get_cubic_interpolation);
+ ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enabled"), &PathFollow2D::set_cubic_interpolation_enabled);
+ ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow2D::is_cubic_interpolation_enabled);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow2D::set_loop);
ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow2D::has_loop);
- ClassDB::bind_method(D_METHOD("set_lookahead", "lookahead"), &PathFollow2D::set_lookahead);
- ClassDB::bind_method(D_METHOD("get_lookahead"), &PathFollow2D::get_lookahead);
-
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress", PROPERTY_HINT_RANGE, "0,10000,0.01,or_less,or_greater,suffix:px"), "set_progress", "get_progress");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001,or_less,or_greater", PROPERTY_USAGE_EDITOR), "set_progress_ratio", "get_progress_ratio");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset");
@@ -331,7 +328,6 @@ void PathFollow2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotates"), "set_rotates", "is_rotating");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lookahead", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001"), "set_lookahead", "get_lookahead");
}
void PathFollow2D::set_progress(real_t p_progress) {
@@ -395,20 +391,12 @@ real_t PathFollow2D::get_progress_ratio() const {
}
}
-void PathFollow2D::set_lookahead(real_t p_lookahead) {
- lookahead = p_lookahead;
-}
-
-real_t PathFollow2D::get_lookahead() const {
- return lookahead;
-}
-
-void PathFollow2D::set_rotates(bool p_rotates) {
- rotates = p_rotates;
+void PathFollow2D::set_rotation_enabled(bool p_enabled) {
+ rotates = p_enabled;
_update_transform();
}
-bool PathFollow2D::is_rotating() const {
+bool PathFollow2D::is_rotation_enabled() const {
return rotates;
}
diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h
index 89c77c49ebf..bfd5cde5e9c 100644
--- a/scene/2d/path_2d.h
+++ b/scene/2d/path_2d.h
@@ -70,7 +70,6 @@ private:
Timer *update_timer = nullptr;
real_t h_offset = 0.0;
real_t v_offset = 0.0;
- real_t lookahead = 4.0;
bool cubic = true;
bool loop = true;
bool rotates = true;
@@ -98,17 +97,14 @@ public:
void set_progress_ratio(real_t p_ratio);
real_t get_progress_ratio() const;
- void set_lookahead(real_t p_lookahead);
- real_t get_lookahead() const;
-
void set_loop(bool p_loop);
bool has_loop() const;
- void set_rotates(bool p_rotates);
- bool is_rotating() const;
+ void set_rotation_enabled(bool p_enabled);
+ bool is_rotation_enabled() const;
- void set_cubic_interpolation(bool p_enable);
- bool get_cubic_interpolation() const;
+ void set_cubic_interpolation_enabled(bool p_enabled);
+ bool is_cubic_interpolation_enabled() const;
PackedStringArray get_configuration_warnings() const override;
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index f02826b1c44..c71f80ea0e4 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -192,6 +192,9 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) {
t.origin = pos;
} else {
t = c->sample_baked_with_rotation(progress, cubic, false);
+ if (use_model_front) {
+ t.basis *= Basis::from_scale(Vector3(-1.0, 1.0, -1.0));
+ }
Vector3 forward = t.basis.get_column(2); // Retain tangent for applying tilt
t = PathFollow3D::correct_posture(t, rotation_mode);
@@ -230,11 +233,11 @@ void PathFollow3D::_notification(int p_what) {
}
}
-void PathFollow3D::set_cubic_interpolation(bool p_enable) {
- cubic = p_enable;
+void PathFollow3D::set_cubic_interpolation_enabled(bool p_enabled) {
+ cubic = p_enabled;
}
-bool PathFollow3D::get_cubic_interpolation() const {
+bool PathFollow3D::is_cubic_interpolation_enabled() const {
return cubic;
}
@@ -314,8 +317,11 @@ void PathFollow3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_rotation_mode", "rotation_mode"), &PathFollow3D::set_rotation_mode);
ClassDB::bind_method(D_METHOD("get_rotation_mode"), &PathFollow3D::get_rotation_mode);
- ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &PathFollow3D::set_cubic_interpolation);
- ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow3D::get_cubic_interpolation);
+ ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enabled"), &PathFollow3D::set_cubic_interpolation_enabled);
+ ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow3D::is_cubic_interpolation_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_use_model_front", "enabled"), &PathFollow3D::set_use_model_front);
+ ClassDB::bind_method(D_METHOD("is_using_model_front"), &PathFollow3D::is_using_model_front);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow3D::set_loop);
ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow3D::has_loop);
@@ -330,6 +336,7 @@ void PathFollow3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_mode", PROPERTY_HINT_ENUM, "None,Y,XY,XYZ,Oriented"), "set_rotation_mode", "get_rotation_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_model_front"), "set_use_model_front", "is_using_model_front");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tilt_enabled"), "set_tilt_enabled", "is_tilt_enabled");
@@ -412,6 +419,14 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
return rotation_mode;
}
+void PathFollow3D::set_use_model_front(bool p_use_model_front) {
+ use_model_front = p_use_model_front;
+}
+
+bool PathFollow3D::is_using_model_front() const {
+ return use_model_front;
+}
+
void PathFollow3D::set_loop(bool p_loop) {
loop = p_loop;
}
@@ -420,8 +435,8 @@ bool PathFollow3D::has_loop() const {
return loop;
}
-void PathFollow3D::set_tilt_enabled(bool p_enable) {
- tilt_enabled = p_enable;
+void PathFollow3D::set_tilt_enabled(bool p_enabled) {
+ tilt_enabled = p_enabled;
}
bool PathFollow3D::is_tilt_enabled() const {
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index 9fdcc0f0ef3..6116e980540 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -72,6 +72,8 @@ public:
ROTATION_ORIENTED
};
+ bool use_model_front = false;
+
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
private:
@@ -108,14 +110,17 @@ public:
void set_loop(bool p_loop);
bool has_loop() const;
- void set_tilt_enabled(bool p_enable);
+ void set_tilt_enabled(bool p_enabled);
bool is_tilt_enabled() const;
void set_rotation_mode(RotationMode p_rotation_mode);
RotationMode get_rotation_mode() const;
- void set_cubic_interpolation(bool p_enable);
- bool get_cubic_interpolation() const;
+ void set_use_model_front(bool p_use_model_front);
+ bool is_using_model_front() const;
+
+ void set_cubic_interpolation_enabled(bool p_enabled);
+ bool is_cubic_interpolation_enabled() const;
PackedStringArray get_configuration_warnings() const override;
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index a0bd22c79bf..64e466cf758 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -1648,9 +1648,9 @@ void Curve3D::_bake() const {
Vector3 forward = forward_ptr[0];
if (abs(forward.dot(Vector3(0, 1, 0))) > 1.0 - UNIT_EPSILON) {
- frame_prev = Basis::looking_at(-forward, Vector3(1, 0, 0));
+ frame_prev = Basis::looking_at(forward, Vector3(1, 0, 0));
} else {
- frame_prev = Basis::looking_at(-forward, Vector3(0, 1, 0));
+ frame_prev = Basis::looking_at(forward, Vector3(0, 1, 0));
}
up_write[0] = frame_prev.get_column(1);
@@ -1809,8 +1809,8 @@ Basis Curve3D::_sample_posture(Interval p_interval, bool p_apply_tilt) const {
}
// Build frames at both ends of the interval, then interpolate.
- const Basis frame_begin = Basis::looking_at(-forward_begin, up_begin);
- const Basis frame_end = Basis::looking_at(-forward_end, up_end);
+ const Basis frame_begin = Basis::looking_at(forward_begin, up_begin);
+ const Basis frame_end = Basis::looking_at(forward_end, up_end);
const Basis frame = frame_begin.slerp(frame_end, frac).orthonormalized();
if (!p_apply_tilt) {
diff --git a/tests/scene/test_curve_3d.h b/tests/scene/test_curve_3d.h
index 0f0d413354f..4d7b718d7e0 100644
--- a/tests/scene/test_curve_3d.h
+++ b/tests/scene/test_curve_3d.h
@@ -178,9 +178,9 @@ TEST_CASE("[Curve3D] Sampling") {
}
SUBCASE("sample_baked_with_rotation") {
- CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 0, 0)));
- CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 25, 0)));
- CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, 1), Vector3(1, 0, 0), Vector3(0, 1, 0)), Vector3(0, 50, 0)));
+ CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0)));
+ CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0)));
+ CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0)));
}
SUBCASE("sample_baked_tilt") {