Implement Animation Blend Shape Tracks

* New track type BLEND_SHAPE
* Blend shapes are imported via this new track type
* Processing is more optimized (no longer relies on variants)
* Modified the Blend Shape API in MeshInstance3D to use indices rather than StringNames (more optimizes)
* Promo: Fixed a small bug in gizmo updating in Node3D that affected performance

Dedicated BlendShape tracks are required for both optimization and eventually implementing them in animation compression.
This commit is contained in:
reduz 2021-10-15 19:04:35 -03:00
parent 96410f55b2
commit ae1c016547
18 changed files with 734 additions and 101 deletions

View file

@ -201,6 +201,14 @@
Sets the value of the key identified by [code]key_idx[/code] to the given value. The [code]track_idx[/code] must be the index of a Bezier Track.
</description>
</method>
<method name="blend_shape_track_insert_key">
<return type="int" />
<argument index="0" name="track_idx" type="int" />
<argument index="1" name="time" type="float" />
<argument index="2" name="amount" type="float" />
<description>
</description>
</method>
<method name="clear">
<return type="void" />
<description>
@ -552,16 +560,18 @@
</constant>
<constant name="TYPE_SCALE_3D" value="3" enum="TrackType">
</constant>
<constant name="TYPE_METHOD" value="4" enum="TrackType">
<constant name="TYPE_BLEND_SHAPE" value="4" enum="TrackType">
</constant>
<constant name="TYPE_METHOD" value="5" enum="TrackType">
Method tracks call functions with given arguments per key.
</constant>
<constant name="TYPE_BEZIER" value="5" enum="TrackType">
<constant name="TYPE_BEZIER" value="6" enum="TrackType">
Bezier tracks are used to interpolate a value using custom curves. They can also be used to animate sub-properties of vectors and colors (e.g. alpha value of a [Color]).
</constant>
<constant name="TYPE_AUDIO" value="6" enum="TrackType">
<constant name="TYPE_AUDIO" value="7" enum="TrackType">
Audio tracks are used to play an audio stream with either type of [AudioStreamPlayer]. The stream can be trimmed and previewed in the animation.
</constant>
<constant name="TYPE_ANIMATION" value="7" enum="TrackType">
<constant name="TYPE_ANIMATION" value="8" enum="TrackType">
Animation tracks play animations in other [AnimationPlayer] nodes.
</constant>
<constant name="INTERPOLATION_NEAREST" value="0" enum="InterpolationType">

View file

@ -41,6 +41,12 @@
This helper creates a [StaticBody3D] child node with a [ConcavePolygonShape3D] collision shape calculated from the mesh geometry. It's mainly used for testing.
</description>
</method>
<method name="find_blend_shape_by_name">
<return type="int" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="get_active_material" qualifiers="const">
<return type="Material" />
<argument index="0" name="surface" type="int" />
@ -48,6 +54,17 @@
Returns the [Material] that will be used by the [Mesh] when drawing. This can return the [member GeometryInstance3D.material_override], the surface override [Material] defined in this [MeshInstance3D], or the surface [Material] defined in the [Mesh]. For example, if [member GeometryInstance3D.material_override] is used, all surfaces will return the override material.
</description>
</method>
<method name="get_blend_shape_count" qualifiers="const">
<return type="int" />
<description>
</description>
</method>
<method name="get_blend_shape_value" qualifiers="const">
<return type="float" />
<argument index="0" name="blend_shape_idx" type="int" />
<description>
</description>
</method>
<method name="get_surface_override_material" qualifiers="const">
<return type="Material" />
<argument index="0" name="surface" type="int" />
@ -61,6 +78,13 @@
Returns the number of surface override materials. This is equivalent to [method Mesh.get_surface_count].
</description>
</method>
<method name="set_blend_shape_value">
<return type="void" />
<argument index="0" name="blend_shape_idx" type="int" />
<argument index="1" name="value" type="float" />
<description>
</description>
</method>
<method name="set_surface_override_material">
<return type="void" />
<argument index="0" name="surface" type="int" />

View file

