From ada95cb543f21bb9f39d5fc0d1f55bcdcf0480e4 Mon Sep 17 00:00:00 2001
From: "Silc Lizard (Tokage) Renew"
<61938263+TokageItLab@users.noreply.github.com>
Date: Tue, 30 Sep 2025 11:16:06 +0900
Subject: [PATCH] Add LimitAngularVelocityModifier3D
---
.../LimitAngularVelocityModifier3D.xml | 102 +++++
.../icons/LimitAngularVelocityModifier3D.svg | 1 +
.../3d/limit_angular_velocity_modifier_3d.cpp | 412 ++++++++++++++++++
scene/3d/limit_angular_velocity_modifier_3d.h | 110 +++++
scene/register_scene_types.cpp | 2 +
5 files changed, 627 insertions(+)
create mode 100644 doc/classes/LimitAngularVelocityModifier3D.xml
create mode 100644 editor/icons/LimitAngularVelocityModifier3D.svg
create mode 100644 scene/3d/limit_angular_velocity_modifier_3d.cpp
create mode 100644 scene/3d/limit_angular_velocity_modifier_3d.h
diff --git a/doc/classes/LimitAngularVelocityModifier3D.xml b/doc/classes/LimitAngularVelocityModifier3D.xml
new file mode 100644
index 00000000000..32b83d58d57
--- /dev/null
+++ b/doc/classes/LimitAngularVelocityModifier3D.xml
@@ -0,0 +1,102 @@
+
+
+
+ Limit bone rotation angular velocity.
+
+
+ This modifier limits bone rotation angular velocity by comparing poses between previous and current frame.
+ You can add bone chains by specifying their root and end bones, then add the bones between them to a list. Modifier processes either that list or the bones excluding those in the list depending on the option [member exclude].
+
+
+
+
+
+
+
+ Clear all chains.
+
+
+
+
+
+
+ Returns the end bone index of the bone chain.
+
+
+
+
+
+
+ Returns the end bone name of the bone chain.
+
+
+
+
+
+
+ Returns the root bone index of the bone chain.
+
+
+
+
+
+
+ Returns the root bone name of the bone chain.
+
+
+
+
+
+ Sets the reference pose for angle comparison to the current pose with the influence of constraints removed. This function is automatically triggered when joints change or upon activation.
+
+
+
+
+
+
+
+ Sets the end bone index of the bone chain.
+
+
+
+
+
+
+
+ Sets the end bone name of the bone chain.
+ [b]Note:[/b] End bone must be the root bone or a child of the root bone.
+
+
+
+
+
+
+
+ Sets the root bone index of the bone chain.
+
+
+
+
+
+
+
+ Sets the root bone name of the bone chain.
+
+
+
+
+
+ The number of chains.
+
+
+ If [code]true[/code], the modifier processes bones not included in the bone list.
+ If [code]false[/code], the bones processed by the modifier are equal to the bone list.
+
+
+ The number of joints in the list which created by chains dynamically.
+
+
+ The maximum angular velocity per second.
+
+
+
diff --git a/editor/icons/LimitAngularVelocityModifier3D.svg b/editor/icons/LimitAngularVelocityModifier3D.svg
new file mode 100644
index 00000000000..f6e910eb4a6
--- /dev/null
+++ b/editor/icons/LimitAngularVelocityModifier3D.svg
@@ -0,0 +1 @@
+
diff --git a/scene/3d/limit_angular_velocity_modifier_3d.cpp b/scene/3d/limit_angular_velocity_modifier_3d.cpp
new file mode 100644
index 00000000000..89008a39021
--- /dev/null
+++ b/scene/3d/limit_angular_velocity_modifier_3d.cpp
@@ -0,0 +1,412 @@
+/**************************************************************************/
+/* limit_angular_velocity_modifier_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 "limit_angular_velocity_modifier_3d.h"
+
+bool LimitAngularVelocityModifier3D::_set(const StringName &p_path, const Variant &p_value) {
+ String path = p_path;
+
+ if (path.begins_with("chains/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, (int)chains.size(), false);
+
+ if (what == "root_bone_name") {
+ set_root_bone_name(which, p_value);
+ } else if (what == "root_bone") {
+ set_root_bone(which, p_value);
+ } else if (what == "end_bone_name") {
+ set_end_bone_name(which, p_value);
+ } else if (what == "end_bone") {
+ set_end_bone(which, p_value);
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LimitAngularVelocityModifier3D::_get(const StringName &p_path, Variant &r_ret) const {
+ String path = p_path;
+
+ if (path.begins_with("chains/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, (int)chains.size(), false);
+
+ if (what == "root_bone_name") {
+ r_ret = get_root_bone_name(which);
+ } else if (what == "root_bone") {
+ r_ret = get_root_bone(which);
+ } else if (what == "end_bone_name") {
+ r_ret = get_end_bone_name(which);
+ } else if (what == "end_bone") {
+ r_ret = get_end_bone(which);
+ } else {
+ return false;
+ }
+ }
+ if (path.begins_with("joints/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_COND_V(!joints.has(which), false);
+ if (what == "bone_name") {
+ r_ret = _get_joint_bone_name(which);
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void LimitAngularVelocityModifier3D::_get_property_list(List *p_list) const {
+ String enum_hint;
+ Skeleton3D *skeleton = get_skeleton();
+ if (skeleton) {
+ enum_hint = skeleton->get_concatenated_bone_names();
+ }
+
+ for (uint32_t i = 0; i < chains.size(); i++) {
+ String path = "chains/" + itos(i) + "/";
+ p_list->push_back(PropertyInfo(Variant::STRING, path + "root_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint));
+ p_list->push_back(PropertyInfo(Variant::INT, path + "root_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, path + "end_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint));
+ p_list->push_back(PropertyInfo(Variant::INT, path + "end_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
+ }
+
+ for (const KeyValue &E : joints) {
+ String path = "joints/" + itos(E.key) + "/";
+ p_list->push_back(PropertyInfo(Variant::STRING, path + "bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint, PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY));
+ }
+}
+
+void LimitAngularVelocityModifier3D::_validate_property(PropertyInfo &p_property) const {
+ if (p_property.name == "joint_count") {
+ p_property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY | PROPERTY_USAGE_READ_ONLY;
+ p_property.class_name = "Joints,joints/,static,const";
+ }
+}
+
+void LimitAngularVelocityModifier3D::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ _make_joints_dirty();
+ } break;
+ }
+}
+
+// Setting.
+
+void LimitAngularVelocityModifier3D::set_root_bone_name(int p_index, const String &p_bone_name) {
+ ERR_FAIL_INDEX(p_index, (int)chains.size());
+ chains[p_index].root_bone.name = p_bone_name;
+ Skeleton3D *sk = get_skeleton();
+ if (sk) {
+ set_root_bone(p_index, sk->find_bone(chains[p_index].root_bone.name));
+ }
+}
+
+String LimitAngularVelocityModifier3D::get_root_bone_name(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, (int)chains.size(), String());
+ return chains[p_index].root_bone.name;
+}
+
+void LimitAngularVelocityModifier3D::set_root_bone(int p_index, int p_bone) {
+ ERR_FAIL_INDEX(p_index, (int)chains.size());
+ bool changed = chains[p_index].root_bone.bone != p_bone;
+ chains[p_index].root_bone.bone = p_bone;
+ Skeleton3D *sk = get_skeleton();
+ if (sk) {
+ if (chains[p_index].root_bone.bone <= -1 || chains[p_index].root_bone.bone >= sk->get_bone_count()) {
+ WARN_PRINT("Root bone index out of range!");
+ chains[p_index].root_bone.bone = -1;
+ } else {
+ chains[p_index].root_bone.name = sk->get_bone_name(chains[p_index].root_bone.bone);
+ }
+ }
+ if (changed) {
+ _make_joints_dirty();
+ }
+}
+
+int LimitAngularVelocityModifier3D::get_root_bone(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, (int)chains.size(), -1);
+ return chains[p_index].root_bone.bone;
+}
+
+void LimitAngularVelocityModifier3D::set_end_bone_name(int p_index, const String &p_bone_name) {
+ ERR_FAIL_INDEX(p_index, (int)chains.size());
+ chains[p_index].end_bone.name = p_bone_name;
+ Skeleton3D *sk = get_skeleton();
+ if (sk) {
+ set_end_bone(p_index, sk->find_bone(chains[p_index].end_bone.name));
+ }
+}
+
+String LimitAngularVelocityModifier3D::get_end_bone_name(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, (int)chains.size(), String());
+ return chains[p_index].end_bone.name;
+}
+
+void LimitAngularVelocityModifier3D::set_end_bone(int p_index, int p_bone) {
+ ERR_FAIL_INDEX(p_index, (int)chains.size());
+ bool changed = chains[p_index].end_bone.bone != p_bone;
+ chains[p_index].end_bone.bone = p_bone;
+ Skeleton3D *sk = get_skeleton();
+ if (sk) {
+ if (chains[p_index].end_bone.bone <= -1 || chains[p_index].end_bone.bone >= sk->get_bone_count()) {
+ WARN_PRINT("End bone index out of range!");
+ chains[p_index].end_bone.bone = -1;
+ } else {
+ chains[p_index].end_bone.name = sk->get_bone_name(chains[p_index].end_bone.bone);
+ }
+ }
+ if (changed) {
+ _make_joints_dirty();
+ }
+ notify_property_list_changed();
+}
+
+int LimitAngularVelocityModifier3D::get_end_bone(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, (int)chains.size(), -1);
+ return chains[p_index].end_bone.bone;
+}
+
+void LimitAngularVelocityModifier3D::set_chain_count(int p_count) {
+ ERR_FAIL_COND(p_count < 0);
+ chains.resize(p_count);
+ _make_joints_dirty();
+ notify_property_list_changed();
+}
+
+int LimitAngularVelocityModifier3D::get_chain_count() const {
+ return chains.size();
+}
+
+void LimitAngularVelocityModifier3D::clear_chains() {
+ set_chain_count(0);
+}
+
+String LimitAngularVelocityModifier3D::_get_joint_bone_name(int p_bone) const {
+ ERR_FAIL_COND_V(!joints.has(p_bone), String());
+ return joints[p_bone];
+}
+
+int LimitAngularVelocityModifier3D::_get_joint_count() const {
+ return joints.size();
+}
+
+void LimitAngularVelocityModifier3D::set_max_angular_velocity(double p_angular_velocity) {
+ max_angular_velocity = p_angular_velocity;
+}
+
+double LimitAngularVelocityModifier3D::get_max_angular_velocity() const {
+ return max_angular_velocity;
+}
+
+void LimitAngularVelocityModifier3D::set_exclude(bool p_exclude) {
+ exclude = p_exclude;
+}
+
+bool LimitAngularVelocityModifier3D::is_exclude() const {
+ return exclude;
+}
+
+void LimitAngularVelocityModifier3D::_bind_methods() {
+ // Setting.
+ ClassDB::bind_method(D_METHOD("set_root_bone_name", "index", "bone_name"), &LimitAngularVelocityModifier3D::set_root_bone_name);
+ ClassDB::bind_method(D_METHOD("get_root_bone_name", "index"), &LimitAngularVelocityModifier3D::get_root_bone_name);
+ ClassDB::bind_method(D_METHOD("set_root_bone", "index", "bone"), &LimitAngularVelocityModifier3D::set_root_bone);
+ ClassDB::bind_method(D_METHOD("get_root_bone", "index"), &LimitAngularVelocityModifier3D::get_root_bone);
+
+ ClassDB::bind_method(D_METHOD("set_end_bone_name", "index", "bone_name"), &LimitAngularVelocityModifier3D::set_end_bone_name);
+ ClassDB::bind_method(D_METHOD("get_end_bone_name", "index"), &LimitAngularVelocityModifier3D::get_end_bone_name);
+ ClassDB::bind_method(D_METHOD("set_end_bone", "index", "bone"), &LimitAngularVelocityModifier3D::set_end_bone);
+ ClassDB::bind_method(D_METHOD("get_end_bone", "index"), &LimitAngularVelocityModifier3D::get_end_bone);
+
+ ClassDB::bind_method(D_METHOD("set_chain_count", "count"), &LimitAngularVelocityModifier3D::set_chain_count);
+ ClassDB::bind_method(D_METHOD("get_chain_count"), &LimitAngularVelocityModifier3D::get_chain_count);
+ ClassDB::bind_method(D_METHOD("clear_chains"), &LimitAngularVelocityModifier3D::clear_chains);
+
+ ClassDB::bind_method(D_METHOD("set_max_angular_velocity", "angular_velocity"), &LimitAngularVelocityModifier3D::set_max_angular_velocity);
+ ClassDB::bind_method(D_METHOD("get_max_angular_velocity"), &LimitAngularVelocityModifier3D::get_max_angular_velocity);
+ ClassDB::bind_method(D_METHOD("set_exclude", "exclude"), &LimitAngularVelocityModifier3D::set_exclude);
+ ClassDB::bind_method(D_METHOD("is_exclude"), &LimitAngularVelocityModifier3D::is_exclude);
+
+ ClassDB::bind_method(D_METHOD("reset"), &LimitAngularVelocityModifier3D::reset);
+
+ ClassDB::bind_method(D_METHOD("_get_joint_count"), &LimitAngularVelocityModifier3D::_get_joint_count);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_angular_velocity", PROPERTY_HINT_RANGE, "0,720,or_greater,radians_as_degrees,suffix:" + String(U"°") + "/s"), "set_max_angular_velocity", "get_max_angular_velocity");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude"), "set_exclude", "is_exclude");
+ ADD_ARRAY_COUNT("Chains", "chain_count", "set_chain_count", "get_chain_count", "chains/");
+ ADD_ARRAY_COUNT("Joints", "joint_count", "", "_get_joint_count", "joints/");
+}
+
+void LimitAngularVelocityModifier3D::_set_active(bool p_active) {
+ if (p_active) {
+ reset();
+ }
+}
+
+void LimitAngularVelocityModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
+ _make_joints_dirty();
+}
+
+void LimitAngularVelocityModifier3D::_validate_bone_names() {
+ for (uint32_t i = 0; i < chains.size(); i++) {
+ // Prior bone name.
+ if (!chains[i].root_bone.name.is_empty()) {
+ set_root_bone_name(i, chains[i].root_bone.name);
+ } else if (chains[i].root_bone.bone != -1) {
+ set_root_bone(i, chains[i].root_bone.bone);
+ }
+ // Prior bone name.
+ if (!chains[i].end_bone.name.is_empty()) {
+ set_end_bone_name(i, chains[i].end_bone.name);
+ } else if (chains[i].end_bone.bone != -1) {
+ set_end_bone(i, chains[i].end_bone.bone);
+ }
+ }
+}
+
+void LimitAngularVelocityModifier3D::_make_joints_dirty() {
+ if (joints_dirty) {
+ return;
+ }
+ joints_dirty = true;
+ callable_mp(this, &LimitAngularVelocityModifier3D::_update_joints).call_deferred();
+}
+
+void LimitAngularVelocityModifier3D::_update_joints() {
+ joints.clear();
+ bones.clear();
+
+ Skeleton3D *sk = get_skeleton();
+ if (!sk) {
+ joints_dirty = false;
+ return;
+ }
+
+ LocalVector tmp_joints;
+ for (uint32_t i = 0; i < chains.size(); i++) {
+ tmp_joints.clear();
+ Chain cn = chains[i];
+ int current_bone = cn.end_bone.bone;
+ int root_bone = cn.root_bone.bone;
+ if (current_bone < 0 || root_bone < 0) {
+ continue;
+ }
+ // Validation.
+ bool valid = false;
+ while (current_bone >= 0) {
+ if (current_bone == root_bone) {
+ valid = true;
+ break;
+ }
+ current_bone = sk->get_bone_parent(current_bone);
+ }
+ if (!valid) {
+ ERR_FAIL_EDMSG("Chains[" + itos(i) + "]: End bone must be the same as or a child of root bone.");
+ continue;
+ }
+ current_bone = cn.end_bone.bone;
+ while (current_bone != root_bone) {
+ tmp_joints.push_back(current_bone);
+ current_bone = sk->get_bone_parent(current_bone);
+ }
+ tmp_joints.push_back(current_bone);
+ for (uint32_t j = 0; j < tmp_joints.size(); j++) {
+ int bn = tmp_joints[j];
+ if (!joints.has(bn)) {
+ joints.insert(bn, sk->get_bone_name(bn));
+ }
+ }
+ }
+
+ if (exclude) {
+ for (int b = 0; b < sk->get_bone_count(); b++) {
+ if (joints.has(b)) {
+ continue;
+ }
+ BoneRot br;
+ br.first = b;
+ br.second = sk->get_bone_pose_rotation(b);
+ bones.push_back(br);
+ }
+ } else {
+ for (const KeyValue &E : joints) {
+ BoneRot br;
+ br.first = E.key;
+ br.second = sk->get_bone_pose_rotation(E.key);
+ bones.push_back(br);
+ }
+ }
+
+ joints_dirty = false;
+ reset();
+}
+
+void LimitAngularVelocityModifier3D::_process_modification(double p_delta) {
+ Skeleton3D *skeleton = get_skeleton();
+ if (!skeleton) {
+ return;
+ }
+
+ if (init_needed) {
+ // Note:
+ // The pose retrieval within `_update_joints()` is done outside the skeleton's update process,
+ // so it ignores the pose resulting from the previous modifier's modification.
+ // This causes unintended initialization when `active` is set to true, so it must be initialized here.
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ bones[i].second = skeleton->get_bone_pose_rotation(bones[i].first);
+ }
+ init_needed = false;
+ }
+
+ double limit_in_frame = max_angular_velocity * p_delta;
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ int bn = bones[i].first;
+ Quaternion dest = skeleton->get_bone_pose_rotation(bn);
+ double diff = bones[i].second.angle_to(dest);
+ if (!Math::is_zero_approx(diff)) {
+ bones[i].second = bones[i].second.slerp(dest, MIN(1.0, limit_in_frame / diff));
+ }
+ skeleton->set_bone_pose_rotation(bn, bones[i].second);
+ }
+}
+
+void LimitAngularVelocityModifier3D::reset() {
+ init_needed = true;
+}
+
+LimitAngularVelocityModifier3D::~LimitAngularVelocityModifier3D() {
+ clear_chains();
+}
diff --git a/scene/3d/limit_angular_velocity_modifier_3d.h b/scene/3d/limit_angular_velocity_modifier_3d.h
new file mode 100644
index 00000000000..21c0ee35186
--- /dev/null
+++ b/scene/3d/limit_angular_velocity_modifier_3d.h
@@ -0,0 +1,110 @@
+/**************************************************************************/
+/* limit_angular_velocity_modifier_3d.h */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#pragma once
+
+#include "scene/3d/skeleton_modifier_3d.h"
+
+class LimitAngularVelocityModifier3D : public SkeletonModifier3D {
+ GDCLASS(LimitAngularVelocityModifier3D, SkeletonModifier3D);
+
+public:
+ struct BoneJoint {
+ StringName name;
+ int bone = -1;
+ };
+
+ struct Chain {
+ BoneJoint root_bone;
+ BoneJoint end_bone;
+ };
+
+ typedef Pair BoneRot;
+
+private:
+ bool exclude = false;
+ double max_angular_velocity = Math::TAU;
+
+ LocalVector chains;
+ RBMap joints;
+ LocalVector bones;
+
+ bool joints_dirty = false;
+ bool init_needed = true;
+
+protected:
+ bool _get(const StringName &p_path, Variant &r_ret) const;
+ bool _set(const StringName &p_path, const Variant &p_value);
+ void _get_property_list(List *p_list) const;
+ void _validate_property(PropertyInfo &p_property) const;
+
+ static void _bind_methods();
+
+ void _notification(int p_what);
+
+ virtual void _set_active(bool p_active) override;
+ virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override;
+
+ virtual void _validate_bone_names() override;
+
+ void _make_joints_dirty();
+ void _update_joints();
+
+ // For editor.
+ String _get_joint_bone_name(int p_bone) const;
+ int _get_joint_count() const;
+
+ virtual void _process_modification(double p_delta) override;
+
+public:
+ void set_root_bone_name(int p_index, const String &p_bone_name);
+ String get_root_bone_name(int p_index) const;
+ void set_root_bone(int p_index, int p_bone);
+ int get_root_bone(int p_index) const;
+
+ void set_end_bone_name(int p_index, const String &p_bone_name);
+ String get_end_bone_name(int p_index) const;
+ void set_end_bone(int p_index, int p_bone);
+ int get_end_bone(int p_index) const;
+
+ void set_chain_count(int p_count);
+ int get_chain_count() const;
+ void clear_chains();
+
+ void set_max_angular_velocity(double p_angular_velocity);
+ double get_max_angular_velocity() const;
+
+ void set_exclude(bool p_exclude);
+ bool is_exclude() const;
+
+ void reset();
+
+ ~LimitAngularVelocityModifier3D();
+};
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 57135e03483..68452b78868 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -239,6 +239,7 @@
#include "scene/3d/light_3d.h"
#include "scene/3d/lightmap_gi.h"
#include "scene/3d/lightmap_probe.h"
+#include "scene/3d/limit_angular_velocity_modifier_3d.h"
#include "scene/3d/look_at_modifier_3d.h"
#include "scene/3d/marker_3d.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -682,6 +683,7 @@ void register_scene_types() {
GDREGISTER_CLASS(FABRIK3D);
GDREGISTER_CLASS(CCDIK3D);
GDREGISTER_CLASS(JacobianIK3D);
+ GDREGISTER_CLASS(LimitAngularVelocityModifier3D);
#ifndef XR_DISABLED
GDREGISTER_CLASS(XRCamera3D);