1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-21 14:57:09 +00:00

Add LimitAngularVelocityModifier3D

This commit is contained in:
Silc Lizard (Tokage) Renew
2025-09-30 11:16:06 +09:00
parent a6e7084b40
commit ada95cb543
5 changed files with 627 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="LimitAngularVelocityModifier3D" inherits="SkeletonModifier3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Limit bone rotation angular velocity.
</brief_description>
<description>
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].
</description>
<tutorials>
</tutorials>
<methods>
<method name="clear_chains">
<return type="void" />
<description>
Clear all chains.
</description>
</method>
<method name="get_end_bone" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the end bone index of the bone chain.
</description>
</method>
<method name="get_end_bone_name" qualifiers="const">
<return type="String" />
<param index="0" name="index" type="int" />
<description>
Returns the end bone name of the bone chain.
</description>
</method>
<method name="get_root_bone" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the root bone index of the bone chain.
</description>
</method>
<method name="get_root_bone_name" qualifiers="const">
<return type="String" />
<param index="0" name="index" type="int" />
<description>
Returns the root bone name of the bone chain.
</description>
</method>
<method name="reset">
<return type="void" />
<description>
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.
</description>
</method>
<method name="set_end_bone">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone" type="int" />
<description>
Sets the end bone index of the bone chain.
</description>
</method>
<method name="set_end_bone_name">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone_name" type="String" />
<description>
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.
</description>
</method>
<method name="set_root_bone">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone" type="int" />
<description>
Sets the root bone index of the bone chain.
</description>
</method>
<method name="set_root_bone_name">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="bone_name" type="String" />
<description>
Sets the root bone name of the bone chain.
</description>
</method>
</methods>
<members>
<member name="chain_count" type="int" setter="set_chain_count" getter="get_chain_count" default="0">
The number of chains.
</member>
<member name="exclude" type="bool" setter="set_exclude" getter="is_exclude" default="false">
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.
</member>
<member name="joint_count" type="int" setter="" getter="_get_joint_count" default="0">
The number of joints in the list which created by chains dynamically.
</member>
<member name="max_angular_velocity" type="float" setter="set_max_angular_velocity" getter="get_max_angular_velocity" default="6.2831855">
The maximum angular velocity per second.
</member>
</members>
</class>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#fc7f7f"><path d="m9.427 16-.252-.252c-1.563-1.557-1.567-4.095-.011-5.658 1.559-1.563 4.098-1.567 5.66-.01.756.754 1.174 1.758 1.176 2.827.002 1.067-.411 2.074-1.168 2.831l-.252.253-.263-.243-.857-.857.507-.505.592.59c.398-.494.644-1.086.709-1.713h-.982v-.716h.98c-.068-.628-.315-1.217-.717-1.706l-.582.583-.507-.505.582-.58c-.498-.396-1.085-.625-1.686-.691v.981h-.715v-.982c-.601.066-1.188.295-1.685.691l.579.58-.505.505-.582-.583c-.408.501-.647 1.097-.715 1.706h.979v.716h-.98c.064.611.301 1.21.708 1.713l.591-.59.505.505z"/><path d="M11.499 12.578c-.189.266-.129.636.138.825.266.188.633.127.822-.139.06-.08.095-.176.107-.276l.274-1.733-.386-.146zM8.456 9.384c.622-.625 1.388-1.042 2.217-1.266l.502-.501c1.134.753 2.665.445 3.419-.689s.446-2.665-.688-3.419c-.309-.205-.66-.338-1.026-.389-.188-1.349-1.433-2.291-2.782-2.103s-2.29 1.433-2.103 2.782c.051.367.184.717.389 1.026l-3.56 3.56c-1.134-.755-2.665-.447-3.419.687s-.446 2.664.688 3.419c.308.204.659.338 1.026.389.188 1.349 1.433 2.29 2.782 2.103.519-.072.976-.303 1.333-.634-.509-1.703-.117-3.62 1.222-4.965z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -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<PropertyInfo> *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<int, StringName> &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<int> 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<int, StringName> &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();
}

View File

@@ -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<int, Quaternion> BoneRot;
private:
bool exclude = false;
double max_angular_velocity = Math::TAU;
LocalVector<Chain> chains;
RBMap<int, StringName> joints;
LocalVector<BoneRot> 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<PropertyInfo> *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();
};

View File

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