@ -198,6 +198,7 @@ public:
}
} break;
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
if (name == "value") {
Variant value = p_value;
@ -438,6 +439,7 @@ public:
return true;
}
} break;
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
if (name == "value") {
r_ret = animation->track_get_key_value(track, key);
@ -551,6 +553,9 @@ public:
case Animation::TYPE_SCALE_3D: {
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
} break;
case Animation::TYPE_BLEND_SHAPE: {
p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
} break;
case Animation::TYPE_VALUE: {
Variant v = animation->track_get_key_value(track, key);
@ -828,6 +833,7 @@ public:
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
update_obj = true;
} break;
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
if (name == "value") {
Variant value = p_value;
@ -1054,6 +1060,7 @@ public:
}
} break;
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
if (name == "value") {
r_ret = animation->track_get_key_value(track, key);
@ -1206,6 +1213,9 @@ public:
case Animation::TYPE_SCALE_3D: {
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
} break;
case Animation::TYPE_BLEND_SHAPE: {
p_list->push_back(PropertyInfo(Variant::FLOAT, "value"));
} break;
case Animation::TYPE_VALUE: {
if (same_key_type) {
Variant v = animation->track_get_key_value(first_track, first_key);
@ -1404,6 +1414,7 @@ void AnimationTimelineEdit::_notification(int p_what) {
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")), TTR("3D Position Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")), TTR("3D Rotation Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")), TTR("3D Scale Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBlendShape"), SNAME("EditorIcons")), TTR("Blend Shape Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")), TTR("Call Method Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")), TTR("Bezier Curve Track"));
add_track->get_popup()->add_icon_item(get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")), TTR("Audio Playback Track"));
@ -1872,11 +1883,12 @@ void AnimationTrackEdit::_notification(int p_what) {
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
Color color = get_theme_color(SNAME("font_color"), SNAME("Label"));
Ref<Texture2D> type_icons[8] = {
Ref<Texture2D> type_icons[9] = {
get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyTrackPosition"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyTrackRotation"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyTrackScale"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyTrackBlendShape"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")),
@ -2067,7 +2079,7 @@ void AnimationTrackEdit::_notification(int p_what) {
interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
interp_mode_rect.size = icon->get_size();
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
draw_texture(icon, interp_mode_rect.position);
}
// Make it easier to click.
@ -2077,7 +2089,7 @@ void AnimationTrackEdit::_notification(int p_what) {
ofs += icon->get_width() + hsep;
interp_mode_rect.size.x += hsep;
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
interp_mode_rect.size.x += down_icon->get_width();
} else {
@ -2100,7 +2112,7 @@ void AnimationTrackEdit::_notification(int p_what) {
loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
loop_mode_rect.size = icon->get_size();
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
draw_texture(icon, loop_mode_rect.position);
}
@ -2110,7 +2122,7 @@ void AnimationTrackEdit::_notification(int p_what) {
ofs += icon->get_width() + hsep;
loop_mode_rect.size.x += hsep;
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
loop_mode_rect.size.x += down_icon->get_width();
} else {
@ -2330,11 +2342,12 @@ void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animati
track = p_track;
update();
Ref<Texture2D> type_icons[8] = {
Ref<Texture2D> type_icons[9] = {
get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyXPosition"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyXRotation"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyXScale"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyBlendShape"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyCall"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyBezier"), SNAME("EditorIcons")),
get_theme_icon(SNAME("KeyAudio"), SNAME("EditorIcons")),
@ -2516,6 +2529,10 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
Vector3 t = animation->track_get_key_value(track, key_idx);
text += "Scale: " + String(t) + "\n";
} break;
case Animation::TYPE_BLEND_SHAPE: {
float t = animation->track_get_key_value(track, key_idx);
text += "Blend Shape: " + itos(t) + "\n";
} break;
case Animation::TYPE_VALUE: {
const Variant &v = animation->track_get_key_value(track, key_idx);
text += "Type: " + Variant::get_type_name(v.get_type()) + "\n";
@ -3369,6 +3386,8 @@ static bool track_type_is_resettable(Animation::TrackType p_type) {
switch (p_type) {
case Animation::TYPE_VALUE:
[[fallthrough]];
case Animation::TYPE_BLEND_SHAPE:
[[fallthrough]];
case Animation::TYPE_BEZIER:
[[fallthrough]];
case Animation::TYPE_POSITION_3D:
@ -4049,6 +4068,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
case Animation::TYPE_SCALE_3D:
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
value = p_id.value;
@ -4468,6 +4488,11 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
ERR_FAIL_COND(!node);
NodePath path_to = root->get_path_to(node);
if (adding_track_type == Animation::TYPE_BLEND_SHAPE && !node->is_class("MeshInstance3D")) {
EditorNode::get_singleton()->show_warning(TTR("Blend Shape tracks only apply to MeshInstance3D nodes."));
return;
}
if ((adding_track_type == Animation::TYPE_POSITION_3D || adding_track_type == Animation::TYPE_ROTATION_3D || adding_track_type == Animation::TYPE_SCALE_3D) && !node->is_class("Node3D")) {
EditorNode::get_singleton()->show_warning(TTR("Position/Rotation/Scale 3D tracks only apply to 3D-based nodes."));
return;
@ -4479,6 +4504,13 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
prop_selector->set_type_filter(Vector<Variant::Type>());
prop_selector->select_property_from_instance(node);
} break;
case Animation::TYPE_BLEND_SHAPE: {
adding_track_path = path_to;
Vector<Variant::Type> filter;
filter.push_back(Variant::FLOAT);
prop_selector->set_type_filter(filter);
prop_selector->select_property_from_instance(node);
} break;
case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D:
case Animation::TYPE_SCALE_3D:
@ -4710,6 +4742,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
undo_redo->commit_action();
} break;
case Animation::TYPE_BLEND_SHAPE:
case Animation::TYPE_VALUE: {
NodePath bp;
Variant value;
@ -5388,6 +5421,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
case Animation::TYPE_SCALE_3D:
text += " (Scale)";
break;
case Animation::TYPE_BLEND_SHAPE:
text += " (BlendShape)";
break;
case Animation::TYPE_METHOD:
text += " (Methods)";
break;

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="10"
viewBox="0 0 10 10"
width="10"
version="1.1"
id="svg80"
sodipodi:docname="KeyBlendShape.svg"
inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs84" />
<sodipodi:namedview
id="namedview82"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="84.4"
inkscape:cx="2.6599526"
inkscape:cy="5.0059242"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="72"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg80" />
<rect
fill="#3cf34e"
height="6.1027"
ry=".76286"
transform="matrix(.70710678 -.70710678 .70710678 .70710678 0 -1042.4)"
width="6.1027"
x="-740.13947"
y="741.10779"
id="rect78"
style="fill:#5ad5c4;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="10"
viewBox="0 0 10 10"
width="10"
version="1.1"
id="svg12"
sodipodi:docname="KeyTrackBlendShape.svg"
inkscape:version="1.1 (1:1.1+202106031931+af4d65493e)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs16" />
<sodipodi:namedview
id="namedview14"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="29.839906"
inkscape:cx="-3.5690461"
inkscape:cy="9.0985541"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="72"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<ellipse
style="fill:none;fill-opacity:0.401212;stroke:none;stroke-width:4.7811;stroke-linejoin:round"
id="path921"
cx="-0.88986981"
cy="6.0959954"
rx="1.2495773"
ry="1.0867468" />
<path
id="path1910"
style="color:#000000;fill:#5ad5c4;stroke-linejoin:round;-inkscape-stroke:none"
d="m 4.5230825,1.1341567 c -2.1310744,0.017055 -3.86718737,1.7635044 -3.86718737,3.8984375 0,1.8778511 1.34348597,3.4523891 3.11718737,3.8164061 L 3.95277,7.5794693 C 2.7929991,7.3095662 1.9351921,6.2780435 1.9351921,5.0325942 c 0,-1.4262775 1.123493,-2.5732858 2.5390622,-2.6152344 v 0.017578 h 0.2011719 l 0.1796875,-1.28125 H 4.5230825 v -0.011719 c 0,-0.00263 -2.64e-5,-0.00518 0,-0.00781 z m 1.6816406,0.019531 -0.1796875,1.28125 h 1.3085937 c 0.078866,0 0.1230469,0.044181 0.1230469,0.1230469 v 4.9882815 c 0,0.07887 -0.044181,0.121093 -0.1230469,0.121094 H 5.2887075 L 5.10902,8.9486103 h 2.2246093 c 0.7663818,0 1.4042969,-0.635962 1.4042969,-1.402344 V 2.5579848 c 0,-0.7663818 -0.637915,-1.4042969 -1.4042969,-1.4042969 z" />
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1710,7 +1710,7 @@ void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) {
NodeMap &nm = node_map[at.target];
String path = scene->get_path_to(nm.node);
animation->add_track(Animation::TYPE_VALUE);
animation->add_track(Animation::TYPE_BLEND_SHAPE);
int track = animation->get_track_count() - 1;
path = path + ":" + at.param;
@ -1732,7 +1732,7 @@ void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) {
WARN_PRINT("Collada: Unexpected amount of value keys: " + itos(data.size()));
}
animation->track_insert_key(track, time, value);
animation->blend_shape_track_insert_key(track, time, value);
}
tracks_found = true;

View file

@ -1036,6 +1036,10 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
} else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
Variant var = default_anim->value_track_interpolate(j, from);
new_anim->track_insert_key(dtrack, 0, var);
} else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) {
float interp;
default_anim->blend_shape_track_interpolate(j, from, &interp);
new_anim->blend_shape_track_insert_key(dtrack, 0, interp);
}
}
}
@ -1055,6 +1059,10 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
} else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
Variant var = default_anim->track_get_key_value(j, k);
new_anim->track_insert_key(dtrack, kt - from, var);
} else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) {
float interp;
default_anim->blend_shape_track_get_key(j, k, &interp);
new_anim->blend_shape_track_insert_key(dtrack, kt - from, interp);
}
}
@ -1074,6 +1082,10 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
} else if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
Variant var = default_anim->value_track_interpolate(j, to);
new_anim->track_insert_key(dtrack, to - from, var);
} else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) {
float interp;
default_anim->blend_shape_track_interpolate(j, to, &interp);
new_anim->blend_shape_track_insert_key(dtrack, to - from, interp);
}
}
}
@ -1105,6 +1117,12 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
new_anim->track_insert_key(dtrack, 0, var);
Variant to_var = default_anim->value_track_interpolate(j, to);
new_anim->track_insert_key(dtrack, to - from, to_var);
} else if (default_anim->track_get_type(j) == Animation::TYPE_BLEND_SHAPE) {
float interp;
default_anim->blend_shape_track_interpolate(j, from, &interp);
new_anim->blend_shape_track_insert_key(dtrack, 0, interp);
default_anim->blend_shape_track_interpolate(j, to, &interp);
new_anim->blend_shape_track_insert_key(dtrack, to - from, interp);
}
}
}
@ -1652,7 +1670,7 @@ void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, Ani
ERR_FAIL_COND(parent == nullptr);
OrderedHashMap<NodePath, uint32_t> used_tracks[TRACK_CHANNEL_MAX];
bool tracks_to_add = false;
static const Animation::TrackType track_types[TRACK_CHANNEL_MAX] = { Animation::TYPE_POSITION_3D, Animation::TYPE_ROTATION_3D, Animation::TYPE_SCALE_3D };
static const Animation::TrackType track_types[TRACK_CHANNEL_MAX] = { Animation::TYPE_POSITION_3D, Animation::TYPE_ROTATION_3D, Animation::TYPE_SCALE_3D, Animation::TYPE_BLEND_SHAPE };
for (const StringName &I : anims) {
Ref<Animation> anim = p_player->get_animation(I);
for (int i = 0; i < anim->get_track_count(); i++) {
@ -1710,67 +1728,84 @@ void ResourceImporterScene::_optimize_track_usage(AnimationPlayer *p_player, Ani
NodePath path = J.key();
Node *n = parent->get_node(path);
Skeleton3D *skel = Object::cast_to<Skeleton3D>(n);
Node3D *n3d = Object::cast_to<Node3D>(n);
Vector3 loc;
Quaternion rot;
Vector3 scale;
if (skel && path.get_subname_count() > 0) {
StringName bone = path.get_subname(0);
int bone_idx = skel->find_bone(bone);
if (bone_idx == -1) {
if (j == TRACK_CHANNEL_BLEND_SHAPE) {
MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(n);
if (mi && path.get_subname_count() > 0) {
StringName bs = path.get_subname(0);
bool valid;
float value = mi->get(bs, &valid);
if (valid) {
int track_idx = anim->add_track(track_types[j]);
anim->track_set_path(track_idx, path);
anim->track_set_imported(track_idx, true);
anim->blend_shape_track_insert_key(track_idx, 0, value);
}
}
} else {
Skeleton3D *skel = Object::cast_to<Skeleton3D>(n);
Node3D *n3d = Object::cast_to<Node3D>(n);
Vector3 loc;
Quaternion rot;
Vector3 scale;
if (skel && path.get_subname_count() > 0) {
StringName bone = path.get_subname(0);
int bone_idx = skel->find_bone(bone);
if (bone_idx == -1) {
continue;
}
skel->get_bone_pose(bone_idx);
loc = skel->get_bone_pose_position(bone_idx);
rot = skel->get_bone_pose_rotation(bone_idx);
scale = skel->get_bone_pose_scale(bone_idx);
} else if (n3d) {
loc = n3d->get_position();
rot = n3d->get_transform().basis.get_rotation_quaternion();
scale = n3d->get_scale();
} else {
continue;
}
skel->get_bone_pose(bone_idx);
loc = skel->get_bone_pose_position(bone_idx);
rot = skel->get_bone_pose_rotation(bone_idx);
scale = skel->get_bone_pose_scale(bone_idx);
} else if (n3d) {
loc = n3d->get_position();
rot = n3d->get_transform().basis.get_rotation_quaternion();
scale = n3d->get_scale();
} else {
continue;
}
// Ensure insertion keeps tracks together and ordered by type (loc/rot/scale)
int insert_at_pos = -1;
for (int k = 0; k < anim->get_track_count(); k++) {
NodePath tpath = anim->track_get_path(k);
// Ensure insertion keeps tracks together and ordered by type (loc/rot/scale)
int insert_at_pos = -1;
for (int k = 0; k < anim->get_track_count(); k++) {
NodePath tpath = anim->track_get_path(k);
if (path == tpath) {
Animation::TrackType ttype = anim->track_get_type(k);
if (insert_at_pos == -1) {
// First insert, determine whether replacing or kicking back
if (track_types[j] < ttype) {
insert_at_pos = k;
break; // No point in continuing.
} else {
if (path == tpath) {
Animation::TrackType ttype = anim->track_get_type(k);
if (insert_at_pos == -1) {
// First insert, determine whether replacing or kicking back
if (track_types[j] < ttype) {
insert_at_pos = k;
break; // No point in continuing.
} else {
insert_at_pos = k + 1;
}
} else if (ttype < track_types[j]) {
// Kick back.
insert_at_pos = k + 1;
}
} else if (ttype < track_types[j]) {
// Kick back.
insert_at_pos = k + 1;
} else if (insert_at_pos >= 0) {
break;
}
} else if (insert_at_pos >= 0) {
break;
}
}
int track_idx = anim->add_track(track_types[j], insert_at_pos);
int track_idx = anim->add_track(track_types[j], insert_at_pos);
anim->track_set_path(track_idx, path);
anim->track_set_imported(track_idx, true);
switch (j) {
case TRACK_CHANNEL_POSITION: {
anim->position_track_insert_key(track_idx, 0, loc);
} break;
case TRACK_CHANNEL_ROTATION: {
anim->rotation_track_insert_key(track_idx, 0, rot);
} break;
case TRACK_CHANNEL_SCALE: {
anim->scale_track_insert_key(track_idx, 0, scale);
} break;
default: {
anim->track_set_path(track_idx, path);
anim->track_set_imported(track_idx, true);
switch (j) {
case TRACK_CHANNEL_POSITION: {
anim->position_track_insert_key(track_idx, 0, loc);
} break;
case TRACK_CHANNEL_ROTATION: {
anim->rotation_track_insert_key(track_idx, 0, rot);
} break;
case TRACK_CHANNEL_SCALE: {
anim->scale_track_insert_key(track_idx, 0, scale);
} break;
default: {
}
}
}
}

View file

@ -207,6 +207,7 @@ class ResourceImporterScene : public ResourceImporter {
TRACK_CHANNEL_POSITION,
TRACK_CHANNEL_ROTATION,
TRACK_CHANNEL_SCALE,
TRACK_CHANNEL_BLEND_SHAPE,
TRACK_CHANNEL_MAX
};

View file

@ -5991,12 +5991,11 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
ERR_CONTINUE(mesh.is_null());
ERR_CONTINUE(mesh->get_mesh().is_null());
ERR_CONTINUE(mesh->get_mesh()->get_mesh().is_null());
const String prop = "blend_shapes/" + mesh->get_mesh()->get_blend_shape_name(i);
const String blend_path = String(node_path) + ":" + prop;
const String blend_path = String(node_path) + ":" + String(mesh->get_mesh()->get_blend_shape_name(i));
const int track_idx = animation->get_track_count();
animation->add_track(Animation::TYPE_VALUE);
animation->add_track(Animation::TYPE_BLEND_SHAPE);
animation->track_set_path(track_idx, blend_path);
// Only LINEAR and STEP (NEAREST) can be supported out of the box by Godot's Animation,
@ -6007,7 +6006,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
for (int j = 0; j < track.weight_tracks[i].times.size(); j++) {
const float t = track.weight_tracks[i].times[j];
const float attribs = track.weight_tracks[i].values[j];
animation->track_insert_key(track_idx, t, attribs);
animation->blend_shape_track_insert_key(track_idx, t, attribs);
}
} else {
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
@ -6015,7 +6014,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
double time = 0.0;
bool last = false;
while (true) {
_interpolate_track<float>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp);
float blend = _interpolate_track<float>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp);
animation->blend_shape_track_insert_key(track_idx, time, blend);
if (last) {
break;
}
@ -6459,7 +6459,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
if (!tracks.has(mesh_index)) {
for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
String shape_name = mesh->get_blend_shape_name(shape_i);
NodePath shape_path = String(path) + ":blend_shapes/" + shape_name;
NodePath shape_path = String(path) + ":" + shape_name;
int32_t shape_track_i = animation->find_track(shape_path);
if (shape_track_i == -1) {
GLTFAnimation::Channel<float> weight;

View file

@ -42,10 +42,9 @@ bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
Map<StringName, BlendShapeTrack>::Element *E = blend_shape_tracks.find(p_name);
Map<StringName, int>::Element *E = blend_shape_properties.find(p_name);
if (E) {
E->get().value = p_value;
RenderingServer::get_singleton()->instance_set_blend_shape_weight(get_instance(), E->get().idx, E->get().value);
set_blend_shape_value(E->get(), p_value);
return true;
}
@ -67,9 +66,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
return false;
}
const Map<StringName, BlendShapeTrack>::Element *E = blend_shape_tracks.find(p_name);
const Map<StringName, int>::Element *E = blend_shape_properties.find(p_name);
if (E) {
r_ret = E->get().value;
r_ret = get_blend_shape_value(E->get());
return true;
}
@ -86,7 +85,7 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const {
void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const {
List<String> ls;
for (const KeyValue<StringName, BlendShapeTrack> &E : blend_shape_tracks) {
for (const KeyValue<StringName, int> &E : blend_shape_properties) {
ls.push_back(E.key);
}
@ -114,25 +113,17 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) {
mesh = p_mesh;
blend_shape_tracks.clear();
if (mesh.is_valid()) {
for (int i = 0; i < mesh->get_blend_shape_count(); i++) {
BlendShapeTrack mt;
mt.idx = i;
mt.value = 0;
blend_shape_tracks["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = mt;
}
mesh->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &MeshInstance3D::_mesh_changed));
surface_override_materials.resize(mesh->get_surface_count());
_mesh_changed();
set_base(mesh->get_rid());
} else {
blend_shape_tracks.clear();
blend_shape_properties.clear();
set_base(RID());
update_gizmos();
}
update_gizmos();
notify_property_list_changed();
}
@ -140,6 +131,35 @@ Ref<Mesh> MeshInstance3D::get_mesh() const {
return mesh;
}
int MeshInstance3D::get_blend_shape_count() const {
if (mesh.is_null()) {
return 0;
}
return mesh->get_blend_shape_count();
}
int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) {
if (mesh.is_null()) {
return -1;
}
for (int i = 0; i < mesh->get_blend_shape_count(); i++) {
if (mesh->get_blend_shape_name(i) == p_name) {
return i;
}
}
return -1;
}
float MeshInstance3D::get_blend_shape_value(int p_blend_shape) const {
ERR_FAIL_COND_V(mesh.is_null(), 0.0);
ERR_FAIL_INDEX_V(p_blend_shape, (int)blend_shape_tracks.size(), 0);
return blend_shape_tracks[p_blend_shape];
}
void MeshInstance3D::set_blend_shape_value(int p_blend_shape, float p_value) {
ERR_FAIL_COND(mesh.is_null());
ERR_FAIL_INDEX(p_blend_shape, (int)blend_shape_tracks.size());
blend_shape_tracks[p_blend_shape] = p_value;
RenderingServer::get_singleton()->instance_set_blend_shape_weight(get_instance(), p_blend_shape, p_value);
}
void MeshInstance3D::_resolve_skeleton_path() {
Ref<SkinReference> new_skin_reference;
@ -357,6 +377,19 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const {
void MeshInstance3D::_mesh_changed() {
ERR_FAIL_COND(mesh.is_null());
surface_override_materials.resize(mesh->get_surface_count());
uint32_t initialize_bs_from = blend_shape_tracks.size();
blend_shape_tracks.resize(mesh->get_blend_shape_count());
for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) {
blend_shape_properties["blend_shapes/" + String(mesh->get_blend_shape_name(i))] = i;
if (i < initialize_bs_from) {
set_blend_shape_value(i, blend_shape_tracks[i]);
} else {
set_blend_shape_value(i, 0);
}
}
update_gizmos();
}
@ -459,6 +492,11 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions"), &MeshInstance3D::create_multiple_convex_collisions);
ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT);
ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &MeshInstance3D::get_blend_shape_count);
ClassDB::bind_method(D_METHOD("find_blend_shape_by_name", "name"), &MeshInstance3D::find_blend_shape_by_name);
ClassDB::bind_method(D_METHOD("get_blend_shape_value", "blend_shape_idx"), &MeshInstance3D::get_blend_shape_value);
ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
ClassDB::set_method_flags("MeshInstance3D", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);

View file

@ -31,8 +31,8 @@
#ifndef MESH_INSTANCE_H
#define MESH_INSTANCE_H
#include "core/templates/local_vector.h"
#include "scene/3d/visual_instance_3d.h"
class Skin;
class SkinReference;
@ -46,12 +46,8 @@ protected:
Ref<SkinReference> skin_ref;
NodePath skeleton_path = NodePath("..");
struct BlendShapeTrack {
int idx = 0;
float value = 0.0;
};
Map<StringName, BlendShapeTrack> blend_shape_tracks;
LocalVector<float> blend_shape_tracks;
Map<StringName, int> blend_shape_properties;
Vector<Ref<Material>> surface_override_materials;
void _mesh_changed();
@ -75,6 +71,11 @@ public:
void set_skeleton_path(const NodePath &p_skeleton);
NodePath get_skeleton_path();
int get_blend_shape_count() const;
int find_blend_shape_by_name(const StringName &p_name);
float get_blend_shape_value(int p_blend_shape) const;
void set_blend_shape_value(int p_blend_shape, float p_value);
int get_surface_override_material_count() const;
void set_surface_override_material(int p_surface, const Ref<Material> &p_material);
Ref<Material> get_surface_override_material(int p_surface) const;

View file

@ -371,6 +371,9 @@ void Node3D::update_gizmos() {
if (data.gizmos.is_empty()) {
return;
}
if (data.gizmos_dirty) {
return;
}
data.gizmos_dirty = true;
MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_update_gizmos));
#endif
@ -467,6 +470,7 @@ Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const {
void Node3D::_update_gizmos() {
#ifdef TOOLS_ENABLED
if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) {
data.gizmos_dirty = false;
return;
}
data.gizmos_dirty = false;

View file

@ -257,6 +257,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
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
ObjectID id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id();
int bone_idx = -1;
int blend_shape_idx = -1;
#ifndef _3D_DISABLED
if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to<Skeleton3D>(child)) {
@ -266,6 +267,22 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
continue;
}
}
if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) {
MeshInstance3D *mi_3d = Object::cast_to<MeshInstance3D>(child);
if (!mi_3d) {
continue;
}
if (a->track_get_path(i).get_subname_count() != 1) {
continue;
}
blend_shape_idx = mi_3d->find_blend_shape_by_name(a->track_get_path(i).get_subname(0));
if (blend_shape_idx == -1) {
continue;
}
}
#endif // _3D_DISABLED
{
@ -277,6 +294,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
TrackNodeCacheKey key;
key.id = id;
key.bone_idx = bone_idx;
key.blend_shape_idx = blend_shape_idx;
if (!node_cache_map.has(key)) {
node_cache_map[key] = TrackNodeCache();
@ -334,6 +352,13 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
}
}
}
if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) {
// special cases and caches for transform tracks
node_cache->node_blend_shape = Object::cast_to<MeshInstance3D>(child);
node_cache->blend_shape_idx = blend_shape_idx;
}
#endif // _3D_DISABLED
if (a->track_get_type(i) == Animation::TYPE_VALUE) {
@ -479,6 +504,31 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
} else {
nc->scale_accum = nc->scale_accum.lerp(scale, p_interp);
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
if (!nc->node_blend_shape) {
continue;
}
float blend;
Error err = a->blend_shape_track_interpolate(i, p_time, &blend);
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
if (err != OK) {
continue;
}
if (nc->accum_pass != accum_pass) {
ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX);
nc->accum_pass = accum_pass;
cache_update[cache_update_size++] = nc;
nc->blend_shape_accum = blend;
} else {
nc->blend_shape_accum = Math::lerp(nc->blend_shape_accum, blend, p_interp);
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_VALUE: {
@ -947,6 +997,8 @@ void AnimationPlayer::_animation_update_transforms() {
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) {
if (nc->loc_used) {
nc->node_3d->set_position(nc->loc_accum);

View file

@ -32,6 +32,7 @@
#define ANIMATION_PLAYER_H
#include "scene/2d/node_2d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
@ -99,6 +100,8 @@ private:
#ifndef _3D_DISABLED
Node3D *node_3d = nullptr;
Skeleton3D *skeleton = nullptr;
MeshInstance3D *node_blend_shape = nullptr;
int blend_shape_idx = -1;
#endif // _3D_DISABLED
int bone_idx = -1;
// accumulated transforms
@ -110,6 +113,7 @@ private:
Vector3 loc_accum;
Quaternion rot_accum;
Vector3 scale_accum;
float blend_shape_accum = 0;
uint64_t accum_pass = 0;
bool audio_playing = false;
@ -147,10 +151,15 @@ private:
struct TrackNodeCacheKey {
ObjectID id;
int bone_idx = -1;
int blend_shape_idx = -1;
inline bool operator<(const TrackNodeCacheKey &p_right) const {
if (id == p_right.id) {
return bone_idx < p_right.bone_idx;
if (blend_shape_idx == p_right.blend_shape_idx) {
return bone_idx < p_right.bone_idx;
} else {
return blend_shape_idx < p_right.blend_shape_idx;
}
} else {
return id < p_right.id;
}

View file

@ -639,6 +639,37 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
if (path.get_subname_count() != 1) {
ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'");
continue;
}
MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child);
if (!mesh_3d) {
ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'");
continue;
}
StringName blend_shape_name = path.get_subname(0);
int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name);
if (blend_shape_idx == -1) {
ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'");
continue;
}
TrackCacheBlendShape *track_bshape = memnew(TrackCacheBlendShape);
track_bshape->mesh_3d = mesh_3d;
track_bshape->shape_index = blend_shape_idx;
track_bshape->object = mesh_3d;
track_bshape->object_id = mesh_3d->get_instance_id();
track = track_bshape;
#endif
} break;
case Animation::TYPE_METHOD: {
TrackCacheMethod *track_method = memnew(TrackCacheMethod);
@ -1087,6 +1118,28 @@ void AnimationTree::_process_graph(real_t p_delta) {
t->scale = t->scale.lerp(scale, blend);
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
if (t->process_pass != process_pass) {
t->process_pass = process_pass;
t->value = 0;
}
float value;
Error err = a->blend_shape_track_interpolate(i, time, &value);
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
if (err != OK) {
continue;
}
t->value = Math::lerp(t->value, value, blend);
#endif // _3D_DISABLED
} break;
case Animation::TYPE_VALUE: {
@ -1382,6 +1435,15 @@ void AnimationTree::_process_graph(real_t p_delta) {
t->node_3d->set_scale(t->scale);
}
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
if (t->mesh_3d) {
t->mesh_3d->set_blend_shape_value(t->shape_index, t->value);
}
#endif // _3D_DISABLED
} break;
case Animation::TYPE_VALUE: {

View file

@ -210,6 +210,13 @@ private:
}
};
struct TrackCacheBlendShape : public TrackCache {
MeshInstance3D *mesh_3d = nullptr;
float value = 0;
int shape_index = -1;
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
};
struct TrackCacheValue : public TrackCache {
Variant value;
Vector<StringName> subpath;

View file

@ -49,6 +49,8 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
add_track(TYPE_ROTATION_3D);
} else if (type == "scale_3d") {
add_track(TYPE_SCALE_3D);
} else if (type == "blend_shape") {
add_track(TYPE_BLEND_SHAPE);
} else if (type == "value") {
add_track(TYPE_VALUE);
} else if (type == "method") {
@ -146,6 +148,25 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
sk.value.y = ofs[3];
sk.value.z = ofs[4];
}
} else if (track_get_type(track) == TYPE_BLEND_SHAPE) {
BlendShapeTrack *st = static_cast<BlendShapeTrack *>(tracks[track]);
Vector<real_t> values = p_value;
int vcount = values.size();
ERR_FAIL_COND_V(vcount % BLEND_SHAPE_TRACK_SIZE, false);
const real_t *r = values.ptr();
int64_t count = vcount / BLEND_SHAPE_TRACK_SIZE;
st->blend_shapes.resize(count);
TKey<float> *sw = st->blend_shapes.ptrw();
for (int i = 0; i < count; i++) {
TKey<float> &sk = sw[i];
const real_t *ofs = &r[i * BLEND_SHAPE_TRACK_SIZE];
sk.time = ofs[0];
sk.transition = ofs[1];
sk.value = ofs[2];
}
} else if (track_get_type(track) == TYPE_VALUE) {
ValueTrack *vt = static_cast<ValueTrack *>(tracks[track]);
@ -369,6 +390,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
case TYPE_SCALE_3D:
r_ret = "scale_3d";
break;
case TYPE_BLEND_SHAPE:
r_ret = "blend_shape";
break;
case TYPE_VALUE:
r_ret = "value";
break;
@ -462,6 +486,25 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
w[idx++] = scale.z;
}
r_ret = keys;
return true;
} else if (track_get_type(track) == TYPE_BLEND_SHAPE) {
Vector<real_t> keys;
int kk = track_get_key_count(track);
keys.resize(kk * BLEND_SHAPE_TRACK_SIZE);
real_t *w = keys.ptrw();
int idx = 0;
for (int i = 0; i < track_get_key_count(track); i++) {
float bs;
blend_shape_track_get_key(track, i, &bs);
w[idx++] = track_get_key_time(track, i);
w[idx++] = track_get_key_transition(track, i);
w[idx++] = bs;
}
r_ret = keys;
return true;
} else if (track_get_type(track) == TYPE_VALUE) {
@ -682,6 +725,10 @@ int Animation::add_track(TrackType p_type, int p_at_pos) {
ScaleTrack *st = memnew(ScaleTrack);
tracks.insert(p_at_pos, st);
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = memnew(BlendShapeTrack);
tracks.insert(p_at_pos, bst);
} break;
case TYPE_VALUE: {
tracks.insert(p_at_pos, memnew(ValueTrack));
@ -730,6 +777,11 @@ void Animation::remove_track(int p_track) {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
_clear(st->scales);
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
_clear(bst->blend_shapes);
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@ -993,6 +1045,53 @@ Error Animation::scale_track_interpolate(int p_track, double p_time, Vector3 *r_
return OK;
}
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];
ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, -1);
BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
TKey<float> tkey;
tkey.time = p_time;
tkey.value = p_blend_shape;
int ret = _insert(p_time, st->blend_shapes, tkey);
emit_changed();
return ret;
}
Error Animation::blend_shape_track_get_key(int p_track, int p_key, float *r_blend_shape) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
Track *t = tracks[p_track];
BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
ERR_FAIL_INDEX_V(p_key, st->blend_shapes.size(), ERR_INVALID_PARAMETER);
*r_blend_shape = st->blend_shapes[p_key].value;
return OK;
}
Error Animation::blend_shape_track_interpolate(int p_track, double p_time, float *r_interpolation) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
Track *t = tracks[p_track];
ERR_FAIL_COND_V(t->type != TYPE_BLEND_SHAPE, ERR_INVALID_PARAMETER);
BlendShapeTrack *st = static_cast<BlendShapeTrack *>(t);
bool ok = false;
float tk = _interpolate(st->blend_shapes, p_time, st->interpolation, st->loop_wrap, &ok);
if (!ok) {
return ERR_UNAVAILABLE;
}
*r_interpolation = tk;
return OK;
}
void Animation::track_remove_key_at_time(int p_track, double p_time) {
int idx = track_find_key(p_track, p_time, true);
ERR_FAIL_COND(idx < 0);
@ -1021,6 +1120,12 @@ void Animation::track_remove_key(int p_track, int p_idx) {
ERR_FAIL_INDEX(p_idx, st->scales.size());
st->scales.remove(p_idx);
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX(p_idx, bst->blend_shapes.size());
bst->blend_shapes.remove(p_idx);
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@ -1087,12 +1192,24 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
} break;
case TYPE_SCALE_3D: {
ScaleTrack *rt = static_cast<ScaleTrack *>(t);
int k = _find(rt->scales, p_time);
if (k < 0 || k >= rt->scales.size()) {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
int k = _find(st->scales, p_time);
if (k < 0 || k >= st->scales.size()) {
return -1;
}
if (rt->scales[k].time != p_time && p_exact) {
if (st->scales[k].time != p_time && p_exact) {
return -1;
}
return k;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
int k = _find(bst->blend_shapes, p_time);
if (k < 0 || k >= bst->blend_shapes.size()) {
return -1;
}
if (bst->blend_shapes[k].time != p_time && p_exact) {
return -1;
}
return k;
@ -1185,6 +1302,12 @@ void Animation::track_insert_key(int p_track, double p_time, const Variant &p_ke
int idx = scale_track_insert_key(p_track, p_time, p_key);
track_set_key_transition(p_track, idx, p_transition);
} break;
case TYPE_BLEND_SHAPE: {
ERR_FAIL_COND((p_key.get_type() != Variant::FLOAT) && (p_key.get_type() != Variant::INT));
int idx = blend_shape_track_insert_key(p_track, p_time, p_key);
track_set_key_transition(p_track, idx, p_transition);
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@ -1279,6 +1402,10 @@ int Animation::track_get_key_count(int p_track) const {
ScaleTrack *st = static_cast<ScaleTrack *>(t);
return st->scales.size();
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
return bst->blend_shapes.size();
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
return vt->values.size();
@ -1328,6 +1455,12 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
return st->scales[p_key_idx].value;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), Variant());
return bst->blend_shapes[p_key_idx].value;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, vt->values.size(), Variant());
@ -1400,6 +1533,11 @@ double Animation::track_get_key_time(int p_track, int p_key_idx) const {
ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
return st->scales[p_key_idx].time;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
return bst->blend_shapes[p_key_idx].time;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, vt->values.size(), -1);
@ -1467,6 +1605,15 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) {
_insert(p_time, tt->scales, key);
return;
}
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *tt = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX(p_key_idx, tt->blend_shapes.size());
TKey<float> key = tt->blend_shapes[p_key_idx];
key.time = p_time;
tt->blend_shapes.remove(p_key_idx);
_insert(p_time, tt->blend_shapes, key);
return;
}
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
ERR_FAIL_INDEX(p_key_idx, vt->values.size());
@ -1537,6 +1684,11 @@ real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const {
ERR_FAIL_INDEX_V(p_key_idx, st->scales.size(), -1);
return st->scales[p_key_idx].transition;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, bst->blend_shapes.size(), -1);
return bst->blend_shapes[p_key_idx].transition;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
ERR_FAIL_INDEX_V(p_key_idx, vt->values.size(), -1);
@ -1591,6 +1743,14 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
st->scales.write[p_key_idx].value = p_value;
} break;
case TYPE_BLEND_SHAPE: {
ERR_FAIL_COND((p_value.get_type() != Variant::FLOAT) && (p_value.get_type() != Variant::INT));
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
bst->blend_shapes.write[p_key_idx].value = p_value;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
@ -1673,6 +1833,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
ERR_FAIL_INDEX(p_key_idx, st->scales.size());
st->scales.write[p_key_idx].transition = p_transition;
} break;
case TYPE_BLEND_SHAPE: {
BlendShapeTrack *bst = static_cast<BlendShapeTrack *>(t);
ERR_FAIL_INDEX(p_key_idx, bst->blend_shapes.size());
bst->blend_shapes.write[p_key_idx].transition = p_transition;
} break;
case TYPE_VALUE: {
ValueTrack *vt = static_cast<ValueTrack *>(t);
ERR_FAIL_INDEX(p_key_idx, vt->values.size());
@ -2184,6 +2349,12 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
_track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
_track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
} break;
case TYPE_BLEND_SHAPE: {
const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
_track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
_track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
} break;
case TYPE_VALUE: {
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
@ -2249,6 +2420,11 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
_track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
} break;
case TYPE_BLEND_SHAPE: {
const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
_track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
} break;
case TYPE_VALUE: {
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
@ -2867,6 +3043,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("position_track_insert_key", "track_idx", "time", "position"), &Animation::position_track_insert_key);
ClassDB::bind_method(D_METHOD("rotation_track_insert_key", "track_idx", "time", "rotation"), &Animation::rotation_track_insert_key);
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);
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);
@ -2943,6 +3120,7 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_POSITION_3D);
BIND_ENUM_CONSTANT(TYPE_ROTATION_3D);
BIND_ENUM_CONSTANT(TYPE_SCALE_3D);
BIND_ENUM_CONSTANT(TYPE_BLEND_SHAPE);
BIND_ENUM_CONSTANT(TYPE_METHOD);
BIND_ENUM_CONSTANT(TYPE_BEZIER);
BIND_ENUM_CONSTANT(TYPE_AUDIO);
@ -3089,6 +3267,41 @@ bool Animation::_scale_track_optimize_key(const TKey<Vector3> &t0, const TKey<Ve
return true;
}
bool Animation::_blend_shape_track_optimize_key(const TKey<float> &t0, const TKey<float> &t1, const TKey<float> &t2, real_t p_allowed_unit_error) {
float v0 = t0.value;
float v1 = t1.value;
float v2 = t2.value;
if (Math::is_equal_approx(v1, v2, p_allowed_unit_error)) {
//0 and 2 are close, let's see if 1 is close
if (!Math::is_equal_approx(v0, v1, p_allowed_unit_error)) {
//not close, not optimizable
return false;
}
} else {
/*
TODO eventually discuss a way to optimize these better.
float pd = (v2 - v0);
real_t d0 = pd.dot(v0);
real_t d1 = pd.dot(v1);
real_t d2 = pd.dot(v2);
if (d1 < d0 || d1 > d2) {
return false; //beyond segment range
}
float s[2] = { v0, v2 };
real_t d = Geometry3D::get_closest_point_to_segment(v1, s).distance_to(v1);
if (d > pd.length() * p_allowed_linear_error) {
return false; //beyond allowed error for colinearity
}
*/
}
return true;
}
void Animation::_position_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err) {
ERR_FAIL_INDEX(p_idx, tracks.size());
ERR_FAIL_COND(tracks[p_idx]->type != TYPE_POSITION_3D);
@ -3197,6 +3410,41 @@ void Animation::_scale_track_optimize(int p_idx, real_t p_allowed_linear_err) {
}
}
void Animation::_blend_shape_track_optimize(int p_idx, real_t p_allowed_linear_err) {
ERR_FAIL_INDEX(p_idx, tracks.size());
ERR_FAIL_COND(tracks[p_idx]->type != TYPE_BLEND_SHAPE);
BlendShapeTrack *tt = static_cast<BlendShapeTrack *>(tracks[p_idx]);
bool prev_erased = false;
TKey<float> first_erased;
first_erased.value = 0.0;
for (int i = 1; i < tt->blend_shapes.size() - 1; i++) {
TKey<float> &t0 = tt->blend_shapes.write[i - 1];
TKey<float> &t1 = tt->blend_shapes.write[i];
TKey<float> &t2 = tt->blend_shapes.write[i + 1];
bool erase = _blend_shape_track_optimize_key(t0, t1, t2, p_allowed_linear_err);
if (prev_erased && !_blend_shape_track_optimize_key(t0, first_erased, t2, p_allowed_linear_err)) {
//avoid error to go beyond first erased key
erase = false;
}
if (erase) {
if (!prev_erased) {
first_erased = t1;
prev_erased = true;
}
tt->blend_shapes.remove(i);
i--;
} else {
prev_erased = false;
}
}
}
void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) {
for (int i = 0; i < tracks.size(); i++) {
if (tracks[i]->type == TYPE_POSITION_3D) {
@ -3205,6 +3453,8 @@ void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_e
_rotation_track_optimize(i, p_allowed_angular_err, p_max_optimizable_angle);
} else if (tracks[i]->type == TYPE_SCALE_3D) {
_scale_track_optimize(i, p_allowed_linear_err);
} else if (tracks[i]->type == TYPE_BLEND_SHAPE) {
_blend_shape_track_optimize(i, p_allowed_linear_err);
}
}
}

