From 13fe177457821ac9abe2ac1c6bd8d967cbe53933 Mon Sep 17 00:00:00 2001 From: Silc 'Tokage' Renew Date: Mon, 7 Mar 2022 21:12:57 +0900 Subject: [PATCH] Implement skeleton retarget and overhaul some animation features --- core/extension/gdnative_interface.cpp | 8 + core/extension/gdnative_interface.h | 1 + core/math/quaternion.cpp | 19 + core/math/quaternion.h | 2 + core/variant/variant.h | 1 + core/variant/variant_call.cpp | 2 + core/variant/variant_setget.cpp | 104 ++ doc/classes/Animation.xml | 69 + doc/classes/AnimationPlayer.xml | 13 + doc/classes/Quaternion.xml | 10 + doc/classes/RetargetBoneMap.xml | 67 + doc/classes/RetargetBoneOption.xml | 63 + doc/classes/RetargetProfile.xml | 67 + doc/classes/RetargetRichProfile.xml | 97 + doc/classes/Skeleton3D.xml | 144 ++ doc/classes/SkeletonRetarget.xml | 32 + editor/animation_track_editor.cpp | 265 ++- editor/animation_track_editor.h | 16 +- editor/editor_node.cpp | 4 + editor/icons/AutoVolumeDisable.svg | 1 + editor/icons/AutoVolumeEnable.svg | 1 + editor/icons/BoneMapperHandle.svg | 1 + editor/icons/BoneMapperHandleCircle.svg | 1 + editor/icons/BoneMapperHandleSelected.svg | 1 + editor/icons/RetargetAbsolute.svg | 1 + editor/icons/RetargetGlobal.svg | 1 + editor/icons/RetargetLocal.svg | 1 + editor/icons/SkeletonRetarget.svg | 1 + editor/import/resource_importer_scene.cpp | 2 +- .../animation_player_editor_plugin.cpp | 449 ++++- .../plugins/animation_player_editor_plugin.h | 16 +- .../skeleton_retarget_editor_plugin.cpp | 1596 +++++++++++++++++ .../plugins/skeleton_retarget_editor_plugin.h | 525 ++++++ editor/scene_tree_dock.cpp | 1 + scene/3d/skeleton_3d.cpp | 192 +- scene/3d/skeleton_3d.h | 30 +- scene/3d/skeleton_ik_3d.cpp | 3 + scene/animation/animation_blend_tree.cpp | 4 +- scene/animation/animation_player.cpp | 198 +- scene/animation/animation_player.h | 27 +- scene/animation/animation_tree.cpp | 1066 +++++++---- scene/animation/animation_tree.h | 23 +- scene/animation/skeleton_retarget.cpp | 940 ++++++++++ scene/animation/skeleton_retarget.h | 233 +++ scene/register_scene_types.cpp | 7 + scene/resources/animation.cpp | 159 +- scene/resources/animation.h | 23 + 47 files changed, 5983 insertions(+), 504 deletions(-) create mode 100644 doc/classes/RetargetBoneMap.xml create mode 100644 doc/classes/RetargetBoneOption.xml create mode 100644 doc/classes/RetargetProfile.xml create mode 100644 doc/classes/RetargetRichProfile.xml create mode 100644 doc/classes/SkeletonRetarget.xml create mode 100644 editor/icons/AutoVolumeDisable.svg create mode 100644 editor/icons/AutoVolumeEnable.svg create mode 100644 editor/icons/BoneMapperHandle.svg create mode 100644 editor/icons/BoneMapperHandleCircle.svg create mode 100644 editor/icons/BoneMapperHandleSelected.svg create mode 100644 editor/icons/RetargetAbsolute.svg create mode 100644 editor/icons/RetargetGlobal.svg create mode 100644 editor/icons/RetargetLocal.svg create mode 100644 editor/icons/SkeletonRetarget.svg create mode 100644 editor/plugins/skeleton_retarget_editor_plugin.cpp create mode 100644 editor/plugins/skeleton_retarget_editor_plugin.h create mode 100644 scene/animation/skeleton_retarget.cpp create mode 100644 scene/animation/skeleton_retarget.h diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp index d9ec42dc9d3b..6e7ea68d1fc8 100644 --- a/core/extension/gdnative_interface.cpp +++ b/core/extension/gdnative_interface.cpp @@ -236,6 +236,13 @@ static GDNativeBool gdnative_variant_booleanize(const GDNativeVariantPtr p_self) return self->booleanize(); } +static void gdnative_variant_sub(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, GDNativeVariantPtr r_dst) { + const Variant *a = (const Variant *)p_a; + const Variant *b = (const Variant *)p_b; + memnew_placement(r_dst, Variant); + Variant::sub(*a, *b, *(Variant *)r_dst); +} + static void gdnative_variant_blend(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst) { const Variant *a = (const Variant *)p_a; const Variant *b = (const Variant *)p_b; @@ -925,6 +932,7 @@ void gdnative_setup_interface(GDNativeInterface *p_interface) { gdni.variant_iter_get = gdnative_variant_iter_get; gdni.variant_hash_compare = gdnative_variant_hash_compare; gdni.variant_booleanize = gdnative_variant_booleanize; + gdni.variant_sub = gdnative_variant_sub; gdni.variant_blend = gdnative_variant_blend; gdni.variant_interpolate = gdnative_variant_interpolate; gdni.variant_duplicate = gdnative_variant_duplicate; diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 76e87eaf233d..f24f6029eda0 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -330,6 +330,7 @@ typedef struct { void (*variant_iter_get)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_iter, GDNativeVariantPtr r_ret, GDNativeBool *r_valid); GDNativeBool (*variant_hash_compare)(const GDNativeVariantPtr p_self, const GDNativeVariantPtr p_other); GDNativeBool (*variant_booleanize)(const GDNativeVariantPtr p_self); + void (*variant_sub)(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, GDNativeVariantPtr r_dst); void (*variant_blend)(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst); void (*variant_interpolate)(const GDNativeVariantPtr p_a, const GDNativeVariantPtr p_b, float p_c, GDNativeVariantPtr r_dst); void (*variant_duplicate)(const GDNativeVariantPtr p_self, GDNativeVariantPtr r_ret, GDNativeBool p_deep); diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index 0a650a8578aa..11bfcc1a6f58 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -102,6 +102,22 @@ Quaternion Quaternion::inverse() const { return Quaternion(-x, -y, -z, w); } +Quaternion Quaternion::log() const { + Quaternion src = *this; + Vector3 src_v = src.get_axis() * src.get_angle(); + return Quaternion(src_v.x, src_v.y, src_v.z, 0); +} + +Quaternion Quaternion::exp() const { + Quaternion src = *this; + Vector3 src_v = Vector3(src.x, src.y, src.z); + float theta = src_v.length(); + if (theta < CMP_EPSILON) { + return Quaternion(0, 0, 0, 1); + } + return Quaternion(src_v.normalized(), theta); +} + Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) const { #ifdef MATH_CHECKS ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); @@ -190,6 +206,9 @@ Quaternion::operator String() const { } Vector3 Quaternion::get_axis() const { + if (Math::abs(w) > 1 - CMP_EPSILON) { + return Vector3(x, y, z); + } real_t r = ((real_t)1) / Math::sqrt(1 - w * w); return Vector3(x * r, y * r, z * r); } diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 38729ac3df82..9801746659a7 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -60,6 +60,8 @@ struct _NO_DISCARD_ Quaternion { Quaternion normalized() const; bool is_normalized() const; Quaternion inverse() const; + Quaternion log() const; + Quaternion exp() const; _FORCE_INLINE_ real_t dot(const Quaternion &p_q) const; real_t angle_to(const Quaternion &p_to) const; diff --git a/core/variant/variant.h b/core/variant/variant.h index ca18249f36c6..475bf7158d62 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -511,6 +511,7 @@ class Variant { Variant recursive_duplicate(bool p_deep, int recursion_count) const; static void blend(const Variant &a, const Variant &b, float c, Variant &r_dst); static void interpolate(const Variant &a, const Variant &b, float c, Variant &r_dst); + static void sub(const Variant &a, const Variant &b, Variant &r_dst); /* Built-In Methods */ diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index bc29be77fc35..17be51eba612 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1626,6 +1626,8 @@ static void _register_variant_builtin_methods() { bind_method(Quaternion, is_normalized, sarray(), varray()); bind_method(Quaternion, is_equal_approx, sarray("to"), varray()); bind_method(Quaternion, inverse, sarray(), varray()); + bind_method(Quaternion, log, sarray(), varray()); + bind_method(Quaternion, exp, sarray(), varray()); bind_method(Quaternion, angle_to, sarray("to"), varray()); bind_method(Quaternion, dot, sarray("with"), varray()); bind_method(Quaternion, slerp, sarray("to", "weight"), varray()); diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index e604ff9567e2..705aa27be6e8 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -1868,6 +1868,110 @@ Variant Variant::recursive_duplicate(bool p_deep, int recursion_count) const { } } +void Variant::sub(const Variant &a, const Variant &b, Variant &r_dst) { + if (a.type != b.type) { + return; + } + + switch (a.type) { + case NIL: { + r_dst = Variant(); + } + return; + case INT: { + int64_t va = a._data._int; + int64_t vb = b._data._int; + r_dst = int(va - vb); + } + return; + case FLOAT: { + double ra = a._data._float; + double rb = b._data._float; + r_dst = ra - rb; + } + return; + case VECTOR2: { + r_dst = *reinterpret_cast(a._data._mem) - *reinterpret_cast(b._data._mem); + } + return; + case VECTOR2I: { + int32_t vax = reinterpret_cast(a._data._mem)->x; + int32_t vbx = reinterpret_cast(b._data._mem)->x; + int32_t vay = reinterpret_cast(a._data._mem)->y; + int32_t vby = reinterpret_cast(b._data._mem)->y; + r_dst = Vector2i(int32_t(vax - vbx), int32_t(vay - vby)); + } + return; + case RECT2: { + const Rect2 *ra = reinterpret_cast(a._data._mem); + const Rect2 *rb = reinterpret_cast(b._data._mem); + r_dst = Rect2(ra->position - rb->position, ra->size - rb->size); + } + return; + case RECT2I: { + const Rect2i *ra = reinterpret_cast(a._data._mem); + const Rect2i *rb = reinterpret_cast(b._data._mem); + + int32_t vax = ra->position.x; + int32_t vay = ra->position.y; + int32_t vbx = ra->size.x; + int32_t vby = ra->size.y; + int32_t vcx = rb->position.x; + int32_t vcy = rb->position.y; + int32_t vdx = rb->size.x; + int32_t vdy = rb->size.y; + + r_dst = Rect2i(int32_t(vax - vbx), int32_t(vay - vby), int32_t(vcx - vdx), int32_t(vcy - vdy)); + } + return; + case VECTOR3: { + r_dst = *reinterpret_cast(a._data._mem) - *reinterpret_cast(b._data._mem); + } + return; + case VECTOR3I: { + int32_t vax = reinterpret_cast(a._data._mem)->x; + int32_t vbx = reinterpret_cast(b._data._mem)->x; + int32_t vay = reinterpret_cast(a._data._mem)->y; + int32_t vby = reinterpret_cast(b._data._mem)->y; + int32_t vaz = reinterpret_cast(a._data._mem)->z; + int32_t vbz = reinterpret_cast(b._data._mem)->z; + r_dst = Vector3i(int32_t(vax - vbx), int32_t(vay - vby), int32_t(vaz - vbz)); + } + return; + case AABB: { + const ::AABB *ra = reinterpret_cast(a._data._mem); + const ::AABB *rb = reinterpret_cast(b._data._mem); + r_dst = ::AABB(ra->position - rb->position, ra->size - rb->size); + } + return; + case QUATERNION: { + Quaternion empty_rot; + const Quaternion *qa = reinterpret_cast(a._data._mem); + const Quaternion *qb = reinterpret_cast(b._data._mem); + r_dst = (*qb).inverse() * *qa; + } + return; + case COLOR: { + const Color *ca = reinterpret_cast(a._data._mem); + const Color *cb = reinterpret_cast(b._data._mem); + float new_r = ca->r - cb->r; + float new_g = ca->g - cb->g; + float new_b = ca->b - cb->b; + float new_a = ca->a - cb->a; + new_r = new_r > 1.0 ? 1.0 : new_r; + new_g = new_g > 1.0 ? 1.0 : new_g; + new_b = new_b > 1.0 ? 1.0 : new_b; + new_a = new_a > 1.0 ? 1.0 : new_a; + r_dst = Color(new_r, new_g, new_b, new_a); + } + return; + default: { + r_dst = a; + } + return; + } +} + void Variant::blend(const Variant &a, const Variant &b, float c, Variant &r_dst) { if (a.type != b.type) { if (a.is_num() && b.is_num()) { diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index a92b237624ba..72f5c32b8954 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -65,6 +65,13 @@ Sets the key identified by [code]key_idx[/code] to value [code]animation[/code]. The [code]track_idx[/code] must be the index of an Animation Track. + + + + + Returns [code]true[/code] if the track at [code]idx[/code] override the volume in [AudioStreamPlayer]. + + @@ -103,6 +110,14 @@ [code]stream[/code] is the [AudioStream] resource to play. [code]start_offset[/code] is the number of seconds cut off at the beginning of the audio stream, while [code]end_offset[/code] is at the ending. + + + + + + If [code]true[/code], the track at [code]idx[/code] override the volume in [AudioStreamPlayer]. + + @@ -291,6 +306,13 @@ Returns the arguments values to be called on a method track for a given key in a given track. + + + + + Returns the retarget mode of a given track. + + @@ -299,6 +321,14 @@ + + + + + + Sets the retarget mode of a given track. + + @@ -306,6 +336,13 @@ Removes a track by specifying the track index. + + + + + Returns the retarget mode of a given track. + + @@ -314,6 +351,21 @@ + + + + + + Sets the retarget mode of a given track. + + + + + + + Returns the retarget mode of a given track. + + @@ -322,6 +374,14 @@ + + + + + + Sets the retarget mode of a given track. + + @@ -646,5 +706,14 @@ Assigning the balanced handle mode to a Bezier Track's keyframe makes it so the two handles of the keyframe always stay aligned when changing either the keyframe's left or right handle. + + Retarget the global transform in the model space relative to the bone rest. + + + Retarget the local transform relative to the bone rest. + + + Retarget the local transform relative to the initial value of transform which is [code]Transform()[/code]. + diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index b1d04ce1f21e..6b8328e001f9 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -46,6 +46,7 @@ + [AnimationPlayer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again. @@ -215,6 +216,18 @@ This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation applied (as if it had been seeked to time 0), then reverted after saving. In other words, the saved scene file will contain the "default pose", as defined by the reset animation, if any, with the editor keeping the values that the nodes had before saving. + + The map used when extracting and applying retarget tracks. + + + The option used when extracting retarget tracks. + + + The profile used when editing [RetargetBoneMap] and [RetargetBoneOption]. + + + The [Skeleton3D] to extract or apply retarget tracks. + The node from which node path references will travel. diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml index c94b649b5880..b962cf35cb62 100644 --- a/doc/classes/Quaternion.xml +++ b/doc/classes/Quaternion.xml @@ -91,6 +91,11 @@ Returns the dot product of two quaternions. + + + + + @@ -138,6 +143,11 @@ Returns the length of the quaternion, squared. + + + + + diff --git a/doc/classes/RetargetBoneMap.xml b/doc/classes/RetargetBoneMap.xml new file mode 100644 index 000000000000..4de773672582 --- /dev/null +++ b/doc/classes/RetargetBoneMap.xml @@ -0,0 +1,67 @@ + + + + A map of skeleton bone names with intermediate bone names as key. + + + To register a key in the editor, a [RetargetProfile] is required. + In the inspector, maps are organized by [RetargetProfile], but if there is no [RetargetProfile], they are listed in the unprofiled bones section. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Emitted when the drawing on the inspector needs to be updated, it is enable only when [code]tools[/code] is enabled. + + + + + + + + diff --git a/doc/classes/RetargetBoneOption.xml b/doc/classes/RetargetBoneOption.xml new file mode 100644 index 000000000000..6d6a95b9efb7 --- /dev/null +++ b/doc/classes/RetargetBoneOption.xml @@ -0,0 +1,63 @@ + + + + Set the options for each intermediate bone. + + + It has [code]RetargetMode[/code] as one of the options. + For example, if you have two models that are both in T-stance and have different bone rests, using [code]RETARGET_MODE_GLOBAL[/code] will generally look good. + However, for bones that are slanted even at T-stance, such as fingers, the joints may rotate in odd directions. In such a case, it is recommended to use [code]RETARGET_MODE_LOCAL[/code] with a restriction in the bone rest axis such as "+X axis rotation to bend". + Thus, the properties that should be set for each intermediate bone and do not need to be set for each skeleton, will be put together in [RetargetBoneOption] instead of [RetargetBoneMap]. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Emitted when the drawing on the inspector needs to be updated, it is enable only when [code]tools[/code] is enabled. + + + + + + + + diff --git a/doc/classes/RetargetProfile.xml b/doc/classes/RetargetProfile.xml new file mode 100644 index 000000000000..66b8ffed62a3 --- /dev/null +++ b/doc/classes/RetargetProfile.xml @@ -0,0 +1,67 @@ + + + + A resource that defines the intermediate bones for retarget. + + + This resource is special since it is only used in the editor. It does not have any effect on the retargeting calculation. + The [RetargetBoneMap] is needed for each skeleton with a different bone name, but the keys must be the same. Therefore, to make it easier to generate the same keys in those, [RetargetProfile] lists the keys as intermediate bones. + You need a [RetargetProfile] when you register a key to [RetargetBoneMap] or [RetargetBoneOption] in the editor. However, once you register the keys in them, you can discard the [RetargetProfile] if you don't need it. Also, you can register keys in them with the script always. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Emitted when the drawing on the inspector needs to be updated, it is enable only when [code]tools[/code] is enabled. + + + + diff --git a/doc/classes/RetargetRichProfile.xml b/doc/classes/RetargetRichProfile.xml new file mode 100644 index 000000000000..ee63349a02fa --- /dev/null +++ b/doc/classes/RetargetRichProfile.xml @@ -0,0 +1,97 @@ + + + + A resource that defines the intermediate bones and the mapper layout in the inspector for retarget. + + + A resource that defines textures, coordinates, and groups in addition to the intermediate bones. + This provides a GUI with the buttons placed on the silhouette image to register the keys to [RetargetBoneMap] and [RetargetBoneOption]. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 80a36acacc92..f2493ec66ea3 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -60,6 +60,62 @@ Executes all the modifications on the [SkeletonModificationStack3D], if the Skeleton3D has one assigned. + + + + + Returns the pose position used by global retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose rotation used by global retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose scale used by global retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose used by global retarget for [code]bone_idx[/code]. + + + + + + + Returns the pose position used by local retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose rotation used by local retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose scale used by local retarget without override transform for [code]bone_idx[/code]. + + + + + + + Returns the pose used by local retarget for [code]bone_idx[/code]. + + @@ -93,6 +149,13 @@ Returns the amount of bones in the skeleton. + + + + + Returns the final transform of the specified bone, if pose is overridden, it take that into account. + + @@ -108,6 +171,13 @@ + + + + Returns the [code]bone_global_pose_override[/code] for a bone [code]bone_idx[/code]. + + + @@ -146,18 +216,21 @@ + Returns the bone pose position for a bone [code]bone_idx[/code]. + Returns the bone pose rotation for a bone [code]bone_idx[/code]. + Returns the bone pose scale for a bone [code]bone_idx[/code]. @@ -205,6 +278,38 @@ This is helper function to make using [method Transform3D.looking_at] easier with bone poses. + + + + + + Takes the passed-in global retarget position and converts it to local pose position. + + + + + + + + Takes the passed-in global retarget rotation and converts it to local pose rotation. + + + + + + + + Takes the passed-in global retarget scale and converts it to local pose scale. + + + + + + + + Takes the passed-in global retarget transform and converts it to local pose. + + @@ -221,6 +326,38 @@ This could be used to convert [method get_bone_pose] for use with the [method set_bone_global_pose_override] function. + + + + + + Takes the passed-in local retarget position and converts it to local pose position. + + + + + + + + Takes the passed-in local retarget rotation and converts it to local pose rotation. + + + + + + + + Takes the passed-in local retarget scale and converts it to local pose scale. + + + + + + + + Takes the passed-in local retarget transform and converts it to local pose. + + @@ -318,6 +455,7 @@ + Sets the bone name for bone [code]bone_idx[/code]. @@ -334,6 +472,7 @@ + Sets the bone pose position for bone [code]bone_idx[/code]. @@ -341,6 +480,7 @@ + Sets the bone pose rotation for bone [code]bone_idx[/code]. @@ -348,6 +488,7 @@ + Sets the bone pose scale for bone [code]bone_idx[/code]. @@ -391,6 +532,7 @@ + This signal is emitted when the [code]bone_enabled[/code] have changed. @@ -401,10 +543,12 @@ + This signal is emitted when the bone's dirty flag have solved by calculation with iterate all bones. + This signal is emitted when the [code]show_rest_only[/code] have changed. diff --git a/doc/classes/SkeletonRetarget.xml b/doc/classes/SkeletonRetarget.xml new file mode 100644 index 000000000000..7a12147d69d8 --- /dev/null +++ b/doc/classes/SkeletonRetarget.xml @@ -0,0 +1,32 @@ + + + + Transfer the transforms between the two [Skeleton3D]s. + + + [RetargetProfile] provides the intermediate bones. The source skeleton and target skeleton bones are mapped to each other through intermediate bones using [RetargetBoneMap]. + The transfer value is calculated differently by [code]RetargetMode[/code]. The default mode is [code]RETARGET_MODE_GLOBAL[/code]. You can set it for each intermediate bone using [RetargetBoneOption]. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 11995e8cb473..dc52ccfa8525 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -170,7 +170,9 @@ class AnimationTrackKeyEdit : public Object { case Animation::TYPE_ROTATION_3D: case Animation::TYPE_SCALE_3D: { if (name == "position" || name == "rotation" || name == "scale") { - Variant old = animation->track_get_key_value(track, key); + Dictionary d_old = animation->track_get_key_value(track, key); + Dictionary d_new = d_old.duplicate(); + d_new[name] = p_value; setting = true; String chan; switch (animation->track_get_type(track)) { @@ -189,7 +191,7 @@ class AnimationTrackKeyEdit : public Object { undo_redo->create_action(vformat(TTR("Anim Change %s"), chan)); undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old[name]); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); undo_redo->commit_action(); @@ -451,8 +453,10 @@ class AnimationTrackKeyEdit : public Object { case Animation::TYPE_POSITION_3D: case Animation::TYPE_ROTATION_3D: case Animation::TYPE_SCALE_3D: { + Dictionary d = animation->track_get_key_value(track, key); if (name == "position" || name == "rotation" || name == "scale") { - r_ret = animation->track_get_key_value(track, key); + ERR_FAIL_COND_V(!d.has(name), false); + r_ret = d[name]; return true; } } break; @@ -832,7 +836,9 @@ class AnimationMultiTrackKeyEdit : public Object { case Animation::TYPE_POSITION_3D: case Animation::TYPE_ROTATION_3D: case Animation::TYPE_SCALE_3D: { - Variant old = animation->track_get_key_value(track, key); + Dictionary d_old = animation->track_get_key_value(track, key); + Dictionary d_new = d_old.duplicate(); + d_new[name] = p_value; if (!setting) { String chan; switch (animation->track_get_type(track)) { @@ -853,7 +859,7 @@ class AnimationMultiTrackKeyEdit : public Object { undo_redo->create_action(vformat(TTR("Anim Multi Change %s"), chan)); } undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old[name]); update_obj = true; } break; case Animation::TYPE_BLEND_SHAPE: @@ -1088,8 +1094,10 @@ class AnimationMultiTrackKeyEdit : public Object { case Animation::TYPE_POSITION_3D: case Animation::TYPE_ROTATION_3D: case Animation::TYPE_SCALE_3D: { + Dictionary d = animation->track_get_key_value(track, key); if (name == "position" || name == "rotation" || name == "scale") { - r_ret = animation->track_get_key_value(track, key); + ERR_FAIL_COND_V(!d.has(name), false); + r_ret = d[name]; return true; } @@ -1417,14 +1425,14 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) { void AnimationTimelineEdit::_anim_loop_pressed() { undo_redo->create_action(TTR("Change Animation Loop")); switch (animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_LINEAR); + case Animation::LOOP_NONE: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR); } break; - case Animation::LoopMode::LOOP_LINEAR: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_PINGPONG); + case Animation::LOOP_LINEAR: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG); } break; - case Animation::LoopMode::LOOP_PINGPONG: { - undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_NONE); + case Animation::LOOP_PINGPONG: { + undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE); } break; default: break; @@ -1725,15 +1733,15 @@ void AnimationTimelineEdit::update_values() { } switch (animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); loop->set_pressed(false); } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); loop->set_pressed(true); } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { loop->set_icon(get_theme_icon(SNAME("PingPongLoop"), SNAME("EditorIcons"))); loop->set_pressed(true); } break; @@ -1979,9 +1987,9 @@ void AnimationTrackEdit::_notification(int p_what) { linecolor.a = 0.2; // NAMES AND ICONS // - + bool is_retarget_track = animation->track_is_retarget(track); { - Ref check = animation->track_is_enabled(track) ? get_theme_icon(SNAME("checked"), SNAME("CheckBox")) : get_theme_icon(SNAME("unchecked"), SNAME("CheckBox")); + Ref check = is_retarget_track ? get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")) : get_theme_icon(animation->track_is_enabled(track) ? SNAME("checked") : SNAME("unchecked"), SNAME("CheckBox")); int ofs = in_group ? check->get_width() : 0; // Not the best reference for margin but.. @@ -2093,6 +2101,17 @@ void AnimationTrackEdit::_notification(int p_what) { get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")) }; + Ref volume_icon[2] = { + get_theme_icon(SNAME("AutoVolumeDisable"), SNAME("EditorIcons")), + get_theme_icon(SNAME("AutoVolumeEnable"), SNAME("EditorIcons")), + }; + + Ref retarget_icon[3] = { + get_theme_icon(SNAME("RetargetGlobal"), SNAME("EditorIcons")), + get_theme_icon(SNAME("RetargetLocal"), SNAME("EditorIcons")), + get_theme_icon(SNAME("RetargetAbsolute"), SNAME("EditorIcons")) + }; + int ofs = get_size().width - timeline->get_buttons_width(); Ref down_icon = get_theme_icon(SNAME("select_arrow"), SNAME("Tree")); @@ -2117,23 +2136,49 @@ void AnimationTrackEdit::_notification(int p_what) { update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2; update_mode_rect.size = update_icon->get_size(); - if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) { - draw_texture(update_icon, update_mode_rect.position); + if (!animation->track_is_compressed(track)) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + draw_texture(update_icon, update_mode_rect.position); + } + if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { + Ref auto_volume_icon = volume_icon[animation->audio_track_get_auto_volume(track) ? 1 : 0]; + Vector2 auto_volume_icon_pos = update_mode_rect.position + (update_mode_rect.size - auto_volume_icon->get_size()) / 2; + draw_texture(auto_volume_icon, auto_volume_icon_pos); + } } - // Make it easier to click. +#ifndef _3D_DISABLED + if (is_retarget_track) { + Vector2 centerize = Vector2(down_icon->get_width() * 0.5, 0); + if (animation->track_get_type(track) == Animation::TYPE_POSITION_3D) { + Ref retarget_mode_icon = retarget_icon[animation->position_track_get_retarget_mode(track)]; + Vector2 retarget_mode_icon_pos = centerize + update_mode_rect.position + (update_mode_rect.size - retarget_mode_icon->get_size()) / 2; + draw_texture(retarget_mode_icon, retarget_mode_icon_pos); + } + if (animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) { + Ref retarget_mode_icon = retarget_icon[animation->rotation_track_get_retarget_mode(track)]; + Vector2 retarget_mode_icon_pos = centerize + update_mode_rect.position + (update_mode_rect.size - retarget_mode_icon->get_size()) / 2; + draw_texture(retarget_mode_icon, retarget_mode_icon_pos); + } + if (animation->track_get_type(track) == Animation::TYPE_SCALE_3D) { + Ref retarget_mode_icon = retarget_icon[animation->scale_track_get_retarget_mode(track)]; + Vector2 retarget_mode_icon_pos = centerize + update_mode_rect.position + (update_mode_rect.size - retarget_mode_icon->get_size()) / 2; + draw_texture(retarget_mode_icon, retarget_mode_icon_pos); + } + } +#endif // _3D_DISABLED + update_mode_rect.position.y = 0; update_mode_rect.size.y = get_size().height; ofs += update_icon->get_width() + hsep / 2; update_mode_rect.size.x += hsep / 2; - if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); update_mode_rect.size.x += down_icon->get_width(); } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { Ref bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")); update_mode_rect.size.x += down_icon->get_width(); - update_mode_rect = Rect2(); } else { update_mode_rect = Rect2(); @@ -2555,7 +2600,11 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } if (update_mode_rect.has_point(p_pos)) { - return TTR("Update Mode (How this property is set)"); + if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { + return TTR("Auto Volume (Allow animation tree to override stream player volume)"); + } else { + return TTR("Update Mode (How this property is set)"); + } } if (interp_mode_rect.has_point(p_pos)) { @@ -2735,21 +2784,32 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { // Don't overlap track keys if they start at 0. if (path_rect.has_point(pos + Size2(type_icon->get_width(), 0))) { + if (read_only) { + return; + } clicking_on_name = true; accept_event(); } if (update_mode_rect.has_point(pos)) { + if (read_only) { + return; + } if (!menu) { menu = memnew(PopupMenu); add_child(menu); menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected)); } menu->clear(); - menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS); - menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE); - menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER); - menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE); + if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { + menu->add_icon_item(get_theme_icon(SNAME("AutoVolumeDisable"), SNAME("EditorIcons")), TTR("Disable"), MENU_AUTO_VOLUME_DISABLE); + menu->add_icon_item(get_theme_icon(SNAME("AutoVolumeEnable"), SNAME("EditorIcons")), TTR("Enable"), MENU_AUTO_VOLUME_ENABLE); + } else { + menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS); + menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE); + menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER); + menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE); + } menu->reset_size(); Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height); @@ -2759,6 +2819,9 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } if (interp_mode_rect.has_point(pos)) { + if (read_only) { + return; + } if (!menu) { menu = memnew(PopupMenu); add_child(menu); @@ -2777,6 +2840,9 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } if (loop_wrap_rect.has_point(pos)) { + if (read_only) { + return; + } if (!menu) { menu = memnew(PopupMenu); add_child(menu); @@ -2802,6 +2868,9 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { // Check keyframes. if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible. + if (read_only) { + return; + } float scale = timeline->get_zoom_scale(); int limit = timeline->get_name_limit(); @@ -2863,6 +2932,10 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { + if (read_only) { + return; + } + Point2 pos = mb->get_position(); if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) { // Can do something with menu too! show insert key. @@ -2898,6 +2971,10 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && clicking_on_name) { + if (read_only) { + return; + } + if (!path) { path_popup = memnew(Popup); path_popup->set_wrap_controls(true); @@ -2919,6 +2996,10 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } if (mb.is_valid() && moving_selection_attempt) { + if (read_only) { + return; + } + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { moving_selection_attempt = false; if (moving_selection) { @@ -2939,6 +3020,10 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { Ref mm = p_event; if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && moving_selection_attempt) { + if (read_only) { + return; + } + if (!moving_selection) { moving_selection = true; emit_signal(SNAME("move_selection_begin")); @@ -2950,7 +3035,7 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { } Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) { - if (!clicking_on_name) { + if (!clicking_on_name || read_only) { return Variant(); } @@ -3081,6 +3166,15 @@ void AnimationTrackEdit::_menu_selected(int p_index) { emit_signal(SNAME("delete_request")); } break; + case MENU_AUTO_VOLUME_DISABLE: + case MENU_AUTO_VOLUME_ENABLE: { + bool auto_volume = p_index == MENU_AUTO_VOLUME_ENABLE; + undo_redo->create_action(TTR("Change Animation Auto Volume")); + undo_redo->add_do_method(animation.ptr(), "audio_track_set_auto_volume", track, auto_volume); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_auto_volume", track, animation->audio_track_get_auto_volume(track)); + undo_redo->commit_action(); + update(); + } break; } } @@ -3096,6 +3190,15 @@ void AnimationTrackEdit::set_in_group(bool p_enable) { update(); } +void AnimationTrackEdit::set_read_only(bool p_read_only) { + read_only = p_read_only; + update(); +} + +bool AnimationTrackEdit::get_read_only() { + return read_only; +} + void AnimationTrackEdit::append_to_selection(const Rect2 &p_box, bool p_deselection) { if (animation->track_is_compressed(track)) { return; // Compressed keyframes can't be edited @@ -3487,9 +3590,24 @@ void AnimationTrackEditor::_track_remove_request(int p_track) { } undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", idx, animation->track_get_interpolation_type(idx)); + undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", idx, animation->track_get_interpolation_loop_wrap(idx)); if (animation->track_get_type(idx) == Animation::TYPE_VALUE) { undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", idx, animation->value_track_get_update_mode(idx)); } + if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) { + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_auto_volume", idx, animation->audio_track_get_auto_volume(idx)); + } + if (animation->track_is_retarget(idx)) { + if (animation->track_get_type(idx) == Animation::TYPE_POSITION_3D) { + undo_redo->add_undo_method(animation.ptr(), "position_track_set_retarget_mode", idx, animation->position_track_get_retarget_mode(idx)); + } + if (animation->track_get_type(idx) == Animation::TYPE_ROTATION_3D) { + undo_redo->add_undo_method(animation.ptr(), "rotation_track_set_retarget_mode", idx, animation->rotation_track_get_retarget_mode(idx)); + } + if (animation->track_get_type(idx) == Animation::TYPE_SCALE_3D) { + undo_redo->add_undo_method(animation.ptr(), "scale_track_set_retarget_mode", idx, animation->scale_track_get_retarget_mode(idx)); + } + } undo_redo->commit_action(); } @@ -4276,6 +4394,7 @@ void AnimationTrackEditor::_update_tracks() { } Map group_sort; + VBoxContainer *retarget_vbox = nullptr; bool use_grouping = !view_group->is_pressed(); bool use_filter = selected_filter->is_pressed(); @@ -4362,39 +4481,59 @@ void AnimationTrackEditor::_update_tracks() { track_edits.push_back(track_edit); - if (use_grouping) { - String base_path = animation->track_get_path(i); - base_path = base_path.get_slice(":", 0); // Remove sub-path. - - if (!group_sort.has(base_path)) { - AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup); - Ref icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons")); - String name = base_path; - String tooltip; - if (root && root->has_node(base_path)) { - Node *n = root->get_node(base_path); - if (n) { - icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); - name = n->get_name(); - tooltip = root->get_path_to(n); + bool is_retarget_track = animation->track_is_retarget(i); + if (use_grouping || is_retarget_track) { + if (is_retarget_track) { + // Special case for retarget tracks. + if (!retarget_vbox) { + AnimationTrackEditGroup *retarget_group = memnew(AnimationTrackEditGroup); + retarget_group->set_type_and_name(get_theme_icon(SNAME("SkeletonRetarget"), SNAME("EditorIcons")), "== Retarget ==", animation->track_get_path(i)); + retarget_group->set_root(root); + retarget_group->set_tooltip(TTR("Intermediate Bones for retarget.")); + retarget_group->set_timeline(timeline); + groups.insert(0, retarget_group); // Insert to top since it's special tracks and readonly, so it get in the way multiple keys selection. + retarget_vbox = memnew(VBoxContainer); + retarget_vbox->add_theme_constant_override("separation", 0); + retarget_vbox->add_child(retarget_group); + track_vbox->add_child(retarget_vbox); + } + track_edit->set_read_only(true); + track_edit->set_in_group(true); + retarget_vbox->add_child(track_edit); + } else { + // Normal tracks. + String base_path = animation->track_get_path(i); + base_path = base_path.get_slice(":", 0); // Remove sub-path. + + if (!group_sort.has(base_path)) { + AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup); + Ref icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons")); + String name = base_path; + String tooltip; + if (root && root->has_node(base_path)) { + Node *n = root->get_node(base_path); + if (n) { + icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); + name = n->get_name(); + tooltip = root->get_path_to(n); + } } + + g->set_type_and_name(icon, name, animation->track_get_path(i)); + g->set_root(root); + g->set_tooltip(tooltip); + g->set_timeline(timeline); + groups.push_back(g); + VBoxContainer *vb = memnew(VBoxContainer); + vb->add_theme_constant_override("separation", 0); + vb->add_child(g); + track_vbox->add_child(vb); + group_sort[base_path] = vb; } - g->set_type_and_name(icon, name, animation->track_get_path(i)); - g->set_root(root); - g->set_tooltip(tooltip); - g->set_timeline(timeline); - groups.push_back(g); - VBoxContainer *vb = memnew(VBoxContainer); - vb->add_theme_constant_override("separation", 0); - vb->add_child(g); - track_vbox->add_child(vb); - group_sort[base_path] = vb; + track_edit->set_in_group(true); + group_sort[base_path]->add_child(track_edit); } - - track_edit->set_in_group(true); - group_sort[base_path]->add_child(track_edit); - } else { track_edit->set_in_group(false); track_vbox->add_child(track_edit); @@ -4974,6 +5113,9 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) { ERR_FAIL_INDEX(p_track, animation->get_track_count()); ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track)); + if (track_edits[p_track]->get_read_only()) { + return; + } SelectedKey sk; sk.key = p_key; @@ -5526,6 +5668,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { TreeItem *troot = track_copy_select->create_item(); for (int i = 0; i < animation->get_track_count(); i++) { + if (animation->track_is_retarget(i)) { + continue; + } NodePath path = animation->track_get_path(i); Node *node = nullptr; @@ -5617,6 +5762,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (tc.track_type == Animation::TYPE_VALUE) { tc.update_mode = animation->value_track_get_update_mode(idx); } + if (tc.track_type == Animation::TYPE_AUDIO) { + tc.auto_volume = animation->audio_track_get_auto_volume(idx); + } tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx); tc.enabled = animation->track_is_enabled(idx); for (int i = 0; i < animation->track_get_key_count(idx); i++) { @@ -5660,6 +5808,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (track_clipboard[i].track_type == Animation::TYPE_VALUE) { undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode); } + if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) { + undo_redo->add_do_method(animation.ptr(), "audio_track_set_auto_volume", base_track, track_clipboard[i].auto_volume); + } for (int j = 0; j < track_clipboard[i].keys.size(); j++) { undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 5f803ae79623..1d0ef4a2851a 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -142,7 +142,9 @@ class AnimationTrackEdit : public Control { MENU_KEY_INSERT, MENU_KEY_DUPLICATE, MENU_KEY_ADD_RESET, - MENU_KEY_DELETE + MENU_KEY_DELETE, + MENU_AUTO_VOLUME_DISABLE, + MENU_AUTO_VOLUME_ENABLE }; AnimationTimelineEdit *timeline; UndoRedo *undo_redo; @@ -168,6 +170,7 @@ class AnimationTrackEdit : public Control { Ref selected_icon; PopupMenu *menu; + bool read_only = false; bool clicking_on_name; @@ -238,6 +241,8 @@ class AnimationTrackEdit : public Control { void cancel_drop(); void set_in_group(bool p_enable); + void set_read_only(bool p_read_only); + bool get_read_only(); void append_to_selection(const Rect2 &p_box, bool p_deselection); AnimationTrackEdit(); @@ -473,12 +478,13 @@ class AnimationTrackEditor : public VBoxContainer { struct TrackClipboard { NodePath full_path; NodePath base_path; - Animation::TrackType track_type = Animation::TrackType::TYPE_ANIMATION; - Animation::InterpolationType interp_type = Animation::InterpolationType::INTERPOLATION_CUBIC; - Animation::UpdateMode update_mode = Animation::UpdateMode::UPDATE_CAPTURE; - Animation::LoopMode loop_mode = Animation::LoopMode::LOOP_LINEAR; + Animation::TrackType track_type = Animation::TYPE_ANIMATION; + Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC; + Animation::UpdateMode update_mode = Animation::UPDATE_CAPTURE; + Animation::LoopMode loop_mode = Animation::LOOP_LINEAR; bool loop_wrap = false; bool enabled = false; + bool auto_volume = false; struct Key { float time = 0; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 17b219f8dc9f..8237b91e3952 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -179,6 +179,7 @@ #include "editor/plugins/skeleton_2d_editor_plugin.h" #include "editor/plugins/skeleton_3d_editor_plugin.h" #include "editor/plugins/skeleton_ik_3d_editor_plugin.h" +#include "editor/plugins/skeleton_retarget_editor_plugin.h" #include "editor/plugins/sprite_2d_editor_plugin.h" #include "editor/plugins/sprite_frames_editor_plugin.h" #include "editor/plugins/style_box_editor_plugin.h" @@ -7027,6 +7028,9 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(Skeleton3DEditorPlugin)); add_editor_plugin(memnew(SkeletonIK3DEditorPlugin)); add_editor_plugin(memnew(PhysicalBone3DEditorPlugin)); + add_editor_plugin(memnew(RetargetProfileEditorPlugin)); + add_editor_plugin(memnew(RetargetBoneOptionEditorPlugin)); + add_editor_plugin(memnew(RetargetBoneMapEditorPlugin)); add_editor_plugin(memnew(MeshEditorPlugin)); add_editor_plugin(memnew(MaterialEditorPlugin)); add_editor_plugin(memnew(GPUParticlesCollisionSDF3DEditorPlugin)); diff --git a/editor/icons/AutoVolumeDisable.svg b/editor/icons/AutoVolumeDisable.svg new file mode 100644 index 000000000000..f8310b706ef6 --- /dev/null +++ b/editor/icons/AutoVolumeDisable.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/AutoVolumeEnable.svg b/editor/icons/AutoVolumeEnable.svg new file mode 100644 index 000000000000..0f217033fec9 --- /dev/null +++ b/editor/icons/AutoVolumeEnable.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/BoneMapperHandle.svg b/editor/icons/BoneMapperHandle.svg new file mode 100644 index 000000000000..8c7d7e1d7016 --- /dev/null +++ b/editor/icons/BoneMapperHandle.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/BoneMapperHandleCircle.svg b/editor/icons/BoneMapperHandleCircle.svg new file mode 100644 index 000000000000..ecf97669b885 --- /dev/null +++ b/editor/icons/BoneMapperHandleCircle.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/BoneMapperHandleSelected.svg b/editor/icons/BoneMapperHandleSelected.svg new file mode 100644 index 000000000000..729a443f6e15 --- /dev/null +++ b/editor/icons/BoneMapperHandleSelected.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/RetargetAbsolute.svg b/editor/icons/RetargetAbsolute.svg new file mode 100644 index 000000000000..3a9ffd4e826d --- /dev/null +++ b/editor/icons/RetargetAbsolute.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/RetargetGlobal.svg b/editor/icons/RetargetGlobal.svg new file mode 100644 index 000000000000..9f5c9001b708 --- /dev/null +++ b/editor/icons/RetargetGlobal.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/RetargetLocal.svg b/editor/icons/RetargetLocal.svg new file mode 100644 index 000000000000..b54f4fe5410b --- /dev/null +++ b/editor/icons/RetargetLocal.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/SkeletonRetarget.svg b/editor/icons/SkeletonRetarget.svg new file mode 100644 index 000000000000..f8fd29fa8e26 --- /dev/null +++ b/editor/icons/SkeletonRetarget.svg @@ -0,0 +1 @@ + diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index e7c605aaf0c3..36aa5e85fcbf 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -471,7 +471,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Mapset_loop_mode(Animation::LoopMode::LOOP_LINEAR); + anim->set_loop_mode(Animation::LOOP_LINEAR); animname = _fixstr(animname, loop_strings[i]); ap->rename_animation(E, animname); } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index fba6d8e57f13..81ec7b0d328c 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -42,6 +42,8 @@ #include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning. #include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning. #include "editor/scene_tree_dock.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_player.h" #include "scene/main/window.h" #include "scene/resources/animation.h" #include "scene/scene_string_names.h" @@ -155,6 +157,10 @@ void AnimationPlayerEditor::_notification(int p_what) { ITEM_ICON(TOOL_RENAME_ANIM, "Rename"); ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend"); ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit"); + ITEM_ICON(TOOL_MAKE_REATRGET_TRACKS, "SkeletonRetarget"); + ITEM_ICON(TOOL_RESTORE_REATRGET_TRACKS, "SkeletonRetarget"); + ITEM_ICON(TOOL_DELETE_REATRGET_TRACKS, "Remove"); + ITEM_ICON(TOOL_DELETE_NOT_REATRGET_TRACKS, "Remove"); ITEM_ICON(TOOL_REMOVE_ANIM, "Remove"); _update_animation_list_icons(); @@ -840,6 +846,10 @@ void AnimationPlayerEditor::_update_player() { ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0); ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0); ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_MAKE_REATRGET_TRACKS, animlist.size() == 0); + ITEM_DISABLED(TOOL_RESTORE_REATRGET_TRACKS, animlist.size() == 0); + ITEM_DISABLED(TOOL_DELETE_REATRGET_TRACKS, animlist.size() == 0); + ITEM_DISABLED(TOOL_DELETE_NOT_REATRGET_TRACKS, animlist.size() == 0); stop->set_disabled(animlist.size() == 0); play->set_disabled(animlist.size() == 0); @@ -1220,6 +1230,20 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { Ref anim2 = player->get_animation(current2); EditorNode::get_singleton()->edit_resource(anim2); } break; +#ifndef _3D_DISABLED + case TOOL_MAKE_REATRGET_TRACKS: { + _make_retarget_tracks(); + } break; + case TOOL_RESTORE_REATRGET_TRACKS: { + _restore_retarget_tracks(); + } break; + case TOOL_DELETE_REATRGET_TRACKS: { + _delete_retarget_tracks(); + } break; + case TOOL_DELETE_NOT_REATRGET_TRACKS: { + _delete_not_retarget_tracks(); + } break; +#endif // _3D_DISABLED } } @@ -1474,7 +1498,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { float pos = cpos + step_off * anim->get_step(); - bool valid = anim->get_loop_mode() != Animation::LoopMode::LOOP_NONE || (pos >= 0 && pos <= anim->get_length()); + bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length()); onion.captures_valid.write[cidx] = valid; if (valid) { player->seek(pos, true); @@ -1538,6 +1562,422 @@ void AnimationPlayerEditor::_pin_pressed() { SceneTreeDock::get_singleton()->get_tree_editor()->update_tree(); } +#ifndef _3D_DISABLED +void AnimationPlayerEditor::_make_retarget_tracks() { + // Init. + player->stop(); + Skeleton3D *skeleton = Object::cast_to(player->get_node_or_null(player->get_retarget_skeleton())); + Ref retarget_map = player->get_retarget_map(); + Ref retarget_option = player->get_retarget_option(); + if (!skeleton || !retarget_map.is_valid()) { + ERR_FAIL_MSG("Retarget Skeleton and Source Setting are needed."); + } + skeleton->clear_bones_local_pose_override(); + skeleton->clear_bones_global_pose_override(); + undo_redo->create_action(TTR("Make Retarget Tracks")); + // Process. + Ref anim = track_editor->get_current_animation(); + int len = anim->get_track_count(); + Node *root = player->get_node(player->get_root()); + Vector remove_queue; + int insert_count = 0; + for (int i = 0; i < len; i++) { + if (!anim->track_is_enabled(i)) { + continue; + } + Skeleton3D *track_skeleton = Object::cast_to(root->get_node_or_null(anim->track_get_path(i))); + if (track_skeleton && (track_skeleton->get_path() == skeleton->get_path())) { + if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D) { + // Find intermediate bone. + String bone_name = anim->track_get_path(i).get_concatenated_subnames(); + String imbone = retarget_map->find_key(bone_name); + if (imbone == "") { + continue; // Key not found or broken. + } + // Find skeleton bone. + int bone_id = skeleton->find_bone(bone_name); + if (bone_id == -1) { + continue; // Bone not found. + } + // Insert track. + int new_track = len + insert_count; + undo_redo->add_do_method(anim.ptr(), "add_track", anim->track_get_type(i), new_track); + undo_redo->add_do_method(anim.ptr(), "track_set_path", new_track, ":" + imbone); + undo_redo->add_do_method(anim.ptr(), "track_set_interpolation_type", new_track, anim->track_get_interpolation_type(i)); + undo_redo->add_do_method(anim.ptr(), "track_set_interpolation_loop_wrap", new_track, anim->track_get_interpolation_loop_wrap(i)); + insert_count++; + // Iterate keys. + int tr_len = anim->track_get_key_count(i); + Animation::RetargetMode retarget_mode = Animation::RETARGET_MODE_GLOBAL; + if (retarget_option.is_valid() && retarget_option->has_key(imbone)) { + retarget_mode = retarget_option->get_retarget_mode(imbone); + } + switch (retarget_mode) { + case Animation::RETARGET_MODE_GLOBAL: { + switch (anim->track_get_type(i)) { + case Animation::TYPE_POSITION_3D: { + undo_redo->add_do_method(anim.ptr(), "position_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_GLOBAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, anim->track_get_key_value(i, j)); + skeleton->set_bone_pose_rotation(bone_id, Quaternion()); + skeleton->set_bone_pose_scale(bone_id, Vector3(1, 1, 1)); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, skeleton->extract_global_retarget_position(bone_id)); + } + } break; + case Animation::TYPE_ROTATION_3D: { + undo_redo->add_do_method(anim.ptr(), "rotation_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_GLOBAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, Vector3()); + skeleton->set_bone_pose_rotation(bone_id, anim->track_get_key_value(i, j)); + skeleton->set_bone_pose_scale(bone_id, Vector3(1, 1, 1)); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, skeleton->extract_global_retarget_rotation(bone_id)); + } + } break; + case Animation::TYPE_SCALE_3D: { + undo_redo->add_do_method(anim.ptr(), "scale_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_GLOBAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, Vector3()); + skeleton->set_bone_pose_rotation(bone_id, Quaternion()); + skeleton->set_bone_pose_scale(bone_id, anim->track_get_key_value(i, j)); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, skeleton->extract_global_retarget_scale(bone_id)); + } + } break; + default: { + } break; + } + } break; + case Animation::RETARGET_MODE_LOCAL: { + switch (anim->track_get_type(i)) { + case Animation::TYPE_POSITION_3D: { + undo_redo->add_do_method(anim.ptr(), "position_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_LOCAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, anim->track_get_key_value(i, j)); + skeleton->set_bone_pose_rotation(bone_id, Quaternion()); + skeleton->set_bone_pose_scale(bone_id, Vector3(1, 1, 1)); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, skeleton->extract_local_retarget_position(bone_id)); + } + } break; + case Animation::TYPE_ROTATION_3D: { + undo_redo->add_do_method(anim.ptr(), "rotation_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_LOCAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, Vector3()); + skeleton->set_bone_pose_rotation(bone_id, anim->track_get_key_value(i, j)); + skeleton->set_bone_pose_scale(bone_id, Vector3(1, 1, 1)); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, skeleton->extract_local_retarget_rotation(bone_id)); + } + } break; + case Animation::TYPE_SCALE_3D: { + undo_redo->add_do_method(anim.ptr(), "scale_track_set_retarget_mode", new_track, Animation::RETARGET_MODE_LOCAL); + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + skeleton->set_bone_pose_position(bone_id, Vector3()); + skeleton->set_bone_pose_rotation(bone_id, Quaternion()); + skeleton->set_bone_pose_scale(bone_id, anim->track_get_key_value(i, j)); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, skeleton->extract_local_retarget_scale(bone_id)); + } + } break; + default: { + } break; + } + } break; + default: { + switch (anim->track_get_type(i)) { + case Animation::TYPE_POSITION_3D: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + case Animation::TYPE_ROTATION_3D: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + case Animation::TYPE_SCALE_3D: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + default: { + } break; + } + } break; + } + // Init pose. + Transform3D rest = skeleton->get_bone_rest(bone_id); + skeleton->set_bone_pose_position(bone_id, rest.origin); + skeleton->set_bone_pose_rotation(bone_id, rest.basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(bone_id, rest.basis.get_scale()); + // Queue delete source track. + remove_queue.push_back(i); + undo_redo->add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), i); + undo_redo->add_undo_method(anim.ptr(), "track_set_path", i, anim->track_get_path(i)); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + Variant v = anim->track_get_key_value(i, j); + float time = anim->track_get_key_time(i, j); + float trans = anim->track_get_key_transition(i, j); + undo_redo->add_undo_method(anim.ptr(), "track_insert_key", i, time, v); + undo_redo->add_undo_method(anim.ptr(), "track_set_key_transition", i, j, trans); + } + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_type", i, anim->track_get_interpolation_type(i)); + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_loop_wrap", i, anim->track_get_interpolation_loop_wrap(i)); + } + } + } + for (int i = len + insert_count - 1; i >= len; i--) { + undo_redo->add_undo_method(anim.ptr(), "remove_track", i); + } + remove_queue.reverse(); + len = remove_queue.size(); + for (int i = 0; i < len; i++) { + undo_redo->add_do_method(anim.ptr(), "remove_track", remove_queue[i]); + } + undo_redo->commit_action(); + _update_player(); +} + +void AnimationPlayerEditor::_restore_retarget_tracks() { + // Init. + player->stop(); + Skeleton3D *skeleton = Object::cast_to(player->get_node_or_null(player->get_retarget_skeleton())); + Ref retarget_map = player->get_retarget_map(); + if (!skeleton || !retarget_map.is_valid()) { + ERR_FAIL_MSG("Retarget Skeleton and Target Setting are needed."); + } + skeleton->clear_bones_local_pose_override(); + skeleton->clear_bones_global_pose_override(); + undo_redo->create_action(TTR("Restore Retarget Tracks")); + // Process. + Ref anim = track_editor->get_current_animation(); + int len = anim->get_track_count(); + Node *root = player->get_node(player->get_root()); + Vector remove_queue; + int insert_count = 0; + for (int i = 0; i < len; i++) { + if (anim->track_get_path(i).get_name_count() == 0 && anim->track_get_path(i).get_subname_count() == 1) { + if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D) { + // Find intermediate bone. + String imbone = anim->track_get_path(i).get_concatenated_subnames(); + if (!retarget_map->has_key(imbone)) { + continue; // Key not found or broken. + } + String bone_name = retarget_map->get_bone_name(imbone); + // Find skeleton bone. + int bone_id = skeleton->find_bone(bone_name); + if (bone_id == -1) { + continue; // Bone not found. + } + // Insert track. + int new_track = len + insert_count; + undo_redo->add_do_method(anim.ptr(), "add_track", anim->track_get_type(i), new_track); + undo_redo->add_do_method(anim.ptr(), "track_set_path", new_track, String(root->get_path_to(skeleton)) + ":" + bone_name); + undo_redo->add_do_method(anim.ptr(), "track_set_interpolation_type", new_track, anim->track_get_interpolation_type(i)); + undo_redo->add_do_method(anim.ptr(), "track_set_interpolation_loop_wrap", new_track, anim->track_get_interpolation_loop_wrap(i)); + insert_count++; + // Iterate keys. + int tr_len = anim->track_get_key_count(i); + switch (anim->track_get_type(i)) { + case Animation::TYPE_POSITION_3D: { + switch (anim->position_track_get_retarget_mode(i)) { + case Animation::RETARGET_MODE_GLOBAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, skeleton->global_retarget_position_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_LOCAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, skeleton->local_retarget_position_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_ABSOLUTE: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "position_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + default: { + } break; + } + } break; + case Animation::TYPE_ROTATION_3D: { + switch (anim->rotation_track_get_retarget_mode(i)) { + case Animation::RETARGET_MODE_GLOBAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, skeleton->global_retarget_rotation_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_LOCAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, skeleton->local_retarget_rotation_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_ABSOLUTE: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "rotation_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + default: { + } break; + } + } break; + case Animation::TYPE_SCALE_3D: { + switch (anim->scale_track_get_retarget_mode(i)) { + case Animation::RETARGET_MODE_GLOBAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, skeleton->global_retarget_scale_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_LOCAL: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, skeleton->local_retarget_scale_to_local_pose(bone_id, anim->track_get_key_value(i, j))); + } + } break; + case Animation::RETARGET_MODE_ABSOLUTE: { + for (int j = 0; j < tr_len; j++) { + double time = anim->track_get_key_time(i, j); + undo_redo->add_do_method(anim.ptr(), "scale_track_insert_key", new_track, time, anim->track_get_key_value(i, j)); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } + } + // Queue delete source track. + remove_queue.push_back(i); + undo_redo->add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), i); + undo_redo->add_undo_method(anim.ptr(), "track_set_path", i, anim->track_get_path(i)); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + Variant v = anim->track_get_key_value(i, j); + float time = anim->track_get_key_time(i, j); + float trans = anim->track_get_key_transition(i, j); + undo_redo->add_undo_method(anim.ptr(), "track_insert_key", i, time, v); + undo_redo->add_undo_method(anim.ptr(), "track_set_key_transition", i, j, trans); + } + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_type", i, anim->track_get_interpolation_type(i)); + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_loop_wrap", i, anim->track_get_interpolation_loop_wrap(i)); + if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D) { + undo_redo->add_undo_method(anim.ptr(), "position_track_set_retarget_mode", i, anim->position_track_get_retarget_mode(i)); + } + if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) { + undo_redo->add_undo_method(anim.ptr(), "rotation_track_set_retarget_mode", i, anim->rotation_track_get_retarget_mode(i)); + } + if (anim->track_get_type(i) == Animation::TYPE_SCALE_3D) { + undo_redo->add_undo_method(anim.ptr(), "scale_track_set_retarget_mode", i, anim->scale_track_get_retarget_mode(i)); + } + } + } + for (int i = len + insert_count - 1; i >= len; i--) { + undo_redo->add_undo_method(anim.ptr(), "remove_track", i); + } + remove_queue.reverse(); + len = remove_queue.size(); + for (int i = 0; i < len; i++) { + undo_redo->add_do_method(anim.ptr(), "remove_track", remove_queue[i]); + } + undo_redo->commit_action(); + _update_player(); +} + +void AnimationPlayerEditor::_delete_retarget_tracks() { + player->stop(); + undo_redo->create_action(TTR("Delete Retarget Tracks")); + // Process. + Ref anim = track_editor->get_current_animation(); + int len = anim->get_track_count(); + Vector remove_queue; + for (int i = 0; i < len; i++) { + if (anim->track_get_path(i).get_name_count() == 0 && anim->track_get_path(i).get_subname_count() == 1) { + // Queue delete source track. + remove_queue.push_back(i); + undo_redo->add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), i); + undo_redo->add_undo_method(anim.ptr(), "track_set_path", i, anim->track_get_path(i)); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + Variant v = anim->track_get_key_value(i, j); + float time = anim->track_get_key_time(i, j); + float trans = anim->track_get_key_transition(i, j); + undo_redo->add_undo_method(anim.ptr(), "track_insert_key", i, time, v); + undo_redo->add_undo_method(anim.ptr(), "track_set_key_transition", i, j, trans); + } + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_type", i, anim->track_get_interpolation_type(i)); + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_loop_wrap", i, anim->track_get_interpolation_loop_wrap(i)); + if (anim->track_get_type(i) == Animation::TYPE_POSITION_3D) { + undo_redo->add_undo_method(anim.ptr(), "position_track_set_retarget_mode", i, anim->position_track_get_retarget_mode(i)); + } + if (anim->track_get_type(i) == Animation::TYPE_ROTATION_3D) { + undo_redo->add_undo_method(anim.ptr(), "rotation_track_set_retarget_mode", i, anim->rotation_track_get_retarget_mode(i)); + } + if (anim->track_get_type(i) == Animation::TYPE_SCALE_3D) { + undo_redo->add_undo_method(anim.ptr(), "scale_track_set_retarget_mode", i, anim->scale_track_get_retarget_mode(i)); + } + } + } + remove_queue.reverse(); + len = remove_queue.size(); + for (int i = 0; i < len; i++) { + undo_redo->add_do_method(anim.ptr(), "remove_track", remove_queue[i]); + } + undo_redo->commit_action(); + _update_player(); +} + +void AnimationPlayerEditor::_delete_not_retarget_tracks() { + player->stop(); + undo_redo->create_action(TTR("Delete Not Retarget Tracks")); + // Process. + Ref anim = track_editor->get_current_animation(); + int len = anim->get_track_count(); + Vector remove_queue; + for (int i = 0; i < len; i++) { + if (anim->track_get_path(i).get_name_count() != 0 || anim->track_get_path(i).get_subname_count() != 1) { + // Queue delete source track. + remove_queue.push_back(i); + undo_redo->add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), i); + undo_redo->add_undo_method(anim.ptr(), "track_set_path", i, anim->track_get_path(i)); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + Variant v = anim->track_get_key_value(i, j); + float time = anim->track_get_key_time(i, j); + float trans = anim->track_get_key_transition(i, j); + undo_redo->add_undo_method(anim.ptr(), "track_insert_key", i, time, v); + undo_redo->add_undo_method(anim.ptr(), "track_set_key_transition", i, j, trans); + } + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_type", i, anim->track_get_interpolation_type(i)); + undo_redo->add_undo_method(anim.ptr(), "track_set_interpolation_loop_wrap", i, anim->track_get_interpolation_loop_wrap(i)); + if (anim->track_get_type(i) == Animation::TYPE_VALUE) { + undo_redo->add_undo_method(anim.ptr(), "value_track_set_update_mode", i, anim->value_track_get_update_mode(i)); + } + if (anim->track_get_type(i) == Animation::TYPE_AUDIO) { + undo_redo->add_undo_method(anim.ptr(), "audio_track_set_auto_volume", i, anim->audio_track_get_auto_volume(i)); + } + } + } + remove_queue.reverse(); + len = remove_queue.size(); + for (int i = 0; i < len; i++) { + undo_redo->add_do_method(anim.ptr(), "remove_track", remove_queue[i]); + } + undo_redo->commit_action(); + _update_player(); +} +#endif // _3D_DISABLED + void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new); ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename); @@ -1642,6 +2082,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE); tool_anim->get_popup()->add_separator(); +#ifndef _3D_DISABLED + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/make_retarget_tracks", TTR("Make Retarget Tracks")), TOOL_MAKE_REATRGET_TRACKS); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/restore_retarget_tracks", TTR("Restore Retarget Tracks")), TOOL_RESTORE_REATRGET_TRACKS); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/delete_retarget_tracks", TTR("Delete Retarget Tracks")), TOOL_DELETE_REATRGET_TRACKS); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/delete_not_retarget_tracks", TTR("Delete Not Retarget Tracks")), TOOL_DELETE_NOT_REATRGET_TRACKS); + tool_anim->get_popup()->add_separator(); +#endif // _3D_DISABLED tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM); hb->add_child(tool_anim); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 5bb32e25e6e3..ebef1bd366b8 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -61,7 +61,13 @@ class AnimationPlayerEditor : public VBoxContainer { TOOL_COPY_ANIM, TOOL_PASTE_ANIM, TOOL_PASTE_ANIM_REF, - TOOL_EDIT_RESOURCE + TOOL_EDIT_RESOURCE, +#ifndef _3D_DISABLED + TOOL_MAKE_REATRGET_TRACKS, + TOOL_RESTORE_REATRGET_TRACKS, + TOOL_DELETE_REATRGET_TRACKS, + TOOL_DELETE_NOT_REATRGET_TRACKS, +#endif // _3D_DISABLED }; enum { @@ -220,6 +226,14 @@ class AnimationPlayerEditor : public VBoxContainer { void _pin_pressed(); +#ifndef _3D_DISABLED + void _make_retarget_tracks(); + void _restore_retarget_tracks(); + void _delete_retarget_tracks(); + void _delete_not_retarget_tracks(); + void _delete_confrict_tracks(); +#endif // _3D_DISABLED + ~AnimationPlayerEditor(); protected: diff --git a/editor/plugins/skeleton_retarget_editor_plugin.cpp b/editor/plugins/skeleton_retarget_editor_plugin.cpp new file mode 100644 index 000000000000..709e152a5926 --- /dev/null +++ b/editor/plugins/skeleton_retarget_editor_plugin.cpp @@ -0,0 +1,1596 @@ +/*************************************************************************/ +/* skeleton_retarget_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "skeleton_retarget_editor_plugin.h" + +#include "editor/editor_scale.h" +#include "scene/animation/animation_player.h" + +// Base classes + +void RetargetEditorForm::create_editors() { +} + +void RetargetEditorForm::submit() { +} + +void RetargetEditorForm::create_button_submit() { + button_submit = memnew(Button); + button_submit->set_text(TTR("Append Item")); + button_submit->connect("pressed", callable_mp(this, &RetargetEditorForm::submit)); + add_child(button_submit); +} + +void RetargetEditorForm::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + create_button_submit(); + } break; + } +} + +RetargetEditorForm::RetargetEditorForm() { +} + +RetargetEditorForm::~RetargetEditorForm() { +} + +VBoxContainer *RetargetEditorItem::get_vbox() { + return vbox; +} + +void RetargetEditorItem::create_editors() { + HBoxContainer *hb = memnew(HBoxContainer); + add_child(hb); + + Label *label = memnew(Label); + label->set_text(itos(index)); + hb->add_child(label); + vbox = memnew(VBoxContainer); + vbox->set_h_size_flags(SIZE_EXPAND_FILL); + hb->add_child(vbox); + button_remove = memnew(Button); + button_remove->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + button_remove->connect("pressed", callable_mp(this, &RetargetEditorItem::fire_remove)); + hb->add_child(button_remove); + + HSeparator *separator = memnew(HSeparator); + add_child(separator); +} + +void RetargetEditorItem::fire_remove() { + emit_signal("remove", index); +} + +void RetargetEditorItem::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::INT, "index"))); +} + +void RetargetEditorItem::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + } +} + +RetargetEditorItem::RetargetEditorItem(const int p_index) { + index = p_index; +} + +RetargetEditorItem::~RetargetEditorItem() { +} + +// Mapper base + +void MapperButton::fetch_textures() { + if (selected) { + set_normal_texture(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons"))); + } else { + set_normal_texture(get_theme_icon(SNAME("BoneMapperHandle"), SNAME("EditorIcons"))); + } + set_offset(SIDE_LEFT, 0); + set_offset(SIDE_RIGHT, 0); + set_offset(SIDE_TOP, 0); + set_offset(SIDE_BOTTOM, 0); + + circle = memnew(TextureRect); + circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons"))); + add_child(circle); + + set_state(state); +} + +void MapperButton::set_state(MapperState p_state) { + state = p_state; + switch (state) { + case MAPPER_STATE_UNSET: { + circle->set_modulate(EditorSettings::get_singleton()->get("editors/retarget_mapper/button_colors/unset")); + } break; + case MAPPER_STATE_SET: { + circle->set_modulate(EditorSettings::get_singleton()->get("editors/retarget_mapper/button_colors/set")); + } break; + case MAPPER_STATE_ERROR: { + circle->set_modulate(EditorSettings::get_singleton()->get("editors/retarget_mapper/button_colors/error")); + } break; + default: { + } break; + } +} + +StringName MapperButton::get_name() { + return name; +} + +void MapperButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + fetch_textures(); + } break; + } +} + +MapperButton::MapperButton(const StringName &p_name, bool p_selected, MapperState p_state) { + name = p_name; + selected = p_selected; + state = p_state; +} + +MapperButton::~MapperButton() { +} + +void RetargetEditorMapperItem::create_editors() { +} + +void RetargetEditorMapperItem::create_buttons() { + HBoxContainer *label_box = memnew(HBoxContainer); + label_box->add_theme_color_override("background_color", Color(0.5, 0.5, 0.5)); + add_child(label_box); + Label *label = memnew(Label); + label->set_text("[" + String(key_name) + "]"); + label->set_h_size_flags(SIZE_EXPAND_FILL); + label_box->add_child(label); + + button_enable = memnew(Button); + button_enable->set_text(TTR("Register")); + button_enable->connect("pressed", callable_mp(this, &RetargetEditorMapperItem::fire_enable)); + + button_remove = memnew(Button); + button_remove->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + button_remove->connect("pressed", callable_mp(this, &RetargetEditorMapperItem::fire_remove)); + + inputs_vbox = memnew(VBoxContainer); + + if (enabled) { + button_enable->set_visible(false); + } else { + button_remove->set_visible(false); + inputs_vbox->set_visible(false); + } + + label_box->add_child(button_remove); + label_box->add_child(button_enable); + add_child(inputs_vbox); + + HSeparator *separator = memnew(HSeparator); + add_child(separator); +} + +void RetargetEditorMapperItem::fire_remove() { + emit_signal("remove", key_name); +} + +void RetargetEditorMapperItem::fire_enable() { + emit_signal("enable", key_name); +} + +void RetargetEditorMapperItem::assign_button_id(int p_button_id) { + button_id = p_button_id; +} + +void RetargetEditorMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { +} + +void RetargetEditorMapperItem::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_buttons(); + create_editors(); + } break; + } +} + +void RetargetEditorMapperItem::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::STRING_NAME, "intermediate_bone_name"))); + ADD_SIGNAL(MethodInfo("enable", PropertyInfo(Variant::STRING_NAME, "intermediate_bone_name"))); + ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "intermediate_bone_name"))); +} + +RetargetEditorMapperItem::RetargetEditorMapperItem(const StringName &p_name, const bool p_enabled) { + key_name = p_name; + enabled = p_enabled; +} + +RetargetEditorMapperItem::~RetargetEditorMapperItem() { +} + +void RetargetEditorMapper::create_rich_editor() { + if (!use_rich_profile) { + return; + } + + profile_group_selector = memnew(EditorPropertyEnum); + profile_group_selector->set_label("Group"); + profile_group_selector->set_selectable(false); + profile_group_selector->set_object_and_property(this, "current_group"); + profile_group_selector->update_property(); + profile_group_selector->connect("property_changed", callable_mp(this, &RetargetEditorMapper::_value_changed)); + add_child(profile_group_selector); + + rich_profile_field = memnew(AspectRatioContainer); + rich_profile_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT); + rich_profile_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE); + rich_profile_field->set_h_size_flags(Control::SIZE_FILL); + add_child(rich_profile_field); + + profile_bg = memnew(ColorRect); + profile_bg->set_color(Color(0, 0, 0, 1)); + profile_bg->set_h_size_flags(Control::SIZE_FILL); + profile_bg->set_v_size_flags(Control::SIZE_FILL); + rich_profile_field->add_child(profile_bg); + + profile_texture = memnew(TextureRect); + profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + profile_texture->set_ignore_texture_size(true); + profile_texture->set_h_size_flags(Control::SIZE_FILL); + profile_texture->set_v_size_flags(Control::SIZE_FILL); + rich_profile_field->add_child(profile_texture); +} + +void RetargetEditorMapper::update_group_ids() { + if (!use_rich_profile) { + return; + } + + Ref rich_profile = static_cast>(profile); + PackedStringArray group_names; + int len = rich_profile->get_groups_size(); + for (int i = 0; i < len; i++) { + group_names.push_back(rich_profile->get_group_name(i)); + } + if (current_group >= len) { + current_group = 0; + } + if (len > 0) { + profile_group_selector->setup(group_names); + profile_group_selector->update_property(); + profile_group_selector->set_read_only(false); + } else { + group_names.push_back("--"); + profile_group_selector->setup(group_names); + profile_group_selector->set_read_only(true); + } +} + +void RetargetEditorMapper::set_current_group(int p_group) { + current_group = p_group; + recreate_rich_editor(); +} + +int RetargetEditorMapper::get_current_group() const { + return current_group; +} + +void RetargetEditorMapper::set_current_intermediate_bone(int p_bone) { + current_intermediate_bone = p_bone; + recreate_rich_editor(); +} + +int RetargetEditorMapper::get_current_intermediate_bone() const { + return current_intermediate_bone; +} + +void RetargetEditorMapper::recreate_rich_editor() { + if (!use_rich_profile) { + return; + } + + // Clear buttons. + int len = mapper_buttons.size(); + for (int i = 0; i < len; i++) { + profile_texture->remove_child(mapper_buttons[i]); + memdelete(mapper_buttons[i]); + } + mapper_buttons.clear(); + + // Organize mapper items. + len = mapper_items.size(); + for (int i = 0; i < len; i++) { + mapper_items[i]->set_visible(current_intermediate_bone == i); + } + + Ref rich_profile = static_cast>(profile); + if (rich_profile->get_groups_size() > 0) { + profile_texture->set_texture(rich_profile->get_group_texture(current_group)); + } else { + profile_texture->set_texture(Ref()); + } + + int j = 0; + for (int i = 0; i < len; i++) { + if (rich_profile->get_intermediate_bone_group_id(i) == current_group) { + MapperButton *mb = memnew(MapperButton(rich_profile->get_intermediate_bone_name(i), current_intermediate_bone == i, get_mapper_state(rich_profile->get_intermediate_bone_name(i)))); + mb->connect("pressed", callable_mp(this, &RetargetEditorMapper::set_current_intermediate_bone), varray(i), CONNECT_DEFERRED); + mb->set_h_grow_direction(GROW_DIRECTION_BOTH); + mb->set_v_grow_direction(GROW_DIRECTION_BOTH); + Vector2 vc = rich_profile->get_intermediate_bone_handle_offset(i); + mapper_buttons.push_back(mb); + profile_texture->add_child(mb); + mb->set_anchor(SIDE_LEFT, vc.x); + mb->set_anchor(SIDE_RIGHT, vc.x); + mb->set_anchor(SIDE_TOP, vc.y); + mb->set_anchor(SIDE_BOTTOM, vc.y); + mapper_items[i]->assign_button_id(j); + j++; + } + } +} + +MapperButton::MapperState RetargetEditorMapper::get_mapper_state(const StringName &p_bone_name) { + return MapperButton::MAPPER_STATE_UNSET; +} + +void RetargetEditorMapper::set_mapper_state(int p_bone, MapperButton::MapperState p_state) { + ERR_FAIL_INDEX(p_bone, mapper_buttons.size()); + mapper_buttons[p_bone]->set_state(p_state); +} + +void RetargetEditorMapper::create_editors() { + create_rich_editor(); + + map_vbox = memnew(VBoxContainer); + add_child(map_vbox); + + const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + section_unprofiled = memnew(EditorInspectorSection); + section_unprofiled->setup("unprofiled_bones", "Unprofiled Bones", this, section_color, true); + add_child(section_unprofiled); + + unprofiled_vbox = memnew(VBoxContainer); + section_unprofiled->get_vbox()->add_child(unprofiled_vbox); + + recreate_items(); +} + +void RetargetEditorMapper::set_profile(const Ref &p_profile) { + profile = p_profile; + Ref rrp = static_cast>(profile); + use_rich_profile = rrp.is_valid(); +} + +void RetargetEditorMapper::clear_items() { +} + +void RetargetEditorMapper::recreate_items() { +} + +void RetargetEditorMapper::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_current_group", "current_group"), &RetargetEditorMapper::set_current_group); + ClassDB::bind_method(D_METHOD("get_current_group"), &RetargetEditorMapper::get_current_group); + ClassDB::bind_method(D_METHOD("set_current_intermediate_bone", "current_intermediate_bone"), &RetargetEditorMapper::set_current_intermediate_bone); + ClassDB::bind_method(D_METHOD("get_current_intermediate_bone"), &RetargetEditorMapper::get_current_intermediate_bone); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group"), "set_current_group", "get_current_group"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_intermediate_bone"), "set_current_intermediate_bone", "get_current_intermediate_bone"); +} + +void RetargetEditorMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + set(p_property, p_value); + recreate_rich_editor(); +} + +void RetargetEditorMapper::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + } +} + +RetargetEditorMapper::RetargetEditorMapper() { +} + +RetargetEditorMapper::~RetargetEditorMapper() { +} + +// Bone picker + +void Skeleton3DBonePicker::create_editors() { + set_title(TTR("Bone Picker:")); + + VBoxContainer *vbox = memnew(VBoxContainer); + add_child(vbox); + + bones = memnew(Tree); + bones->set_select_mode(Tree::SELECT_SINGLE); + bones->set_v_size_flags(Control::SIZE_EXPAND_FILL); + bones->set_hide_root(true); + vbox->add_child(bones); +} + +void Skeleton3DBonePicker::create_bones_tree(Skeleton3D *p_skeleton) { + bones->clear(); + + if (!p_skeleton) { + return; + } + + TreeItem *root = bones->create_item(); + + Map items; + + items.insert(-1, root); + + Ref bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")); + + Vector bones_to_process = p_skeleton->get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx); + TreeItem *parent_item = items.find(parent_idx)->get(); + + TreeItem *joint_item = bones->create_item(parent_item); + items.insert(current_bone_idx, joint_item); + + joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); + + // Add the bone's children to the list of bones to be processed. + Vector current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx); + int child_bone_size = current_bone_child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(current_bone_child_bones[i]); + } + } +} + +void Skeleton3DBonePicker::popup_bones_tree(Skeleton3D *p_skeleton, const Size2i &p_minsize) { + create_bones_tree(p_skeleton); + popup_centered(p_minsize); +} + +bool Skeleton3DBonePicker::has_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return false; + } + return true; +} + +StringName Skeleton3DBonePicker::get_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return StringName(); + } + return selected->get_text(0); +} + +void Skeleton3DBonePicker::_bind_methods() { +} + +void Skeleton3DBonePicker::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + } +} + +Skeleton3DBonePicker::Skeleton3DBonePicker() { +} + +Skeleton3DBonePicker::~Skeleton3DBonePicker() { +} + +// Retarget profile + +void RetargetProfileEditorForm::create_editors() { + key_name = memnew(EditorPropertyText); + key_name->set_label("Name"); + key_name->set_selectable(false); + key_name->set_object_and_property(this, "key_name"); + key_name->update_property(); + key_name->connect("property_changed", callable_mp(this, &RetargetProfileEditorForm::_value_changed)); + add_child(key_name); +} + +void RetargetProfileEditorForm::submit() { + emit_signal("submit", prop_key_name); + set("key_name", StringName()); // Initialize. + key_name->update_property(); +} + +void RetargetProfileEditorForm::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_key_name", "key_name"), &RetargetProfileEditorForm::set_key_name); + ClassDB::bind_method(D_METHOD("get_key_name"), &RetargetProfileEditorForm::get_key_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "key_name"), "set_key_name", "get_key_name"); + ADD_SIGNAL(MethodInfo("submit", PropertyInfo(Variant::STRING_NAME, "key_name"))); +} + +void RetargetProfileEditorForm::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + set(p_property, p_value); +} + +void RetargetProfileEditorForm::set_key_name(const StringName &p_key_name) { + prop_key_name = p_key_name; +} + +StringName RetargetProfileEditorForm::get_key_name() const { + return prop_key_name; +} + +RetargetProfileEditorForm::RetargetProfileEditorForm() { +} + +RetargetProfileEditorForm::~RetargetProfileEditorForm() { +} + +void RetargetProfileEditor::create_editors() { + imb_vbox = memnew(VBoxContainer); + add_child(imb_vbox); + + imb_form = memnew(RetargetProfileEditorForm); + imb_form->connect("submit", callable_mp(this, &RetargetProfileEditor::_add_intermediate_bone)); + add_child(imb_form); + + recreate_items(); +} + +void RetargetProfileEditor::recreate_items() { + clear_items(); + + // Create items. + int len = retarget_profile->get_intermediate_bones_size(); + for (int i = 0; i < len; i++) { + intermediate_bones.append(memnew(RetargetEditorItem(i))); + intermediate_bones[i]->connect("remove", callable_mp(this, &RetargetProfileEditor::_remove_intermediate_bone), varray(), CONNECT_DEFERRED); + imb_vbox->add_child(intermediate_bones[i]); + + String prep = "intermediate_bones/" + itos(i) + "/"; + intermediate_bone_names.append(memnew(EditorPropertyText())); + intermediate_bone_names[i]->set_label("Bone Name"); + intermediate_bone_names[i]->set_selectable(false); + intermediate_bone_names[i]->set_object_and_property(retarget_profile, prep + "bone_name"); + intermediate_bone_names[i]->update_property(); + intermediate_bone_names[i]->connect("property_changed", callable_mp(this, &RetargetProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + intermediate_bones[i]->get_vbox()->add_child(intermediate_bone_names[i]); + } +} + +void RetargetProfileEditor::clear_items() { + // Clear items. + int len = intermediate_bones.size(); + for (int i = 0; i < len; i++) { + intermediate_bone_names[i]->disconnect("property_changed", callable_mp(this, &RetargetProfileEditor::_value_changed)); + intermediate_bones[i]->get_vbox()->remove_child(intermediate_bone_names[i]); + memdelete(intermediate_bone_names[i]); + imb_vbox->remove_child(intermediate_bones[i]); + memdelete(intermediate_bones[i]); + } + intermediate_bone_names.clear(); + intermediate_bones.clear(); +} + +void RetargetProfileEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Retarget Profile Property"), UndoRedo::MERGE_ENDS); + ur->add_undo_property(retarget_profile, p_property, retarget_profile->get(p_property)); + ur->add_do_property(retarget_profile, p_property, p_value); + ur->commit_action(); +} + +void RetargetProfileEditor::_update_intermediate_bone_property() { + int len = intermediate_bone_names.size(); + for (int i = 0; i < len; i++) { + if (intermediate_bone_names[i]->get_edited_object() && intermediate_bone_names[i]->get_edited_property()) { + intermediate_bone_names[i]->update_property(); + } + } +} + +void RetargetProfileEditor::_add_intermediate_bone(const StringName &p_bone_name) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Intermediate Bone")); + ur->add_undo_method(retarget_profile, "remove_intermediate_bone", retarget_profile->get_intermediate_bones_size()); + ur->add_do_method(retarget_profile, "add_intermediate_bone", p_bone_name); + ur->commit_action(); +} + +void RetargetProfileEditor::_remove_intermediate_bone(const int p_id) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Intermediate Bone")); + ur->add_undo_method(retarget_profile, "add_intermediate_bone", retarget_profile->get_intermediate_bone_name(p_id), p_id); + ur->add_do_method(retarget_profile, "remove_intermediate_bone", p_id); + ur->commit_action(); +} + +void RetargetProfileEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + case NOTIFICATION_POST_ENTER_TREE: { + retarget_profile->connect("intermediate_bone_updated", callable_mp(this, &RetargetProfileEditor::_update_intermediate_bone_property)); + retarget_profile->connect("redraw_needed", callable_mp(this, &RetargetProfileEditor::recreate_items)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_profile) { + if (retarget_profile->is_connected("intermediate_bone_updated", callable_mp(this, &RetargetProfileEditor::_update_intermediate_bone_property))) { + retarget_profile->disconnect("intermediate_bone_updated", callable_mp(this, &RetargetProfileEditor::_update_intermediate_bone_property)); + } + if (retarget_profile->is_connected("redraw_needed", callable_mp(this, &RetargetProfileEditor::recreate_items))) { + retarget_profile->disconnect("redraw_needed", callable_mp(this, &RetargetProfileEditor::recreate_items)); + } + } + } break; + } +} + +RetargetProfileEditor::RetargetProfileEditor(RetargetProfile *p_retarget_profile) { + retarget_profile = p_retarget_profile; +} + +RetargetProfileEditor::~RetargetProfileEditor() { +} + +void RetargetRichProfileEditor::create_editors() { + const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + + // Group settings. + section_grp = memnew(EditorInspectorSection); + section_grp->setup("groups", "Groups", this, section_color, true); + add_child(section_grp); + + grp_vbox = memnew(VBoxContainer); + section_grp->get_vbox()->add_child(grp_vbox); + + grp_form = memnew(RetargetProfileEditorForm); + grp_form->connect("submit", callable_mp(this, &RetargetRichProfileEditor::_add_group)); + section_grp->get_vbox()->add_child(grp_form); + + // Intermediate bones. + section_imb = memnew(EditorInspectorSection); + section_imb->setup("intermediate_bones", "Intermediate Bones", this, section_color, true); + add_child(section_imb); + + imb_vbox = memnew(VBoxContainer); + section_imb->get_vbox()->add_child(imb_vbox); + + imb_form = memnew(RetargetProfileEditorForm); + imb_form->connect("submit", callable_mp(this, &RetargetRichProfileEditor::_add_intermediate_bone)); + section_imb->get_vbox()->add_child(imb_form); + + recreate_items(); +} + +void RetargetRichProfileEditor::recreate_items() { + clear_items(); + + // Create items. + // Group settings. + PackedStringArray group_names_array = PackedStringArray(); + PackedStringArray dummy_group_names_array = PackedStringArray(); + int grp_len = retarget_profile->get_groups_size(); + for (int i = 0; i < grp_len; i++) { + groups.append(memnew(RetargetEditorItem(i))); + groups[i]->connect("remove", callable_mp(this, &RetargetRichProfileEditor::_remove_group), varray(), CONNECT_DEFERRED); + grp_vbox->add_child(groups[i]); + + String prep = "groups/" + itos(i) + "/"; + group_names.append(memnew(EditorPropertyText())); + group_names[i]->set_label("Group Name"); + group_names[i]->set_selectable(false); + group_names[i]->set_object_and_property(retarget_profile, prep + "group_name"); + group_names[i]->update_property(); + group_names[i]->connect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + groups[i]->get_vbox()->add_child(group_names[i]); + group_textures.append(memnew(EditorPropertyResource())); + group_textures[i]->setup(retarget_profile, prep + "group_texture", "Texture2D"); + group_textures[i]->set_label("Group Texture"); + group_textures[i]->set_selectable(false); + group_textures[i]->set_object_and_property(retarget_profile, prep + "group_texture"); + group_textures[i]->update_property(); + group_textures[i]->connect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + groups[i]->get_vbox()->add_child(group_textures[i]); + + group_names_array.push_back(retarget_profile->get_group_name(i)); + } + + bool has_group = grp_len > 0; + if (!has_group) { + dummy_group_names_array.push_back("--"); + } + + // Intermediate bones. + int imb_len = retarget_profile->get_intermediate_bones_size(); + for (int i = 0; i < imb_len; i++) { + intermediate_bones.append(memnew(RetargetEditorItem(i))); + intermediate_bones[i]->connect("remove", callable_mp(this, &RetargetRichProfileEditor::_remove_intermediate_bone), varray(), CONNECT_DEFERRED); + imb_vbox->add_child(intermediate_bones[i]); + + String prep = "intermediate_bones/" + itos(i) + "/"; + intermediate_bone_names.append(memnew(EditorPropertyText())); + intermediate_bone_names[i]->set_label("Bone Name"); + intermediate_bone_names[i]->set_selectable(false); + intermediate_bone_names[i]->set_object_and_property(retarget_profile, prep + "bone_name"); + intermediate_bone_names[i]->update_property(); + intermediate_bone_names[i]->connect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + intermediate_bones[i]->get_vbox()->add_child(intermediate_bone_names[i]); + intermediate_bone_handle_offsets.append(memnew(EditorPropertyVector2())); + intermediate_bone_handle_offsets[i]->setup(0.0, 1.0, 0.001, false); + intermediate_bone_handle_offsets[i]->set_label("Handle Offset"); + intermediate_bone_handle_offsets[i]->set_selectable(false); + intermediate_bone_handle_offsets[i]->set_object_and_property(retarget_profile, prep + "handle_offset"); + intermediate_bone_handle_offsets[i]->update_property(); + intermediate_bone_handle_offsets[i]->connect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + intermediate_bones[i]->get_vbox()->add_child(intermediate_bone_handle_offsets[i]); + intermediate_bone_group_ids.append(memnew(EditorPropertyEnum())); + intermediate_bone_group_ids[i]->set_label("Group"); + intermediate_bone_group_ids[i]->set_selectable(false); + if (has_group) { + intermediate_bone_group_ids[i]->setup(group_names_array); + intermediate_bone_group_ids[i]->set_object_and_property(retarget_profile, prep + "group_id"); + intermediate_bone_group_ids[i]->update_property(); + } else { + intermediate_bone_group_ids[i]->setup(dummy_group_names_array); + intermediate_bone_group_ids[i]->set_read_only(true); + } + intermediate_bone_group_ids[i]->connect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed), varray(), CONNECT_DEFERRED); + intermediate_bones[i]->get_vbox()->add_child(intermediate_bone_group_ids[i]); + } +} + +void RetargetRichProfileEditor::clear_items() { + // Clear items. + // Group settings. + int len = groups.size(); + for (int i = 0; i < len; i++) { + group_names[i]->disconnect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed)); + groups[i]->get_vbox()->remove_child(group_names[i]); + memdelete(group_names[i]); + group_textures[i]->disconnect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed)); + groups[i]->get_vbox()->remove_child(group_textures[i]); + memdelete(group_textures[i]); + grp_vbox->remove_child(groups[i]); + memdelete(groups[i]); + } + group_names.clear(); + group_textures.clear(); + groups.clear(); + // Intermediate bones. + len = intermediate_bones.size(); + for (int i = 0; i < len; i++) { + intermediate_bone_names[i]->disconnect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed)); + intermediate_bones[i]->get_vbox()->remove_child(intermediate_bone_names[i]); + memdelete(intermediate_bone_names[i]); + intermediate_bone_handle_offsets[i]->disconnect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed)); + intermediate_bones[i]->get_vbox()->remove_child(intermediate_bone_handle_offsets[i]); + memdelete(intermediate_bone_handle_offsets[i]); + intermediate_bone_group_ids[i]->disconnect("property_changed", callable_mp(this, &RetargetRichProfileEditor::_value_changed)); + intermediate_bones[i]->get_vbox()->remove_child(intermediate_bone_group_ids[i]); + memdelete(intermediate_bone_group_ids[i]); + imb_vbox->remove_child(intermediate_bones[i]); + memdelete(intermediate_bones[i]); + } + intermediate_bone_names.clear(); + intermediate_bone_handle_offsets.clear(); + intermediate_bone_group_ids.clear(); + intermediate_bones.clear(); +} + +void RetargetRichProfileEditor::_update_group_ids() { + ERR_FAIL_COND(!retarget_profile); + PackedStringArray group_names_array = PackedStringArray(); + int len = retarget_profile->get_groups_size(); + for (int i = 0; i < len; i++) { + group_names_array.push_back(retarget_profile->get_group_name(i)); + } + if (len > 0) { + len = intermediate_bone_group_ids.size(); + for (int i = 0; i < len; i++) { + intermediate_bone_group_ids[i]->setup(group_names_array); + intermediate_bone_group_ids[i]->set_read_only(false); + } + } else { + group_names_array.push_back("--"); + len = intermediate_bone_group_ids.size(); + for (int i = 0; i < len; i++) { + intermediate_bone_group_ids[i]->setup(group_names_array); + intermediate_bone_group_ids[i]->set_read_only(true); + } + } +} + +void RetargetRichProfileEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Retarget Profile Property"), UndoRedo::MERGE_ENDS); + ur->add_undo_property(retarget_profile, p_property, retarget_profile->get(p_property)); + ur->add_do_property(retarget_profile, p_property, p_value); + ur->commit_action(); +} + +void RetargetRichProfileEditor::_update_group_property() { + int len = group_names.size(); + for (int i = 0; i < len; i++) { + if (group_names[i]->get_edited_object() && group_names[i]->get_edited_property()) { + group_names[i]->update_property(); + } + if (group_textures[i]->get_edited_object() && group_textures[i]->get_edited_property()) { + group_textures[i]->update_property(); + } + } + _update_group_ids(); +} + +void RetargetRichProfileEditor::_update_intermediate_bone_property() { + int len = intermediate_bone_names.size(); + for (int i = 0; i < len; i++) { + if (intermediate_bone_names[i]->get_edited_object() && intermediate_bone_names[i]->get_edited_property()) { + intermediate_bone_names[i]->update_property(); + } + if (intermediate_bone_handle_offsets[i]->get_edited_object() && intermediate_bone_handle_offsets[i]->get_edited_property()) { + intermediate_bone_handle_offsets[i]->update_property(); + } + if (intermediate_bone_group_ids[i]->get_edited_object() && intermediate_bone_group_ids[i]->get_edited_property()) { + intermediate_bone_group_ids[i]->update_property(); + } + } +} + +void RetargetRichProfileEditor::_add_intermediate_bone(const StringName &p_bone_name) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Intermediate Bone")); + ur->add_undo_method(retarget_profile, "remove_intermediate_bone", retarget_profile->get_intermediate_bones_size()); + ur->add_do_method(retarget_profile, "add_intermediate_bone", p_bone_name); + ur->commit_action(); +} + +void RetargetRichProfileEditor::_remove_intermediate_bone(const int p_id) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Intermediate Bone")); + ur->add_undo_method(retarget_profile, "add_intermediate_bone", retarget_profile->get_intermediate_bone_name(p_id), p_id); + ur->add_undo_method(retarget_profile, "set_intermediate_bone_handle_offset", p_id, retarget_profile->get_intermediate_bone_handle_offset(p_id)); + ur->add_undo_method(retarget_profile, "set_intermediate_bone_group_id", p_id, retarget_profile->get_intermediate_bone_group_id(p_id)); + ur->add_do_method(retarget_profile, "remove_intermediate_bone", p_id); + ur->commit_action(); +} + +void RetargetRichProfileEditor::_add_group(const StringName &p_group_name) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Group")); + ur->add_undo_method(retarget_profile, "remove_group", retarget_profile->get_groups_size()); + ur->add_do_method(retarget_profile, "add_group", p_group_name); + ur->commit_action(); +} + +void RetargetRichProfileEditor::_remove_group(const int p_id) { + ERR_FAIL_COND(!retarget_profile); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Group")); + ur->add_undo_method(retarget_profile, "add_group", retarget_profile->get_group_name(p_id), p_id); + ur->add_undo_method(retarget_profile, "set_group_texture", p_id, retarget_profile->get_group_texture(p_id)); + int len = retarget_profile->get_intermediate_bones_size(); + if (len > 0) { + len--; + for (int i = 0; i < len; i++) { + // Don't emit signal again. + ur->add_undo_method(retarget_profile, "set_intermediate_bone_group_id", i, retarget_profile->get_intermediate_bone_group_id(i), false); + } + // Emit signal. + ur->add_undo_method(retarget_profile, "set_intermediate_bone_group_id", len, retarget_profile->get_intermediate_bone_group_id(len)); + } + ur->add_do_method(retarget_profile, "remove_group", p_id); + ur->commit_action(); +} + +void RetargetRichProfileEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + case NOTIFICATION_POST_ENTER_TREE: { + retarget_profile->connect("group_updated", callable_mp(this, &RetargetRichProfileEditor::_update_group_property)); + retarget_profile->connect("intermediate_bone_updated", callable_mp(this, &RetargetRichProfileEditor::_update_intermediate_bone_property)); + retarget_profile->connect("redraw_needed", callable_mp(this, &RetargetRichProfileEditor::recreate_items)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_profile) { + if (retarget_profile->is_connected("group_updated", callable_mp(this, &RetargetRichProfileEditor::_update_group_property))) { + retarget_profile->disconnect("group_updated", callable_mp(this, &RetargetRichProfileEditor::_update_group_property)); + } + if (retarget_profile->is_connected("intermediate_bone_updated", callable_mp(this, &RetargetRichProfileEditor::_update_intermediate_bone_property))) { + retarget_profile->disconnect("intermediate_bone_updated", callable_mp(this, &RetargetRichProfileEditor::_update_intermediate_bone_property)); + } + if (retarget_profile->is_connected("redraw_needed", callable_mp(this, &RetargetRichProfileEditor::recreate_items))) { + retarget_profile->disconnect("redraw_needed", callable_mp(this, &RetargetRichProfileEditor::recreate_items)); + } + } + } break; + } +} + +RetargetRichProfileEditor::RetargetRichProfileEditor(RetargetRichProfile *p_retarget_profile) { + retarget_profile = p_retarget_profile; +} + +RetargetRichProfileEditor::~RetargetRichProfileEditor() { +} + +bool EditorInspectorPluginRetargetProfile::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; +} + +void EditorInspectorPluginRetargetProfile::parse_begin(Object *p_object) { + RetargetRichProfile *rrp = Object::cast_to(p_object); + if (rrp) { + rp_editor = memnew(RetargetRichProfileEditor(rrp)); + add_custom_control(rp_editor); + return; + } + + RetargetProfile *rp = Object::cast_to(p_object); + if (rp) { + rp_editor = memnew(RetargetProfileEditor(rp)); + add_custom_control(rp_editor); + return; + } +} + +RetargetProfileEditorPlugin::RetargetProfileEditorPlugin() { + // Register properties in editor settings. + EDITOR_DEF("editors/retarget_mapper/button_colors/set", Color(0.1, 0.6, 0.25)); + EDITOR_DEF("editors/retarget_mapper/button_colors/error", Color(0.8, 0.2, 0.2)); + EDITOR_DEF("editors/retarget_mapper/button_colors/unset", Color(0.3, 0.3, 0.3)); + + Ref plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} + +// Retarget source setting + +void RetargetBoneOptionMapperItem::create_editors() { + retarget_mode = memnew(EditorPropertyEnum); + retarget_mode->setup(retarget_mode_arr); + retarget_mode->set_label("Retarget Mode"); + retarget_mode->set_selectable(false); + retarget_mode->connect("property_changed", callable_mp(this, &RetargetBoneOptionMapperItem::_value_changed)); + inputs_vbox->add_child(retarget_mode); + + if (enabled) { + String prep = String(key_name) + "/"; + retarget_mode->set_object_and_property(retarget_option, prep + "retarget_mode"); + retarget_mode->update_property(); + } +} + +void RetargetBoneOptionMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + ERR_FAIL_COND(!retarget_option); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Retarget Option Property"), UndoRedo::MERGE_ENDS); + ur->add_undo_property(retarget_option, p_property, retarget_option->get(p_property)); + ur->add_do_property(retarget_option, p_property, p_value); + ur->commit_action(); +} + +void RetargetBoneOptionMapperItem::_update_property() { + if (retarget_mode->get_edited_object() && retarget_mode->get_edited_property()) { + retarget_mode->update_property(); + } +} + +void RetargetBoneOptionMapperItem::_bind_methods() { +} + +void RetargetBoneOptionMapperItem::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + retarget_option->connect("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapperItem::_update_property)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_option && retarget_option->is_connected("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapperItem::_update_property))) { + retarget_option->disconnect("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapperItem::_update_property)); + } + } break; + } +} + +RetargetBoneOptionMapperItem::RetargetBoneOptionMapperItem(RetargetBoneOption *p_retarget_option, const StringName &p_name, const bool p_enabled) { + retarget_option = p_retarget_option; + key_name = p_name; + enabled = p_enabled; + retarget_mode_arr.push_back("Global"); + retarget_mode_arr.push_back("Local"); + retarget_mode_arr.push_back("Absolute"); +} + +RetargetBoneOptionMapperItem::~RetargetBoneOptionMapperItem() { +} + +void RetargetBoneOptionMapper::clear_items() { + // Clear items. + int len = mapper_items.size(); + for (int i = 0; i < len; i++) { + mapper_items[i]->disconnect("remove", callable_mp(this, &RetargetBoneOptionMapper::_remove_item)); + mapper_items[i]->disconnect("enable", callable_mp(this, &RetargetBoneOptionMapper::_add_item)); + map_vbox->remove_child(mapper_items[i]); + memdelete(mapper_items[i]); + } + mapper_items.clear(); + + len = unprofiled_items.size(); + for (int i = 0; i < len; i++) { + unprofiled_items[i]->disconnect("remove", callable_mp(this, &RetargetBoneOptionMapper::_remove_item)); + unprofiled_items[i]->disconnect("enable", callable_mp(this, &RetargetBoneOptionMapper::_add_item)); + unprofiled_vbox->remove_child(unprofiled_items[i]); + memdelete(unprofiled_items[i]); + } + unprofiled_items.clear(); +} + +void RetargetBoneOptionMapper::recreate_items() { + clear_items(); + // Create items by profile. + Vector found_keys; + if (profile.is_valid()) { + int len = profile->get_intermediate_bones_size(); + for (int i = 0; i < len; i++) { + StringName bn = profile->get_intermediate_bone_name(i); + bool is_found = retarget_option->has_key(bn); + if (is_found) { + found_keys.push_back(bn); + } + mapper_items.append(memnew(RetargetBoneOptionMapperItem(retarget_option, bn, is_found))); + mapper_items[i]->connect("remove", callable_mp(this, &RetargetBoneOptionMapper::_remove_item), varray(), CONNECT_DEFERRED); + mapper_items[i]->connect("enable", callable_mp(this, &RetargetBoneOptionMapper::_add_item), varray(), CONNECT_DEFERRED); + map_vbox->add_child(mapper_items[i]); + } + } + + // Create items by setting. + Vector keys = retarget_option->get_keys(); + int len = keys.size(); + int j = 0; + for (int i = 0; i < len; i++) { + StringName bn = keys[i]; + if (!found_keys.has(bn)) { + unprofiled_items.append(memnew(RetargetBoneOptionMapperItem(retarget_option, bn, true))); + unprofiled_items[j]->connect("remove", callable_mp(this, &RetargetBoneOptionMapper::_remove_item), varray(), CONNECT_DEFERRED); + unprofiled_items[j]->connect("enable", callable_mp(this, &RetargetBoneOptionMapper::_add_item), varray(), CONNECT_DEFERRED); + unprofiled_vbox->add_child(unprofiled_items[j]); + j++; + } + } + + update_group_ids(); + recreate_rich_editor(); +} + +void RetargetBoneOptionMapper::_update_mapper_state() { + int len = mapper_buttons.size(); + for (int i = 0; i < len; i++) { + set_mapper_state(i, get_mapper_state(mapper_buttons[i]->get_name())); + } +} + +void RetargetBoneOptionMapper::_add_item(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND(!retarget_option); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Retarget Option Item")); + ur->add_undo_method(retarget_option, "remove_key", p_intermediate_bone_name); + ur->add_do_method(retarget_option, "add_key", p_intermediate_bone_name); + ur->commit_action(); + recreate_items(); +} + +void RetargetBoneOptionMapper::_remove_item(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND(!retarget_option); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Retarget Map Item")); + ur->add_undo_method(retarget_option, "add_key", p_intermediate_bone_name); + ur->add_undo_method(retarget_option, "set_retarget_mode", p_intermediate_bone_name, retarget_option->get_retarget_mode(p_intermediate_bone_name)); + ur->add_do_method(retarget_option, "remove_key", p_intermediate_bone_name); + ur->commit_action(); + recreate_items(); +} + +MapperButton::MapperState RetargetBoneOptionMapper::get_mapper_state(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND_V(!retarget_option, MapperButton::MAPPER_STATE_UNSET); + if (retarget_option->has_key(p_intermediate_bone_name)) { + return MapperButton::MAPPER_STATE_SET; + } + return MapperButton::MAPPER_STATE_UNSET; +} + +RetargetBoneOptionMapper::RetargetBoneOptionMapper(RetargetBoneOption *p_retarget_option) { + retarget_option = p_retarget_option; +} + +void RetargetBoneOptionMapper::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + retarget_option->connect("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapper::_update_mapper_state)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_option && retarget_option->is_connected("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapper::_update_mapper_state))) { + retarget_option->disconnect("retarget_option_updated", callable_mp(this, &RetargetBoneOptionMapper::_update_mapper_state)); + } + } break; + } +} + +RetargetBoneOptionMapper::~RetargetBoneOptionMapper() { +} + +void RetargetBoneOptionEditor::create_editors() { + mapper = memnew(RetargetBoneOptionMapper(retarget_option)); + mapper->set_profile(profile); + add_child(mapper); +} + +void RetargetBoneOptionEditor::clear_editors() { + remove_child(mapper); + memdelete(mapper); +} + +void RetargetBoneOptionEditor::set_profile(const Ref &p_profile) { + profile = p_profile; + clear_editors(); + create_editors(); +} + +Ref RetargetBoneOptionEditor::get_profile() const { + return profile; +} + +void RetargetBoneOptionEditor::fetch_objects() { + EditorSelection *es = EditorInterface::get_singleton()->get_selection(); + if (es->get_selected_nodes().size() == 1) { + Node *nd = Object::cast_to(es->get_selected_nodes()[0]); + if (!nd) { + return; + } + // SkeletonRetarget + SkeletonRetarget *sr = Object::cast_to(nd); + if (sr) { + profile = sr->get_retarget_profile(); + } + // AnimationPlayer + AnimationPlayer *ap = Object::cast_to(nd); + if (ap) { + profile = ap->get_retarget_profile(); + } + } else { + // Editor should not exist. + profile = Ref(); + } +} + +void RetargetBoneOptionEditor::redraw() { + mapper->recreate_items(); +} + +void RetargetBoneOptionEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + fetch_objects(); + create_editors(); + } break; + case NOTIFICATION_POST_ENTER_TREE: { + retarget_option->connect("redraw_needed", callable_mp(this, &RetargetBoneOptionEditor::redraw)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_option && retarget_option->is_connected("redraw_needed", callable_mp(this, &RetargetBoneOptionEditor::redraw))) { + retarget_option->disconnect("redraw_needed", callable_mp(this, &RetargetBoneOptionEditor::redraw)); + } + } break; + } +} + +RetargetBoneOptionEditor::RetargetBoneOptionEditor(RetargetBoneOption *p_retarget_option) { + retarget_option = p_retarget_option; +} + +RetargetBoneOptionEditor::~RetargetBoneOptionEditor() { +} + +bool EditorInspectorPluginRetargetBoneOption::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; +} + +void EditorInspectorPluginRetargetBoneOption::parse_begin(Object *p_object) { + RetargetBoneOption *rs = Object::cast_to(p_object); + rs_editor = memnew(RetargetBoneOptionEditor(rs)); + add_custom_control(rs_editor); +} + +RetargetBoneOptionEditorPlugin::RetargetBoneOptionEditorPlugin() { + Ref plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} + +// Retarget target setting + +void RetargetBoneMapMapperItem::create_editors() { + HBoxContainer *bone_name_box = memnew(HBoxContainer); + bone_name = memnew(EditorPropertyText); + bone_name->set_label("Retarget Bone"); + bone_name->set_h_size_flags(SIZE_EXPAND_FILL); + bone_name->set_selectable(false); + bone_name->connect("property_changed", callable_mp(this, &RetargetBoneMapMapperItem::_value_changed)); + bone_name_box->add_child(bone_name); + + button_pick = memnew(Button); + button_pick->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + button_pick->connect("pressed", callable_mp(this, &RetargetBoneMapMapperItem::fire_pick)); + bone_name_box->add_child(button_pick); + + inputs_vbox->add_child(bone_name_box); + + if (enabled) { + String prep = String(key_name) + "/"; + bone_name->set_object_and_property(retarget_map, prep); + bone_name->update_property(); + } +} + +void RetargetBoneMapMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + ERR_FAIL_COND(!retarget_map); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Retarget Map Property"), UndoRedo::MERGE_ENDS); + ur->add_undo_property(retarget_map, p_property, retarget_map->get(p_property)); + ur->add_do_property(retarget_map, p_property, p_value); + ur->commit_action(); +} + +void RetargetBoneMapMapperItem::_update_property() { + if (bone_name->get_edited_object() && bone_name->get_edited_property()) { + bone_name->update_property(); + } +} + +void RetargetBoneMapMapperItem::fire_pick() { + emit_signal("pick", key_name); +} + +void RetargetBoneMapMapperItem::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + retarget_map->connect("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapperItem::_update_property)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_map && retarget_map->is_connected("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapperItem::_update_property))) { + retarget_map->disconnect("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapperItem::_update_property)); + } + } break; + } +} + +void RetargetBoneMapMapperItem::_bind_methods() { +} + +RetargetBoneMapMapperItem::RetargetBoneMapMapperItem(RetargetBoneMap *p_retarget_map, const StringName &p_name, const bool p_enabled) { + retarget_map = p_retarget_map; + key_name = p_name; + enabled = p_enabled; +} + +RetargetBoneMapMapperItem::~RetargetBoneMapMapperItem() { +} + +void RetargetBoneMapMapper::apply_picker_selection() { + if (!picker->has_selected_bone()) { + return; + } + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Name")); + ur->add_undo_method(retarget_map, "set_bone_name", picker_key_name, retarget_map->get_bone_name(picker_key_name)); + ur->add_do_method(retarget_map, "set_bone_name", picker_key_name, picker->get_selected_bone()); + ur->commit_action(); + recreate_items(); +} + +void RetargetBoneMapMapper::clear_items() { + // Clear items. + int len = mapper_items.size(); + for (int i = 0; i < len; i++) { + mapper_items[i]->disconnect("remove", callable_mp(this, &RetargetBoneMapMapper::_remove_item)); + mapper_items[i]->disconnect("enable", callable_mp(this, &RetargetBoneMapMapper::_add_item)); + mapper_items[i]->disconnect("pick", callable_mp(this, &RetargetBoneMapMapper::_pick_bone)); + map_vbox->remove_child(mapper_items[i]); + memdelete(mapper_items[i]); + } + mapper_items.clear(); + + len = unprofiled_items.size(); + for (int i = 0; i < len; i++) { + unprofiled_items[i]->disconnect("remove", callable_mp(this, &RetargetBoneMapMapper::_remove_item)); + unprofiled_items[i]->disconnect("enable", callable_mp(this, &RetargetBoneMapMapper::_add_item)); + unprofiled_items[i]->disconnect("pick", callable_mp(this, &RetargetBoneMapMapper::_pick_bone)); + unprofiled_vbox->remove_child(unprofiled_items[i]); + memdelete(unprofiled_items[i]); + } + unprofiled_items.clear(); +} + +void RetargetBoneMapMapper::recreate_items() { + clear_items(); + // Create items by profile. + Vector found_keys; + if (profile.is_valid()) { + int len = profile->get_intermediate_bones_size(); + for (int i = 0; i < len; i++) { + StringName bn = profile->get_intermediate_bone_name(i); + bool is_found = retarget_map->has_key(bn); + if (is_found) { + found_keys.push_back(bn); + } + mapper_items.append(memnew(RetargetBoneMapMapperItem(retarget_map, bn, is_found))); + mapper_items[i]->connect("remove", callable_mp(this, &RetargetBoneMapMapper::_remove_item), varray(), CONNECT_DEFERRED); + mapper_items[i]->connect("enable", callable_mp(this, &RetargetBoneMapMapper::_add_item), varray(), CONNECT_DEFERRED); + mapper_items[i]->connect("pick", callable_mp(this, &RetargetBoneMapMapper::_pick_bone), varray(), CONNECT_DEFERRED); + map_vbox->add_child(mapper_items[i]); + } + } + + // Create items by setting. + Vector keys = retarget_map->get_keys(); + int len = keys.size(); + int j = 0; + for (int i = 0; i < len; i++) { + StringName bn = keys[i]; + if (!found_keys.has(bn)) { + unprofiled_items.append(memnew(RetargetBoneMapMapperItem(retarget_map, bn, true))); + unprofiled_items[j]->connect("remove", callable_mp(this, &RetargetBoneMapMapper::_remove_item), varray(), CONNECT_DEFERRED); + unprofiled_items[j]->connect("enable", callable_mp(this, &RetargetBoneMapMapper::_add_item), varray(), CONNECT_DEFERRED); + unprofiled_items[j]->connect("pick", callable_mp(this, &RetargetBoneMapMapper::_pick_bone), varray(), CONNECT_DEFERRED); + unprofiled_vbox->add_child(unprofiled_items[j]); + j++; + } + } + + update_group_ids(); + recreate_rich_editor(); +} + +void RetargetBoneMapMapper::_update_mapper_state() { + int len = mapper_buttons.size(); + for (int i = 0; i < len; i++) { + set_mapper_state(i, get_mapper_state(mapper_buttons[i]->get_name())); + } +} + +void RetargetBoneMapMapper::_pick_bone(const StringName &p_intermediate_bone_name) { + picker_key_name = p_intermediate_bone_name; + // Get bone names. + ERR_FAIL_COND_MSG(!skeleton, "Skeleton is not found."); + picker->popup_bones_tree(skeleton, Size2(500, 500) * EDSCALE); +} + +void RetargetBoneMapMapper::_add_item(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND(!retarget_map); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Retarget Map Item")); + ur->add_undo_method(retarget_map, "remove_key", p_intermediate_bone_name); + ur->add_do_method(retarget_map, "add_key", p_intermediate_bone_name); + ur->commit_action(); + recreate_items(); +} + +void RetargetBoneMapMapper::_remove_item(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND(!retarget_map); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Retarget Map Item")); + ur->add_undo_method(retarget_map, "add_key", p_intermediate_bone_name); + ur->add_undo_method(retarget_map, "set_bone_name", p_intermediate_bone_name, retarget_map->get_bone_name(p_intermediate_bone_name)); + ur->add_do_method(retarget_map, "remove_key", p_intermediate_bone_name); + ur->commit_action(); + recreate_items(); +} + +MapperButton::MapperState RetargetBoneMapMapper::get_mapper_state(const StringName &p_intermediate_bone_name) { + ERR_FAIL_COND_V(!retarget_map, MapperButton::MAPPER_STATE_UNSET); + if (retarget_map->has_key(p_intermediate_bone_name)) { + if (!skeleton) { + return MapperButton::MAPPER_STATE_ERROR; + } + if (skeleton->find_bone(retarget_map->get_bone_name(p_intermediate_bone_name)) >= 0) { + return MapperButton::MAPPER_STATE_SET; + } + return MapperButton::MAPPER_STATE_ERROR; + } + return MapperButton::MAPPER_STATE_UNSET; +} + +void RetargetBoneMapMapper::set_skeleton(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; +} + +void RetargetBoneMapMapper::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + picker = memnew(Skeleton3DBonePicker); + picker->connect("confirmed", callable_mp(this, &RetargetBoneMapMapper::apply_picker_selection)); + add_child(picker, false, INTERNAL_MODE_FRONT); + } break; + case NOTIFICATION_POST_ENTER_TREE: { + retarget_map->connect("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapper::_update_mapper_state)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (picker && picker->is_connected("confirmed", callable_mp(this, &RetargetBoneMapMapper::apply_picker_selection))) { + picker->disconnect("confirmed", callable_mp(this, &RetargetBoneMapMapper::apply_picker_selection)); + } + if (retarget_map && retarget_map->is_connected("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapper::_update_mapper_state))) { + retarget_map->disconnect("retarget_map_updated", callable_mp(this, &RetargetBoneMapMapper::_update_mapper_state)); + } + } break; + } +} + +RetargetBoneMapMapper::RetargetBoneMapMapper(RetargetBoneMap *p_retarget_map) { + retarget_map = p_retarget_map; +} + +RetargetBoneMapMapper::~RetargetBoneMapMapper() { +} + +void RetargetBoneMapEditor::create_editors() { + mapper = memnew(RetargetBoneMapMapper(retarget_map)); + mapper->set_skeleton(skeleton); + mapper->set_profile(profile); + add_child(mapper); +} + +void RetargetBoneMapEditor::clear_editors() { + remove_child(mapper); + memdelete(mapper); +} + +void RetargetBoneMapEditor::set_skeleton(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; + clear_editors(); + create_editors(); +} + +Skeleton3D *RetargetBoneMapEditor::get_skeleton() { + return skeleton; +} + +void RetargetBoneMapEditor::set_profile(const Ref &p_profile) { + profile = p_profile; + clear_editors(); + create_editors(); +} + +Ref RetargetBoneMapEditor::get_profile() const { + return profile; +} + +void RetargetBoneMapEditor::fetch_objects() { + EditorSelection *es = EditorInterface::get_singleton()->get_selection(); + if (es->get_selected_nodes().size() == 1) { + Node *nd = Object::cast_to(es->get_selected_nodes()[0]); + if (!nd) { + return; + } + // SkeletonRetarget + SkeletonRetarget *sr = Object::cast_to(nd); + if (sr) { + Object *obj = nullptr; + // Note: + // If maps are same, maps only refer to source skeleton. + // That's why the SkeletonRetarget doesn't allow to set same maps in set_target_map() and set_source_map(). + if (retarget_map == sr->get_source_map().ptr()) { + obj = sr->get_node_or_null(sr->get_source_skeleton()); + } else { + obj = sr->get_node_or_null(sr->get_target_skeleton()); + } + skeleton = Object::cast_to(obj); + profile = sr->get_retarget_profile(); + } + // AnimationPlayer + AnimationPlayer *ap = Object::cast_to(nd); + if (ap) { + Object *obj = ap->get_node_or_null(ap->get_retarget_skeleton()); + skeleton = Object::cast_to(obj); + profile = ap->get_retarget_profile(); + } + } else { + // Editor should not exist. + skeleton = nullptr; + profile = Ref(); + } +} + +void RetargetBoneMapEditor::redraw() { + mapper->recreate_items(); +} + +void RetargetBoneMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + fetch_objects(); + create_editors(); + } break; + case NOTIFICATION_POST_ENTER_TREE: { + retarget_map->connect("redraw_needed", callable_mp(this, &RetargetBoneMapEditor::redraw)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (retarget_map && retarget_map->is_connected("redraw_needed", callable_mp(this, &RetargetBoneMapEditor::redraw))) { + retarget_map->disconnect("redraw_needed", callable_mp(this, &RetargetBoneMapEditor::redraw)); + } + } break; + } +} + +RetargetBoneMapEditor::RetargetBoneMapEditor(RetargetBoneMap *p_retarget_map) { + retarget_map = p_retarget_map; +} + +RetargetBoneMapEditor::~RetargetBoneMapEditor() { +} + +bool EditorInspectorPluginRetargetBoneMap::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; +} + +void EditorInspectorPluginRetargetBoneMap::parse_begin(Object *p_object) { + RetargetBoneMap *rt = Object::cast_to(p_object); + rt_editor = memnew(RetargetBoneMapEditor(rt)); + add_custom_control(rt_editor); +} + +RetargetBoneMapEditorPlugin::RetargetBoneMapEditorPlugin() { + Ref plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/skeleton_retarget_editor_plugin.h b/editor/plugins/skeleton_retarget_editor_plugin.h new file mode 100644 index 000000000000..2fdea919062d --- /dev/null +++ b/editor/plugins/skeleton_retarget_editor_plugin.h @@ -0,0 +1,525 @@ +/*************************************************************************/ +/* skeleton_retarget_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef SKELETON_RETARGET_EDITOR_PLUGIN_H +#define SKELETON_RETARGET_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "scene/animation/skeleton_retarget.h" +#include "scene/gui/color_rect.h" +#include "scene/gui/dialogs.h" +#include "scene/resources/texture.h" + +// Bone picker + +class Skeleton3DBonePicker : public AcceptDialog { + GDCLASS(Skeleton3DBonePicker, AcceptDialog); + + Tree *bones; + +public: + void popup_bones_tree(Skeleton3D *p_skeleton, const Size2i &p_minsize = Size2i()); + bool has_selected_bone(); + StringName get_selected_bone(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +private: + void create_editors(); + void create_bones_tree(Skeleton3D *p_skeleton); + +public: + Skeleton3DBonePicker(); + ~Skeleton3DBonePicker(); +}; + +// Base classes + +class RetargetEditorForm : public VBoxContainer { + GDCLASS(RetargetEditorForm, VBoxContainer); + + Button *button_submit; + +protected: + void _notification(int p_what); + virtual void create_editors(); + virtual void submit(); + +private: + void create_button_submit(); + +public: + RetargetEditorForm(); + ~RetargetEditorForm(); +}; + +class RetargetEditorItem : public VBoxContainer { + GDCLASS(RetargetEditorItem, VBoxContainer); + + int index = 0; + VBoxContainer *vbox; + Button *button_remove; + +public: + VBoxContainer *get_vbox(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +private: + void create_editors(); + void fire_remove(); + +public: + RetargetEditorItem(const int p_index); + ~RetargetEditorItem(); +}; + +// Mapper base + +class MapperButton : public TextureButton { + GDCLASS(MapperButton, TextureButton); + + StringName name; + +public: + enum MapperState { + MAPPER_STATE_UNSET, + MAPPER_STATE_SET, + MAPPER_STATE_ERROR + }; + + void set_state(MapperState p_state); + StringName get_name(); + +protected: + void _notification(int p_what); + +private: + bool selected = false; + MapperState state = MAPPER_STATE_UNSET; + + TextureRect *circle; + void fetch_textures(); + +public: + MapperButton(const StringName &p_name, bool p_selected, MapperState p_state); + ~MapperButton(); +}; + +class RetargetEditorMapperItem : public VBoxContainer { + GDCLASS(RetargetEditorMapperItem, VBoxContainer); + + Button *button_enable; + Button *button_remove; + +protected: + int button_id = -1; + StringName key_name; + bool enabled; + + VBoxContainer *inputs_vbox; + + void _notification(int p_what); + static void _bind_methods(); + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + virtual void create_editors(); + +private: + void create_buttons(); + void fire_remove(); + void fire_enable(); + +public: + void assign_button_id(int p_button_id); + + RetargetEditorMapperItem(const StringName &p_name = StringName(), const bool p_enabled = false); + ~RetargetEditorMapperItem(); +}; + +class RetargetEditorMapper : public VBoxContainer { + GDCLASS(RetargetEditorMapper, VBoxContainer); + +public: + void set_profile(const Ref &p_profile); + virtual void clear_items(); + virtual void recreate_items(); + +protected: + Ref profile; + + EditorInspectorSection *section_unprofiled; + + Vector mapper_items; + Vector unprofiled_items; + + VBoxContainer *map_vbox; + VBoxContainer *unprofiled_vbox; + + void _notification(int p_what); + static void _bind_methods(); + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + + // For rich profile. + bool use_rich_profile = false; + int current_group = 0; + int current_intermediate_bone = 0; + + AspectRatioContainer *rich_profile_field; + EditorPropertyEnum *profile_group_selector; + ColorRect *profile_bg; + TextureRect *profile_texture; + Vector mapper_buttons; + + void set_current_group(int p_group); + int get_current_group() const; + void set_current_intermediate_bone(int p_bone); + int get_current_intermediate_bone() const; + + void update_group_ids(); + void recreate_rich_editor(); + + virtual MapperButton::MapperState get_mapper_state(const StringName &p_bone_name); + void set_mapper_state(int p_bone, MapperButton::MapperState p_state); + +private: + void create_editors(); + void create_rich_editor(); + +public: + RetargetEditorMapper(); + ~RetargetEditorMapper(); +}; + +// Retarget profile + +class RetargetProfileEditorForm : public RetargetEditorForm { + GDCLASS(RetargetProfileEditorForm, VBoxContainer); + EditorPropertyText *key_name; + StringName prop_key_name = StringName(); + +protected: + static void _bind_methods(); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + virtual void create_editors() override; + virtual void submit() override; + +private: + void set_key_name(const StringName &p_key_name); + StringName get_key_name() const; + +public: + RetargetProfileEditorForm(); + ~RetargetProfileEditorForm(); +}; + +class RetargetProfileEditor : public VBoxContainer { + GDCLASS(RetargetProfileEditor, VBoxContainer); + + RetargetProfile *retarget_profile; + + Vector intermediate_bones; + Vector intermediate_bone_names; + + VBoxContainer *imb_vbox; + RetargetProfileEditorForm *imb_form; + +protected: + void _notification(int p_what); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + void _update_intermediate_bone_property(); + void _add_intermediate_bone(const StringName &p_bone_name); + void _remove_intermediate_bone(const int p_id); + + void create_editors(); + void recreate_items(); + void clear_items(); + +public: + RetargetProfileEditor(RetargetProfile *p_retarget_profile); + ~RetargetProfileEditor(); +}; + +class RetargetRichProfileEditor : public VBoxContainer { + GDCLASS(RetargetRichProfileEditor, VBoxContainer); + + RetargetRichProfile *retarget_profile; + + EditorInspectorSection *section_grp; + EditorInspectorSection *section_imb; + + Vector groups; + Vector group_names; + Vector group_textures; + + VBoxContainer *grp_vbox; + RetargetProfileEditorForm *grp_form; + + Vector intermediate_bones; + Vector intermediate_bone_names; + Vector intermediate_bone_handle_offsets; + Vector intermediate_bone_group_ids; + + VBoxContainer *imb_vbox; + RetargetProfileEditorForm *imb_form; + +protected: + void _notification(int p_what); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + void _update_group_ids(); + void _update_group_property(); + void _update_intermediate_bone_property(); + void _add_intermediate_bone(const StringName &p_bone_name); + void _remove_intermediate_bone(const int p_id); + void _add_group(const StringName &p_group_name); + void _remove_group(const int p_id); + + void create_editors(); + void recreate_items(); + void clear_items(); + + void redraw(); + +public: + RetargetRichProfileEditor(RetargetRichProfile *p_retarget_profile); + ~RetargetRichProfileEditor(); +}; + +class EditorInspectorPluginRetargetProfile : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginRetargetProfile, EditorInspectorPlugin); + Control *rp_editor; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class RetargetProfileEditorPlugin : public EditorPlugin { + GDCLASS(RetargetProfileEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "RetargetProfile"; } + RetargetProfileEditorPlugin(); +}; + +// Retarget source setting + +class RetargetBoneOptionMapperItem : public RetargetEditorMapperItem { + GDCLASS(RetargetBoneOptionMapperItem, RetargetEditorMapperItem); + RetargetBoneOption *retarget_option; + + PackedStringArray retarget_mode_arr; + EditorPropertyEnum *retarget_mode; + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void create_editors() override; + void _update_property(); + +private: + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) override; + +public: + RetargetBoneOptionMapperItem(RetargetBoneOption *p_retarget_option, const StringName &p_name, const bool p_enabled); + ~RetargetBoneOptionMapperItem(); +}; + +class RetargetBoneOptionMapper : public RetargetEditorMapper { + GDCLASS(RetargetBoneOptionMapper, RetargetEditorMapper); + RetargetBoneOption *retarget_option; + +public: + virtual void clear_items() override; + virtual void recreate_items() override; + +protected: + void _notification(int p_what); + void _add_item(const StringName &p_intermediate_bone_name); + void _remove_item(const StringName &p_intermediate_bone_name); + void _update_mapper_state(); + + virtual MapperButton::MapperState get_mapper_state(const StringName &p_intermediate_bone_name) override; + +public: + RetargetBoneOptionMapper(RetargetBoneOption *p_retarget_option); + ~RetargetBoneOptionMapper(); +}; + +class RetargetBoneOptionEditor : public VBoxContainer { + GDCLASS(RetargetBoneOptionEditor, VBoxContainer); + + Ref profile; + RetargetBoneOption *retarget_option; + + RetargetBoneOptionMapper *mapper; + +public: + void set_profile(const Ref &p_profile); + Ref get_profile() const; + void fetch_objects(); + void redraw(); + +protected: + void _notification(int p_what); + +private: + void clear_editors(); + void create_editors(); + +public: + RetargetBoneOptionEditor(RetargetBoneOption *p_retarget_option); + ~RetargetBoneOptionEditor(); +}; + +class EditorInspectorPluginRetargetBoneOption : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginRetargetBoneOption, EditorInspectorPlugin); + RetargetBoneOptionEditor *rs_editor; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class RetargetBoneOptionEditorPlugin : public EditorPlugin { + GDCLASS(RetargetBoneOptionEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "RetargetBoneOption"; } + RetargetBoneOptionEditorPlugin(); +}; + +// Retarget target setting + +class RetargetBoneMapMapperItem : public RetargetEditorMapperItem { + GDCLASS(RetargetBoneMapMapperItem, RetargetEditorMapperItem); + RetargetBoneMap *retarget_map; + + EditorPropertyText *bone_name; + Button *button_pick; + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void create_editors() override; + void _update_property(); + +private: + void fire_pick(); + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) override; + +public: + RetargetBoneMapMapperItem(RetargetBoneMap *p_retarget_map, const StringName &p_name, const bool p_enabled); + ~RetargetBoneMapMapperItem(); +}; + +class RetargetBoneMapMapper : public RetargetEditorMapper { + GDCLASS(RetargetBoneMapMapper, RetargetEditorMapper); + RetargetBoneMap *retarget_map; + +public: + void set_skeleton(Skeleton3D *p_skeleton); + + virtual void clear_items() override; + virtual void recreate_items() override; + +protected: + Skeleton3D *skeleton; + Skeleton3DBonePicker *picker; + StringName picker_key_name; + + void apply_picker_selection(); + void _add_item(const StringName &p_intermediate_bone_name); + void _remove_item(const StringName &p_intermediate_bone_name); + void _pick_bone(const StringName &p_intermediate_bone_name); + void _update_mapper_state(); + + virtual MapperButton::MapperState get_mapper_state(const StringName &p_intermediate_bone_name) override; + + void _notification(int p_what); + +public: + RetargetBoneMapMapper(RetargetBoneMap *p_retarget_map); + ~RetargetBoneMapMapper(); +}; + +class RetargetBoneMapEditor : public VBoxContainer { + GDCLASS(RetargetBoneMapEditor, VBoxContainer); + + Skeleton3D *skeleton; + Ref profile; + + RetargetBoneMap *retarget_map; + + RetargetBoneMapMapper *mapper; + +public: + void set_skeleton(Skeleton3D *p_skeleton); + Skeleton3D *get_skeleton(); + void set_profile(const Ref &p_profile); + Ref get_profile() const; + void fetch_objects(); + void redraw(); + +protected: + void _notification(int p_what); + +private: + void clear_editors(); + void create_editors(); + +public: + RetargetBoneMapEditor(RetargetBoneMap *p_retarget_map); + ~RetargetBoneMapEditor(); +}; + +class EditorInspectorPluginRetargetBoneMap : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginRetargetBoneMap, EditorInspectorPlugin); + RetargetBoneMapEditor *rt_editor; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class RetargetBoneMapEditorPlugin : public EditorPlugin { + GDCLASS(RetargetBoneMapEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "RetargetBoneMap"; } + RetargetBoneMapEditorPlugin(); +}; + +#endif // SKELETON_RETARGET_EDITOR_PLUGIN_H diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 571b87a0aa10..5d62eddb1554 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1592,6 +1592,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, Map *p_ editor_data->get_undo_redo().add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), idx); editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_path", idx, track_np); editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_interpolation_type", idx, anim->track_get_interpolation_type(i)); + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_interpolation_loop_wrap", idx, anim->track_get_interpolation_loop_wrap(i)); for (int j = 0; j < anim->track_get_key_count(i); j++) { editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_insert_key", idx, anim->track_get_key_time(i, j), anim->track_get_key_value(i, j), anim->track_get_key_transition(i, j)); } diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 598897456dfa..72acbfb4039b 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -318,9 +318,8 @@ void Skeleton3D::_notification(int p_what) { rs->skeleton_bone_set_transform(skeleton, i, bonesptr[bone_index].pose_global * skin->get_bind_pose(i)); } } -#ifdef TOOLS_ENABLED + emit_signal(SceneStringNames::get_singleton()->pose_updated); -#endif // TOOLS_ENABLED } break; #ifndef _3D_DISABLED @@ -509,6 +508,7 @@ void Skeleton3D::add_bone(const String &p_name) { b.name = p_name; bones.push_back(b); process_order_dirty = true; + rest_dirty = true; version++; _make_dirty(); update_gizmos(); @@ -568,6 +568,8 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { bones.write[p_bone].parent = p_parent; process_order_dirty = true; + + rest_dirty = true; _make_dirty(); } @@ -586,6 +588,7 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) { bones.write[p_bone].parent = -1; process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -608,6 +611,7 @@ void Skeleton3D::set_bone_children(int p_bone, Vector p_children) { bones.write[p_bone].child_bones = p_children; process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -617,6 +621,7 @@ void Skeleton3D::add_bone_child(int p_bone, int p_child) { bones.write[p_bone].child_bones.push_back(p_child); process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -632,6 +637,7 @@ void Skeleton3D::remove_bone_child(int p_bone, int p_child) { } process_order_dirty = true; + rest_dirty = true; _make_dirty(); } @@ -644,8 +650,10 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].rest = p_rest; + rest_dirty = true; _make_dirty(); } + Transform3D Skeleton3D::get_bone_rest(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); @@ -653,6 +661,15 @@ Transform3D Skeleton3D::get_bone_rest(int p_bone) const { return bones[p_bone].rest; } +Transform3D Skeleton3D::get_bone_global_rest(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + if (rest_dirty) { + const_cast(this)->notification(NOTIFICATION_UPDATE_SKELETON); + } + return bones[p_bone].global_rest; +} + void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); @@ -681,6 +698,7 @@ bool Skeleton3D::is_show_rest_only() const { void Skeleton3D::clear_bones() { bones.clear(); process_order_dirty = true; + rest_dirty = true; version++; _make_dirty(); } @@ -743,6 +761,17 @@ Transform3D Skeleton3D::get_bone_pose(int p_bone) const { return bones[p_bone].pose_cache; } +Transform3D Skeleton3D::get_bone_final_pose(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + ((Skeleton3D *)this)->bones.write[p_bone].update_pose_cache(); + if (bones[p_bone].local_pose_override_amount >= CMP_EPSILON || bones[p_bone].global_pose_override_amount >= CMP_EPSILON) { + return ((Skeleton3D *)this)->global_pose_to_local_pose(p_bone, get_bone_global_pose(p_bone)); + } else { + return bones[p_bone].pose_cache; + } +} + void Skeleton3D::_make_dirty() { if (dirty) { return; @@ -1046,9 +1075,15 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { if (b.parent >= 0) { b.pose_global = bonesptr[b.parent].pose_global * pose; b.pose_global_no_override = b.pose_global; + if (rest_dirty) { + b.global_rest = bonesptr[b.parent].global_rest * b.rest; + } } else { b.pose_global = pose; b.pose_global_no_override = b.pose_global; + if (rest_dirty) { + b.global_rest = b.rest; + } } } else { if (b.parent >= 0) { @@ -1089,6 +1124,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx); } + rest_dirty = false; } // Helper functions @@ -1150,6 +1186,134 @@ Basis Skeleton3D::global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_ return return_basis; } +// Retarget functions + +Transform3D Skeleton3D::extract_global_retarget_transform(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D()); + Transform3D tr = Transform3D(); + Transform3D s_grest = Transform3D(); + int s_parent = get_bone_parent(p_bone_idx); + if (s_parent >= 0) { + s_grest = get_bone_global_rest(s_parent); + } + tr.basis = s_grest.basis * get_bone_final_pose(p_bone_idx).basis * bones[p_bone_idx].rest.basis.inverse() * s_grest.basis.inverse(); + tr.origin = s_grest.basis.xform(get_bone_pose(p_bone_idx).origin - get_bone_rest(p_bone_idx).origin); + return tr; +} + +Vector3 Skeleton3D::extract_global_retarget_position(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3()); + Quaternion s_grest = Quaternion(); + int s_parent = get_bone_parent(p_bone_idx); + if (s_parent >= 0) { + s_grest = get_bone_global_rest(s_parent).basis.get_rotation_quaternion(); + } + return s_grest.xform(get_bone_pose_position(p_bone_idx) - get_bone_rest(p_bone_idx).origin); +} + +Quaternion Skeleton3D::extract_global_retarget_rotation(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Quaternion()); + Quaternion s_grest = Quaternion(); + int s_parent = get_bone_parent(p_bone_idx); + if (s_parent >= 0) { + s_grest = get_bone_global_rest(s_parent).basis.get_rotation_quaternion(); + } + return s_grest * get_bone_pose_rotation(p_bone_idx) * bones[p_bone_idx].rest.basis.get_rotation_quaternion().inverse() * s_grest.inverse(); +} + +Vector3 Skeleton3D::extract_global_retarget_scale(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3(1, 1, 1)); + Basis s_grest = Basis(); + int s_parent = get_bone_parent(p_bone_idx); + if (s_parent >= 0) { + s_grest = get_bone_global_rest(s_parent).basis; + } + return (s_grest * get_bone_pose(p_bone_idx).basis * bones[p_bone_idx].rest.basis.inverse() * s_grest.inverse()).get_scale(); +} + +Transform3D Skeleton3D::global_retarget_transform_to_local_pose(int p_bone_idx, Transform3D p_transform) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D()); + Transform3D tr = Transform3D(); + Transform3D t_grest = Transform3D(); + int t_parent = get_bone_parent(p_bone_idx); + if (t_parent >= 0) { + t_grest = get_bone_global_rest(t_parent); + } + tr.basis = t_grest.basis.inverse() * p_transform.basis * t_grest.basis * bones[p_bone_idx].rest.basis; + tr.origin = t_grest.basis.xform_inv(p_transform.origin) + get_bone_rest(p_bone_idx).origin; + return tr; +} + +Vector3 Skeleton3D::global_retarget_position_to_local_pose(int p_bone_idx, Vector3 p_position) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3()); + Quaternion t_grest = Quaternion(); + int t_parent = get_bone_parent(p_bone_idx); + if (t_parent >= 0) { + t_grest = get_bone_global_rest(t_parent).basis.get_rotation_quaternion(); + } + return t_grest.xform_inv(p_position) + get_bone_rest(p_bone_idx).origin; +} + +Quaternion Skeleton3D::global_retarget_rotation_to_local_pose(int p_bone_idx, Quaternion p_rotation) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Quaternion()); + Quaternion t_grest = Quaternion(); + int t_parent = get_bone_parent(p_bone_idx); + if (t_parent >= 0) { + t_grest = get_bone_global_rest(t_parent).basis.get_rotation_quaternion(); + } + return t_grest.inverse() * p_rotation * t_grest * bones[p_bone_idx].rest.basis.get_rotation_quaternion(); +} + +Vector3 Skeleton3D::global_retarget_scale_to_local_pose(int p_bone_idx, Vector3 p_scale) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3(1, 1, 1)); + Basis t_grest = Basis(); + int t_parent = get_bone_parent(p_bone_idx); + if (t_parent >= 0) { + t_grest = get_bone_global_rest(t_parent).basis; + } + return (t_grest.inverse() * Basis().scaled(p_scale) * t_grest * bones[p_bone_idx].rest.basis).get_scale(); +} + +Transform3D Skeleton3D::extract_local_retarget_transform(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D()); + return bones[p_bone_idx].rest.affine_inverse() * get_bone_pose(p_bone_idx); +} + +Vector3 Skeleton3D::extract_local_retarget_position(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3()); + return get_bone_pose_position(p_bone_idx) - bones[p_bone_idx].rest.origin; +} + +Quaternion Skeleton3D::extract_local_retarget_rotation(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Quaternion()); + return bones[p_bone_idx].rest.basis.get_rotation_quaternion().inverse() * get_bone_pose_rotation(p_bone_idx); +} + +Vector3 Skeleton3D::extract_local_retarget_scale(int p_bone_idx) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3(1, 1, 1)); + return (bones[p_bone_idx].rest.basis.inverse() * get_bone_pose(p_bone_idx).basis).get_scale(); +} + +Transform3D Skeleton3D::local_retarget_transform_to_local_pose(int p_bone_idx, Transform3D p_transform) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Transform3D()); + return bones[p_bone_idx].rest * p_transform; +} + +Vector3 Skeleton3D::local_retarget_position_to_local_pose(int p_bone_idx, Vector3 p_position) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3()); + return bones[p_bone_idx].rest.origin + p_position; +} + +Quaternion Skeleton3D::local_retarget_rotation_to_local_pose(int p_bone_idx, Quaternion p_rotation) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Quaternion()); + return bones[p_bone_idx].rest.basis.get_rotation_quaternion() * p_rotation; +} + +Vector3 Skeleton3D::local_retarget_scale_to_local_pose(int p_bone_idx, Vector3 p_scale) { + ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector3(1, 1, 1)); + return (bones[p_bone_idx].rest.basis * Basis().scaled(p_scale)).get_scale(); +} + // Modifications #ifndef _3D_DISABLED @@ -1216,6 +1380,7 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_bones"), &Skeleton3D::clear_bones); ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton3D::get_bone_pose); + ClassDB::bind_method(D_METHOD("get_bone_final_pose", "bone_idx"), &Skeleton3D::get_bone_final_pose); ClassDB::bind_method(D_METHOD("set_bone_pose_position", "bone_idx", "position"), &Skeleton3D::set_bone_pose_position); ClassDB::bind_method(D_METHOD("set_bone_pose_rotation", "bone_idx", "rotation"), &Skeleton3D::set_bone_pose_rotation); ClassDB::bind_method(D_METHOD("set_bone_pose_scale", "bone_idx", "scale"), &Skeleton3D::set_bone_pose_scale); @@ -1227,6 +1392,7 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_bone_enabled", "bone_idx"), &Skeleton3D::is_bone_enabled); ClassDB::bind_method(D_METHOD("set_bone_enabled", "bone_idx", "enabled"), &Skeleton3D::set_bone_enabled, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_bone_global_rest", "bone_idx"), &Skeleton3D::get_bone_global_rest); ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override); ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override); @@ -1258,6 +1424,25 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); + // Retarget functions + ClassDB::bind_method(D_METHOD("extract_global_retarget_transform", "bone_idx"), &Skeleton3D::extract_global_retarget_transform); + ClassDB::bind_method(D_METHOD("extract_global_retarget_position", "bone_idx"), &Skeleton3D::extract_global_retarget_position); + ClassDB::bind_method(D_METHOD("extract_global_retarget_rotation", "bone_idx"), &Skeleton3D::extract_global_retarget_rotation); + ClassDB::bind_method(D_METHOD("extract_global_retarget_scale", "bone_idx"), &Skeleton3D::extract_global_retarget_scale); + ClassDB::bind_method(D_METHOD("global_retarget_transform_to_local_pose", "bone_idx", "transform"), &Skeleton3D::global_retarget_transform_to_local_pose); + ClassDB::bind_method(D_METHOD("global_retarget_position_to_local_pose", "bone_idx", "position"), &Skeleton3D::global_retarget_position_to_local_pose); + ClassDB::bind_method(D_METHOD("global_retarget_rotation_to_local_pose", "bone_idx", "rotation"), &Skeleton3D::global_retarget_rotation_to_local_pose); + ClassDB::bind_method(D_METHOD("global_retarget_scale_to_local_pose", "bone_idx", "scale"), &Skeleton3D::global_retarget_scale_to_local_pose); + + ClassDB::bind_method(D_METHOD("extract_local_retarget_transform", "bone_idx"), &Skeleton3D::extract_local_retarget_transform); + ClassDB::bind_method(D_METHOD("extract_local_retarget_position", "bone_idx"), &Skeleton3D::extract_local_retarget_position); + ClassDB::bind_method(D_METHOD("extract_local_retarget_rotation", "bone_idx"), &Skeleton3D::extract_local_retarget_rotation); + ClassDB::bind_method(D_METHOD("extract_local_retarget_scale", "bone_idx"), &Skeleton3D::extract_local_retarget_scale); + ClassDB::bind_method(D_METHOD("local_retarget_transform_to_local_pose", "bone_idx", "transform"), &Skeleton3D::local_retarget_transform_to_local_pose); + ClassDB::bind_method(D_METHOD("local_retarget_position_to_local_pose", "bone_idx", "position"), &Skeleton3D::local_retarget_position_to_local_pose); + ClassDB::bind_method(D_METHOD("local_retarget_rotation_to_local_pose", "bone_idx", "rotation"), &Skeleton3D::local_retarget_rotation_to_local_pose); + ClassDB::bind_method(D_METHOD("local_retarget_scale_to_local_pose", "bone_idx", "scale"), &Skeleton3D::local_retarget_scale_to_local_pose); + // Modifications ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton3D::set_modification_stack); ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton3D::get_modification_stack); @@ -1268,10 +1453,7 @@ void Skeleton3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones"); #endif // _3D_DISABLED -#ifdef TOOLS_ENABLED ADD_SIGNAL(MethodInfo("pose_updated")); -#endif // TOOLS_ENABLED - ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx"))); ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx"))); ADD_SIGNAL(MethodInfo("show_rest_only_changed")); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 80ff2a1f79e7..9f87dc09709d 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -77,6 +77,7 @@ class Skeleton3D : public Node3D { int parent; Transform3D rest; + Transform3D global_rest; _FORCE_INLINE_ void update_pose_cache() { if (pose_cache_dirty) { @@ -142,6 +143,7 @@ class Skeleton3D : public Node3D { void _make_dirty(); bool dirty = false; + bool rest_dirty = false; bool show_rest_only = false; @@ -175,7 +177,7 @@ class Skeleton3D : public Node3D { NOTIFICATION_UPDATE_SKELETON = 50 }; - // skeleton creation api + // Skeleton creation API void add_bone(const String &p_name); int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; @@ -200,6 +202,7 @@ class Skeleton3D : public Node3D { Transform3D get_bone_rest(int p_bone) const; Transform3D get_bone_global_pose(int p_bone) const; Transform3D get_bone_global_pose_no_override(int p_bone) const; + Transform3D get_bone_global_rest(int p_bone) const; void set_bone_enabled(int p_bone, bool p_enabled); bool is_bone_enabled(int p_bone) const; @@ -208,13 +211,13 @@ class Skeleton3D : public Node3D { bool is_show_rest_only() const; void clear_bones(); - // posing api - + // Posing api void set_bone_pose_position(int p_bone, const Vector3 &p_position); void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation); void set_bone_pose_scale(int p_bone, const Vector3 &p_scale); Transform3D get_bone_pose(int p_bone) const; + Transform3D get_bone_final_pose(int p_bone) const; Vector3 get_bone_pose_position(int p_bone) const; Quaternion get_bone_pose_rotation(int p_bone) const; @@ -251,6 +254,25 @@ class Skeleton3D : public Node3D { Basis global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis); + // Retarget functions + Transform3D extract_global_retarget_transform(int p_bone_idx); + Vector3 extract_global_retarget_position(int p_bone_idx); + Quaternion extract_global_retarget_rotation(int p_bone_idx); + Vector3 extract_global_retarget_scale(int p_bone_idx); + Transform3D global_retarget_transform_to_local_pose(int p_bone_idx, Transform3D p_transform); + Vector3 global_retarget_position_to_local_pose(int p_bone_idx, Vector3 p_position); + Quaternion global_retarget_rotation_to_local_pose(int p_bone_idx, Quaternion p_rotation); + Vector3 global_retarget_scale_to_local_pose(int p_bone_idx, Vector3 p_scale); + + Transform3D extract_local_retarget_transform(int p_bone_idx); + Vector3 extract_local_retarget_position(int p_bone_idx); + Quaternion extract_local_retarget_rotation(int p_bone_idx); + Vector3 extract_local_retarget_scale(int p_bone_idx); + Transform3D local_retarget_transform_to_local_pose(int p_bone_idx, Transform3D p_transform); + Vector3 local_retarget_position_to_local_pose(int p_bone_idx, Vector3 p_position); + Quaternion local_retarget_rotation_to_local_pose(int p_bone_idx, Quaternion p_rotation); + Vector3 local_retarget_scale_to_local_pose(int p_bone_idx, Vector3 p_scale); + // Modifications #ifndef _3D_DISABLED Ref get_modification_stack(); @@ -270,7 +292,7 @@ class Skeleton3D : public Node3D { PhysicalBone3D *get_physical_bone_parent(int p_bone); private: - /// This is a slow API, so it's cached + /// This is a slow API, so it's cached. PhysicalBone3D *_get_physical_bone_parent(int p_bone); void _rebuild_physical_bones_cache(); diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index f29b06006997..2793928cec5e 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -417,6 +417,9 @@ void SkeletonIK3D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { reload_chain(); + if (skeleton) { + skeleton->clear_bones_global_pose_override(); + } } break; } } diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 3d0ac291b8a5..7b65fc487e2f 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -101,7 +101,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) { } } - if (anim->get_loop_mode() == Animation::LoopMode::LOOP_PINGPONG) { + if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { if (anim_size) { if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) { if (prev_time > 0 && time <= 0) { @@ -116,7 +116,7 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) { time = Math::pingpong(time, anim_size); } } else { - if (anim->get_loop_mode() == Animation::LoopMode::LOOP_LINEAR) { + if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { if (anim_size) { time = Math::fposmod(time, anim_size); } diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 402418e5a927..d11e273bac25 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -258,8 +258,27 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov p_anim->node_cache.write[i] = nullptr; RES resource; Vector leftover_path; - Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); + Node *child = nullptr; +#ifndef _3D_DISABLED + // Special case for retarget tracks. + bool is_retarget_track = a->track_is_retarget(i); + if (is_retarget_track) { + if (!retarget_map.is_valid()) { + continue; + } + child = get_node_or_null(retarget_skeleton_path); + if (!child) { + continue; + } + } else { + child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); + ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node + } +#endif // _3D_DISABLED +#ifdef _3D_DISABLED + child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node +#endif // _3D_DISABLED ObjectID id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id(); int bone_idx = -1; int blend_shape_idx = -1; @@ -267,7 +286,16 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov #ifndef _3D_DISABLED if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to(child)) { Skeleton3D *sk = Object::cast_to(child); - bone_idx = sk->find_bone(a->track_get_path(i).get_subname(0)); + if (is_retarget_track) { + String imbone = a->track_get_path(i).get_subname(0); + if (retarget_map->has_key(imbone)) { + bone_idx = sk->find_bone(retarget_map->get_bone_name(imbone)); + } else { + continue; + } + } else { + bone_idx = sk->find_bone(a->track_get_path(i).get_subname(0)); + } if (bone_idx == -1) { continue; } @@ -328,9 +356,20 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov node_cache->skeleton = Object::cast_to(child); if (node_cache->skeleton) { if (a->track_get_path(i).get_subname_count() == 1) { - StringName bone_name = a->track_get_path(i).get_subname(0); - + StringName bone_name; + // Special case for retarget tracks; + if (is_retarget_track) { + String imbone = a->track_get_path(i).get_subname(0); + if (retarget_map->has_key(imbone)) { + bone_name = retarget_map->get_bone_name(imbone); + } else { + continue; + } + } else { + bone_name = a->track_get_path(i).get_subname(0); + } node_cache->bone_idx = node_cache->skeleton->find_bone(bone_name); + if (node_cache->bone_idx < 0) { // broken track (nonexistent bone) node_cache->skeleton = nullptr; @@ -421,6 +460,11 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double _ensure_node_caches(p_anim); ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); +#ifndef _3D_DISABLED + Skeleton3D *rtg_sk = Object::cast_to(get_node_or_null(retarget_skeleton_path)); + bool retarget_is_valid = retarget_map.is_valid() && rtg_sk; +#endif // _3D_DISABLED + Animation *a = p_anim->animation.operator->(); bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); bool backward = signbit(p_delta); @@ -462,6 +506,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } + if (a->track_is_retarget(i) && retarget_is_valid) { + if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + loc = rtg_sk->global_retarget_position_to_local_pose(nc->bone_idx, loc); + } else if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + loc = rtg_sk->local_retarget_position_to_local_pose(nc->bone_idx, loc); + } + } + if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); cache_update[cache_update_size++] = nc; @@ -489,6 +541,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } + if (a->track_is_retarget(i) && retarget_is_valid) { + if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + rot = rtg_sk->global_retarget_rotation_to_local_pose(nc->bone_idx, rot); + } else if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + rot = rtg_sk->local_retarget_rotation_to_local_pose(nc->bone_idx, rot); + } + } + if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); cache_update[cache_update_size++] = nc; @@ -516,6 +576,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } + if (a->track_is_retarget(i) && retarget_is_valid) { + if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + scale = rtg_sk->global_retarget_scale_to_local_pose(nc->bone_idx, scale); + } else if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + scale = rtg_sk->local_retarget_scale_to_local_pose(nc->bone_idx, scale); + } + } + if (nc->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); cache_update[cache_update_size++] = nc; @@ -804,7 +872,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double nc->audio_start = p_time; } } else if (nc->audio_playing) { - bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; + bool loop = a->get_loop_mode() != Animation::LOOP_NONE; bool stop = false; @@ -855,15 +923,15 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length()); } break; @@ -916,7 +984,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, int pingponged = 0; switch (cd.from->animation->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { if (next_pos < 0) { next_pos = 0; } else if (next_pos > len) { @@ -941,7 +1009,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { double looped_next_pos = Math::fposmod(next_pos, (double)len); if (looped_next_pos == 0 && next_pos != 0) { // Loop multiples of the length to it, rather than 0 @@ -952,7 +1020,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { if ((int)Math::floor(abs(next_pos - cd.pos) / len) % 2 == 0) { if (next_pos < 0 && cd.pos >= 0) { cd.speed_scale *= -1.0; @@ -1009,7 +1077,6 @@ void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { void AnimationPlayer::_animation_update_transforms() { { - Transform3D t; for (int i = 0; i < cache_update_size; i++) { TrackNodeCache *nc = cache_update[i]; @@ -1025,7 +1092,6 @@ void AnimationPlayer::_animation_update_transforms() { if (nc->scale_used) { nc->skeleton->set_bone_pose_scale(nc->bone_idx, nc->scale_accum); } - } else if (nc->node_blend_shape) { nc->node_blend_shape->set_blend_shape_value(nc->blend_shape_idx, nc->blend_shape_accum); } else if (nc->node_3d) { @@ -1039,7 +1105,6 @@ void AnimationPlayer::_animation_update_transforms() { nc->node_3d->set_scale(nc->scale_accum); } } - #endif // _3D_DISABLED } } @@ -1523,8 +1588,7 @@ float AnimationPlayer::get_current_animation_length() const { } void AnimationPlayer::_animation_changed() { - clear_caches(); - emit_signal(SNAME("caches_cleared")); + clear_caches(true); if (is_playing()) { playback.seeked = true; //need to restart stuff, like audio } @@ -1551,7 +1615,7 @@ void AnimationPlayer::_node_removed(Node *p_node) { clear_caches(); // nodes contained here are being removed, clear the caches } -void AnimationPlayer::clear_caches() { +void AnimationPlayer::clear_caches(bool p_emit_signal) { _stop_playing_caches(); node_cache_map.clear(); @@ -1563,6 +1627,10 @@ void AnimationPlayer::clear_caches() { cache_update_size = 0; cache_update_prop_size = 0; cache_update_bezier_size = 0; + + if (p_emit_signal) { + emit_signal(SNAME("caches_cleared")); + } } void AnimationPlayer::set_active(bool p_active) { @@ -1635,6 +1703,71 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode() return method_call_mode; } +#ifndef _3D_DISABLED +void AnimationPlayer::set_retarget_skeleton(const NodePath &p_skeleton) { + retarget_skeleton_path = p_skeleton; + clear_caches(true); + notify_property_list_changed(); +} + +NodePath AnimationPlayer::get_retarget_skeleton() const { + return retarget_skeleton_path; +} + +void AnimationPlayer::set_retarget_profile(const Ref &p_retarget_profile) { +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && retarget_profile->is_connected("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings))) { + retarget_profile->disconnect("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings)); + } +#endif // TOOLS_ENABLED + retarget_profile = p_retarget_profile; +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && !retarget_profile->is_connected("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings))) { + retarget_profile->connect("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings)); + } +#endif // TOOLS_ENABLED + notify_property_list_changed(); +} + +Ref AnimationPlayer::get_retarget_profile() const { + return retarget_profile; +} + +void AnimationPlayer::set_retarget_option(const Ref &p_retarget_option) { + retarget_option = p_retarget_option; +} + +Ref AnimationPlayer::get_retarget_option() const { + return retarget_option; +} + +void AnimationPlayer::set_retarget_map(const Ref &p_retarget_map) { + if (retarget_map.is_valid() && retarget_map->is_connected("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches))) { + retarget_map->disconnect("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches)); + } + retarget_map = p_retarget_map; + if (retarget_map.is_valid() && !retarget_map->is_connected("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches))) { + retarget_map->connect("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches), varray(true)); + } + clear_caches(true); +} + +Ref AnimationPlayer::get_retarget_map() const { + return retarget_map; +} + +#ifdef TOOLS_ENABLED +void AnimationPlayer::_redraw_retarget_settings() { + if (retarget_option.is_valid()) { + retarget_option->redraw(); + } + if (retarget_map.is_valid()) { + retarget_map->redraw(); + } +} +#endif // TOOLS_ENABLED +#endif // _3D_DISABLED + void AnimationPlayer::_set_process(bool p_process, bool p_force) { if (processing == p_process && !p_force) { return; @@ -1839,7 +1972,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation); - ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches); + ClassDB::bind_method(D_METHOD("clear_caches", "p_emit_signal"), &AnimationPlayer::clear_caches, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::set_process_callback); ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::get_process_callback); @@ -1847,6 +1980,17 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode); ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("set_retarget_skeleton", "retarget_skeleton_path"), &AnimationPlayer::set_retarget_skeleton); + ClassDB::bind_method(D_METHOD("get_retarget_skeleton"), &AnimationPlayer::get_retarget_skeleton); + ClassDB::bind_method(D_METHOD("set_retarget_profile", "retarget_profile"), &AnimationPlayer::set_retarget_profile); + ClassDB::bind_method(D_METHOD("get_retarget_profile"), &AnimationPlayer::get_retarget_profile); + ClassDB::bind_method(D_METHOD("set_retarget_option", "retarget_option"), &AnimationPlayer::set_retarget_option); + ClassDB::bind_method(D_METHOD("get_retarget_option"), &AnimationPlayer::get_retarget_option); + ClassDB::bind_method(D_METHOD("set_retarget_map", "retarget_map"), &AnimationPlayer::set_retarget_map); + ClassDB::bind_method(D_METHOD("get_retarget_map"), &AnimationPlayer::get_retarget_map); +#endif // _3D_DISABLED + ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position); ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length); @@ -1868,6 +2012,14 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); +#ifndef _3D_DISABLED + ADD_GROUP("Retarget Options", "retarget_"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "retarget_profile", PROPERTY_HINT_RESOURCE_TYPE, "RetargetProfile"), "set_retarget_profile", "get_retarget_profile"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "retarget_option", PROPERTY_HINT_RESOURCE_TYPE, "RetargetBoneOption"), "set_retarget_option", "get_retarget_option"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "retarget_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_retarget_skeleton", "get_retarget_skeleton"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "retarget_map", PROPERTY_HINT_RESOURCE_TYPE, "RetargetBoneMap"), "set_retarget_map", "get_retarget_map"); +#endif // _3D_DISABLED + ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING_NAME, "anim_name"))); ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING_NAME, "old_name"), PropertyInfo(Variant::STRING_NAME, "new_name"))); ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING_NAME, "anim_name"))); @@ -1886,4 +2038,14 @@ AnimationPlayer::AnimationPlayer() { } AnimationPlayer::~AnimationPlayer() { +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && retarget_profile->is_connected("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings))) { + retarget_profile->disconnect("profile_updated", callable_mp(this, &AnimationPlayer::_redraw_retarget_settings)); + } +#endif // TOOLS_ENABLED + if (retarget_map.is_valid() && retarget_map->is_connected("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches))) { + retarget_map->disconnect("retarget_map_updated", callable_mp(this, &AnimationPlayer::clear_caches)); + } +#endif // _3D_DISABLED } diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index c4fc69f370db..70ad24c55ebf 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -36,6 +36,7 @@ #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" +#include "skeleton_retarget.h" #ifdef TOOLS_ENABLED class AnimatedValuesBackup : public RefCounted { @@ -229,6 +230,13 @@ class AnimationPlayer : public Node { bool processing = false; bool active = true; +#ifndef _3D_DISABLED + Ref retarget_profile; + Ref retarget_option; + NodePath retarget_skeleton_path; + Ref retarget_map; +#endif // _3D_DISABLED + NodePath root; void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, int p_pingponged = 0); @@ -262,6 +270,12 @@ class AnimationPlayer : public Node { bool playing = false; +#ifndef _3D_DISABLED +#ifdef TOOLS_ENABLED + void _redraw_retarget_settings(); +#endif // TOOLS_ENABLED +#endif // _3D_DISABLED + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -321,6 +335,17 @@ class AnimationPlayer : public Node { void set_method_call_mode(AnimationMethodCallMode p_mode); AnimationMethodCallMode get_method_call_mode() const; +#ifndef _3D_DISABLED + void set_retarget_skeleton(const NodePath &p_skeleton); + NodePath get_retarget_skeleton() const; + void set_retarget_profile(const Ref &p_retarget_profile); + Ref get_retarget_profile() const; + void set_retarget_option(const Ref &p_retarget_option); + Ref get_retarget_option() const; + void set_retarget_map(const Ref &p_retarget_map); + Ref get_retarget_map() const; +#endif // _3D_DISABLED + void seek(double p_time, bool p_update = false); void seek_delta(double p_time, float p_delta); float get_current_animation_position() const; @@ -331,7 +356,7 @@ class AnimationPlayer : public Node { void set_root(const NodePath &p_root); NodePath get_root() const; - void clear_caches(); ///< must be called by hand if an animation was modified after added + void clear_caches(bool p_emit_signal = false); ///< must be called by hand if an animation was modified after added void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const override; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index e0e94d8632f4..f12963f47602 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -273,10 +273,6 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vectorbase_path) + String(p_subpath) + "/"; } + + if (!p_seek && p_optimize && !any_valid) { // Need to blend with initial value. + return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_connections); + } return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_connections); } @@ -536,32 +536,74 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { return false; } Node *parent = player->get_node(player->get_root()); - +#ifndef _3D_DISABLED + Skeleton3D *rtg_sk = Object::cast_to(player->get_node_or_null(player->get_retarget_skeleton())); + bool retarget_is_valid = player->get_retarget_map().is_valid() && rtg_sk; +#endif // _3D_DISABLED List sname; player->get_animation_list(&sname); + Ref reset_anim; + bool has_reset_anim = player->has_animation("RESET"); + if (has_reset_anim) { + reset_anim = player->get_animation("RESET"); + } for (const StringName &E : sname) { Ref anim = player->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { NodePath path = anim->track_get_path(i); Animation::TrackType track_type = anim->track_get_type(i); +#ifndef _3D_DISABLED + if (anim->track_is_retarget(i)) { + if (retarget_is_valid) { + String imbone = path.get_concatenated_subnames(); + if (player->get_retarget_map()->has_key(imbone)) { + path = String(parent->get_path_to(rtg_sk)) + ":" + player->get_retarget_map()->get_bone_name(imbone); + } else { + continue; + } + } else { + // Prevent to animate root. + // Empty path are treated as root. Usually it is ".:subpath", not ":subpath". + continue; + } + } +#endif // _3D_DISABLED - Animation::TrackType track_cache_type = track_type; - if (track_cache_type == Animation::TYPE_POSITION_3D || track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) { - track_cache_type = Animation::TYPE_POSITION_3D; //reference them as position3D tracks, even if they modify rotation or scale + // May only used by transform 3d tracks. + Animation::TrackType track_src_type = track_type; + + if (!track_cache.has(path)) { + track_cache[path] = Vector(); } - TrackCache *track = nullptr; - if (track_cache.has(path)) { - track = track_cache.get(path); + Vector &tracks = track_cache.get(path); + + // Merge some caches. + if (track_type == Animation::TYPE_BEZIER) { + track_type = Animation::TYPE_VALUE; + } + if (track_type == Animation::TYPE_ROTATION_3D || track_type == Animation::TYPE_SCALE_3D) { + track_type = Animation::TYPE_POSITION_3D; //reference them as position3D tracks, even if they modify rotation or scale } - //if not valid, delete track - if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) { - playing_caches.erase(track); - memdelete(track); - track_cache.erase(path); - track = nullptr; + TrackCache *track = nullptr; + int tracks_len = tracks.size(); + for (int j = 0; j < tracks_len; j++) { + if (tracks.get(j)->type == track_type) { + track = tracks.get(j); + //if not valid, delete track + if (ObjectDB::get_instance(track->object_id) == nullptr) { + playing_caches.erase(track); + tracks.remove_at(j); + memdelete(track); + track = nullptr; + if (tracks.size() <= 0) { + track_cache.erase(path); + } + } + break; + } } if (!track) { @@ -593,10 +635,14 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track = track_value; + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_src_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + track_value->init_value = reset_anim->track_get_key_value(rt, 0); + } + } } break; - case Animation::TYPE_POSITION_3D: - case Animation::TYPE_ROTATION_3D: - case Animation::TYPE_SCALE_3D: { + case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED Node3D *node_3d = Object::cast_to(child); @@ -618,6 +664,11 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { int bone_idx = sk->find_bone(path.get_subname(0)); if (bone_idx != -1) { track_xform->bone_idx = bone_idx; + Transform3D rest = sk->get_bone_rest(bone_idx); + track_xform->init_loc = rest.origin; + track_xform->ref_rot = rest.basis.get_rotation_quaternion(); + track_xform->init_rot = track_xform->ref_rot.log(); + track_xform->init_scale = rest.basis.get_scale(); } } @@ -626,7 +677,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track = track_xform; - switch (track_type) { + switch (track_src_type) { case Animation::TYPE_POSITION_3D: { track_xform->loc_used = true; } break; @@ -640,6 +691,25 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } } + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + switch (track_type) { + case Animation::TYPE_POSITION_3D: { + track_xform->init_loc = reset_anim->track_get_key_value(rt, 0); + } break; + case Animation::TYPE_ROTATION_3D: { + track_xform->ref_rot = reset_anim->track_get_key_value(rt, 0); + track_xform->init_rot = track_xform->ref_rot.log(); + } break; + case Animation::TYPE_SCALE_3D: { + track_xform->init_scale = reset_anim->track_get_key_value(rt, 0); + } break; + default: { + } + } + } + } #endif // _3D_DISABLED } break; case Animation::TYPE_BLEND_SHAPE: { @@ -671,6 +741,13 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_bshape->object = mesh_3d; track_bshape->object_id = mesh_3d->get_instance_id(); track = track_bshape; + + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + track_bshape->init_value = reset_anim->track_get_key_value(rt, 0); + } + } #endif } break; case Animation::TYPE_METHOD: { @@ -687,20 +764,6 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track = track_method; } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *track_bezier = memnew(TrackCacheBezier); - - if (resource.is_valid()) { - track_bezier->object = resource.ptr(); - } else { - track_bezier->object = child; - } - - track_bezier->subpath = leftover_path; - track_bezier->object_id = track_bezier->object->get_instance_id(); - - track = track_bezier; - } break; case Animation::TYPE_AUDIO: { TrackCacheAudio *track_audio = memnew(TrackCacheAudio); @@ -724,16 +787,15 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { continue; } } - - track_cache[path] = track; - } else if (track_cache_type == Animation::TYPE_POSITION_3D) { + track_cache[path].push_back(track); + } else if (track_type == Animation::TYPE_POSITION_3D) { TrackCacheTransform *track_xform = static_cast(track); if (track->setup_pass != setup_pass) { track_xform->loc_used = false; track_xform->rot_used = false; track_xform->scale_used = false; } - switch (track_type) { + switch (track_src_type) { case Animation::TYPE_POSITION_3D: { track_xform->loc_used = true; } break; @@ -752,21 +814,34 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } } - List to_delete; + List to_delete_path; + List to_delete_idx; const NodePath *K = nullptr; while ((K = track_cache.next(K))) { - TrackCache *tc = track_cache[*K]; - if (tc->setup_pass != setup_pass) { - to_delete.push_back(*K); + to_delete_idx.clear(); + Vector &tcs = track_cache[*K]; + int tracks_len = tcs.size(); + for (int i = 0; i < tracks_len; i++) { + TrackCache *tc = tcs.get(i); + if (tc->setup_pass != setup_pass) { + to_delete_idx.push_front(i); + } + } + while (to_delete_idx.front()) { + int idx = to_delete_idx.front()->get(); + tcs.remove_at(idx); + to_delete_path.pop_front(); + } + if (tcs.size() <= 0) { + to_delete_path.push_back(*K); } } - while (to_delete.front()) { - NodePath np = to_delete.front()->get(); - memdelete(track_cache[np]); + while (to_delete_path.front()) { + NodePath np = to_delete_path.front()->get(); track_cache.erase(np); - to_delete.pop_front(); + to_delete_path.pop_front(); } state.track_map.clear(); @@ -788,7 +863,12 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { void AnimationTree::_clear_caches() { const NodePath *K = nullptr; while ((K = track_cache.next(K))) { - memdelete(track_cache[*K]); + Vector &tracks = track_cache[*K]; + int tracks_len = tracks.size(); + for (int i = 0; i < tracks_len; i++) { + memdelete(tracks.get(i)); + } + tracks.clear(); } playing_caches.clear(); @@ -907,6 +987,11 @@ void AnimationTree::_process_graph(double p_delta) { //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks { +#ifndef _3D_DISABLED + Node *parent = player->get_node(player->get_root()); + Skeleton3D *rtg_sk = Object::cast_to(player->get_node_or_null(player->get_retarget_skeleton())); + bool retarget_is_valid = player->get_retarget_map().is_valid() && rtg_sk; +#endif // _3D_DISABLED bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); for (const AnimationNode::AnimationState &as : state.animation_states) { @@ -922,15 +1007,42 @@ void AnimationTree::_process_graph(double p_delta) { for (int i = 0; i < a->get_track_count(); i++) { NodePath path = a->track_get_path(i); - +#ifndef _3D_DISABLED + if (a->track_is_retarget(i)) { + if (retarget_is_valid) { + if (player->get_retarget_map()->has_key(path.get_concatenated_subnames())) { + path = String(parent->get_path_to(rtg_sk)) + ":" + player->get_retarget_map()->get_bone_name(path.get_concatenated_subnames()); + } else { + continue; + } + } else { + continue; + } + } +#endif // _3D_DISABLED ERR_CONTINUE(!track_cache.has(path)); - TrackCache *track = track_cache[path]; + TrackCache *track = nullptr; + Animation::TrackType cache_type = a->track_get_type(i); - Animation::TrackType ttype = a->track_get_type(i); - if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) { - //broken animation, but avoid error spamming - continue; + // Merge some caches + if (cache_type == Animation::TYPE_BEZIER) { + cache_type = Animation::TYPE_VALUE; + } + if (cache_type == Animation::TYPE_ROTATION_3D || cache_type == Animation::TYPE_SCALE_3D) { + cache_type = Animation::TYPE_POSITION_3D; + } + + Vector &tcs = track_cache[path]; + int tracks_len = tcs.size(); + for (int j = 0; j < tracks_len; j++) { + if (tcs.get(j)->type == cache_type) { + track = tcs.get(j); + break; + } + } + if (!track) { + continue; //may happen should not } track->root_motion = root_motion_track == path; @@ -942,299 +1054,428 @@ void AnimationTree::_process_graph(double p_delta) { real_t blend = (*as.track_blends)[blend_idx] * weight; - if (blend < CMP_EPSILON) { - continue; //nothing to blend - } - - switch (ttype) { + switch (cache_type) { case Animation::TYPE_POSITION_3D: { + switch (a->track_get_type(i)) { + case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED - TrackCacheTransform *t = static_cast(track); - - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(); - t->rot = Quaternion(); - t->rot_blend_accum = 0; - t->scale = Vector3(1, 1, 1); - } - - if (track->root_motion) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } + TrackCacheTransform *t = static_cast(track); + if (t->process_pass != process_pass) { + t->process_pass = process_pass; + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; + if (track->root_motion) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } } - } - } - Vector3 loc[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; + Vector3 loc[2]; + + if (!backward) { + if (prev_time > time) { + Error err = a->position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + a->position_track_interpolate(i, (double)a->get_length(), &loc[1]); + Vector3 v = loc[1] - loc[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_position_to_local_pose(bone_idx, v); + } else if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_position_to_local_pose(bone_idx, v); + } + } + t->loc += v * blend; + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + a->position_track_interpolate(i, 0, &loc[1]); + Vector3 v = loc[1] - loc[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_position_to_local_pose(bone_idx, v); + } else if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_position_to_local_pose(bone_idx, v); + } + } + t->loc += v * blend; + prev_time = 0; + } } - a->position_track_interpolate(i, (double)a->get_length(), &loc[1]); - t->loc += (loc[1] - loc[0]) * blend; - prev_time = 0; - } - } else { - if (prev_time < time) { + Error err = a->position_track_interpolate(i, prev_time, &loc[0]); if (err != OK) { continue; } - a->position_track_interpolate(i, 0, &loc[1]); - t->loc += (loc[1] - loc[0]) * blend; - prev_time = 0; - } - } - Error err = a->position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; - } + a->position_track_interpolate(i, time, &loc[1]); + Vector3 v = loc[1] - loc[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_position_to_local_pose(bone_idx, v); + } else if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_position_to_local_pose(bone_idx, v); + } + } + t->loc += v * blend; + prev_time = !backward ? 0 : (double)a->get_length(); - a->position_track_interpolate(i, time, &loc[1]); - t->loc += (loc[1] - loc[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + } else { + Vector3 loc; - } else { - Vector3 loc; + Error err = a->position_track_interpolate(i, time, &loc); + if (err != OK) { + continue; + } - Error err = a->position_track_interpolate(i, time, &loc); - if (err != OK) { - continue; - } + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + loc = rtg_sk->global_retarget_position_to_local_pose(bone_idx, loc); + } else if (a->position_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + loc = rtg_sk->local_retarget_position_to_local_pose(bone_idx, loc); + } + } - t->loc = t->loc.lerp(loc, blend); - } + t->loc += (loc - t->init_loc) * blend; + } #endif // _3D_DISABLED - } break; - case Animation::TYPE_ROTATION_3D: { + } break; + case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED - TrackCacheTransform *t = static_cast(track); - - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(); - t->rot = Quaternion(); - t->rot_blend_accum = 0; - t->scale = Vector3(1, 1, 1); - } - - if (track->root_motion) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } + TrackCacheTransform *t = static_cast(track); + if (t->process_pass != process_pass) { + t->process_pass = process_pass; + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; + if (track->root_motion) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } } - } - } - Quaternion rot[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; + Quaternion rot[2]; + + if (!backward) { + if (prev_time > time) { + Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); + Quaternion q = (rot[1].log() - rot[0].log()) * blend; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + q = rtg_sk->global_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } else if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + q = rtg_sk->local_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } + } + t->rot += q.log(); + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + a->rotation_track_interpolate(i, 0, &rot[1]); + Quaternion q = (rot[1].log() - rot[0].log()) * blend; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + q = rtg_sk->global_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } else if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + q = rtg_sk->local_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } + } + t->rot += q; + prev_time = 0; + } } - a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); - t->rot = (t->rot * q).normalized(); - prev_time = 0; - } - } else { - if (prev_time < time) { + Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); if (err != OK) { continue; } - a->rotation_track_interpolate(i, 0, &rot[1]); - Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); - t->rot = (t->rot * q).normalized(); - prev_time = 0; - } - } - - Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; - } - a->rotation_track_interpolate(i, time, &rot[1]); - Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); - t->rot = (t->rot * q).normalized(); - prev_time = !backward ? 0 : (double)a->get_length(); + a->rotation_track_interpolate(i, time, &rot[1]); + Quaternion q = (rot[1].log() - rot[0].log()) * blend; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + q = rtg_sk->global_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } else if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + q = rtg_sk->local_retarget_rotation_to_local_pose(bone_idx, q.exp()).log(); + } + } + t->rot += q; + prev_time = !backward ? 0 : (double)a->get_length(); - } else { - Quaternion rot; + } else { + Quaternion rot; - Error err = a->rotation_track_interpolate(i, time, &rot); - if (err != OK) { - continue; - } + Error err = a->rotation_track_interpolate(i, time, &rot); + if (err != OK) { + continue; + } - if (t->rot_blend_accum == 0) { - t->rot = rot; - t->rot_blend_accum = blend; - } else { - real_t rot_total = t->rot_blend_accum + blend; - t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized(); - t->rot_blend_accum = rot_total; - } - } + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + rot = rtg_sk->global_retarget_rotation_to_local_pose(bone_idx, rot); + } else if (a->rotation_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + rot = rtg_sk->local_retarget_rotation_to_local_pose(bone_idx, rot); + } + } + if (signbit(rot.dot(t->ref_rot))) { + rot = -rot; + } + t->rot += (rot.log() - t->init_rot) * blend; + } #endif // _3D_DISABLED - } break; - case Animation::TYPE_SCALE_3D: { + } break; + case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED - TrackCacheTransform *t = static_cast(track); - - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(); - t->rot = Quaternion(); - t->rot_blend_accum = 0; - t->scale = Vector3(1, 1, 1); - } - - if (track->root_motion) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } + TrackCacheTransform *t = static_cast(track); + if (t->process_pass != process_pass) { + t->process_pass = process_pass; + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; + if (track->root_motion) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } } - } - } - Vector3 scale[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; + Vector3 scale[2]; + + if (!backward) { + if (prev_time > time) { + Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]); + Vector3 v = scale[1] - scale[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_scale_to_local_pose(bone_idx, v); + } else if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_scale_to_local_pose(bone_idx, v); + } + } + t->scale += v * blend; + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + a->scale_track_interpolate(i, 0, &scale[1]); + Vector3 v = scale[1] - scale[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_scale_to_local_pose(bone_idx, v); + } else if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_scale_to_local_pose(bone_idx, v); + } + } + t->scale += v * blend; + prev_time = 0; + } } - a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - prev_time = 0; - } - } else { - if (prev_time < time) { + Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); if (err != OK) { continue; } - a->scale_track_interpolate(i, 0, &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - prev_time = 0; - } - } - Error err = a->scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; - } + a->scale_track_interpolate(i, time, &scale[1]); + Vector3 v = scale[1] - scale[0]; + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + v = rtg_sk->global_retarget_scale_to_local_pose(bone_idx, v); + } else if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + v = rtg_sk->local_retarget_scale_to_local_pose(bone_idx, v); + } + } + t->scale += v * blend; + prev_time = !backward ? 0 : (double)a->get_length(); - a->scale_track_interpolate(i, time, &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + } else { + Vector3 scale; - } else { - Vector3 scale; + Error err = a->scale_track_interpolate(i, time, &scale); + if (err != OK) { + continue; + } - Error err = a->scale_track_interpolate(i, time, &scale); - if (err != OK) { - continue; - } + if (a->track_is_retarget(i) && retarget_is_valid) { + int bone_idx = rtg_sk->find_bone(path.get_subname(0)); + if (bone_idx == -1) { + continue; + } + if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_GLOBAL) { + scale = rtg_sk->global_retarget_scale_to_local_pose(bone_idx, scale); + } else if (a->scale_track_get_retarget_mode(i) == Animation::RETARGET_MODE_LOCAL) { + scale = rtg_sk->local_retarget_scale_to_local_pose(bone_idx, scale); + } + } - t->scale = t->scale.lerp(scale, blend); - } + t->scale += (scale - t->init_scale) * blend; + } #endif // _3D_DISABLED + } break; + default: { + } break; + } } break; case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED @@ -1242,7 +1483,7 @@ void AnimationTree::_process_graph(double p_delta) { if (t->process_pass != process_pass) { t->process_pass = process_pass; - t->value = 0; + t->value = t->init_value; } float value; @@ -1254,42 +1495,72 @@ void AnimationTree::_process_graph(double p_delta) { continue; } - t->value = Math::lerp(t->value, value, (float)blend); + t->value += (value - t->init_value) * blend; #endif // _3D_DISABLED } break; case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast(track); + switch (a->track_get_type(i)) { + case Animation::TYPE_VALUE: { + Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); - Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek - if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek + Variant value = a->value_track_interpolate(i, time); - Variant value = a->value_track_interpolate(i, time); + if (value == Variant()) { + continue; + } - if (value == Variant()) { - continue; - } + if (t->process_pass != process_pass) { + if (!t->init_value) { + t->init_value = value; + t->init_value.zero(); + } + t->value = t->init_value; + t->process_pass = process_pass; + } - if (t->process_pass != process_pass) { - t->value = value; - t->process_pass = process_pass; - } + Variant::sub(value, t->init_value, value); + Variant::blend(t->value, value, blend, t->value); + } else { + if (blend < CMP_EPSILON) { + continue; //nothing to blend + } + List indices; + a->value_track_get_key_indices(i, time, delta, &indices, pingponged); - Variant::interpolate(t->value, value, blend, t->value); + for (int &F : indices) { + Variant value = a->track_get_key_value(i, F); + t->object->set_indexed(t->subpath, value); + } + } + } break; + case Animation::TYPE_BEZIER: { + Variant bezier = a->bezier_track_interpolate(i, time); - } else { - List indices; - a->value_track_get_key_indices(i, time, delta, &indices, pingponged); + if (t->process_pass != process_pass) { + if (!t->init_value) { + t->init_value = bezier; + t->init_value.zero(); + } + t->value = t->init_value; + t->process_pass = process_pass; + } - for (int &F : indices) { - Variant value = a->track_get_key_value(i, F); - t->object->set_indexed(t->subpath, value); - } + Variant::sub(bezier, t->init_value, bezier); + Variant::blend(t->value, bezier, blend, t->value); + } break; + default: { + } break; } } break; case Animation::TYPE_METHOD: { - if (delta == 0) { + if (blend < CMP_EPSILON) { + continue; //nothing to blend + } + if (!seeked && Math::is_zero_approx(delta)) { continue; } TrackCacheMethod *t = static_cast(track); @@ -1306,20 +1577,10 @@ void AnimationTree::_process_graph(double p_delta) { } } } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *t = static_cast(track); - - real_t bezier = a->bezier_track_interpolate(i, time); - - if (t->process_pass != process_pass) { - t->value = bezier; - t->process_pass = process_pass; - } - - t->value = Math::lerp(t->value, bezier, blend); - - } break; case Animation::TYPE_AUDIO: { + if (blend < CMP_EPSILON) { + continue; //nothing to blend + } TrackCacheAudio *t = static_cast(track); if (seeked) { @@ -1392,7 +1653,7 @@ void AnimationTree::_process_graph(double p_delta) { t->start = time; } } else if (t->playing) { - bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE; + bool loop = a->get_loop_mode() != Animation::LOOP_NONE; bool stop = false; @@ -1423,14 +1684,19 @@ void AnimationTree::_process_graph(double p_delta) { } } - real_t db = Math::linear2db(MAX(blend, 0.00001)); - if (t->object->has_method(SNAME("set_unit_db"))) { - t->object->call(SNAME("set_unit_db"), db); - } else { - t->object->call(SNAME("set_volume_db"), db); + if (a->audio_track_get_auto_volume(i)) { + real_t db = Math::linear2db(MAX(blend, 0.00001)); + if (t->object->has_method(SNAME("set_unit_db"))) { + t->object->call(SNAME("set_unit_db"), db); + } else { + t->object->call(SNAME("set_volume_db"), db); + } } } break; case Animation::TYPE_ANIMATION: { + if (blend < CMP_EPSILON) { + continue; //nothing to blend + } TrackCacheAnimation *t = static_cast(track); AnimationPlayer *player2 = Object::cast_to(t->object); @@ -1458,13 +1724,13 @@ void AnimationTree::_process_graph(double p_delta) { double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { - case Animation::LoopMode::LOOP_NONE: { + case Animation::LOOP_NONE: { at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end } break; - case Animation::LoopMode::LOOP_LINEAR: { + case Animation::LOOP_LINEAR: { at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop } break; - case Animation::LoopMode::LOOP_PINGPONG: { + case Animation::LOOP_PINGPONG: { at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); } break; default: @@ -1503,6 +1769,8 @@ void AnimationTree::_process_graph(double p_delta) { } } break; + default: { + } break; } } } @@ -1512,70 +1780,76 @@ void AnimationTree::_process_graph(double p_delta) { // finally, set the tracks const NodePath *K = nullptr; while ((K = track_cache.next(K))) { - TrackCache *track = track_cache[*K]; - if (track->process_pass != process_pass) { - continue; //not processed, ignore - } + Vector &tcs = track_cache[*K]; + int tracks_len = tcs.size(); + for (int i = 0; i < tracks_len; i++) { + TrackCache *track = tcs.get(i); + + if (!track) { + continue; //may happen should not + } - switch (track->type) { - case Animation::TYPE_POSITION_3D: { + if (track->process_pass != process_pass) { + continue; //not processed, ignore + } + + switch (track->type) { + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED - TrackCacheTransform *t = static_cast(track); + TrackCacheTransform *t = static_cast(track); + t->rot = t->rot.exp(); - if (t->root_motion) { - Transform3D xform; - xform.origin = t->loc; - xform.basis.set_quaternion_scale(t->rot, t->scale); + if (t->root_motion) { + Transform3D xform; + xform.origin = t->loc; + xform.basis.set_quaternion_scale(t->rot, t->scale); - root_motion_transform = xform; + root_motion_transform = xform; - } else if (t->skeleton && t->bone_idx >= 0) { - if (t->loc_used) { - t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); - } - if (t->rot_used) { - t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); - } - if (t->scale_used) { - t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale); - } + } else if (t->skeleton && t->bone_idx >= 0) { + if (t->loc_used) { + t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); + } + if (t->rot_used) { + t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); + } + if (t->scale_used) { + t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale); + } - } else if (!t->skeleton) { - if (t->loc_used) { - t->node_3d->set_position(t->loc); - } - if (t->rot_used) { - t->node_3d->set_rotation(t->rot.get_euler()); - } - if (t->scale_used) { - t->node_3d->set_scale(t->scale); + } else if (!t->skeleton) { + if (t->loc_used) { + t->node_3d->set_position(t->loc); + } + if (t->rot_used) { + t->node_3d->set_rotation(t->rot.get_euler()); + } + if (t->scale_used) { + t->node_3d->set_scale(t->scale); + } } - } #endif // _3D_DISABLED - } break; - case Animation::TYPE_BLEND_SHAPE: { + } break; + case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED - TrackCacheBlendShape *t = static_cast(track); + TrackCacheBlendShape *t = static_cast(track); - if (t->mesh_3d) { - t->mesh_3d->set_blend_shape_value(t->shape_index, t->value); - } + if (t->mesh_3d) { + t->mesh_3d->set_blend_shape_value(t->shape_index, t->value); + } #endif // _3D_DISABLED - } break; - case Animation::TYPE_VALUE: { - TrackCacheValue *t = static_cast(track); - - t->object->set_indexed(t->subpath, t->value); - - } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *t = static_cast(track); + } break; + case Animation::TYPE_VALUE: { + TrackCacheValue *t = static_cast(track); - t->object->set_indexed(t->subpath, t->value); + t->object->set_indexed(t->subpath, t->value); - } break; - default: { - } //the rest don't matter + } break; + default: { + } //the rest don't matter + } } } } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 705ee91c7629..27af45736717 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -179,7 +179,7 @@ class AnimationTree : public Node { bool root_motion = false; uint64_t setup_pass = 0; uint64_t process_pass = 0; - Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION; + Animation::TrackType type = Animation::TYPE_ANIMATION; Object *object = nullptr; ObjectID object_id; @@ -197,9 +197,12 @@ class AnimationTree : public Node { bool loc_used = false; bool rot_used = false; bool scale_used = false; + Vector3 init_loc = Vector3(); + Quaternion ref_rot = Quaternion(); + Quaternion init_rot = Quaternion(0, 0, 0, 0); + Vector3 init_scale = Vector3(1, 1, 1); Vector3 loc; Quaternion rot; - real_t rot_blend_accum = 0.0; Vector3 scale; TrackCacheTransform() { @@ -209,29 +212,25 @@ class AnimationTree : public Node { struct TrackCacheBlendShape : public TrackCache { MeshInstance3D *mesh_3d = nullptr; + float init_value = 0; float value = 0; int shape_index = -1; TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; } }; struct TrackCacheValue : public TrackCache { + Variant init_value; Variant value; Vector subpath; - TrackCacheValue() { type = Animation::TYPE_VALUE; } + TrackCacheValue() { + type = Animation::TYPE_VALUE; + } }; struct TrackCacheMethod : public TrackCache { TrackCacheMethod() { type = Animation::TYPE_METHOD; } }; - struct TrackCacheBezier : public TrackCache { - real_t value = 0.0; - Vector subpath; - TrackCacheBezier() { - type = Animation::TYPE_BEZIER; - } - }; - struct TrackCacheAudio : public TrackCache { bool playing = false; double start = 0.0; @@ -250,7 +249,7 @@ class AnimationTree : public Node { } }; - HashMap track_cache; + HashMap> track_cache; Set playing_caches; Ref root; diff --git a/scene/animation/skeleton_retarget.cpp b/scene/animation/skeleton_retarget.cpp new file mode 100644 index 000000000000..79e7eb3dbef7 --- /dev/null +++ b/scene/animation/skeleton_retarget.cpp @@ -0,0 +1,940 @@ +/*************************************************************************/ +/* skeleton_retarget.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "skeleton_retarget.h" + +#include "editor/plugins/skeleton_retarget_editor_plugin.h" + +// Retarget profile + +bool RetargetProfile::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (!path.begins_with("intermediate_bones/")) { + return false; + } + + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + if (which == intermediate_bone_names.size() && what == "bone_name") { + add_intermediate_bone(p_value); + return true; + } + + ERR_FAIL_INDEX_V(which, intermediate_bone_names.size(), false); + + if (what == "bone_name") { + set_intermediate_bone_name(which, p_value); + } else { + return false; + } + + return true; +} + +bool RetargetProfile::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (!path.begins_with("intermediate_bones/")) { + return false; + } + + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + ERR_FAIL_INDEX_V(which, intermediate_bone_names.size(), false); + + if (what == "bone_name") { + r_ret = get_intermediate_bone_name(which); + } else { + return false; + } + + return true; +} + +void RetargetProfile::_get_property_list(List *p_list) const { + for (int i = 0; i < intermediate_bone_names.size(); i++) { + String prep = "intermediate_bones/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, prep + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void RetargetProfile::_validate_property(PropertyInfo &property) const { +} + +#ifdef TOOLS_ENABLED +void RetargetProfile::redraw() { + emit_signal("redraw_needed"); +} +#endif // TOOLS_ENABLED + +void RetargetProfile::add_intermediate_bone(const StringName &p_intermediate_bone_name, int to_index) { + if (to_index == -1) { + (const_cast(this)->intermediate_bone_names).push_back(p_intermediate_bone_name); + } else { + ERR_FAIL_INDEX(to_index, intermediate_bone_names.size() + 1); + (const_cast(this)->intermediate_bone_names).insert(to_index, p_intermediate_bone_name); + } +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +void RetargetProfile::remove_intermediate_bone(int p_id) { + ERR_FAIL_INDEX(p_id, intermediate_bone_names.size()); + (const_cast(this)->intermediate_bone_names).remove_at(p_id); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +int RetargetProfile::find_intermediate_bone(const StringName &p_intermediate_bone_name) { + return intermediate_bone_names.find(p_intermediate_bone_name); +} + +int RetargetProfile::get_intermediate_bones_size() { + return intermediate_bone_names.size(); +} + +void RetargetProfile::set_intermediate_bone_name(int p_id, const StringName &p_intermediate_bone_name) { + ERR_FAIL_INDEX(p_id, intermediate_bone_names.size()); + intermediate_bone_names.write[p_id] = p_intermediate_bone_name; + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +StringName RetargetProfile::get_intermediate_bone_name(int p_id) const { + ERR_FAIL_INDEX_V(p_id, intermediate_bone_names.size(), StringName()); + return intermediate_bone_names[p_id]; +} + +void RetargetProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_intermediate_bone", "intermediate_bone_name", "to_index"), &RetargetProfile::add_intermediate_bone, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_intermediate_bone", "id"), &RetargetProfile::remove_intermediate_bone); + ClassDB::bind_method(D_METHOD("find_intermediate_bone", "intermediate_bone_name"), &RetargetProfile::find_intermediate_bone); + ClassDB::bind_method(D_METHOD("get_intermediate_bones_size"), &RetargetProfile::get_intermediate_bones_size); + ClassDB::bind_method(D_METHOD("set_intermediate_bone_name", "id", "intermediate_bone_name"), &RetargetProfile::set_intermediate_bone_name); + ClassDB::bind_method(D_METHOD("get_intermediate_bone_name", "id"), &RetargetProfile::get_intermediate_bone_name); + ADD_SIGNAL(MethodInfo("intermediate_bone_updated")); + ADD_SIGNAL(MethodInfo("profile_updated")); +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("redraw_needed")); +#endif // TOOLS_ENABLED +} + +RetargetProfile::RetargetProfile() { +} + +RetargetProfile::~RetargetProfile() { +} + +// Retarget rich profile + +bool RetargetRichProfile::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("groups/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + if (which == group_names.size() && what == "group_name") { + add_group(p_value); + return true; + } + + ERR_FAIL_INDEX_V(which, group_names.size(), false); + + if (what == "group_name") { + set_group_name(which, p_value); + } else if (what == "group_texture") { + set_group_texture(which, p_value); + } else { + return false; + } + + return true; + } else if (path.begins_with("intermediate_bones/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + if (which == intermediate_bone_names.size() && what == "bone_name") { + add_intermediate_bone(p_value); + return true; + } + + ERR_FAIL_INDEX_V(which, intermediate_bone_names.size(), false); + + if (what == "bone_name") { + set_intermediate_bone_name(which, p_value); + } else if (what == "handle_offset") { + set_intermediate_bone_handle_offset(which, p_value); + } else if (what == "group_id") { + set_intermediate_bone_group_id(which, p_value); + } else { + return false; + } + + return true; + } + + return false; +} + +bool RetargetRichProfile::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("groups/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + ERR_FAIL_INDEX_V(which, group_names.size(), false); + + if (what == "group_name") { + r_ret = get_group_name(which); + } else if (what == "group_texture") { + r_ret = get_group_texture(which); + } else { + return false; + } + + return true; + } else if (path.begins_with("intermediate_bones/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + + ERR_FAIL_INDEX_V(which, intermediate_bone_names.size(), false); + + if (what == "bone_name") { + r_ret = get_intermediate_bone_name(which); + } else if (what == "handle_offset") { + r_ret = get_intermediate_bone_handle_offset(which); + } else if (what == "group_id") { + r_ret = get_intermediate_bone_group_id(which); + } else { + return false; + } + + return true; + } + + return false; +} + +void RetargetRichProfile::_get_property_list(List *p_list) const { + for (int i = 0; i < group_names.size(); i++) { + String prep = "groups/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, prep + "group_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, prep + "group_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR)); + } + + for (int i = 0; i < intermediate_bone_names.size(); i++) { + String prep = "intermediate_bones/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, prep + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, prep + "handle_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, prep + "group_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void RetargetRichProfile::_validate_property(PropertyInfo &property) const { +} + +void RetargetRichProfile::add_group(const StringName &p_group_name, int p_to_index) { + if (p_to_index == -1) { + (const_cast(this)->group_names).push_back(p_group_name); + (const_cast(this)->group_textures).push_back(Ref()); + } else { + ERR_FAIL_INDEX(p_to_index, group_names.size() + 1); + (const_cast(this)->group_names).insert(p_to_index, p_group_name); + (const_cast(this)->group_textures).insert(p_to_index, Ref()); + } +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("group_updated"); + emit_signal("profile_updated"); +} + +void RetargetRichProfile::remove_group(int p_id) { + ERR_FAIL_INDEX(p_id, group_names.size()); + (const_cast(this)->group_names).remove_at(p_id); + (const_cast(this)->group_textures).remove_at(p_id); + int len = intermediate_bone_names.size(); + for (int i = 0; i < len; i++) { + set_intermediate_bone_group_id(i, get_intermediate_bone_group_id(i), false); + } +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("group_updated"); + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +int RetargetRichProfile::find_group(const StringName &p_group_name) { + return group_names.find(p_group_name); +} + +int RetargetRichProfile::get_groups_size() const { + return group_names.size(); +} + +void RetargetRichProfile::set_group_name(int p_id, const StringName &p_group_name) { + ERR_FAIL_INDEX(p_id, group_names.size()); + group_names.write[p_id] = p_group_name; + emit_signal("group_updated"); + emit_signal("profile_updated"); +} + +StringName RetargetRichProfile::get_group_name(int p_id) const { + ERR_FAIL_INDEX_V(p_id, group_names.size(), StringName()); + return group_names[p_id]; +} + +void RetargetRichProfile::set_group_texture(int p_id, const Ref &p_group_texture) { + ERR_FAIL_INDEX(p_id, group_names.size()); + group_textures.write[p_id] = p_group_texture; + emit_signal("group_updated"); + emit_signal("profile_updated"); +} + +Ref RetargetRichProfile::get_group_texture(int p_id) const { + ERR_FAIL_INDEX_V(p_id, group_names.size(), Ref()); + return group_textures[p_id]; +} + +void RetargetRichProfile::add_intermediate_bone(const StringName &p_intermediate_bone_name, int to_index) { + if (to_index == -1) { + (const_cast(this)->intermediate_bone_names).push_back(p_intermediate_bone_name); + (const_cast(this)->intermediate_bone_handle_offsets).push_back(Vector2()); + (const_cast(this)->intermediate_bone_group_ids).push_back(0); + } else { + ERR_FAIL_INDEX(to_index, intermediate_bone_names.size() + 1); + (const_cast(this)->intermediate_bone_names).insert(to_index, p_intermediate_bone_name); + (const_cast(this)->intermediate_bone_handle_offsets).insert(to_index, Vector2()); + (const_cast(this)->intermediate_bone_group_ids).insert(to_index, 0); + } +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +void RetargetRichProfile::remove_intermediate_bone(int p_id) { + ERR_FAIL_INDEX(p_id, intermediate_bone_names.size()); + (const_cast(this)->intermediate_bone_names).remove_at(p_id); + (const_cast(this)->intermediate_bone_handle_offsets).remove_at(p_id); + (const_cast(this)->intermediate_bone_group_ids).remove_at(p_id); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +void RetargetRichProfile::set_intermediate_bone_handle_offset(int p_id, Vector2 p_handle_offset) { + ERR_FAIL_INDEX(p_id, intermediate_bone_names.size()); + intermediate_bone_handle_offsets.write[p_id] = p_handle_offset; + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); +} + +Vector2 RetargetRichProfile::get_intermediate_bone_handle_offset(int p_id) const { + ERR_FAIL_INDEX_V(p_id, intermediate_bone_names.size(), Vector2()); + return intermediate_bone_handle_offsets[p_id]; +} + +void RetargetRichProfile::set_intermediate_bone_group_id(int p_id, int p_group_id, bool p_emit_signal) { + ERR_FAIL_INDEX(p_id, intermediate_bone_names.size()); + intermediate_bone_group_ids.write[p_id] = p_group_id; + if (p_emit_signal) { + emit_signal("intermediate_bone_updated"); + emit_signal("profile_updated"); + } +} + +int RetargetRichProfile::get_intermediate_bone_group_id(int p_id) const { + ERR_FAIL_INDEX_V(p_id, intermediate_bone_names.size(), 0); + return MIN(MAX(0, group_names.size() - 1), intermediate_bone_group_ids[p_id]); +} + +void RetargetRichProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_group", "group_name", "to_index"), &RetargetRichProfile::add_group, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_group", "id"), &RetargetRichProfile::remove_group); + ClassDB::bind_method(D_METHOD("find_group", "group_name"), &RetargetRichProfile::find_group); + ClassDB::bind_method(D_METHOD("get_groups_size"), &RetargetRichProfile::get_groups_size); + ClassDB::bind_method(D_METHOD("set_group_name", "id", "group_name"), &RetargetRichProfile::set_group_name); + ClassDB::bind_method(D_METHOD("get_group_name", "id"), &RetargetRichProfile::get_group_name); + ClassDB::bind_method(D_METHOD("set_group_texture", "id", "group_texture"), &RetargetRichProfile::set_group_texture); + ClassDB::bind_method(D_METHOD("get_group_texture", "id"), &RetargetRichProfile::get_group_texture); + ClassDB::bind_method(D_METHOD("set_intermediate_bone_handle_offset", "id", "handle_offset"), &RetargetRichProfile::set_intermediate_bone_handle_offset); + ClassDB::bind_method(D_METHOD("get_intermediate_bone_handle_offset", "id"), &RetargetRichProfile::get_intermediate_bone_handle_offset); + ClassDB::bind_method(D_METHOD("set_intermediate_bone_group_id", "id", "group_id", "emit_signal"), &RetargetRichProfile::set_intermediate_bone_group_id, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_intermediate_bone_group_id", "id"), &RetargetRichProfile::get_intermediate_bone_group_id); + ADD_SIGNAL(MethodInfo("group_updated")); +} + +RetargetRichProfile::RetargetRichProfile() { +} + +RetargetRichProfile::~RetargetRichProfile() { +} + +// Source setting + +bool RetargetBoneOption::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.split("/").size() < 1) { + return false; + } + + String which = path.get_slicec('/', 0); + String what = path.get_slicec('/', 1); + + if (!retarget_options.has(which)) { + add_key(which); + } + + if (what == "retarget_mode") { + set_retarget_mode(which, Animation::RetargetMode(p_value.operator int())); + } else { + return false; + } + + return true; +} + +bool RetargetBoneOption::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.split("/").size() < 1) { + return false; + } + + String which = path.get_slicec('/', 0); + String what = path.get_slicec('/', 1); + + ERR_FAIL_COND_V(!retarget_options.has(which), false); + + if (what == "retarget_mode") { + r_ret = get_retarget_mode(which); + } else { + return false; + } + + return true; +} + +void RetargetBoneOption::_get_property_list(List *p_list) const { + for (Map::Element *E = retarget_options.front(); E; E = E->next()) { + String prep = String(E->key()) + "/"; + p_list->push_back(PropertyInfo(Variant::INT, prep + "retarget_mode", PROPERTY_HINT_ENUM, "Global,Local,Absolute", PROPERTY_USAGE_NO_EDITOR)); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void RetargetBoneOption::_validate_property(PropertyInfo &property) const { +} + +Vector RetargetBoneOption::get_keys() const { + Vector arr; + for (Map::Element *E = retarget_options.front(); E; E = E->next()) { + arr.push_back(E->key()); + } + return arr; +} + +bool RetargetBoneOption::has_key(const StringName &p_intermediate_bone_name) { + return (const_cast(this)->retarget_options).has(p_intermediate_bone_name); +} + +void RetargetBoneOption::add_key(const StringName &p_intermediate_bone_name) { + RetargetBoneOptionParams params = RetargetBoneOptionParams(); + (const_cast(this)->retarget_options).insert(p_intermediate_bone_name, params); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("retarget_option_updated"); +} + +void RetargetBoneOption::remove_key(const StringName &p_intermediate_bone_name) { + (const_cast(this)->retarget_options).erase(p_intermediate_bone_name); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("retarget_option_updated"); +} + +void RetargetBoneOption::set_retarget_mode(const StringName &p_intermediate_bone_name, Animation::RetargetMode p_retarget_mode) { + ERR_FAIL_COND(!retarget_options.has(p_intermediate_bone_name)); + retarget_options[p_intermediate_bone_name].retarget_mode = p_retarget_mode; + emit_signal("retarget_option_updated"); +} + +Animation::RetargetMode RetargetBoneOption::get_retarget_mode(const StringName &p_intermediate_bone_name) const { + ERR_FAIL_COND_V(!retarget_options.has(p_intermediate_bone_name), Animation::RETARGET_MODE_ABSOLUTE); + return retarget_options[p_intermediate_bone_name].retarget_mode; +} + +#ifdef TOOLS_ENABLED +void RetargetBoneOption::redraw() { + emit_signal("redraw_needed"); +} +#endif // TOOLS_ENABLED + +void RetargetBoneOption::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_keys"), &RetargetBoneOption::get_keys); + ClassDB::bind_method(D_METHOD("has_key", "intermediate_bone_name"), &RetargetBoneOption::has_key); + ClassDB::bind_method(D_METHOD("add_key", "intermediate_bone_name"), &RetargetBoneOption::add_key); + ClassDB::bind_method(D_METHOD("remove_key", "intermediate_bone_name"), &RetargetBoneOption::remove_key); + ClassDB::bind_method(D_METHOD("set_retarget_mode", "intermediate_bone_name", "retarget_mode"), &RetargetBoneOption::set_retarget_mode); + ClassDB::bind_method(D_METHOD("get_retarget_mode", "intermediate_bone_name"), &RetargetBoneOption::get_retarget_mode); + ADD_SIGNAL(MethodInfo("retarget_option_updated")); +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("redraw_needed")); +#endif // TOOLS_ENABLED +} + +RetargetBoneOption::RetargetBoneOption() { +} + +RetargetBoneOption::~RetargetBoneOption() { +} + +// Target setting + +bool RetargetBoneMap::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.split("/").size() < 1) { + return false; + } + + String which = path.get_slicec('/', 0); + String what = path.get_slicec('/', 1); + + if (!retarget_map.has(which)) { + add_key(which); + } + + set_bone_name(which, p_value); + + return true; +} + +bool RetargetBoneMap::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.split("/").size() < 1) { + return false; + } + + String which = path.get_slicec('/', 0); + String what = path.get_slicec('/', 1); + + ERR_FAIL_COND_V(!retarget_map.has(which), false); + + r_ret = get_bone_name(which); + + return true; +} + +void RetargetBoneMap::_get_property_list(List *p_list) const { + for (Map::Element *E = retarget_map.front(); E; E = E->next()) { + p_list->push_back(PropertyInfo(Variant::STRING, E->key(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void RetargetBoneMap::_validate_property(PropertyInfo &property) const { +} + +Vector RetargetBoneMap::get_keys() const { + Vector arr; + for (Map::Element *E = retarget_map.front(); E; E = E->next()) { + arr.push_back(E->key()); + } + return arr; +} + +bool RetargetBoneMap::has_key(const StringName &p_intermediate_bone_name) { + return (const_cast(this)->retarget_map).has(p_intermediate_bone_name); +} + +void RetargetBoneMap::add_key(const StringName &p_intermediate_bone_name) { + (const_cast(this)->retarget_map).insert(p_intermediate_bone_name, StringName()); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("retarget_map_updated"); +} + +void RetargetBoneMap::remove_key(const StringName &p_intermediate_bone_name) { + (const_cast(this)->retarget_map).erase(p_intermediate_bone_name); +#ifdef TOOLS_ENABLED + redraw(); +#endif // TOOLS_ENABLED + emit_signal("retarget_map_updated"); +} + +void RetargetBoneMap::set_bone_name(const StringName &p_intermediate_bone_name, const StringName &p_bone_name) { + ERR_FAIL_COND(!retarget_map.has(p_intermediate_bone_name)); + retarget_map[p_intermediate_bone_name] = p_bone_name; + emit_signal("retarget_map_updated"); +} + +StringName RetargetBoneMap::get_bone_name(const StringName &p_intermediate_bone_name) const { + ERR_FAIL_COND_V(!retarget_map.has(p_intermediate_bone_name), StringName()); + return retarget_map[p_intermediate_bone_name]; +} + +StringName RetargetBoneMap::find_key(const StringName &p_bone_name) const { + StringName found_key = ""; + for (Map::Element *E = retarget_map.front(); E; E = E->next()) { + if (E->get() == p_bone_name) { + found_key = E->key(); + break; + } + } + return found_key; +}; + +#ifdef TOOLS_ENABLED +void RetargetBoneMap::redraw() { + emit_signal("redraw_needed"); +} +#endif // TOOLS_ENABLED + +void RetargetBoneMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_keys"), &RetargetBoneMap::get_keys); + ClassDB::bind_method(D_METHOD("has_key", "intermediate_bone_name"), &RetargetBoneMap::has_key); + ClassDB::bind_method(D_METHOD("add_key", "intermediate_bone_name"), &RetargetBoneMap::add_key); + ClassDB::bind_method(D_METHOD("remove_key", "intermediate_bone_name"), &RetargetBoneMap::remove_key); + ClassDB::bind_method(D_METHOD("find_key", "bone_name"), &RetargetBoneMap::find_key); + ClassDB::bind_method(D_METHOD("set_bone_name", "intermediate_bone_name", "bone_name"), &RetargetBoneMap::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name", "intermediate_bone_name"), &RetargetBoneMap::get_bone_name); + ADD_SIGNAL(MethodInfo("retarget_map_updated")); +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("redraw_needed")); +#endif // TOOLS_ENABLED +} + +RetargetBoneMap::RetargetBoneMap() { +} + +RetargetBoneMap::~RetargetBoneMap() { +} + +// Reatrget Node + +void SkeletonRetarget::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + source_skeleton = Object::cast_to(get_node_or_null(source_skeleton_path)); + if (source_skeleton && !source_skeleton->is_connected("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose))) { + source_skeleton->connect("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose)); + } + } break; + case NOTIFICATION_EXIT_TREE: { +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && retarget_profile->is_connected("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw))) { + retarget_profile->disconnect("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw)); + } +#endif // TOOLS_ENABLED + if (retarget_option.is_valid() && retarget_option->is_connected("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + retarget_option->disconnect("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + if (source_skeleton && source_skeleton->is_connected("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose))) { + source_skeleton->disconnect("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose)); + } + if (source_map.is_valid() && source_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + source_map->disconnect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + if (target_map.is_valid() && target_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + target_map->disconnect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + _clear_override(); + } break; + } +} + +#ifdef TOOLS_ENABLED +void SkeletonRetarget::_redraw() { + if (source_map.is_valid()) { + source_map->redraw(); + } + if (target_map.is_valid()) { + target_map->redraw(); + } +} +#endif // TOOLS_ENABLED + +void SkeletonRetarget::_clear_override() { + Skeleton3D *target_skeleton = Object::cast_to(get_node_or_null(target_skeleton_path)); + if (target_skeleton) { + target_skeleton->clear_bones_local_pose_override(); + } +} + +void SkeletonRetarget::_transpote_pose() { + Skeleton3D *s_sk = Object::cast_to(get_node_or_null(source_skeleton_path)); + Skeleton3D *t_sk = Object::cast_to(get_node_or_null(target_skeleton_path)); + if (s_sk && t_sk && source_map.is_valid() && target_map.is_valid()) { + Vector intermediate_bones = target_map->get_keys(); + int len = intermediate_bones.size(); + for (int i = 0; i < len; i++) { + StringName imbone_name = intermediate_bones[i]; + if (!source_map->has_key(imbone_name) || !target_map->has_key(imbone_name)) { + continue; // Bone is not found in settings. + } + int source_bone = s_sk->find_bone(source_map->get_bone_name(imbone_name)); + int target_bone = t_sk->find_bone(target_map->get_bone_name(imbone_name)); + if (source_bone < 0 || target_bone < 0) { + continue; // Bone is not found in skeletons. + } + + // If use extract_global_retarget_position/rotation/scale(), you get bone_pose_no_override. + // Most of modifications use bone_pose_override, so use extract_global_retarget_transform() + // since it get bone_pose_override. + Transform3D pose = Transform3D(); + if (retarget_option.is_valid() && retarget_option->has_key(imbone_name)) { + switch (retarget_option->get_retarget_mode(imbone_name)) { + case Animation::RETARGET_MODE_GLOBAL: { + pose = t_sk->global_retarget_transform_to_local_pose(target_bone, s_sk->extract_global_retarget_transform(source_bone)); + } break; + case Animation::RETARGET_MODE_LOCAL: { + pose = t_sk->local_retarget_transform_to_local_pose(target_bone, s_sk->extract_local_retarget_transform(source_bone)); + } break; + case Animation::RETARGET_MODE_ABSOLUTE: { + pose = s_sk->get_bone_pose(source_bone); + } break; + default: { + } break; + } + } else { + pose = t_sk->global_retarget_transform_to_local_pose(target_bone, s_sk->extract_global_retarget_transform(source_bone)); + } + + Transform3D final_pose = t_sk->get_bone_rest(target_bone); + if (retarget_position) { + final_pose.origin = pose.get_origin(); + } + if (retarget_rotation) { + final_pose.basis = pose.basis.orthonormalized(); + } + if (retarget_scale) { + final_pose.basis.set_quaternion_scale(final_pose.basis.get_rotation_quaternion(), pose.basis.get_scale()); + } + t_sk->set_bone_local_pose_override(target_bone, final_pose, 1, true); + } + } +} + +void SkeletonRetarget::set_retarget_profile(const Ref &p_retarget_profile) { +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && retarget_profile->is_connected("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw))) { + retarget_profile->disconnect("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw)); + } +#endif // TOOLS_ENABLED + retarget_profile = p_retarget_profile; +#ifdef TOOLS_ENABLED + if (retarget_profile.is_valid() && !retarget_profile->is_connected("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw))) { + retarget_profile->connect("profile_updated", callable_mp(this, &SkeletonRetarget::_redraw)); + } +#endif // TOOLS_ENABLED + notify_property_list_changed(); +} + +Ref SkeletonRetarget::get_retarget_profile() const { + return retarget_profile; +} + +void SkeletonRetarget::set_retarget_option(const Ref &p_retarget_option) { + if (retarget_option.is_valid() && retarget_option->is_connected("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + retarget_option->disconnect("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + _clear_override(); + retarget_option = p_retarget_option; + if (retarget_option.is_valid() && !retarget_option->is_connected("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + retarget_option->connect("retarget_option_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + notify_property_list_changed(); +} + +Ref SkeletonRetarget::get_retarget_option() const { + return retarget_option; +} + +void SkeletonRetarget::set_source_skeleton(const NodePath &p_skeleton) { + ERR_FAIL_COND_MSG(!p_skeleton.is_empty() && !target_skeleton_path.is_empty() && p_skeleton == target_skeleton_path, "The source and target skeletons are not allowed to be the same."); + // Init. + if (source_skeleton && source_skeleton->is_connected("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose))) { + source_skeleton->disconnect("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose)); + } + _clear_override(); + // Set. + source_skeleton_path = p_skeleton; + source_skeleton = Object::cast_to(get_node_or_null(source_skeleton_path)); + if (source_skeleton && !source_skeleton->is_connected("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose))) { + source_skeleton->connect("pose_updated", callable_mp(this, &SkeletonRetarget::_transpote_pose)); + } + notify_property_list_changed(); +} + +NodePath SkeletonRetarget::get_source_skeleton() const { + return source_skeleton_path; +} + +void SkeletonRetarget::set_source_map(const Ref &p_source_map) { + ERR_FAIL_COND_MSG(p_source_map.is_valid() && target_map.is_valid() && p_source_map == target_map, "The source and target maps are not allowed to be the same. You should make one of them unique or duplicate it."); + if (source_map.is_valid() && source_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + source_map->disconnect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + _clear_override(); + source_map = p_source_map; + if (source_map.is_valid() && !source_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + source_map->connect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } +} + +Ref SkeletonRetarget::get_source_map() const { + return source_map; +} + +void SkeletonRetarget::set_target_skeleton(const NodePath &p_skeleton) { + ERR_FAIL_COND_MSG(!p_skeleton.is_empty() && !source_skeleton_path.is_empty() && p_skeleton == source_skeleton_path, "The source and target skeletons are not allowed to be the same."); + // Init. + _clear_override(); + // Set. + target_skeleton_path = p_skeleton; + notify_property_list_changed(); +} + +NodePath SkeletonRetarget::get_target_skeleton() const { + return target_skeleton_path; +} + +void SkeletonRetarget::set_target_map(const Ref &p_target_map) { + ERR_FAIL_COND_MSG(p_target_map.is_valid() && source_map.is_valid() && p_target_map == source_map, "The source and target maps are not allowed to be the same. You should make one of them unique or duplicate it."); + if (target_map.is_valid() && target_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + target_map->disconnect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } + _clear_override(); + target_map = p_target_map; + if (target_map.is_valid() && !target_map->is_connected("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override))) { + target_map->connect("retarget_map_updated", callable_mp(this, &SkeletonRetarget::_clear_override)); + } +} + +Ref SkeletonRetarget::get_target_map() const { + return target_map; +} + +void SkeletonRetarget::set_retarget_position(bool p_enabled) { + retarget_position = p_enabled; +} + +bool SkeletonRetarget::is_retarget_position() const { + return retarget_position; +} + +void SkeletonRetarget::set_retarget_rotation(bool p_enabled) { + retarget_rotation = p_enabled; +} + +bool SkeletonRetarget::is_retarget_rotation() const { + return retarget_rotation; +} + +void SkeletonRetarget::set_retarget_scale(bool p_enabled) { + retarget_scale = p_enabled; +} + +bool SkeletonRetarget::is_retarget_scale() const { + return retarget_scale; +} + +void SkeletonRetarget::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_retarget_profile", "retarget_profile"), &SkeletonRetarget::set_retarget_profile); + ClassDB::bind_method(D_METHOD("get_retarget_profile"), &SkeletonRetarget::get_retarget_profile); + ClassDB::bind_method(D_METHOD("set_retarget_option", "retarget_option"), &SkeletonRetarget::set_retarget_option); + ClassDB::bind_method(D_METHOD("get_retarget_option"), &SkeletonRetarget::get_retarget_option); + ClassDB::bind_method(D_METHOD("set_source_skeleton", "source_skeleton_path"), &SkeletonRetarget::set_source_skeleton); + ClassDB::bind_method(D_METHOD("get_source_skeleton"), &SkeletonRetarget::get_source_skeleton); + ClassDB::bind_method(D_METHOD("set_source_map", "source_map"), &SkeletonRetarget::set_source_map); + ClassDB::bind_method(D_METHOD("get_source_map"), &SkeletonRetarget::get_source_map); + ClassDB::bind_method(D_METHOD("set_target_skeleton", "target_skeleton_path"), &SkeletonRetarget::set_target_skeleton); + ClassDB::bind_method(D_METHOD("get_target_skeleton"), &SkeletonRetarget::get_target_skeleton); + ClassDB::bind_method(D_METHOD("set_target_map", "target_map"), &SkeletonRetarget::set_target_map); + ClassDB::bind_method(D_METHOD("get_target_map"), &SkeletonRetarget::get_target_map); + ClassDB::bind_method(D_METHOD("set_retarget_position", "retarget_position"), &SkeletonRetarget::set_retarget_position); + ClassDB::bind_method(D_METHOD("is_retarget_position"), &SkeletonRetarget::is_retarget_position); + ClassDB::bind_method(D_METHOD("set_retarget_rotation", "retarget_rotation"), &SkeletonRetarget::set_retarget_rotation); + ClassDB::bind_method(D_METHOD("is_retarget_rotation"), &SkeletonRetarget::is_retarget_rotation); + ClassDB::bind_method(D_METHOD("set_retarget_scale", "retarget_scale"), &SkeletonRetarget::set_retarget_scale); + ClassDB::bind_method(D_METHOD("is_retarget_scale"), &SkeletonRetarget::is_retarget_scale); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "retarget_profile", PROPERTY_HINT_RESOURCE_TYPE, "RetargetProfile"), "set_retarget_profile", "get_retarget_profile"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "retarget_option", PROPERTY_HINT_RESOURCE_TYPE, "RetargetBoneOption"), "set_retarget_option", "get_retarget_option"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "source_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_source_skeleton", "get_source_skeleton"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "source_map", PROPERTY_HINT_RESOURCE_TYPE, "RetargetBoneMap"), "set_source_map", "get_source_map"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_target_skeleton", "get_target_skeleton"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "target_map", PROPERTY_HINT_RESOURCE_TYPE, "RetargetBoneMap"), "set_target_map", "get_target_map"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "retarget_position"), "set_retarget_position", "is_retarget_position"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "retarget_rotation"), "set_retarget_rotation", "is_retarget_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "retarget_scale"), "set_retarget_scale", "is_retarget_scale"); +} + +SkeletonRetarget::SkeletonRetarget() { +} + +SkeletonRetarget::~SkeletonRetarget() { +} diff --git a/scene/animation/skeleton_retarget.h b/scene/animation/skeleton_retarget.h new file mode 100644 index 000000000000..bdc8bcf6862c --- /dev/null +++ b/scene/animation/skeleton_retarget.h @@ -0,0 +1,233 @@ +/*************************************************************************/ +/* skeleton_retarget.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef SKELETON_RETARGET_H +#define SKELETON_RETARGET_H + +#include "core/io/resource.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/animation.h" + +// Resources + +class RetargetProfile : public Resource { + GDCLASS(RetargetProfile, Resource); + +protected: + Vector intermediate_bone_names; + + virtual bool _get(const StringName &p_path, Variant &r_ret) const; + virtual bool _set(const StringName &p_path, const Variant &p_value); + virtual void _get_property_list(List *p_list) const; + virtual void _validate_property(PropertyInfo &property) const override; + static void _bind_methods(); +#ifdef TOOLS_ENABLED + void redraw(); +#endif // TOOLS_ENABLED + +public: + virtual void add_intermediate_bone(const StringName &p_intermediate_bone_name, int to_index = -1); + virtual void remove_intermediate_bone(int p_id); + int find_intermediate_bone(const StringName &p_intermediate_bone_name); + int get_intermediate_bones_size(); + + void set_intermediate_bone_name(int p_id, const StringName &p_intermediate_bone_name); + StringName get_intermediate_bone_name(int p_id) const; + + RetargetProfile(); + ~RetargetProfile(); +}; + +class RetargetRichProfile : public RetargetProfile { + GDCLASS(RetargetRichProfile, RetargetProfile); + +protected: + Vector group_names; + Vector> group_textures; + + Vector intermediate_bone_handle_offsets; + Vector intermediate_bone_group_ids; + + virtual bool _get(const StringName &p_path, Variant &r_ret) const override; + virtual bool _set(const StringName &p_path, const Variant &p_value) override; + virtual void _get_property_list(List *p_list) const override; + virtual void _validate_property(PropertyInfo &property) const override; + static void _bind_methods(); + +public: + // Group settings + void add_group(const StringName &p_group_name, int to_index = -1); + void remove_group(int p_id); + int find_group(const StringName &p_group_name); + int get_groups_size() const; + + void set_group_name(int p_id, const StringName &p_group_name); + StringName get_group_name(int p_id) const; + void set_group_texture(int p_id, const Ref &p_group_texture); + Ref get_group_texture(int p_id) const; + + // Intermediate bones + virtual void add_intermediate_bone(const StringName &p_intermediate_bone_name, int to_index = -1) override; + virtual void remove_intermediate_bone(int p_id) override; + + void set_intermediate_bone_handle_offset(int p_id, Vector2 p_handle_offset); + Vector2 get_intermediate_bone_handle_offset(int p_id) const; + void set_intermediate_bone_group_id(int p_id, int p_group_id, bool p_emit_signal = true); + int get_intermediate_bone_group_id(int p_id) const; + + RetargetRichProfile(); + ~RetargetRichProfile(); +}; + +class RetargetBoneOption : public Resource { + GDCLASS(RetargetBoneOption, Resource); + +public: + struct RetargetBoneOptionParams { + Animation::RetargetMode retarget_mode = Animation::RETARGET_MODE_GLOBAL; + }; + +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; + virtual void _validate_property(PropertyInfo &property) const override; + static void _bind_methods(); + +private: + Map retarget_options; + +public: + Vector get_keys() const; + + bool has_key(const StringName &p_intermediate_bone_name); + void add_key(const StringName &p_intermediate_bone_name); + void remove_key(const StringName &p_intermediate_bone_name); + + void set_retarget_mode(const StringName &p_intermediate_bone_name, Animation::RetargetMode p_retarget_mode); + Animation::RetargetMode get_retarget_mode(const StringName &p_intermediate_bone_name) const; +#ifdef TOOLS_ENABLED + void redraw(); +#endif // TOOLS_ENABLED + + RetargetBoneOption(); + ~RetargetBoneOption(); +}; + +class RetargetBoneMap : public Resource { + GDCLASS(RetargetBoneMap, Resource); + +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; + virtual void _validate_property(PropertyInfo &property) const override; + static void _bind_methods(); + +private: + Map retarget_map; + +public: + Vector get_keys() const; + + bool has_key(const StringName &p_intermediate_bone_name); + void add_key(const StringName &p_intermediate_bone_name); + void remove_key(const StringName &p_intermediate_bone_name); + StringName find_key(const StringName &p_bone_name) const; + + void set_bone_name(const StringName &p_intermediate_bone_name, const StringName &p_bone_name); + StringName get_bone_name(const StringName &p_intermediate_bone_name) const; +#ifdef TOOLS_ENABLED + void redraw(); +#endif // TOOLS_ENABLED + + RetargetBoneMap(); + ~RetargetBoneMap(); +}; + +// Retarget Node + +class SkeletonRetarget : public Node { + GDCLASS(SkeletonRetarget, Node); + + Ref retarget_profile; + Ref retarget_option; + NodePath source_skeleton_path; + Ref source_map; + NodePath target_skeleton_path; + Ref target_map; + + bool retarget_position = false; + bool retarget_rotation = true; + bool retarget_scale = false; + +public: + void set_retarget_profile(const Ref &p_retarget_profile); + Ref get_retarget_profile() const; + void set_retarget_option(const Ref &p_retarget_option); + Ref get_retarget_option() const; + + void set_source_skeleton(const NodePath &p_skeleton); + NodePath get_source_skeleton() const; + void set_source_map(const Ref &p_source_map); + Ref get_source_map() const; + + void set_target_skeleton(const NodePath &p_skeleton); + NodePath get_target_skeleton() const; + void set_target_map(const Ref &p_target_map); + Ref get_target_map() const; + + void set_retarget_position(bool p_enabled); + bool is_retarget_position() const; + void set_retarget_rotation(bool p_enabled); + bool is_retarget_rotation() const; + void set_retarget_scale(bool p_enabled); + bool is_retarget_scale() const; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +private: + Skeleton3D *source_skeleton = nullptr; + + void _transpote_pose(); + void _clear_override(); + +#ifdef TOOLS_ENABLED + void _redraw(); +#endif // TOOLS_ENABLED + + SkeletonRetarget(); + ~SkeletonRetarget(); +}; + +#endif // SKELETON_RETARGET_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index fb5d57ab9ecf..d6b36505336f 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -76,6 +76,7 @@ #include "scene/animation/animation_player.h" #include "scene/animation/animation_tree.h" #include "scene/animation/root_motion_view.h" +#include "scene/animation/skeleton_retarget.h" #include "scene/animation/tween.h" #include "scene/audio/audio_stream_player.h" #include "scene/debugger/scene_debugger.h" @@ -524,6 +525,12 @@ void register_scene_types() { GDREGISTER_CLASS(SkeletonIK3D); GDREGISTER_CLASS(BoneAttachment3D); + GDREGISTER_CLASS(SkeletonRetarget); + GDREGISTER_CLASS(RetargetProfile); + GDREGISTER_CLASS(RetargetRichProfile); + GDREGISTER_CLASS(RetargetBoneOption); + GDREGISTER_CLASS(RetargetBoneMap); + GDREGISTER_CLASS(VehicleBody3D); GDREGISTER_CLASS(VehicleWheel3D); GDREGISTER_CLASS(Area3D); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index f6e0df0265ce..a327c5c32848 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -127,6 +127,26 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } } return true; +#ifndef _3D_DISABLED + } else if (what == "retarget_mode") { + if (track_is_retarget(track)) { + if (track_get_type(track) == TYPE_POSITION_3D) { + position_track_set_retarget_mode(track, RetargetMode(p_value.operator int())); + } else if (track_get_type(track) == TYPE_ROTATION_3D) { + rotation_track_set_retarget_mode(track, RetargetMode(p_value.operator int())); + } else if (track_get_type(track) == TYPE_SCALE_3D) { + scale_track_set_retarget_mode(track, RetargetMode(p_value.operator int())); + } else { + return false; + } + } else { + return false; + } +#endif // _3D_DISABLED + } else if (what == "auto_volume") { + if (track_get_type(track) == TYPE_AUDIO) { + audio_track_set_auto_volume(track, p_value); + } } else if (what == "interp") { track_set_interpolation_type(track, InterpolationType(p_value.operator int())); } else if (what == "loop_wrap") { @@ -520,7 +540,26 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { } return true; - +#ifndef _3D_DISABLED + } else if (what == "retarget_mode") { + if (track_is_retarget(track)) { + if (track_get_type(track) == TYPE_POSITION_3D) { + r_ret = position_track_get_retarget_mode(track); + } else if (track_get_type(track) == TYPE_ROTATION_3D) { + r_ret = rotation_track_get_retarget_mode(track); + } else if (track_get_type(track) == TYPE_SCALE_3D) { + r_ret = scale_track_get_retarget_mode(track); + } else { + return false; + } + } else { + return false; + } +#endif // _3D_DISABLED + } else if (what == "auto_volume") { + if (track_get_type(track) == TYPE_AUDIO) { + r_ret = audio_track_get_auto_volume(track); + } } else if (what == "interp") { r_ret = track_get_interpolation_type(track); } else if (what == "loop_wrap") { @@ -815,6 +854,14 @@ void Animation::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } + if (track_get_type(i) == TYPE_AUDIO) { + p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/auto_volume", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +#ifndef _3D_DISABLED + if (track_is_retarget(i)) { + p_list->push_back(PropertyInfo(Variant::INT, "tracks/" + itos(i) + "/retarget_mode", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +#endif // _3D_DISABLED } } @@ -1089,6 +1136,24 @@ Error Animation::position_track_interpolate(int p_track, double p_time, Vector3 return OK; } +#ifndef _3D_DISABLED +void Animation::position_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_POSITION_3D); + PositionTrack *tt = static_cast(t); + tt->retarget_mode = p_retarget_mode; +} + +Animation::RetargetMode Animation::position_track_get_retarget_mode(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), Animation::RETARGET_MODE_ABSOLUTE); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_POSITION_3D, Animation::RETARGET_MODE_ABSOLUTE); + PositionTrack *tt = static_cast(t); + return tt->retarget_mode; +} +#endif // _3D_DISABLED + //// int Animation::rotation_track_insert_key(int p_track, double p_time, const Quaternion &p_rotation) { @@ -1161,6 +1226,24 @@ Error Animation::rotation_track_interpolate(int p_track, double p_time, Quaterni return OK; } +#ifndef _3D_DISABLED +void Animation::rotation_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_ROTATION_3D); + RotationTrack *rt = static_cast(t); + rt->retarget_mode = p_retarget_mode; +} + +Animation::RetargetMode Animation::rotation_track_get_retarget_mode(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), Animation::RETARGET_MODE_ABSOLUTE); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_ROTATION_3D, Animation::RETARGET_MODE_ABSOLUTE); + RotationTrack *rt = static_cast(t); + return rt->retarget_mode; +} +#endif // _3D_DISABLED + //// int Animation::scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale) { @@ -1233,6 +1316,26 @@ Error Animation::scale_track_interpolate(int p_track, double p_time, Vector3 *r_ return OK; } +#ifndef _3D_DISABLED +void Animation::scale_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_SCALE_3D); + ScaleTrack *st = static_cast(t); + st->retarget_mode = p_retarget_mode; +} + +Animation::RetargetMode Animation::scale_track_get_retarget_mode(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), Animation::RETARGET_MODE_ABSOLUTE); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_SCALE_3D, Animation::RETARGET_MODE_ABSOLUTE); + ScaleTrack *st = static_cast(t); + return st->retarget_mode; +} +#endif // _3D_DISABLED + +//// + int Animation::blend_shape_track_insert_key(int p_track, double p_time, float p_blend_shape) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -2081,6 +2184,24 @@ bool Animation::track_is_compressed(int p_track) const { ERR_FAIL_V(false); } +bool Animation::track_is_retarget(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), false); + Track *t = tracks[p_track]; + if (track_get_path(p_track).get_name_count() == 0 && track_get_path(p_track).get_subname_count() == 1) { + switch (t->type) { + case TYPE_POSITION_3D: + case TYPE_ROTATION_3D: + case TYPE_SCALE_3D: { + return true; + } break; + default: { + return false; + } break; + } + } + return false; +} + void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p_value) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; @@ -3568,6 +3689,27 @@ real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { return at->values[p_key].value.end_offset; } +void Animation::audio_track_set_auto_volume(int p_track, bool p_enable) { + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast(t); + + at->auto_volume = p_enable; + emit_changed(); +} + +bool Animation::audio_track_get_auto_volume(int p_track) const { + ERR_FAIL_INDEX_V(p_track, tracks.size(), false); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, false); + + AudioTrack *at = static_cast(t); + + return at->auto_volume; +} + // int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) { @@ -3756,6 +3898,15 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("scale_track_insert_key", "track_idx", "time", "scale"), &Animation::scale_track_insert_key); ClassDB::bind_method(D_METHOD("blend_shape_track_insert_key", "track_idx", "time", "amount"), &Animation::blend_shape_track_insert_key); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("position_track_set_retarget_mode", "track_idx", "retarget_mode"), &Animation::position_track_set_retarget_mode); + ClassDB::bind_method(D_METHOD("rotation_track_set_retarget_mode", "track_idx", "retarget_mode"), &Animation::rotation_track_set_retarget_mode); + ClassDB::bind_method(D_METHOD("scale_track_set_retarget_mode", "track_idx", "retarget_mode"), &Animation::scale_track_set_retarget_mode); + ClassDB::bind_method(D_METHOD("position_track_get_retarget_mode", "track_idx"), &Animation::position_track_get_retarget_mode); + ClassDB::bind_method(D_METHOD("rotation_track_get_retarget_mode", "track_idx"), &Animation::rotation_track_get_retarget_mode); + ClassDB::bind_method(D_METHOD("scale_track_get_retarget_mode", "track_idx"), &Animation::scale_track_get_retarget_mode); +#endif // _3D_DISABLED + ClassDB::bind_method(D_METHOD("track_insert_key", "track_idx", "time", "key", "transition"), &Animation::track_insert_key, DEFVAL(1)); ClassDB::bind_method(D_METHOD("track_remove_key", "track_idx", "key_idx"), &Animation::track_remove_key); ClassDB::bind_method(D_METHOD("track_remove_key_at_time", "track_idx", "time"), &Animation::track_remove_key_at_time); @@ -3806,6 +3957,8 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "track_idx", "key_idx"), &Animation::audio_track_get_key_stream); ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset); ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset); + ClassDB::bind_method(D_METHOD("audio_track_set_auto_volume", "track_idx", "enable"), &Animation::audio_track_set_auto_volume); + ClassDB::bind_method(D_METHOD("audio_track_get_auto_volume", "track_idx"), &Animation::audio_track_get_auto_volume); ClassDB::bind_method(D_METHOD("bezier_track_set_key_handle_mode", "track_idx", "key_idx", "key_handle_mode", "balanced_value_time_ratio"), &Animation::bezier_track_set_key_handle_mode, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("bezier_track_get_key_handle_mode", "track_idx", "key_idx"), &Animation::bezier_track_get_key_handle_mode); @@ -3859,6 +4012,10 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(HANDLE_MODE_FREE); BIND_ENUM_CONSTANT(HANDLE_MODE_BALANCED); + + BIND_ENUM_CONSTANT(RETARGET_MODE_GLOBAL); + BIND_ENUM_CONSTANT(RETARGET_MODE_LOCAL); + BIND_ENUM_CONSTANT(RETARGET_MODE_ABSOLUTE); } void Animation::clear() { diff --git a/scene/resources/animation.h b/scene/resources/animation.h index f9a33da4287d..171db8c7005d 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -77,6 +77,12 @@ class Animation : public Resource { HANDLE_MODE_BALANCED, }; + enum RetargetMode { + RETARGET_MODE_GLOBAL, + RETARGET_MODE_LOCAL, + RETARGET_MODE_ABSOLUTE, + }; + private: struct Track { TrackType type = TrackType::TYPE_ANIMATION; @@ -110,6 +116,7 @@ class Animation : public Resource { struct PositionTrack : public Track { Vector> positions; int32_t compressed_track = -1; + RetargetMode retarget_mode = RETARGET_MODE_ABSOLUTE; PositionTrack() { type = TYPE_POSITION_3D; } }; @@ -118,6 +125,7 @@ class Animation : public Resource { struct RotationTrack : public Track { Vector> rotations; int32_t compressed_track = -1; + RetargetMode retarget_mode = RETARGET_MODE_ABSOLUTE; RotationTrack() { type = TYPE_ROTATION_3D; } }; @@ -126,6 +134,7 @@ class Animation : public Resource { struct ScaleTrack : public Track { Vector> scales; int32_t compressed_track = -1; + RetargetMode retarget_mode = RETARGET_MODE_ABSOLUTE; ScaleTrack() { type = TYPE_SCALE_3D; } }; @@ -189,6 +198,7 @@ class Animation : public Resource { struct AudioTrack : public Track { Vector> values; + bool auto_volume = false; AudioTrack() { type = TYPE_AUDIO; @@ -404,6 +414,7 @@ class Animation : public Resource { double track_get_key_time(int p_track, int p_key_idx) const; real_t track_get_key_transition(int p_track, int p_key_idx) const; bool track_is_compressed(int p_track) const; + bool track_is_retarget(int p_track) const; int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position); Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const; @@ -417,6 +428,15 @@ class Animation : public Resource { Error scale_track_get_key(int p_track, int p_key, Vector3 *r_scale) const; Error scale_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const; +#ifndef _3D_DISABLED + void position_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode); + void rotation_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode); + void scale_track_set_retarget_mode(int p_track, RetargetMode p_retarget_mode); + RetargetMode position_track_get_retarget_mode(int p_track) const; + RetargetMode rotation_track_get_retarget_mode(int p_track) const; + RetargetMode scale_track_get_retarget_mode(int p_track) const; +#endif // _3D_DISABLED + int blend_shape_track_insert_key(int p_track, double p_time, float p_blend); Error blend_shape_track_get_key(int p_track, int p_key, float *r_blend) const; Error blend_shape_track_interpolate(int p_track, double p_time, float *r_blend) const; @@ -443,6 +463,8 @@ class Animation : public Resource { RES audio_track_get_key_stream(int p_track, int p_key) const; real_t audio_track_get_key_start_offset(int p_track, int p_key) const; real_t audio_track_get_key_end_offset(int p_track, int p_key) const; + void audio_track_set_auto_volume(int p_track, bool p_enable); + bool audio_track_get_auto_volume(int p_track) const; int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation); void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation); @@ -487,5 +509,6 @@ VARIANT_ENUM_CAST(Animation::InterpolationType); VARIANT_ENUM_CAST(Animation::UpdateMode); VARIANT_ENUM_CAST(Animation::HandleMode); VARIANT_ENUM_CAST(Animation::LoopMode); +VARIANT_ENUM_CAST(Animation::RetargetMode); #endif