Jelajahi Sumber

Merge pull request #53865 from reduz/implement-blend-shape-tracks

Rémi Verschelde 3 tahun lalu
induk
melakukan
a4e1a07d83

+ 14 - 4
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.
 			</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">

+ 24 - 0
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.
 			</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" />

+ 42 - 6
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> 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;

+ 44 - 0
editor/icons/KeyBlendShape.svg

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

+ 45 - 0
editor/icons/KeyTrackBlendShape.svg

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

+ 2 - 2
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;

+ 90 - 55
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<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) {
-						continue;
+
+				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);
+						}
 					}
-					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;
-				}
+					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;
+					}
 
-				// 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 {
+					// 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 {
+									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);
-
-				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: {
+					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: {
+						}
 					}
 				}
 			}

+ 1 - 0
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
 	};
 

+ 6 - 6
modules/gltf/gltf_document.cpp

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

+ 56 - 18
scene/3d/mesh_instance_3d.cpp

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

+ 8 - 7
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<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;

+ 4 - 0
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<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;

+ 52 - 0
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<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);

+ 10 - 1
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;
 			}

+ 62 - 0
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<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: {

+ 7 - 0
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<StringName> subpath;

+ 254 - 4
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<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 (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 (rt->scales[k].time != p_time && p_exact) {
+			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);
 		}
 	}
 }

+ 15 - 0
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<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;