View file

@ -45,6 +45,7 @@ public:
TYPE_POSITION_3D, ///< Position 3D track
TYPE_ROTATION_3D, ///< Rotation 3D track
TYPE_SCALE_3D, ///< Scale 3D track
TYPE_BLEND_SHAPE, ///< Blend Shape track
TYPE_METHOD, ///< Call any method on a specific node.
TYPE_BEZIER, ///< Bezier curve
TYPE_AUDIO,
@ -91,6 +92,7 @@ private:
const int32_t POSITION_TRACK_SIZE = 5;
const int32_t ROTATION_TRACK_SIZE = 6;
const int32_t SCALE_TRACK_SIZE = 5;
const int32_t BLEND_SHAPE_TRACK_SIZE = 3;
/* POSITION TRACK */
@ -115,6 +117,13 @@ private:
ScaleTrack() { type = TYPE_SCALE_3D; }
};
/* BLEND SHAPE TRACK */
struct BlendShapeTrack : public Track {
Vector<TKey<float>> blend_shapes;
BlendShapeTrack() { type = TYPE_BLEND_SHAPE; }
};
/* PROPERTY VALUE TRACK */
struct ValueTrack : public Track {
@ -247,10 +256,12 @@ private:
bool _position_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_alowed_linear_err, real_t p_allowed_angular_error, const Vector3 &p_norm);
bool _rotation_track_optimize_key(const TKey<Quaternion> &t0, const TKey<Quaternion> &t1, const TKey<Quaternion> &t2, real_t p_allowed_angular_error, float p_max_optimizable_angle);
bool _scale_track_optimize_key(const TKey<Vector3> &t0, const TKey<Vector3> &t1, const TKey<Vector3> &t2, real_t p_allowed_linear_error);
bool _blend_shape_track_optimize_key(const TKey<float> &t0, const TKey<float> &t1, const TKey<float> &t2, real_t p_allowed_unit_error);
void _position_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err);
void _rotation_track_optimize(int p_idx, real_t p_allowed_angular_err, real_t p_max_optimizable_angle);
void _scale_track_optimize(int p_idx, real_t p_allowed_linear_err);
void _blend_shape_track_optimize(int p_idx, real_t p_allowed_unit_error);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@ -308,6 +319,10 @@ public:
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;
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;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;