diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index a35b7841068..ebcced7dc44 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -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. + + + + + + + + @@ -552,16 +560,18 @@ - + + + Method tracks call functions with given arguments per key. - + 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]). - + Audio tracks are used to play an audio stream with either type of [AudioStreamPlayer]. The stream can be trimmed and previewed in the animation. - + Animation tracks play animations in other [AnimationPlayer] nodes. diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index 507a76197ab..b72ce463106 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -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. + + + + + + @@ -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. + + + + + + + + + + + @@ -61,6 +78,13 @@ Returns the number of surface override materials. This is equivalent to [method Mesh.get_surface_count]. + + + + + + + diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 2ee7f0ffa59..7f118532c91 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -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 = 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 type_icons[8] = { + Ref 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 &p_animati track = p_track; update(); - Ref type_icons[8] = { + Ref 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()); prop_selector->select_property_from_instance(node); } break; + case Animation::TYPE_BLEND_SHAPE: { + adding_track_path = path_to; + Vector 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; diff --git a/editor/icons/KeyBlendShape.svg b/editor/icons/KeyBlendShape.svg new file mode 100644 index 00000000000..42f73419422 --- /dev/null +++ b/editor/icons/KeyBlendShape.svg @@ -0,0 +1,44 @@ + + + + + + diff --git a/editor/icons/KeyTrackBlendShape.svg b/editor/icons/KeyTrackBlendShape.svg new file mode 100644 index 00000000000..e82f0d6a6f7 --- /dev/null +++ b/editor/icons/KeyTrackBlendShape.svg @@ -0,0 +1,45 @@ + + + + + + + diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 52be03a1b1c..ac8adedf2f4 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -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; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index c393d7f5fce..319e5ee25f9 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -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 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 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(n); - Node3D *n3d = Object::cast_to(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(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(n); + Node3D *n3d = Object::cast_to(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: { + } } } } diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index fab602b8ff7..e1e7046be50 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -207,6 +207,7 @@ class ResourceImporterScene : public ResourceImporter { TRACK_CHANNEL_POSITION, TRACK_CHANNEL_ROTATION, TRACK_CHANNEL_SCALE, + TRACK_CHANNEL_BLEND_SHAPE, TRACK_CHANNEL_MAX }; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 70df62de6a7..7d4bd998f52 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5991,12 +5991,11 @@ void GLTFDocument::_import_animation(Ref 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 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 state, AnimationPlayer *ap, double time = 0.0; bool last = false; while (true) { - _interpolate_track(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); + float blend = _interpolate_track(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 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 weight; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index cfd90e5da06..c148f954614 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -42,10 +42,9 @@ bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) { return false; } - Map::Element *E = blend_shape_tracks.find(p_name); + Map::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::Element *E = blend_shape_tracks.find(p_name); + const Map::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 *p_list) const { List ls; - for (const KeyValue &E : blend_shape_tracks) { + for (const KeyValue &E : blend_shape_properties) { ls.push_back(E.key); } @@ -114,25 +113,17 @@ void MeshInstance3D::set_mesh(const Ref &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 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 new_skin_reference; @@ -357,6 +377,19 @@ Ref 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); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index beb7f6cf95b..8f217266012 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -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 skin_ref; NodePath skeleton_path = NodePath(".."); - struct BlendShapeTrack { - int idx = 0; - float value = 0.0; - }; - - Map blend_shape_tracks; + LocalVector blend_shape_tracks; + Map blend_shape_properties; Vector> 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 &p_material); Ref get_surface_override_material(int p_surface) const; diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 272a06bce4f..c96204cf600 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -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> 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; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 8407e0dd996..1f000d5f39d 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -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(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(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(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); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index f8e72a67b68..21e309efe03 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -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; } diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index dd5fe46223a..ccb5fa9472e 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -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(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(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(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: { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index ed207dfe0cf..21b49937a77 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -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 subpath; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 6612c5ff3c1..08b78a39b16 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -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(tracks[track]); + Vector 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 *sw = st->blend_shapes.ptrw(); + for (int i = 0; i < count; i++) { + TKey &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(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 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(t); _clear(st->scales); + } break; + case TYPE_BLEND_SHAPE: { + BlendShapeTrack *bst = static_cast(t); + _clear(bst->blend_shapes); + } break; case TYPE_VALUE: { ValueTrack *vt = static_cast(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(t); + + TKey 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(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(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(t); + ERR_FAIL_INDEX(p_idx, bst->blend_shapes.size()); + bst->blend_shapes.remove(p_idx); + } break; case TYPE_VALUE: { ValueTrack *vt = static_cast(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(t); - int k = _find(rt->scales, p_time); - if (k < 0 || k >= rt->scales.size()) { + ScaleTrack *st = static_cast(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(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(t); @@ -1279,6 +1402,10 @@ int Animation::track_get_key_count(int p_track) const { ScaleTrack *st = static_cast(t); return st->scales.size(); } break; + case TYPE_BLEND_SHAPE: { + BlendShapeTrack *bst = static_cast(t); + return bst->blend_shapes.size(); + } break; case TYPE_VALUE: { ValueTrack *vt = static_cast(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(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(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(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(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(t); + ERR_FAIL_INDEX(p_key_idx, tt->blend_shapes.size()); + TKey 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(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(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(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(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(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(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(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(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(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(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(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(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 &t0, const TKey &t0, const TKey &t1, const TKey &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(tracks[p_idx]); + bool prev_erased = false; + TKey first_erased; + first_erased.value = 0.0; + + for (int i = 1; i < tt->blend_shapes.size() - 1; i++) { + TKey &t0 = tt->blend_shapes.write[i - 1]; + TKey &t1 = tt->blend_shapes.write[i]; + TKey &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); } } } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 614e12a5608..4ee0741d870 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -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> 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 &t0, const TKey &t1, const TKey &t2, real_t p_alowed_linear_err, real_t p_allowed_angular_error, const Vector3 &p_norm); bool _rotation_track_optimize_key(const TKey &t0, const TKey &t1, const TKey &t2, real_t p_allowed_angular_error, float p_max_optimizable_angle); bool _scale_track_optimize_key(const TKey &t0, const TKey &t1, const TKey &t2, real_t p_allowed_linear_error); + bool _blend_shape_track_optimize_key(const TKey &t0, const TKey &t1, const TKey &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;