From e777acec151e1e933b7ee5509bd0db1f5fa87378 Mon Sep 17 00:00:00 2001 From: nikolay-egorov Date: Mon, 27 Apr 2026 10:34:26 +0300 Subject: [PATCH] Add quaternion rotation targets to Generic6DOFJoint3D: - SixDOF target orientations supported for Jolt, trivial fallback to identity quaternion is used when explicit angular rotation is unconfigured or unsupported - Conversion between BS and CS is handled inside, surface exposes BS Implements godotengine/godot-proposals#3891 --- doc/classes/Generic6DOFJoint3D.xml | 26 ++++ doc/classes/PhysicsServer3D.xml | 17 +++ doc/classes/PhysicsServer3DExtension.xml | 13 ++ .../godot_physics_server_3d.cpp | 19 +++ .../godot_physics_server_3d.h | 3 + .../joints/jolt_generic_6dof_joint_3d.cpp | 47 ++++++++ .../joints/jolt_generic_6dof_joint_3d.h | 7 ++ .../jolt_physics/jolt_physics_server_3d.cpp | 20 ++++ modules/jolt_physics/jolt_physics_server_3d.h | 3 + .../jolt_physics/misc/jolt_type_conversions.h | 7 +- .../physics/joints/generic_6dof_joint_3d.cpp | 70 +++++++++++ .../3d/physics/joints/generic_6dof_joint_3d.h | 9 ++ servers/physics_3d/physics_server_3d.cpp | 3 + servers/physics_3d/physics_server_3d.h | 3 + servers/physics_3d/physics_server_3d_dummy.h | 3 + .../physics_server_3d_extension.cpp | 3 + .../physics_3d/physics_server_3d_extension.h | 3 + .../physics_3d/physics_server_3d_wrap_mt.h | 3 + tests/scene/test_generic_6dof_joint_3d.cpp | 111 ++++++++++++++++++ 19 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 tests/scene/test_generic_6dof_joint_3d.cpp diff --git a/doc/classes/Generic6DOFJoint3D.xml b/doc/classes/Generic6DOFJoint3D.xml index 0b6a805b17..8f30c0e772 100644 --- a/doc/classes/Generic6DOFJoint3D.xml +++ b/doc/classes/Generic6DOFJoint3D.xml @@ -10,6 +10,18 @@ + + + + Clears the quaternion angular target. After clearing, the joint is driven by [constant PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT] on each axis as configured. + + + + + + Returns the joint's current angular target as a body-space quaternion. If no explicit target was set via [method set_angular_target_rotation], the value is derived from [constant PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT] on backends that support it; other backends return the identity quaternion. + + @@ -46,6 +58,20 @@ + + + + Returns [code]true[/code] if a quaternion angular target was explicitly set via [method set_angular_target_rotation] and has not been cleared. Returns [code]false[/code] when the joint is driven by [constant PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT] only. + + + + + + + Sets the target angular orientation as a body-space quaternion describing the desired orientation of body B relative to body A. Replaces any previously set target and supersedes [constant PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT] until [method clear_angular_target_rotation] is called. + [b]Note:[/b] Only for Jolt backend. Other 3D physics backends may ignore this value. + + diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 54eb85f49d..37024d1405 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -759,6 +759,14 @@ Destroys any of the objects created by PhysicsServer3D. If the [RID] passed is not one of the objects that can be created by PhysicsServer3D, an error will be sent to the console. + + + + + Returns the target angular orientation of a generic 6DOF joint as a body-space quaternion describing the desired orientation of body B relative to body A. + [b]Note:[/b] Only for Jolt backend. Other 3D physics backends return the identity quaternion. + + @@ -777,6 +785,15 @@ Returns the value of a generic 6DOF joint parameter. + + + + + + Sets the target angular orientation of a generic 6DOF joint as a body-space quaternion describing the desired orientation of body B relative to body A. + [b]Note:[/b] Only for Jolt backend. Other 3D physics backends may ignore this value. + + diff --git a/doc/classes/PhysicsServer3DExtension.xml b/doc/classes/PhysicsServer3DExtension.xml index 806173848e..e5c2e59864 100644 --- a/doc/classes/PhysicsServer3DExtension.xml +++ b/doc/classes/PhysicsServer3DExtension.xml @@ -680,6 +680,12 @@ + + + + + + @@ -696,6 +702,13 @@ + + + + + + + diff --git a/modules/godot_physics_3d/godot_physics_server_3d.cpp b/modules/godot_physics_3d/godot_physics_server_3d.cpp index 66ca5ca223..05e4813b77 100644 --- a/modules/godot_physics_3d/godot_physics_server_3d.cpp +++ b/modules/godot_physics_3d/godot_physics_server_3d.cpp @@ -1596,6 +1596,25 @@ bool GodotPhysicsServer3D::generic_6dof_joint_get_flag(RID p_joint, Vector3::Axi return generic_6dof_joint->get_flag(p_axis, p_flag); } +void GodotPhysicsServer3D::generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) { + ERR_FAIL_COND_MSG(!p_target_rotation.is_finite() || p_target_rotation.length_squared() <= CMP_EPSILON, "Angular target rotation must be a finite, non-zero quaternion."); + + GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_6DOF); + WARN_PRINT_ONCE("Quaternion angular target rotations for Generic6DOFJoint3D are only supported by Jolt Physics. This value will be ignored by GodotPhysics3D."); +} + +Quaternion GodotPhysicsServer3D::generic_6dof_joint_get_angular_target_rotation(RID p_joint) const { + const GodotJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, Quaternion()); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_6DOF, Quaternion()); + + // GodotPhysics3D does not apply angular spring equilibrium points, so there is no body-space target to return. + WARN_PRINT_ONCE("Quaternion angular target rotations for Generic6DOFJoint3D are only supported by Jolt Physics. GodotPhysics3D returns the identity quaternion."); + return Quaternion(); +} + void GodotPhysicsServer3D::free_rid(RID p_rid) { _update_shapes(); //just in case diff --git a/modules/godot_physics_3d/godot_physics_server_3d.h b/modules/godot_physics_3d/godot_physics_server_3d.h index 417bfd26a7..052019f33c 100644 --- a/modules/godot_physics_3d/godot_physics_server_3d.h +++ b/modules/godot_physics_3d/godot_physics_server_3d.h @@ -361,6 +361,9 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag, bool p_enable) override; virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag) const override; + virtual void generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) override; + virtual Quaternion generic_6dof_joint_get_angular_target_rotation(RID p_joint) const override; + virtual JointType joint_get_type(RID p_joint) const override; virtual void joint_set_solver_priority(RID p_joint, int p_priority) override; diff --git a/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.cpp b/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.cpp index 765e054ce1..e0707cf409 100644 --- a/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.cpp +++ b/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.cpp @@ -208,6 +208,20 @@ void JoltGeneric6DOFJoint3D::_update_spring_equilibrium(int p_axis) { } } +void JoltGeneric6DOFJoint3D::_update_angular_target_rotation() { + if (!has_angular_target_rotation) { + return; + } + + JPH::SixDOFConstraint *constraint = static_cast(jolt_ref.GetPtr()); + if (unlikely(constraint == nullptr)) { + return; + } + + const JPH::Quat target_orientation = to_jolt(angular_target_rotation); + constraint->SetTargetOrientationBS(target_orientation); +} + void JoltGeneric6DOFJoint3D::_limits_changed() { rebuild(); _wake_up_bodies(); @@ -435,6 +449,7 @@ void JoltGeneric6DOFJoint3D::set_param(Axis p_axis, Param p_param, double p_valu } break; case JoltPhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: { spring_equilibrium[axis_ang] = p_value; + has_angular_target_rotation = false; _spring_equilibrium_changed(axis_ang); } break; default: { @@ -507,6 +522,36 @@ void JoltGeneric6DOFJoint3D::set_flag(Axis p_axis, Flag p_flag, bool p_enabled) } } +void JoltGeneric6DOFJoint3D::set_angular_target_rotation(const Quaternion &p_target_rotation) { + ERR_FAIL_COND_MSG(!p_target_rotation.is_finite() || p_target_rotation.length_squared() <= CMP_EPSILON, "Angular target rotation must be a finite, non-zero quaternion."); + + angular_target_rotation = p_target_rotation.normalized(); + has_angular_target_rotation = true; + _update_angular_target_rotation(); + _wake_up_bodies(); +} + +Quaternion JoltGeneric6DOFJoint3D::get_angular_target_rotation() const { + if (has_angular_target_rotation) { + return angular_target_rotation; + } + + const JPH::SixDOFConstraint *constraint = static_cast(jolt_ref.GetPtr()); + if (unlikely(constraint == nullptr)) { + return Quaternion(); + } + + const Basis target_orientation_cs = Basis::from_euler( + Vector3(static_cast(-spring_equilibrium[AXIS_ANGULAR_X]), + static_cast(-spring_equilibrium[AXIS_ANGULAR_Y]), + static_cast(-spring_equilibrium[AXIS_ANGULAR_Z])), + EulerOrder::ZYX); + const Basis constraint_to_body_1 = to_godot(constraint->GetConstraintToBody1Matrix()).basis; + const Basis constraint_to_body_2 = to_godot(constraint->GetConstraintToBody2Matrix()).basis; + + return (constraint_to_body_1 * target_orientation_cs * constraint_to_body_2.inverse()).get_quaternion(); +} + double JoltGeneric6DOFJoint3D::get_jolt_param(Axis p_axis, JoltParam p_param) const { const int axis_lin = AXES_LINEAR + (int)p_axis; const int axis_ang = AXES_ANGULAR + (int)p_axis; @@ -683,4 +728,6 @@ void JoltGeneric6DOFJoint3D::rebuild() { _update_spring_parameters(axis); _update_spring_equilibrium(axis); } + + _update_angular_target_rotation(); } diff --git a/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.h b/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.h index 56e611962a..04612c4951 100644 --- a/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.h +++ b/modules/jolt_physics/joints/jolt_generic_6dof_joint_3d.h @@ -72,6 +72,9 @@ class JoltGeneric6DOFJoint3D final : public JoltJoint3D { double spring_equilibrium[AXIS_COUNT] = {}; double spring_limit[AXIS_COUNT] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + Quaternion angular_target_rotation; + bool has_angular_target_rotation = false; + bool limit_enabled[AXIS_COUNT] = {}; bool limit_spring_enabled[AXIS_COUNT] = {}; @@ -89,6 +92,7 @@ class JoltGeneric6DOFJoint3D final : public JoltJoint3D { void _update_motor_limit(int p_axis); void _update_spring_parameters(int p_axis); void _update_spring_equilibrium(int p_axis); + void _update_angular_target_rotation(); void _limits_changed(); void _limit_spring_parameters_changed(int p_axis); @@ -111,6 +115,9 @@ public: bool get_flag(Axis p_axis, Flag p_flag) const; void set_flag(Axis p_axis, Flag p_flag, bool p_enabled); + void set_angular_target_rotation(const Quaternion &p_target_rotation); + Quaternion get_angular_target_rotation() const; + double get_jolt_param(Axis p_axis, JoltParam p_param) const; void set_jolt_param(Axis p_axis, JoltParam p_param, double p_value); diff --git a/modules/jolt_physics/jolt_physics_server_3d.cpp b/modules/jolt_physics/jolt_physics_server_3d.cpp index 3b68594b27..c2460dc4f1 100644 --- a/modules/jolt_physics/jolt_physics_server_3d.cpp +++ b/modules/jolt_physics/jolt_physics_server_3d.cpp @@ -1546,6 +1546,26 @@ bool JoltPhysicsServer3D::generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis return g6dof_joint->get_flag(p_axis, p_flag); } +void JoltPhysicsServer3D::generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) { + JoltJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL(joint); + + ERR_FAIL_COND(joint->get_type() != JOINT_TYPE_6DOF); + JoltGeneric6DOFJoint3D *g6dof_joint = static_cast(joint); + + g6dof_joint->set_angular_target_rotation(p_target_rotation); +} + +Quaternion JoltPhysicsServer3D::generic_6dof_joint_get_angular_target_rotation(RID p_joint) const { + const JoltJoint3D *joint = joint_owner.get_or_null(p_joint); + ERR_FAIL_NULL_V(joint, Quaternion()); + + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_6DOF, Quaternion()); + const JoltGeneric6DOFJoint3D *g6dof_joint = static_cast(joint); + + return g6dof_joint->get_angular_target_rotation(); +} + PhysicsServer3D::JointType JoltPhysicsServer3D::joint_get_type(RID p_joint) const { const JoltJoint3D *joint = joint_owner.get_or_null(p_joint); ERR_FAIL_NULL_V(joint, JOINT_TYPE_PIN); diff --git a/modules/jolt_physics/jolt_physics_server_3d.h b/modules/jolt_physics/jolt_physics_server_3d.h index a8db19b899..1ab95b796b 100644 --- a/modules/jolt_physics/jolt_physics_server_3d.h +++ b/modules/jolt_physics/jolt_physics_server_3d.h @@ -405,6 +405,9 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag, bool p_enable) override; virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const override; + virtual void generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) override; + virtual Quaternion generic_6dof_joint_get_angular_target_rotation(RID p_joint) const override; + virtual PhysicsServer3D::JointType joint_get_type(RID p_joint) const override; virtual void joint_set_solver_priority(RID p_joint, int p_priority) override; diff --git a/modules/jolt_physics/misc/jolt_type_conversions.h b/modules/jolt_physics/misc/jolt_type_conversions.h index 52bf9a734e..0e5267456b 100644 --- a/modules/jolt_physics/misc/jolt_type_conversions.h +++ b/modules/jolt_physics/misc/jolt_type_conversions.h @@ -95,9 +95,12 @@ _FORCE_INLINE_ JPH::Vec3 to_jolt(const Vector3 &p_vec) { return JPH::Vec3((float)p_vec.x, (float)p_vec.y, (float)p_vec.z); } +_FORCE_INLINE_ JPH::Quat to_jolt(const Quaternion &p_quat) { + return JPH::Quat((float)p_quat.x, (float)p_quat.y, (float)p_quat.z, (float)p_quat.w); +} + _FORCE_INLINE_ JPH::Quat to_jolt(const Basis &p_basis) { - const Quaternion quat = p_basis.get_quaternion().normalized(); - return JPH::Quat((float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w); + return to_jolt(p_basis.get_quaternion().normalized()); } _FORCE_INLINE_ JPH::Mat44 to_jolt(const Transform3D &p_transform) { diff --git a/scene/3d/physics/joints/generic_6dof_joint_3d.cpp b/scene/3d/physics/joints/generic_6dof_joint_3d.cpp index 87774956e9..475cfb3fcc 100644 --- a/scene/3d/physics/joints/generic_6dof_joint_3d.cpp +++ b/scene/3d/physics/joints/generic_6dof_joint_3d.cpp @@ -51,6 +51,11 @@ void Generic6DOFJoint3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flag_z", "flag", "value"), &Generic6DOFJoint3D::set_flag_z); ClassDB::bind_method(D_METHOD("get_flag_z", "flag"), &Generic6DOFJoint3D::get_flag_z); + ClassDB::bind_method(D_METHOD("set_angular_target_rotation", "target_rotation"), &Generic6DOFJoint3D::set_angular_target_rotation); + ClassDB::bind_method(D_METHOD("get_angular_target_rotation"), &Generic6DOFJoint3D::get_angular_target_rotation); + ClassDB::bind_method(D_METHOD("has_target_rotation"), &Generic6DOFJoint3D::has_target_rotation); + ClassDB::bind_method(D_METHOD("clear_angular_target_rotation"), &Generic6DOFJoint3D::clear_angular_target_rotation); + ADD_GROUP("Linear Limit", "linear_limit_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_limit_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_LIMIT); @@ -201,6 +206,9 @@ void Generic6DOFJoint3D::_bind_methods() { void Generic6DOFJoint3D::set_param_x(Param p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); params_x[p_param] = p_value; + if (p_param == PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT) { + has_angular_target_rotation = false; + } if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } @@ -216,6 +224,9 @@ real_t Generic6DOFJoint3D::get_param_x(Param p_param) const { void Generic6DOFJoint3D::set_param_y(Param p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); params_y[p_param] = p_value; + if (p_param == PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT) { + has_angular_target_rotation = false; + } if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } @@ -230,6 +241,9 @@ real_t Generic6DOFJoint3D::get_param_y(Param p_param) const { void Generic6DOFJoint3D::set_param_z(Param p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); params_z[p_param] = p_value; + if (p_param == PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT) { + has_angular_target_rotation = false; + } if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } @@ -283,6 +297,58 @@ bool Generic6DOFJoint3D::get_flag_z(Flag p_flag) const { return flags_z[p_flag]; } +bool Generic6DOFJoint3D::_is_valid_angular_target_rotation(const Quaternion &p_target_rotation) { + return p_target_rotation.is_finite() && p_target_rotation.length_squared() > CMP_EPSILON; +} + +void Generic6DOFJoint3D::set_angular_target_rotation(const Quaternion &p_target_rotation) { + ERR_FAIL_COND_MSG(!_is_valid_angular_target_rotation(p_target_rotation), "Angular target rotation must be a finite, non-zero quaternion."); + + angular_target_rotation = p_target_rotation.normalized(); + has_angular_target_rotation = true; + + if (!is_configured()) { + return; + } + + PhysicsServer3D::get_singleton()->generic_6dof_joint_set_angular_target_rotation(get_rid(), angular_target_rotation); +} + +Quaternion Generic6DOFJoint3D::get_angular_target_rotation() const { + if (has_angular_target_rotation) { + return angular_target_rotation; + } + + if (is_configured()) { + return PhysicsServer3D::get_singleton()->generic_6dof_joint_get_angular_target_rotation(get_rid()); + } + + // Equilibrium point is in constraint space; body-space conversion requires the constraint frame. + ERR_PRINT("Cannot derive a body-space angular target rotation from Generic6DOFJoint3D equilibrium points before the joint is configured. Returning the identity quaternion."); + return Quaternion(); +} + +bool Generic6DOFJoint3D::has_target_rotation() const { + return has_angular_target_rotation; +} + +void Generic6DOFJoint3D::clear_angular_target_rotation() { + if (!has_angular_target_rotation) { + return; + } + has_angular_target_rotation = false; + angular_target_rotation = Quaternion(); + + if (!is_configured()) { + return; + } + + PhysicsServer3D *server = PhysicsServer3D::get_singleton(); + server->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_X, PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT, params_x[PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT]); + server->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Y, PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT, params_y[PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT]); + server->generic_6dof_joint_set_param(get_rid(), Vector3::AXIS_Z, PhysicsServer3D::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT, params_z[PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT]); +} + void Generic6DOFJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) { Transform3D gt = get_global_transform(); //Vector3 cone_twistpos = gt.origin; @@ -312,6 +378,10 @@ void Generic6DOFJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, Ph PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(p_joint, Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisFlag(i), flags_y[i]); PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(p_joint, Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisFlag(i), flags_z[i]); } + + if (has_angular_target_rotation) { + PhysicsServer3D::get_singleton()->generic_6dof_joint_set_angular_target_rotation(p_joint, angular_target_rotation); + } } Generic6DOFJoint3D::Generic6DOFJoint3D() { diff --git a/scene/3d/physics/joints/generic_6dof_joint_3d.h b/scene/3d/physics/joints/generic_6dof_joint_3d.h index 85f0b604a7..02de0b86e3 100644 --- a/scene/3d/physics/joints/generic_6dof_joint_3d.h +++ b/scene/3d/physics/joints/generic_6dof_joint_3d.h @@ -79,10 +79,14 @@ protected: bool flags_y[FLAG_MAX]; real_t params_z[PARAM_MAX]; bool flags_z[FLAG_MAX]; + Quaternion angular_target_rotation; + bool has_angular_target_rotation = false; virtual void _configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) override; static void _bind_methods(); + static bool _is_valid_angular_target_rotation(const Quaternion &p_target_rotation); + public: void set_param_x(Param p_param, real_t p_value); real_t get_param_x(Param p_param) const; @@ -102,6 +106,11 @@ public: void set_flag_z(Flag p_flag, bool p_enabled); bool get_flag_z(Flag p_flag) const; + void set_angular_target_rotation(const Quaternion &p_target_rotation); + Quaternion get_angular_target_rotation() const; + bool has_target_rotation() const; + void clear_angular_target_rotation(); + Generic6DOFJoint3D(); }; diff --git a/servers/physics_3d/physics_server_3d.cpp b/servers/physics_3d/physics_server_3d.cpp index 67dee00fe2..f2165e05a8 100644 --- a/servers/physics_3d/physics_server_3d.cpp +++ b/servers/physics_3d/physics_server_3d.cpp @@ -1044,6 +1044,9 @@ void PhysicsServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("generic_6dof_joint_set_flag", "joint", "axis", "flag", "enable"), &PhysicsServer3D::generic_6dof_joint_set_flag); ClassDB::bind_method(D_METHOD("generic_6dof_joint_get_flag", "joint", "axis", "flag"), &PhysicsServer3D::generic_6dof_joint_get_flag); + ClassDB::bind_method(D_METHOD("generic_6dof_joint_set_angular_target_rotation", "joint", "target_rotation"), &PhysicsServer3D::generic_6dof_joint_set_angular_target_rotation); + ClassDB::bind_method(D_METHOD("generic_6dof_joint_get_angular_target_rotation", "joint"), &PhysicsServer3D::generic_6dof_joint_get_angular_target_rotation); + ClassDB::bind_method(D_METHOD("free_rid", "rid"), &PhysicsServer3D::free_rid); ClassDB::bind_method(D_METHOD("set_active", "active"), &PhysicsServer3D::set_active); diff --git a/servers/physics_3d/physics_server_3d.h b/servers/physics_3d/physics_server_3d.h index 3738c1ee70..572ce02ae2 100644 --- a/servers/physics_3d/physics_server_3d.h +++ b/servers/physics_3d/physics_server_3d.h @@ -794,6 +794,9 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag, bool p_enable) = 0; virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag) const = 0; + virtual void generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) = 0; + virtual Quaternion generic_6dof_joint_get_angular_target_rotation(RID p_joint) const = 0; + /* QUERY API */ enum AreaBodyStatus { diff --git a/servers/physics_3d/physics_server_3d_dummy.h b/servers/physics_3d/physics_server_3d_dummy.h index d788423ba5..7e4186191d 100644 --- a/servers/physics_3d/physics_server_3d_dummy.h +++ b/servers/physics_3d/physics_server_3d_dummy.h @@ -423,6 +423,9 @@ public: virtual void generic_6dof_joint_set_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag, bool p_enable) override {} virtual bool generic_6dof_joint_get_flag(RID p_joint, Vector3::Axis, G6DOFJointAxisFlag p_flag) const override { return false; } + virtual void generic_6dof_joint_set_angular_target_rotation(RID p_joint, const Quaternion &p_target_rotation) override {} + virtual Quaternion generic_6dof_joint_get_angular_target_rotation(RID p_joint) const override { return Quaternion(); } + /* MISC */ virtual void free_rid(RID p_rid) override {} diff --git a/servers/physics_3d/physics_server_3d_extension.cpp b/servers/physics_3d/physics_server_3d_extension.cpp index 37bf961fba..6d5e9f4ede 100644 --- a/servers/physics_3d/physics_server_3d_extension.cpp +++ b/servers/physics_3d/physics_server_3d_extension.cpp @@ -417,6 +417,9 @@ void PhysicsServer3DExtension::_bind_methods() { GDVIRTUAL_BIND(_generic_6dof_joint_set_flag, "joint", "axis", "flag", "enable"); GDVIRTUAL_BIND(_generic_6dof_joint_get_flag, "joint", "axis", "flag"); + GDVIRTUAL_BIND(_generic_6dof_joint_set_angular_target_rotation, "joint", "target_rotation"); + GDVIRTUAL_BIND(_generic_6dof_joint_get_angular_target_rotation, "joint"); + GDVIRTUAL_BIND(_joint_get_type, "joint"); GDVIRTUAL_BIND(_joint_set_solver_priority, "joint", "priority"); diff --git a/servers/physics_3d/physics_server_3d_extension.h b/servers/physics_3d/physics_server_3d_extension.h index 086208e74d..7473f7ad9d 100644 --- a/servers/physics_3d/physics_server_3d_extension.h +++ b/servers/physics_3d/physics_server_3d_extension.h @@ -522,6 +522,9 @@ public: EXBIND4(generic_6dof_joint_set_flag, RID, Vector3::Axis, G6DOFJointAxisFlag, bool) EXBIND3RC(bool, generic_6dof_joint_get_flag, RID, Vector3::Axis, G6DOFJointAxisFlag) + EXBIND2(generic_6dof_joint_set_angular_target_rotation, RID, const Quaternion &) + EXBIND1RC(Quaternion, generic_6dof_joint_get_angular_target_rotation, RID) + EXBIND1RC(JointType, joint_get_type, RID) EXBIND2(joint_set_solver_priority, RID, int) diff --git a/servers/physics_3d/physics_server_3d_wrap_mt.h b/servers/physics_3d/physics_server_3d_wrap_mt.h index 50aa2d240e..0958a95f2e 100644 --- a/servers/physics_3d/physics_server_3d_wrap_mt.h +++ b/servers/physics_3d/physics_server_3d_wrap_mt.h @@ -383,6 +383,9 @@ public: FUNC4(generic_6dof_joint_set_flag, RID, Vector3::Axis, G6DOFJointAxisFlag, bool) FUNC3RC(bool, generic_6dof_joint_get_flag, RID, Vector3::Axis, G6DOFJointAxisFlag) + FUNC2(generic_6dof_joint_set_angular_target_rotation, RID, const Quaternion &) + FUNC1RC(Quaternion, generic_6dof_joint_get_angular_target_rotation, RID) + FUNC1RC(JointType, joint_get_type, RID); FUNC2(joint_set_solver_priority, RID, int); diff --git a/tests/scene/test_generic_6dof_joint_3d.cpp b/tests/scene/test_generic_6dof_joint_3d.cpp new file mode 100644 index 0000000000..166e267560 --- /dev/null +++ b/tests/scene/test_generic_6dof_joint_3d.cpp @@ -0,0 +1,111 @@ +/**************************************************************************/ +/* test_generic_6dof_joint_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_generic_6dof_joint_3d) + +#ifndef PHYSICS_3D_DISABLED + +#include "scene/3d/physics/joints/generic_6dof_joint_3d.h" + +namespace TestGeneric6DOFJoint3D { + +TEST_CASE("[SceneTree][Generic6DOFJoint3D] Quaternion angular target is normalized and stored") { + Generic6DOFJoint3D *joint = memnew(Generic6DOFJoint3D); + + const Quaternion target_rotation(Vector3(0, 1, 0), 0.5); + joint->set_angular_target_rotation(target_rotation * 2.0); + + CHECK(joint->get_angular_target_rotation().is_normalized()); + CHECK(joint->get_angular_target_rotation().is_equal_approx(target_rotation)); + CHECK(joint->has_target_rotation()); + + memdelete(joint); +} + +TEST_CASE("[SceneTree][Generic6DOFJoint3D] Euler equilibrium setter clears stored quaternion target") { + Generic6DOFJoint3D *joint = memnew(Generic6DOFJoint3D); + + joint->set_angular_target_rotation(Quaternion(Vector3(0, 0, 1), 0.25)); + CHECK(joint->has_target_rotation()); + + joint->set_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0.1); + CHECK_FALSE(joint->has_target_rotation()); + + memdelete(joint); +} + +TEST_CASE("[SceneTree][Generic6DOFJoint3D] clear_angular_target_rotation resets state") { + Generic6DOFJoint3D *joint = memnew(Generic6DOFJoint3D); + + joint->set_angular_target_rotation(Quaternion(Vector3(1, 0, 0), 0.5)); + CHECK(joint->has_target_rotation()); + + joint->clear_angular_target_rotation(); + CHECK_FALSE(joint->has_target_rotation()); + ERR_PRINT_OFF; + CHECK(joint->get_angular_target_rotation().is_equal_approx(Quaternion())); + ERR_PRINT_ON; + + memdelete(joint); +} + +TEST_CASE("[SceneTree][Generic6DOFJoint3D] Getter returns identity before configuration when no explicit target is set") { + Generic6DOFJoint3D *joint = memnew(Generic6DOFJoint3D); + + joint->set_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0.3); + joint->set_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, -0.2); + joint->set_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0.4); + + CHECK_FALSE(joint->has_target_rotation()); + ERR_PRINT_OFF; + CHECK(joint->get_angular_target_rotation().is_equal_approx(Quaternion())); + ERR_PRINT_ON; + + memdelete(joint); +} + +TEST_CASE("[SceneTree][Generic6DOFJoint3D] Explicit quaternion target shadows Euler equilibrium derivation") { + Generic6DOFJoint3D *joint = memnew(Generic6DOFJoint3D); + + joint->set_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_SPRING_EQUILIBRIUM_POINT, 0.3); + const Quaternion explicit_target(Vector3(0, 1, 0), 0.7); + joint->set_angular_target_rotation(explicit_target); + + CHECK(joint->has_target_rotation()); + CHECK(joint->get_angular_target_rotation().is_equal_approx(explicit_target)); + + memdelete(joint); +} + +} // namespace TestGeneric6DOFJoint3D + +#endif // PHYSICS_3D_DISABLED