diff --git a/README.md b/README.md
index 1822abcacfcc..f224778165d2 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
# Based on Godot 4.3 but with changes
* [x] Expose Engine.get_frame_ticks to GDScript
+* [x] Change look_at to have default up of Vector3.INF and find appropriate axis itself if Vector3.INF is argument.
# Get code
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 34ed1c2d855f..c618a4758aaf 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -1042,13 +1042,27 @@ void Basis::rotate_sh(real_t *p_values) {
Basis Basis::looking_at(const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) {
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(p_target.is_zero_approx(), Basis(), "The target vector can't be zero.");
- ERR_FAIL_COND_V_MSG(p_up.is_zero_approx(), Basis(), "The up vector can't be zero.");
+ ERR_FAIL_COND_V_MSG(p_up.is_finite() && p_up.is_zero_approx(), Basis(), "The up vector can't be zero.");
#endif
Vector3 v_z = p_target.normalized();
if (!p_use_model_front) {
v_z = -v_z;
}
- Vector3 v_x = p_up.cross(v_z);
+
+ // Find an up vector that we can rotate around
+ Vector3 up = p_up;
+ if (! up.is_finite()) {
+ static const Vector3 up_candidates[3] = { Vector3(0, 1, 0), Vector3(1, 0, 0), Vector3(0, 0, 1) };
+ for (const Vector3 candidate : up_candidates) {
+ Vector3 v_x = candidate.cross(v_z).normalized();
+ if (! v_x.is_zero_approx()) {
+ up = candidate;
+ break;
+ }
+ }
+ }
+
+ Vector3 v_x = up.cross(v_z);
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(v_x.is_zero_approx(), Basis(), "The target vector and up vector can't be parallel to each other.");
#endif
diff --git a/core/math/basis.h b/core/math/basis.h
index 5c1a5fbdda71..5532284d1129 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -221,7 +221,7 @@ struct [[nodiscard]] Basis {
operator Quaternion() const { return get_quaternion(); }
- static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
+ static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(INFINITY, INFINITY, INFINITY), bool p_use_model_front = false);
Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); };
Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); }
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index b1de233445e5..1921d65f839c 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -52,8 +52,8 @@ struct [[nodiscard]] Transform3D {
void rotate(const Vector3 &p_axis, real_t p_angle);
void rotate_basis(const Vector3 &p_axis, real_t p_angle);
- void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
- Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false) const;
+ void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(INFINITY, INFINITY, INFINITY), bool p_use_model_front = false);
+ Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(INFINITY, INFINITY, INFINITY), bool p_use_model_front = false) const;
void scale(const Vector3 &p_scale);
Transform3D scaled(const Vector3 &p_scale) const;
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 322d2ab9d4ae..71e76c7f002d 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -202,12 +202,13 @@
-
+
Creates a new [Basis] with a rotation such that the forward axis (-Z) points towards the [param target] position.
By default, the -Z axis (camera forward) is treated as forward (implies +X is right). 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.
The up axis (+Y) points as close to the [param up] vector as possible while staying perpendicular to the forward axis. The returned basis is orthonormalized (see [method orthonormalized]). The [param target] and [param up] vectors cannot be [constant Vector3.ZERO], and cannot be parallel to each other.
+ If the [param up] is Vector3.INF, it will use Vector3.UP, Vector3.RIGHT, or Vector3.BACK to rotate around.
diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml
index 125c7ef3eec8..c5495bf6db47 100644
--- a/doc/classes/Node3D.xml
+++ b/doc/classes/Node3D.xml
@@ -114,12 +114,13 @@
-
+
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.
+ 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. If the
+ [param up] is Vector3.INF, it will use Vector3.UP, Vector3.RIGHT, or Vector3.BACK to rotate around.
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).
@@ -128,7 +129,7 @@
-
+
Moves the node to the specified [param position], and then rotates the node to point toward the [param target] as per [method look_at]. Operations take place in global space.
diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml
index 30c141659aa6..b956094ba84c 100644
--- a/doc/classes/Transform3D.xml
+++ b/doc/classes/Transform3D.xml
@@ -97,11 +97,12 @@
-
+
Returns a copy of this transform rotated so 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 the [param up] is Vector3.INF, it will use Vector3.UP, Vector3.RIGHT, or Vector3.BACK to rotate around.
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/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 6b741b7dd719..3bd26545f8e5 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -1735,7 +1735,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
camera->set_environment(environment);
light1 = memnew(DirectionalLight3D);
- light1->set_transform(Transform3D(Basis::looking_at(Vector3(-1, -1, -1))));
+ light1->set_transform(Transform3D(Basis::looking_at(Vector3(-1, -1, -1), Vector3(INFINITY, INFINITY, INFINITY))));
light1->set_shadow(true);
camera->add_child(light1);
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 9c07b664341c..636c02cedb28 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -655,7 +655,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point,
Transform3D t = spatial_node->get_global_transform();
Vector3 camera_position = p_camera->get_camera_transform().origin;
if (!camera_position.is_equal_approx(t.origin)) {
- t.set_look_at(t.origin, camera_position);
+ t.set_look_at(t.origin, camera_position, Vector3(INFINITY, INFINITY, INFINITY));
}
float scale = t.origin.distance_to(p_camera->get_camera_transform().origin);
@@ -672,7 +672,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point,
if (!orig_camera_transform.origin.is_equal_approx(t.origin) &&
ABS(orig_camera_transform.basis.get_column(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) {
- p_camera->look_at(t.origin);
+ p_camera->look_at(t.origin, Vector3(INFINITY, INFINITY, INFINITY));
}
Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale);
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 2e08afb30db1..fa4ee8642923 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -1195,8 +1195,8 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("orthonormalize"), &Node3D::orthonormalize);
ClassDB::bind_method(D_METHOD("set_identity"), &Node3D::set_identity);
- ClassDB::bind_method(D_METHOD("look_at", "target", "up", "use_model_front"), &Node3D::look_at, DEFVAL(Vector3(0, 1, 0)), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("look_at_from_position", "position", "target", "up", "use_model_front"), &Node3D::look_at_from_position, DEFVAL(Vector3(0, 1, 0)), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("look_at", "target", "up", "use_model_front"), &Node3D::look_at, DEFVAL(Vector3(INFINITY, INFINITY, INFINITY)), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("look_at_from_position", "position", "target", "up", "use_model_front"), &Node3D::look_at_from_position, DEFVAL(Vector3(INFINITY, INFINITY, INFINITY)), DEFVAL(false));
ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node3D::to_local);
ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node3D::to_global);
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index c1667221df9f..81b1d54cda03 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -252,8 +252,8 @@ class Node3D : public Node {
void global_scale(const Vector3 &p_scale);
void global_translate(const Vector3 &p_offset);
- void look_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
- void look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
+ void look_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(INFINITY, INFINITY, INFINITY), bool p_use_model_front = false);
+ void look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target, const Vector3 &p_up = Vector3(INFINITY, INFINITY, INFINITY), bool p_use_model_front = false);
Vector3 to_local(Vector3 p_global) const;
Vector3 to_global(Vector3 p_local) const;
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 2bdcc7b9ba53..fbb790222f05 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -326,7 +326,7 @@ Transform3D PathFollow3D::correct_posture(Transform3D p_transform, PathFollow3D:
Vector3 tangent = -t.basis.get_column(2);
// Y-axis points up by default.
- t.basis = Basis::looking_at(tangent);
+ t.basis = Basis::looking_at(tangent, Vector3(INFINITY, INFINITY, INFINITY));
} else {
// Lock some euler axes.
Vector3 euler = t.basis.get_euler_normalized(EulerOrder::YXZ);
diff --git a/tests/scene/test_camera_3d.h b/tests/scene/test_camera_3d.h
index 830c667257ba..8acfe711bb36 100644
--- a/tests/scene/test_camera_3d.h
+++ b/tests/scene/test_camera_3d.h
@@ -143,7 +143,7 @@ TEST_CASE("[SceneTree][Camera3D] Position queries") {
// The orthogonal case is simpler, so we test a more random position + rotation combination here.
// For the other cases we'll use zero translation and rotation instead.
test_camera->set_global_position(Vector3(1, 2, 3));
- test_camera->look_at(Vector3(-4, 5, 1));
+ test_camera->look_at(Vector3(-4, 5, 1), Vector3(INFINITY, INFINITY, INFINITY));
// Width = 5, Aspect Ratio = 400 / 200 = 2, so Height is 2.5.
test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
const Basis basis = test_camera->get_global_basis();