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();