mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2025-03-14 00:48:52 +01:00
Backported: "Remove animation 3D transform track, replace by loc/rot/scale tracks" from Godot4.
ec19ed3723
Its a bit broken right now, more patches need to be ported. Also I kept the Transform track for now, and I did not touch the gltf module yet.
This commit is contained in:
parent
d89dc61436
commit
8fd473f685
@ -240,6 +240,14 @@
|
||||
Returns the arguments values to be called on a method track for a given key in a given track.
|
||||
</description>
|
||||
</method>
|
||||
<method name="position_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="position" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_track">
|
||||
<return type="void" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
@ -247,6 +255,22 @@
|
||||
Removes a track by specifying the track index.
|
||||
</description>
|
||||
</method>
|
||||
<method name="rotation_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="rotation" type="Quaternion" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="scale_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="scale" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="track_find_key" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
@ -531,16 +555,22 @@
|
||||
<constant name="TYPE_TRANSFORM" value="1" enum="TrackType">
|
||||
Transform tracks are used to change node local transforms or skeleton pose bones. Transitions are interpolated.
|
||||
</constant>
|
||||
<constant name="TYPE_METHOD" value="2" enum="TrackType">
|
||||
<constant name="TYPE_POSITION_3D" value="2" enum="TrackType">
|
||||
</constant>
|
||||
<constant name="TYPE_ROTATION_3D" value="3" enum="TrackType">
|
||||
</constant>
|
||||
<constant name="TYPE_SCALE_3D" 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="3" 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="4" 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="5" 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">
|
||||
|
@ -22,7 +22,7 @@
|
||||
<method name="get_root_motion_transform" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<description>
|
||||
Retrieve the motion of the [member root_motion_track] as a [Transform] that can be used elsewhere. If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_TRANSFORM], returns an identity transformation. See also [member root_motion_track] and [RootMotionView].
|
||||
Retrieve the motion of the [member root_motion_track] as a [Transform] that can be used elsewhere. If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_TRANSFORM], [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_SCALE_3D] or [constant Animation.TYPE_ROTATION_3D], returns an identity transformation. See also [member root_motion_track] and [RootMotionView].
|
||||
</description>
|
||||
</method>
|
||||
<method name="rename_parameter">
|
||||
@ -45,7 +45,7 @@
|
||||
</member>
|
||||
<member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath("")">
|
||||
The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code].
|
||||
If the track has type [constant Animation.TYPE_TRANSFORM], the transformation will be cancelled visually, and the animation will appear to stay in place. See also [method get_root_motion_transform] and [RootMotionView].
|
||||
If the track has type [constant Animation.TYPE_TRANSFORM], [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D], the transformation will be cancelled visually, and the animation will appear to stay in place. See also [method get_root_motion_transform] and [RootMotionView].
|
||||
</member>
|
||||
<member name="tree_root" type="AnimationNode" setter="set_tree_root" getter="get_tree_root">
|
||||
The root animation node of this [AnimationTree]. See [AnimationNode].
|
||||
|
@ -95,6 +95,24 @@
|
||||
Returns the pose transform of the specified bone. Pose is applied on top of the custom pose, which is applied on top the rest pose.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_position" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_rotation" qualifiers="const">
|
||||
<return type="Quaternion" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_scale" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_rest" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
@ -198,8 +216,31 @@
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<description>
|
||||
Sets the pose transform for bone [code]bone_idx[/code]. Prefer set_bone_pose_position, set_bone_pose_rotation, or set_bone_pose_scale instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_position">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="position" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_rotation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="rotation" type="Quaternion" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_scale">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="scale" type="Vector3" />
|
||||
<description>
|
||||
Sets the pose transform for bone [code]bone_idx[/code].
|
||||
[b]Note:[/b] The pose transform needs to be in bone space. Use [method world_transform_to_global_pose] to convert a world transform, like one you can get from a [Node3D], to bone space.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_rest">
|
||||
|
@ -31,16 +31,6 @@
|
||||
#include "animation_track_editor.h"
|
||||
|
||||
#include "animation_track_editor_plugins.h"
|
||||
#include "core/os/input.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/animation_bezier_editor.h"
|
||||
#include "editor/plugins/animation_player_editor_plugin.h"
|
||||
#include "editor_node.h"
|
||||
#include "editor_scale.h"
|
||||
#include "scene/3d/spatial.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
#include "core/array.h"
|
||||
#include "core/class_db.h"
|
||||
#include "core/color.h"
|
||||
@ -52,7 +42,9 @@
|
||||
#include "core/math/transform.h"
|
||||
#include "core/math/transform_2d.h"
|
||||
#include "core/math/vector3.h"
|
||||
#include "core/os/input.h"
|
||||
#include "core/os/input_event.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/pair.h"
|
||||
#include "core/resource.h"
|
||||
@ -61,13 +53,18 @@
|
||||
#include "core/string_name.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/undo_redo.h"
|
||||
#include "editor/animation_bezier_editor.h"
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_spin_slider.h"
|
||||
#include "editor/plugins/animation_player_editor_plugin.h"
|
||||
#include "editor/property_selector.h"
|
||||
#include "editor/scene_tree_editor.h"
|
||||
#include "editor_node.h"
|
||||
#include "editor_scale.h"
|
||||
#include "scene/2d/canvas_item.h"
|
||||
#include "scene/3d/spatial.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
@ -87,9 +84,12 @@
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tool_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/main/scene_tree.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/resources/font.h"
|
||||
#include "scene/resources/style_box.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
|
||||
class AnimationTrackKeyEdit : public Object {
|
||||
GDCLASS(AnimationTrackKeyEdit, Object);
|
||||
@ -230,6 +230,40 @@ public:
|
||||
setting = false;
|
||||
return true;
|
||||
} break;
|
||||
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
if (name == "position" || name == "rotation" || name == "scale") {
|
||||
Variant old = animation->track_get_key_value(track, key);
|
||||
setting = true;
|
||||
String chan;
|
||||
switch (animation->track_get_type(track)) {
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
chan = "Position3D";
|
||||
break;
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
chan = "Rotation3D";
|
||||
break;
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
chan = "Scale3D";
|
||||
break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->create_action(vformat(TTR("Anim Change %s"), chan));
|
||||
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
|
||||
undo_redo->add_do_method(this, "_update_obj", animation);
|
||||
undo_redo->add_undo_method(this, "_update_obj", animation);
|
||||
undo_redo->commit_action();
|
||||
|
||||
setting = false;
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (name == "value") {
|
||||
Variant value = p_value;
|
||||
@ -469,6 +503,14 @@ public:
|
||||
return true;
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
if (name == "position" || name == "rotation" || name == "scale") {
|
||||
r_ret = animation->track_get_key_value(track, key);
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (name == "value") {
|
||||
r_ret = animation->track_get_key_value(track, key);
|
||||
@ -578,6 +620,18 @@ public:
|
||||
p_list->push_back(PropertyInfo(Variant::QUAT, "rotation"));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation"));
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
Variant v = animation->track_get_key_value(track, key);
|
||||
@ -853,6 +907,33 @@ public:
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old);
|
||||
update_obj = true;
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
Variant old = animation->track_get_key_value(track, key);
|
||||
if (!setting) {
|
||||
String chan;
|
||||
switch (animation->track_get_type(track)) {
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
chan = "Position3D";
|
||||
break;
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
chan = "Rotation3D";
|
||||
break;
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
chan = "Scale3D";
|
||||
break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
setting = true;
|
||||
undo_redo->create_action(vformat(TTR("Anim Multi Change %s"), chan));
|
||||
}
|
||||
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, p_value);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, old);
|
||||
update_obj = true;
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (name == "value") {
|
||||
Variant value = p_value;
|
||||
@ -1077,6 +1158,15 @@ public:
|
||||
r_ret = d[p_name];
|
||||
return true;
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
if (name == "position" || name == "rotation" || name == "scale") {
|
||||
r_ret = animation->track_get_key_value(track, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (name == "value") {
|
||||
@ -1226,6 +1316,15 @@ public:
|
||||
p_list->push_back(PropertyInfo(Variant::QUAT, "rotation"));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "position"));
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::QUAT, "scale"));
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale"));
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (same_key_type) {
|
||||
Variant v = animation->track_get_key_value(first_track, first_key);
|
||||
@ -1429,6 +1528,9 @@ void AnimationTimelineEdit::_notification(int p_what) {
|
||||
add_track->get_popup()->clear();
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyValue", "EditorIcons"), TTR("Property Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyXform", "EditorIcons"), TTR("3D Transform Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyXPosition", "EditorIcons"), TTR("3D Position Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyXRotation", "EditorIcons"), TTR("3D Rotation Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyXScale", "EditorIcons"), TTR("3D Scale Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyCall", "EditorIcons"), TTR("Call Method Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyBezier", "EditorIcons"), TTR("Bezier Curve Track"));
|
||||
add_track->get_popup()->add_icon_item(get_icon("KeyAudio", "EditorIcons"), TTR("Audio Playback Track"));
|
||||
@ -2106,9 +2208,12 @@ 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_TRANSFORM) {
|
||||
Animation::TrackType track_type = animation->track_get_type(track);
|
||||
|
||||
if (track_type == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM || track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_SCALE_3D || track_type == Animation::TYPE_ROTATION_3D) {
|
||||
draw_texture(icon, interp_mode_rect.position);
|
||||
}
|
||||
|
||||
//make it easier to click
|
||||
interp_mode_rect.position.y = 0;
|
||||
interp_mode_rect.size.y = get_size().height;
|
||||
@ -2116,7 +2221,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_TRANSFORM) {
|
||||
if (track_type == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM || track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_SCALE_3D || track_type == 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 {
|
||||
@ -2139,7 +2244,9 @@ 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_TRANSFORM) {
|
||||
Animation::TrackType track_type = animation->track_get_type(track);
|
||||
|
||||
if (track_type == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM || track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_SCALE_3D || track_type == Animation::TYPE_ROTATION_3D) {
|
||||
draw_texture(icon, loop_mode_rect.position);
|
||||
}
|
||||
|
||||
@ -2149,7 +2256,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_TRANSFORM) {
|
||||
if (track_type == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM || track_type == Animation::TYPE_POSITION_3D || track_type == Animation::TYPE_SCALE_3D || track_type == 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 {
|
||||
@ -2478,9 +2585,12 @@ bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant
|
||||
}
|
||||
|
||||
Ref<Texture> AnimationTrackEdit::_get_key_type_icon() const {
|
||||
Ref<Texture> type_icons[6] = {
|
||||
Ref<Texture> type_icons[9] = {
|
||||
get_icon("KeyValue", "EditorIcons"),
|
||||
get_icon("KeyXform", "EditorIcons"),
|
||||
get_icon("KeyXPosition", "EditorIcons"),
|
||||
get_icon("KeyXRotation", "EditorIcons"),
|
||||
get_icon("KeyXScale", "EditorIcons"),
|
||||
get_icon("KeyCall", "EditorIcons"),
|
||||
get_icon("KeyBezier", "EditorIcons"),
|
||||
get_icon("KeyAudio", "EditorIcons"),
|
||||
@ -2560,6 +2670,18 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
|
||||
text += "Scale: " + String(d["scale"]) + "\n";
|
||||
}
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
Vector3 t = animation->track_get_key_value(track, key_idx);
|
||||
text += TTR("Position:") + " " + String(t) + "\n";
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
Quat t = animation->track_get_key_value(track, key_idx);
|
||||
text += TTR("Rotation:") + " " + String(t) + "\n";
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
Vector3 t = animation->track_get_key_value(track, key_idx);
|
||||
text += TTR("Scale:") + " " + String(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";
|
||||
@ -3419,6 +3541,9 @@ static bool track_type_is_resettable(Animation::TrackType p_type) {
|
||||
case Animation::TYPE_VALUE:
|
||||
case Animation::TYPE_BEZIER:
|
||||
case Animation::TYPE_TRANSFORM:
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@ -3584,34 +3709,53 @@ void AnimationTrackEditor::insert_transform_key(Spatial *p_node, const String &p
|
||||
|
||||
NodePath np = path;
|
||||
|
||||
int track_idx = -1;
|
||||
int position_idx = -1;
|
||||
int rotation_idx = -1;
|
||||
int scale_idx = -1;
|
||||
|
||||
for (int i = 0; i < animation->get_track_count(); i++) {
|
||||
if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM) {
|
||||
continue;
|
||||
}
|
||||
if (animation->track_get_path(i) != np) {
|
||||
continue;
|
||||
}
|
||||
|
||||
track_idx = i;
|
||||
break;
|
||||
if (animation->track_get_type(i) == Animation::TYPE_POSITION_3D) {
|
||||
position_idx = i;
|
||||
}
|
||||
if (animation->track_get_type(i) == Animation::TYPE_ROTATION_3D) {
|
||||
rotation_idx = i;
|
||||
}
|
||||
if (animation->track_get_type(i) == Animation::TYPE_SCALE_3D) {
|
||||
scale_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
InsertData id;
|
||||
Dictionary val;
|
||||
|
||||
id.path = np;
|
||||
id.track_idx = track_idx;
|
||||
id.value = p_xform;
|
||||
id.type = Animation::TYPE_TRANSFORM;
|
||||
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
|
||||
id.query = vformat(TTR("node '%s'"), p_node->get_name());
|
||||
id.advance = false;
|
||||
|
||||
//dialog insert
|
||||
|
||||
_query_insert(id);
|
||||
{
|
||||
id.track_idx = position_idx;
|
||||
id.value = p_xform.origin;
|
||||
id.type = Animation::TYPE_POSITION_3D;
|
||||
_query_insert(id);
|
||||
}
|
||||
{
|
||||
id.track_idx = rotation_idx;
|
||||
id.value = p_xform.basis.get_rotation_quat();
|
||||
id.type = Animation::TYPE_ROTATION_3D;
|
||||
_query_insert(id);
|
||||
}
|
||||
{
|
||||
id.track_idx = scale_idx;
|
||||
id.value = p_xform.basis.get_scale();
|
||||
id.type = Animation::TYPE_SCALE_3D;
|
||||
_query_insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
|
||||
@ -4063,6 +4207,12 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
|
||||
d["rotation"] = Quat(tr.basis);
|
||||
value = d;
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
value = p_id.value;
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_BEZIER: {
|
||||
Array array;
|
||||
array.resize(5);
|
||||
@ -4479,8 +4629,8 @@ 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_TRANSFORM && !node->is_class("Spatial")) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Transform tracks only apply to Spatial-based nodes."));
|
||||
if ((adding_track_type == Animation::TYPE_TRANSFORM || adding_track_type == Animation::TYPE_POSITION_3D || adding_track_type == Animation::TYPE_ROTATION_3D || adding_track_type == Animation::TYPE_SCALE_3D) && !node->is_class("Spatial")) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Transform/Position/Rotation/Scale 3D tracks only apply to Spatial-based nodes."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4491,6 +4641,9 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
|
||||
prop_selector->select_property_from_instance(node);
|
||||
} break;
|
||||
case Animation::TYPE_TRANSFORM:
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
case Animation::TYPE_METHOD: {
|
||||
undo_redo->create_action(TTR("Add Track"));
|
||||
undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type);
|
||||
@ -4683,6 +4836,66 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs);
|
||||
undo_redo->commit_action();
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
if (!root->has_node(animation->track_get_path(p_track))) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
|
||||
return;
|
||||
}
|
||||
Spatial *base = Object::cast_to<Spatial>(root->get_node(animation->track_get_path(p_track)));
|
||||
|
||||
if (!base) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Spatial, can't insert key"));
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 pos = base->get_translation();
|
||||
|
||||
undo_redo->create_action(TTR("Add Position Key"));
|
||||
undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
|
||||
undo_redo->commit_action();
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
if (!root->has_node(animation->track_get_path(p_track))) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
|
||||
return;
|
||||
}
|
||||
Spatial *base = Object::cast_to<Spatial>(root->get_node(animation->track_get_path(p_track)));
|
||||
|
||||
if (!base) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Spatial, can't insert key"));
|
||||
return;
|
||||
}
|
||||
|
||||
Quat rot = base->get_transform().basis.operator Quat();
|
||||
|
||||
undo_redo->create_action(TTR("Add Rotation Key"));
|
||||
undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
|
||||
undo_redo->commit_action();
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
if (!root->has_node(animation->track_get_path(p_track))) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key."));
|
||||
return;
|
||||
}
|
||||
Spatial *base = Object::cast_to<Spatial>(root->get_node(animation->track_get_path(p_track)));
|
||||
|
||||
if (!base) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Track is not of type Spatial, can't insert key"));
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 scale = base->get_scale();
|
||||
|
||||
undo_redo->create_action(TTR("Add Scale Key"));
|
||||
undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, scale);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
|
||||
undo_redo->commit_action();
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
NodePath bp;
|
||||
@ -5365,6 +5578,15 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
|
||||
case Animation::TYPE_TRANSFORM:
|
||||
track_type = TTR("Transform");
|
||||
break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
track_type += TTR("Position");
|
||||
break;
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
track_type += TTR("Rotation");
|
||||
break;
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
track_type += TTR("Scale");
|
||||
break;
|
||||
case Animation::TYPE_METHOD:
|
||||
track_type = TTR("Methods");
|
||||
break;
|
||||
|
1
editor/icons/icon_key_track_position.svg
Normal file
1
editor/icons/icon_key_track_position.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><ellipse cx="-.88987" cy="6.095995" fill="none" rx="1.249577" ry="1.086747"/><path d="m4.949661.60426977a.6444116.6444116 0 0 0 -.445508.1875179l-1.288694 1.28869423.911171.9111713.83314-.8331408.833141.8331408.911172-.9111713-1.288695-1.28869423a.6444116.6444116 0 0 0 -.465644-.1875179zm-3.02289 2.76489363-1.288694 1.2886943a.6444116.6444116 0 0 0 0 .9111713l1.288694 1.2886943.911171-.9111713-.833141-.8331409.833141-.8331409-.911171-.9111713zm6.065948 0-.911171.9111713.83314.8331409-.83314.8331408.911171.9111714 1.288694-1.2886944a.6444116.6444116 0 0 0 0-.9111713l-1.288694-1.2886943zm-3.032942.4555727a1.2886943 1.2886943 0 0 0 -1.288694 1.2886944 1.2886943 1.2886943 0 0 0 1.288694 1.2886943 1.2886943 1.2886943 0 0 0 1.288694-1.2886943 1.2886943 1.2886943 0 0 0 -1.288694-1.2886944zm-.83314 3.4105296-.911172.9111715 1.288694 1.288694a.6444116.6444116 0 0 0 .911171 0l1.288695-1.288694-.911172-.9111715-.833141.8331405-.83314-.8331405z" fill="#ea7940" stroke-width=".644347"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
editor/icons/icon_key_track_rotation.svg
Normal file
1
editor/icons/icon_key_track_rotation.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><ellipse cx="-.88987" cy="6.095995" fill="none" rx="1.249577" ry="1.086747"/><path d="m5.0711986.88214062a4.1086779 4.1086779 0 0 0 -.178839.00115 4.1086779 4.1086779 0 0 0 -.409265.033245 4.1086779 4.1086779 0 0 0 -3.49308098 3.27179898 4.1086779 4.1086779 0 0 0 1.15670998 3.7360798h-.648877v1.1739077h2.347816a.58701268.58701268 0 0 0 .569756-.729119l-.586953-2.3478164-1.139514.2854534.165082.6580347a2.9347699 2.9347699 0 0 1 -.769204-1.9752178 2.9347699 2.9347699 0 0 1 2.93477-2.93477 2.9347699 2.9347699 0 0 1 2.93477 2.93477 2.9347699 2.9347699 0 0 1 -.860944 2.0738257l.831127.8311266a4.1086779 4.1086779 0 0 0 .779534-4.7219857 4.1086779 4.1086779 0 0 0 -3.632953-2.29047068zm-.05159 2.93359608a1.173908 1.173908 0 0 0 -1.173907 1.173908 1.173908 1.173908 0 0 0 1.173907 1.173908 1.173908 1.173908 0 0 0 1.173909-1.173908 1.173908 1.173908 0 0 0 -1.173909-1.173908z" fill="#ff2b88" stroke-width=".586954"/></svg>
|
After Width: | Height: | Size: 1007 B |
1
editor/icons/icon_key_track_scale.svg
Normal file
1
editor/icons/icon_key_track_scale.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><ellipse cx="-.88987" cy="6.095995" fill="none" rx="1.249577" ry="1.086747"/><path d="m5.5742494.94786723a.58774585.58774585 0 0 0 -.5877458.58774577.58774585.58774585 0 0 0 .5877458.5877459h1.519852l-.7599554.7599553.8311314.8311314.7599554-.7599553v1.5198519a.58774585.58774585 0 0 0 .5877459.5877459.58774585.58774585 0 0 0 .5877458-.5877459v-2.9387292a.58780462.58780462 0 0 0 -.5877458-.58774577zm-.5877458 2.93872917a1.1754917 1.1754917 0 0 0 -1.1754917 1.1754917 1.1754917 1.1754917 0 0 0 1.1754917 1.1754917 1.1754917 1.1754917 0 0 0 1.1754917-1.1754917 1.1754917 1.1754917 0 0 0 -1.1754917-1.1754917zm-3.5264751 1.1754917a.58774585.58774585 0 0 0 -.5877459.5877458v2.9387299a.58780462.58780462 0 0 0 .5877459.5877454h2.9387292a.58774585.58774585 0 0 0 .5877459-.5877454.58774585.58774585 0 0 0 -.5877459-.5877465h-1.519852l.7599554-.7599554-.8311314-.8311314-.7599554.7599554v-1.519852a.58774585.58774585 0 0 0 -.5877458-.5877458z" fill="#eac840" stroke-width=".587745"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
editor/icons/icon_key_x_position.svg
Normal file
1
editor/icons/icon_key_x_position.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><rect fill="#ea7940" height="6.1027" ry=".76286" transform="matrix(.70710678 -.70710678 .70710678 .70710678 0 -1042.4)" width="6.1027" x="-740.13947" y="741.10779"/></svg>
|
After Width: | Height: | Size: 255 B |
1
editor/icons/icon_key_x_rotation.svg
Normal file
1
editor/icons/icon_key_x_rotation.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><rect fill="#ff2b88" height="6.1027" ry=".76286" transform="matrix(.70710678 -.70710678 .70710678 .70710678 .000003 -1042.399994)" width="6.103" x="-740.13947" y="741.10779"/></svg>
|
After Width: | Height: | Size: 265 B |
1
editor/icons/icon_key_x_scale.svg
Normal file
1
editor/icons/icon_key_x_scale.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><rect fill="#eac840" height="6.1027" ry=".76286" transform="matrix(.70710678 -.70710678 .70710678 .70710678 .000003 -1042.399994)" width="6.103" x="-740.13947" y="741.10779"/></svg>
|
After Width: | Height: | Size: 265 B |
@ -31,6 +31,7 @@
|
||||
#include "resource_importer_scene.h"
|
||||
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "scene/3d/collision_shape.h"
|
||||
#include "scene/3d/mesh_instance.h"
|
||||
@ -41,16 +42,14 @@
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/box_shape.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "scene/resources/plane_shape.h"
|
||||
#include "scene/resources/ray_shape.h"
|
||||
#include "scene/resources/resource_format_text.h"
|
||||
#include "scene/resources/sphere_shape.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "scene/resources/shape.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "scene/resources/sphere_shape.h"
|
||||
|
||||
uint32_t EditorSceneImporter::get_import_flags() const {
|
||||
if (get_script_instance()) {
|
||||
@ -681,8 +680,19 @@ void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, boo
|
||||
Vector3 s;
|
||||
default_anim->transform_track_interpolate(j, from, &p, &q, &s);
|
||||
new_anim->transform_track_insert_key(dtrack, 0, p, q, s);
|
||||
}
|
||||
if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) {
|
||||
Vector3 p;
|
||||
default_anim->position_track_interpolate(j, from, &p);
|
||||
new_anim->position_track_insert_key(dtrack, 0, p);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) {
|
||||
Quat r;
|
||||
default_anim->rotation_track_interpolate(j, from, &r);
|
||||
new_anim->rotation_track_insert_key(dtrack, 0, r);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) {
|
||||
Vector3 s;
|
||||
default_anim->scale_track_interpolate(j, from, &s);
|
||||
new_anim->scale_track_insert_key(dtrack, 0, s);
|
||||
} 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);
|
||||
}
|
||||
@ -695,8 +705,19 @@ void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, boo
|
||||
Vector3 s;
|
||||
default_anim->transform_track_get_key(j, k, &p, &q, &s);
|
||||
new_anim->transform_track_insert_key(dtrack, kt - from, p, q, s);
|
||||
}
|
||||
if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) {
|
||||
Vector3 p;
|
||||
default_anim->position_track_get_key(j, k, &p);
|
||||
new_anim->position_track_insert_key(dtrack, kt - from, p);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) {
|
||||
Quat r;
|
||||
default_anim->rotation_track_get_key(j, k, &r);
|
||||
new_anim->rotation_track_insert_key(dtrack, kt - from, r);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) {
|
||||
Vector3 s;
|
||||
default_anim->scale_track_get_key(j, k, &s);
|
||||
new_anim->scale_track_insert_key(dtrack, kt - from, s);
|
||||
} 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);
|
||||
}
|
||||
@ -709,8 +730,19 @@ void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, boo
|
||||
Vector3 s;
|
||||
default_anim->transform_track_interpolate(j, to, &p, &q, &s);
|
||||
new_anim->transform_track_insert_key(dtrack, to - from, p, q, s);
|
||||
}
|
||||
if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) {
|
||||
Vector3 p;
|
||||
default_anim->position_track_interpolate(j, to, &p);
|
||||
new_anim->position_track_insert_key(dtrack, to - from, p);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) {
|
||||
Quat r;
|
||||
default_anim->rotation_track_interpolate(j, to, &r);
|
||||
new_anim->rotation_track_insert_key(dtrack, to - from, r);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) {
|
||||
Vector3 s;
|
||||
default_anim->scale_track_interpolate(j, to, &s);
|
||||
new_anim->scale_track_insert_key(dtrack, to - from, s);
|
||||
} 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);
|
||||
}
|
||||
@ -729,8 +761,25 @@ void ResourceImporterScene::_create_clips(Node *scene, const Array &p_clips, boo
|
||||
new_anim->transform_track_insert_key(dtrack, 0, p, q, s);
|
||||
default_anim->transform_track_interpolate(j, to, &p, &q, &s);
|
||||
new_anim->transform_track_insert_key(dtrack, to - from, p, q, s);
|
||||
}
|
||||
if (default_anim->track_get_type(j) == Animation::TYPE_VALUE) {
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_POSITION_3D) {
|
||||
Vector3 p;
|
||||
default_anim->position_track_interpolate(j, from, &p);
|
||||
new_anim->position_track_insert_key(dtrack, 0, p);
|
||||
default_anim->position_track_interpolate(j, to, &p);
|
||||
new_anim->position_track_insert_key(dtrack, to - from, p);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_ROTATION_3D) {
|
||||
Quat r;
|
||||
default_anim->rotation_track_interpolate(j, from, &r);
|
||||
new_anim->rotation_track_insert_key(dtrack, 0, r);
|
||||
default_anim->rotation_track_interpolate(j, to, &r);
|
||||
new_anim->rotation_track_insert_key(dtrack, to - from, r);
|
||||
} else if (default_anim->track_get_type(j) == Animation::TYPE_SCALE_3D) {
|
||||
Vector3 s;
|
||||
default_anim->scale_track_interpolate(j, from, &s);
|
||||
new_anim->scale_track_insert_key(dtrack, 0, s);
|
||||
default_anim->scale_track_interpolate(j, to, &s);
|
||||
new_anim->scale_track_insert_key(dtrack, to - from, s);
|
||||
} 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);
|
||||
Variant to_var = default_anim->value_track_interpolate(j, to);
|
||||
@ -1130,6 +1179,11 @@ void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, in
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angular_error"), 0.01));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::REAL, "animation/optimizer/max_angle"), 22));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/optimizer/remove_unused_tracks"), true));
|
||||
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/position", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Always,Never"), 1));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/rotation", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Always,Never"), 1));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Always,Never"), 1));
|
||||
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "animation/clips/amount", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
|
||||
for (int i = 0; i < 256; i++) {
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "animation/clip_" + itos(i + 1) + "/name"), ""));
|
||||
|
@ -67,6 +67,13 @@ public:
|
||||
|
||||
};
|
||||
|
||||
enum AnimationImportBoneTracks {
|
||||
ANIMATION_IMPORT_BONE_TRACKS_IF_PRESENT,
|
||||
ANIMATION_IMPORT_BONE_TRACKS_IF_PRESENT_FOR_ALL,
|
||||
ANIMATION_IMPORT_BONE_TRACKS_ALWAYS,
|
||||
ANIMATION_IMPORT_BONE_TRACKS_NEVER,
|
||||
};
|
||||
|
||||
virtual uint32_t get_import_flags() const;
|
||||
virtual void get_extensions(List<String> *r_extensions) const;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, uint32_t p_compress_flags, List<String> *r_missing_deps, Error *r_err = nullptr);
|
||||
|
@ -1395,6 +1395,9 @@ bool ModuleSkeletonEditor::forward_spatial_gui_input(int p_index, Camera *p_came
|
||||
skeleton->set_bone_rest(skeleton->get_selected_bone(), t);
|
||||
} else {
|
||||
skeleton->set_bone_pose(skeleton->get_selected_bone(), t);
|
||||
//skeleton->set_bone_pose_position(p_id, t.origin);
|
||||
//skeleton->set_bone_pose_rotation(p_id, t.basis.operator Quaternion());
|
||||
//skeleton->set_bone_pose_scale(p_id, t.basis.get_scale());
|
||||
}
|
||||
|
||||
sev->update_surface();
|
||||
|
@ -95,6 +95,12 @@ bool Skeleton::_set(const StringName &p_path, const Variant &p_value) {
|
||||
set_bone_enabled(which, p_value);
|
||||
} else if (what == "pose") {
|
||||
set_bone_pose(which, p_value);
|
||||
} else if (what == "position") {
|
||||
set_bone_pose_position(which, p_value);
|
||||
} else if (what == "rotation") {
|
||||
set_bone_pose_rotation(which, p_value);
|
||||
} else if (what == "scale") {
|
||||
set_bone_pose_scale(which, p_value);
|
||||
} else if (what == "bound_children") {
|
||||
Array children = p_value;
|
||||
|
||||
@ -138,6 +144,12 @@ bool Skeleton::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
r_ret = is_bone_enabled(which);
|
||||
} else if (what == "pose") {
|
||||
r_ret = get_bone_pose(which);
|
||||
} else if (what == "position") {
|
||||
r_ret = get_bone_pose_position(which);
|
||||
} else if (what == "rotation") {
|
||||
r_ret = get_bone_pose_rotation(which);
|
||||
} else if (what == "scale") {
|
||||
r_ret = get_bone_pose_scale(which);
|
||||
} else if (what == "bound_children") {
|
||||
Array children;
|
||||
|
||||
@ -165,6 +177,9 @@ void Skeleton::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest"));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled"));
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::QUAT, prep + "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::ARRAY, prep + "bound_children"));
|
||||
}
|
||||
}
|
||||
@ -239,7 +254,8 @@ void Skeleton::_notification(int p_what) {
|
||||
|
||||
if (b.disable_rest) {
|
||||
if (b.enabled) {
|
||||
Transform pose = b.pose;
|
||||
b.update_pose_cache();
|
||||
Transform pose = b.pose_cache;
|
||||
if (b.custom_pose_enable) {
|
||||
pose = b.custom_pose * pose;
|
||||
}
|
||||
@ -261,7 +277,8 @@ void Skeleton::_notification(int p_what) {
|
||||
}
|
||||
} else {
|
||||
if (b.enabled) {
|
||||
Transform pose = b.pose;
|
||||
b.update_pose_cache();
|
||||
Transform pose = b.pose_cache;
|
||||
if (b.custom_pose_enable) {
|
||||
pose = b.custom_pose * pose;
|
||||
}
|
||||
@ -561,16 +578,75 @@ void Skeleton::clear_bones() {
|
||||
// posing api
|
||||
|
||||
void Skeleton::set_bone_pose(int p_bone, const Transform &p_pose) {
|
||||
ERR_FAIL_INDEX(p_bone, bones.size());
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_position = p_pose.origin;
|
||||
bones.write[p_bone].pose_rotation = p_pose.basis.operator Quat();
|
||||
bones.write[p_bone].pose_scale = p_pose.basis.get_scale();
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
|
||||
bones.write[p_bone].pose = p_pose;
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
}
|
||||
}
|
||||
void Skeleton::set_bone_pose_position(int p_bone, const Vector3 &p_position) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_position = p_position;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
}
|
||||
}
|
||||
void Skeleton::set_bone_pose_rotation(int p_bone, const Quat &p_rotation) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_rotation = p_rotation;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
}
|
||||
}
|
||||
void Skeleton::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
bones.write[p_bone].pose_scale = p_scale;
|
||||
bones.write[p_bone].pose_cache_dirty = true;
|
||||
|
||||
if (is_inside_tree()) {
|
||||
_make_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
Transform Skeleton::get_bone_pose(int p_bone) const {
|
||||
ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform());
|
||||
return bones[p_bone].pose;
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform());
|
||||
((Skeleton *)this)->bones.write[p_bone].update_pose_cache();
|
||||
return bones[p_bone].pose_cache;
|
||||
}
|
||||
|
||||
Vector3 Skeleton::get_bone_pose_position(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3());
|
||||
return bones[p_bone].pose_position;
|
||||
}
|
||||
|
||||
Quat Skeleton::get_bone_pose_rotation(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Quat());
|
||||
return bones[p_bone].pose_rotation;
|
||||
}
|
||||
|
||||
Vector3 Skeleton::get_bone_pose_scale(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3());
|
||||
return bones[p_bone].pose_scale;
|
||||
}
|
||||
|
||||
void Skeleton::set_bone_custom_pose(int p_bone, const Transform &p_custom_pose) {
|
||||
@ -925,8 +1001,15 @@ void Skeleton::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("clear_bones"), &Skeleton::clear_bones);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton::get_bone_pose);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_pose", "bone_idx", "pose"), &Skeleton::set_bone_pose);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_pose_position", "bone_idx", "position"), &Skeleton::set_bone_pose_position);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_pose_rotation", "bone_idx", "rotation"), &Skeleton::set_bone_pose_rotation);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_pose_scale", "bone_idx", "scale"), &Skeleton::set_bone_pose_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton::get_bone_pose);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_pose_position", "bone_idx"), &Skeleton::get_bone_pose_position);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_pose_rotation", "bone_idx"), &Skeleton::get_bone_pose_rotation);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_pose_scale", "bone_idx"), &Skeleton::get_bone_pose_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton::clear_bones_global_pose_override);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton::set_bone_global_pose_override, DEFVAL(false));
|
||||
|
@ -86,7 +86,21 @@ private:
|
||||
bool disable_rest;
|
||||
Transform rest;
|
||||
|
||||
Transform pose;
|
||||
//Transform pose;
|
||||
|
||||
_FORCE_INLINE_ void update_pose_cache() {
|
||||
if (pose_cache_dirty) {
|
||||
pose_cache.basis.set_quat_scale(pose_rotation, pose_scale);
|
||||
pose_cache.origin = pose_position;
|
||||
pose_cache_dirty = false;
|
||||
}
|
||||
}
|
||||
bool pose_cache_dirty = true;
|
||||
Transform pose_cache;
|
||||
Vector3 pose_position;
|
||||
Quat pose_rotation;
|
||||
Vector3 pose_scale = Vector3(1, 1, 1);
|
||||
|
||||
Transform pose_global;
|
||||
Transform pose_global_no_override;
|
||||
|
||||
@ -192,7 +206,14 @@ public:
|
||||
// posing api
|
||||
|
||||
void set_bone_pose(int p_bone, const Transform &p_pose);
|
||||
void set_bone_pose_position(int p_bone, const Vector3 &p_position);
|
||||
void set_bone_pose_rotation(int p_bone, const Quat &p_rotation);
|
||||
void set_bone_pose_scale(int p_bone, const Vector3 &p_scale);
|
||||
|
||||
Transform get_bone_pose(int p_bone) const;
|
||||
Vector3 get_bone_pose_position(int p_bone) const;
|
||||
Quat get_bone_pose_rotation(int p_bone) const;
|
||||
Vector3 get_bone_pose_scale(int p_bone) const;
|
||||
|
||||
void set_bone_custom_pose(int p_bone, const Transform &p_custom_pose);
|
||||
Transform get_bone_custom_pose(int p_bone) const;
|
||||
|
@ -1,319 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* animation_cache.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "animation_cache.h"
|
||||
|
||||
#include "scene/3d/skeleton.h"
|
||||
#include "scene/resources/animation.h"
|
||||
|
||||
void AnimationCache::_node_exit_tree(Node *p_node) {
|
||||
//it is one shot, so it disconnects upon arrival
|
||||
|
||||
ERR_FAIL_COND(!connected_nodes.has(p_node));
|
||||
|
||||
connected_nodes.erase(p_node);
|
||||
|
||||
for (int i = 0; i < path_cache.size(); i++) {
|
||||
if (path_cache[i].node != p_node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
path_cache.write[i].valid = false; //invalidate path cache
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationCache::_animation_changed() {
|
||||
_clear_cache();
|
||||
}
|
||||
|
||||
void AnimationCache::_clear_cache() {
|
||||
while (connected_nodes.size()) {
|
||||
connected_nodes.front()->get()->disconnect("tree_exiting", this, "_node_exit_tree");
|
||||
connected_nodes.erase(connected_nodes.front());
|
||||
}
|
||||
path_cache.clear();
|
||||
cache_valid = false;
|
||||
cache_dirty = true;
|
||||
}
|
||||
|
||||
void AnimationCache::_update_cache() {
|
||||
cache_valid = false;
|
||||
|
||||
ERR_FAIL_COND(!root);
|
||||
ERR_FAIL_COND(!root->is_inside_tree());
|
||||
ERR_FAIL_COND(animation.is_null());
|
||||
|
||||
for (int i = 0; i < animation->get_track_count(); i++) {
|
||||
NodePath np = animation->track_get_path(i);
|
||||
|
||||
Node *node = root->get_node(np);
|
||||
if (!node) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(!node, "Invalid track path in animation '" + np + "'.");
|
||||
}
|
||||
|
||||
Path path;
|
||||
|
||||
Ref<Resource> res;
|
||||
|
||||
if (animation->track_get_type(i) == Animation::TYPE_TRANSFORM) {
|
||||
if (np.get_subname_count() > 1) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(animation->track_get_type(i) == Animation::TYPE_TRANSFORM, "Transform tracks can't have a subpath '" + np + "'.");
|
||||
}
|
||||
|
||||
Spatial *sp = Object::cast_to<Spatial>(node);
|
||||
|
||||
if (!sp) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(!sp, "Transform track not of type Spatial '" + np + "'.");
|
||||
}
|
||||
|
||||
if (np.get_subname_count() == 1) {
|
||||
StringName property = np.get_subname(0);
|
||||
String ps = property;
|
||||
|
||||
Skeleton *sk = Object::cast_to<Skeleton>(node);
|
||||
if (!sk) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(!sk, "Property defined in Transform track, but not a Skeleton! '" + np + "'.");
|
||||
}
|
||||
|
||||
int idx = sk->find_bone(ps);
|
||||
if (idx == -1) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(idx == -1, "Property defined in Transform track, but not a Skeleton Bone! '" + np + "'.");
|
||||
}
|
||||
|
||||
path.bone_idx = idx;
|
||||
path.skeleton = sk;
|
||||
}
|
||||
|
||||
path.spatial = sp;
|
||||
|
||||
} else {
|
||||
if (np.get_subname_count() > 0) {
|
||||
RES res2;
|
||||
Vector<StringName> leftover_subpath;
|
||||
|
||||
// We don't want to cache the last resource unless it is a method call
|
||||
bool is_method = animation->track_get_type(i) == Animation::TYPE_METHOD;
|
||||
root->get_node_and_resource(np, res2, leftover_subpath, is_method);
|
||||
|
||||
if (res2.is_valid()) {
|
||||
path.resource = res2;
|
||||
} else {
|
||||
path.node = node;
|
||||
}
|
||||
path.object = res2.is_valid() ? res2.ptr() : (Object *)node;
|
||||
path.subpath = leftover_subpath;
|
||||
|
||||
} else {
|
||||
path.node = node;
|
||||
path.object = node;
|
||||
path.subpath = np.get_subnames();
|
||||
}
|
||||
}
|
||||
|
||||
if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
|
||||
if (np.get_subname_count() == 0) {
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(np.get_subname_count() == 0, "Value Track lacks property: " + np + ".");
|
||||
}
|
||||
|
||||
} else if (animation->track_get_type(i) == Animation::TYPE_METHOD) {
|
||||
if (path.subpath.size() != 0) { // Trying to call a method of a non-resource
|
||||
|
||||
path_cache.push_back(Path());
|
||||
ERR_CONTINUE_MSG(path.subpath.size() != 0, "Method Track has property: " + np + ".");
|
||||
}
|
||||
}
|
||||
|
||||
path.valid = true;
|
||||
|
||||
path_cache.push_back(path);
|
||||
|
||||
if (!connected_nodes.has(path.node)) {
|
||||
connected_nodes.insert(path.node);
|
||||
path.node->connect("tree_exiting", this, "_node_exit_tree", Node::make_binds(path.node), CONNECT_ONESHOT);
|
||||
}
|
||||
}
|
||||
|
||||
cache_dirty = false;
|
||||
cache_valid = true;
|
||||
}
|
||||
|
||||
void AnimationCache::set_track_transform(int p_idx, const Transform &p_transform) {
|
||||
if (cache_dirty) {
|
||||
_update_cache();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!cache_valid);
|
||||
ERR_FAIL_INDEX(p_idx, path_cache.size());
|
||||
Path &p = path_cache.write[p_idx];
|
||||
if (!p.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!p.node);
|
||||
ERR_FAIL_COND(!p.spatial);
|
||||
|
||||
if (p.skeleton) {
|
||||
p.skeleton->set_bone_pose(p.bone_idx, p_transform);
|
||||
} else {
|
||||
p.spatial->set_transform(p_transform);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationCache::set_track_value(int p_idx, const Variant &p_value) {
|
||||
if (cache_dirty) {
|
||||
_update_cache();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!cache_valid);
|
||||
ERR_FAIL_INDEX(p_idx, path_cache.size());
|
||||
Path &p = path_cache.write[p_idx];
|
||||
if (!p.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!p.object);
|
||||
p.object->set_indexed(p.subpath, p_value);
|
||||
}
|
||||
|
||||
void AnimationCache::call_track(int p_idx, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
|
||||
if (cache_dirty) {
|
||||
_update_cache();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!cache_valid);
|
||||
ERR_FAIL_INDEX(p_idx, path_cache.size());
|
||||
Path &p = path_cache.write[p_idx];
|
||||
if (!p.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!p.object);
|
||||
p.object->call(p_method, p_args, p_argcount, r_error);
|
||||
}
|
||||
|
||||
void AnimationCache::set_all(float p_time, float p_delta) {
|
||||
if (cache_dirty) {
|
||||
_update_cache();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(!cache_valid);
|
||||
|
||||
int tc = animation->get_track_count();
|
||||
for (int i = 0; i < tc; i++) {
|
||||
switch (animation->track_get_type(i)) {
|
||||
case Animation::TYPE_TRANSFORM: {
|
||||
Vector3 loc, scale;
|
||||
Quat rot;
|
||||
animation->transform_track_interpolate(i, p_time, &loc, &rot, &scale);
|
||||
Transform tr(Basis(rot), loc);
|
||||
tr.basis.scale(scale);
|
||||
|
||||
set_track_transform(i, tr);
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (animation->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (animation->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE && p_delta == 0)) {
|
||||
Variant v = animation->value_track_interpolate(i, p_time);
|
||||
set_track_value(i, v);
|
||||
} else {
|
||||
List<int> indices;
|
||||
animation->value_track_get_key_indices(i, p_time, p_delta, &indices);
|
||||
|
||||
for (List<int>::Element *E = indices.front(); E; E = E->next()) {
|
||||
Variant v = animation->track_get_key_value(i, E->get());
|
||||
set_track_value(i, v);
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_METHOD: {
|
||||
List<int> indices;
|
||||
animation->method_track_get_key_indices(i, p_time, p_delta, &indices);
|
||||
|
||||
for (List<int>::Element *E = indices.front(); E; E = E->next()) {
|
||||
Vector<Variant> args = animation->method_track_get_params(i, E->get());
|
||||
StringName name = animation->method_track_get_name(i, E->get());
|
||||
Variant::CallError err;
|
||||
|
||||
if (!args.size()) {
|
||||
call_track(i, name, nullptr, 0, err);
|
||||
} else {
|
||||
Vector<const Variant *> argptrs;
|
||||
argptrs.resize(args.size());
|
||||
for (int j = 0; j < args.size(); j++) {
|
||||
argptrs.write[j] = &args.write[j];
|
||||
}
|
||||
|
||||
call_track(i, name, (const Variant **)&argptrs[0], args.size(), err);
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationCache::set_animation(const Ref<Animation> &p_animation) {
|
||||
_clear_cache();
|
||||
|
||||
if (animation.is_valid()) {
|
||||
animation->disconnect("changed", this, "_animation_changed");
|
||||
}
|
||||
|
||||
animation = p_animation;
|
||||
|
||||
if (animation.is_valid()) {
|
||||
animation->connect("changed", this, "_animation_changed");
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationCache::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_node_exit_tree"), &AnimationCache::_node_exit_tree);
|
||||
ClassDB::bind_method(D_METHOD("_animation_changed"), &AnimationCache::_animation_changed);
|
||||
}
|
||||
|
||||
void AnimationCache::set_root(Node *p_root) {
|
||||
_clear_cache();
|
||||
root = p_root;
|
||||
}
|
||||
|
||||
AnimationCache::AnimationCache() {
|
||||
root = nullptr;
|
||||
cache_dirty = true;
|
||||
cache_valid = false;
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#ifndef ANIMATION_CACHE_H
|
||||
#define ANIMATION_CACHE_H
|
||||
/*************************************************************************/
|
||||
/* animation_cache.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object.h"
|
||||
#include "core/reference.h"
|
||||
|
||||
class Node;
|
||||
class Spatial;
|
||||
class Resource;
|
||||
class Skeleton;
|
||||
class Animation;
|
||||
|
||||
class AnimationCache : public Object {
|
||||
GDCLASS(AnimationCache, Object);
|
||||
|
||||
struct Path {
|
||||
Ref<Resource> resource;
|
||||
Object *object;
|
||||
Skeleton *skeleton; // haxor
|
||||
Node *node;
|
||||
Spatial *spatial;
|
||||
|
||||
int bone_idx;
|
||||
Vector<StringName> subpath;
|
||||
bool valid;
|
||||
Path() {
|
||||
object = nullptr;
|
||||
skeleton = nullptr;
|
||||
node = nullptr;
|
||||
bone_idx = -1;
|
||||
valid = false;
|
||||
spatial = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
Set<Node *> connected_nodes;
|
||||
Vector<Path> path_cache;
|
||||
|
||||
Node *root;
|
||||
Ref<Animation> animation;
|
||||
bool cache_dirty;
|
||||
bool cache_valid;
|
||||
|
||||
void _node_exit_tree(Node *p_node);
|
||||
|
||||
void _clear_cache();
|
||||
void _update_cache();
|
||||
void _animation_changed();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_track_transform(int p_idx, const Transform &p_transform);
|
||||
void set_track_value(int p_idx, const Variant &p_value);
|
||||
void call_track(int p_idx, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
|
||||
|
||||
void set_all(float p_time, float p_delta = 0);
|
||||
|
||||
void set_animation(const Ref<Animation> &p_animation);
|
||||
void set_root(Node *p_root);
|
||||
|
||||
AnimationCache();
|
||||
};
|
||||
|
||||
#endif // ANIMATION_CACHE_H
|
@ -65,7 +65,16 @@ void AnimatedValuesBackup::restore() const {
|
||||
if (entry->bone_idx == -1) {
|
||||
entry->object->set_indexed(entry->subpath, entry->value);
|
||||
} else {
|
||||
Object::cast_to<Skeleton>(entry->object)->set_bone_pose(entry->bone_idx, entry->value);
|
||||
if (entry->value.get_type() == Variant::TRANSFORM) {
|
||||
Object::cast_to<Skeleton>(entry->object)->set_bone_pose(entry->bone_idx, entry->value);
|
||||
} else {
|
||||
Array arr = entry->value;
|
||||
if (arr.size() == 3) {
|
||||
Object::cast_to<Skeleton>(entry->object)->set_bone_pose_position(entry->bone_idx, arr[0]);
|
||||
Object::cast_to<Skeleton>(entry->object)->set_bone_pose_rotation(entry->bone_idx, arr[1]);
|
||||
Object::cast_to<Skeleton>(entry->object)->set_bone_pose_scale(entry->bone_idx, arr[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,6 +256,8 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
|
||||
|
||||
p_anim->node_cache.resize(a->get_track_count());
|
||||
|
||||
setup_pass++;
|
||||
|
||||
for (int i = 0; i < a->get_track_count(); i++) {
|
||||
p_anim->node_cache.write[i] = NULL;
|
||||
RES resource;
|
||||
@ -278,44 +289,100 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
|
||||
node_cache_map[key] = TrackNodeCache();
|
||||
}
|
||||
|
||||
p_anim->node_cache.write[i] = &node_cache_map[key];
|
||||
p_anim->node_cache[i]->path = a->track_get_path(i);
|
||||
p_anim->node_cache[i]->node = child;
|
||||
p_anim->node_cache[i]->resource = resource;
|
||||
p_anim->node_cache[i]->node_2d = Object::cast_to<Node2D>(child);
|
||||
TrackNodeCache *node_cache = &node_cache_map[key];
|
||||
p_anim->node_cache.write[i] = node_cache;
|
||||
|
||||
node_cache->path = a->track_get_path(i);
|
||||
node_cache->node = child;
|
||||
node_cache->resource = resource;
|
||||
node_cache->node_2d = Object::cast_to<Node2D>(child);
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
if (a->track_get_type(i) == Animation::TYPE_TRANSFORM) {
|
||||
// special cases and caches for transform tracks
|
||||
|
||||
// cache spatial
|
||||
p_anim->node_cache[i]->spatial = Object::cast_to<Spatial>(child);
|
||||
node_cache->spatial = Object::cast_to<Spatial>(child);
|
||||
// cache skeleton
|
||||
p_anim->node_cache[i]->skeleton = Object::cast_to<Skeleton>(child);
|
||||
if (p_anim->node_cache[i]->skeleton) {
|
||||
node_cache->skeleton = Object::cast_to<Skeleton>(child);
|
||||
if (node_cache->skeleton) {
|
||||
if (a->track_get_path(i).get_subname_count() == 1) {
|
||||
StringName bone_name = a->track_get_path(i).get_subname(0);
|
||||
|
||||
p_anim->node_cache[i]->bone_idx = p_anim->node_cache[i]->skeleton->find_bone(bone_name);
|
||||
if (p_anim->node_cache[i]->bone_idx < 0) {
|
||||
node_cache->bone_idx = node_cache->skeleton->find_bone(bone_name);
|
||||
if (node_cache->bone_idx < 0) {
|
||||
// broken track (nonexistent bone)
|
||||
p_anim->node_cache[i]->skeleton = nullptr;
|
||||
p_anim->node_cache[i]->spatial = nullptr;
|
||||
ERR_CONTINUE(p_anim->node_cache[i]->bone_idx < 0);
|
||||
node_cache->skeleton = nullptr;
|
||||
node_cache->spatial = nullptr;
|
||||
ERR_CONTINUE(node_cache->bone_idx < 0);
|
||||
}
|
||||
} else {
|
||||
// no property, just use spatialnode
|
||||
p_anim->node_cache[i]->skeleton = nullptr;
|
||||
node_cache->skeleton = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
node_cache->transform_used = true;
|
||||
|
||||
} else if (a->track_get_type(i) == Animation::TYPE_POSITION_3D || a->track_get_type(i) == Animation::TYPE_ROTATION_3D || a->track_get_type(i) == Animation::TYPE_SCALE_3D) {
|
||||
// special cases and caches for transform tracks
|
||||
|
||||
if (node_cache->last_setup_pass != setup_pass) {
|
||||
node_cache->loc_used = false;
|
||||
node_cache->rot_used = false;
|
||||
node_cache->scale_used = false;
|
||||
}
|
||||
|
||||
// cache spatial
|
||||
node_cache->spatial = Object::cast_to<Spatial>(child);
|
||||
// cache skeleton
|
||||
node_cache->skeleton = Object::cast_to<Skeleton>(child);
|
||||
if (node_cache->skeleton) {
|
||||
if (a->track_get_path(i).get_subname_count() == 1) {
|
||||
StringName bone_name = a->track_get_path(i).get_subname(0);
|
||||
|
||||
node_cache->bone_idx = node_cache->skeleton->find_bone(bone_name);
|
||||
if (node_cache->bone_idx < 0) {
|
||||
// broken track (nonexistent bone)
|
||||
node_cache->skeleton = nullptr;
|
||||
node_cache->spatial = nullptr;
|
||||
ERR_CONTINUE(node_cache->bone_idx < 0);
|
||||
}
|
||||
Transform rest = node_cache->skeleton->get_bone_rest(bone_idx);
|
||||
node_cache->init_loc = rest.origin;
|
||||
node_cache->init_rot = rest.basis.get_rotation_quat();
|
||||
node_cache->init_scale = rest.basis.get_scale();
|
||||
} else {
|
||||
// no property, just use spatialnode
|
||||
node_cache->skeleton = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
switch (a->track_get_type(i)) {
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
node_cache->loc_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
node_cache->rot_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
node_cache->scale_used = true;
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
if (a->track_get_type(i) == Animation::TYPE_VALUE) {
|
||||
if (!p_anim->node_cache[i]->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
|
||||
if (!node_cache->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
|
||||
TrackNodeCache::PropertyAnim pa;
|
||||
pa.subpath = leftover_path;
|
||||
pa.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
|
||||
pa.special = SP_NONE;
|
||||
pa.owner = p_anim->node_cache[i];
|
||||
if (false && p_anim->node_cache[i]->node_2d) {
|
||||
pa.owner = node_cache;
|
||||
if (false && node_cache->node_2d) {
|
||||
if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_pos) {
|
||||
pa.special = SP_NODE2D_POS;
|
||||
} else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_rot) {
|
||||
@ -324,20 +391,22 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
|
||||
pa.special = SP_NODE2D_SCALE;
|
||||
}
|
||||
}
|
||||
p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
|
||||
node_cache->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
|
||||
}
|
||||
}
|
||||
|
||||
if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) {
|
||||
if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
|
||||
if (!node_cache->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
|
||||
TrackNodeCache::BezierAnim ba;
|
||||
ba.bezier_property = leftover_path;
|
||||
ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
|
||||
ba.owner = p_anim->node_cache[i];
|
||||
ba.owner = node_cache;
|
||||
|
||||
p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
|
||||
node_cache->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
|
||||
}
|
||||
}
|
||||
|
||||
node_cache->last_setup_pass = setup_pass;
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,6 +440,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
|
||||
|
||||
switch (a->track_get_type(i)) {
|
||||
case Animation::TYPE_TRANSFORM: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (!nc->spatial) {
|
||||
continue;
|
||||
}
|
||||
@ -399,7 +469,88 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
|
||||
nc->rot_accum = nc->rot_accum.slerp(rot, p_interp);
|
||||
nc->scale_accum = nc->scale_accum.linear_interpolate(scale, p_interp);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (!nc->spatial) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 loc;
|
||||
|
||||
Error err = a->position_track_interpolate(i, p_time, &loc);
|
||||
//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);
|
||||
cache_update[cache_update_size++] = nc;
|
||||
nc->accum_pass = accum_pass;
|
||||
nc->loc_accum = loc;
|
||||
nc->rot_accum = nc->init_rot;
|
||||
nc->scale_accum = nc->init_scale;
|
||||
} else {
|
||||
nc->loc_accum = nc->loc_accum.linear_interpolate(loc, p_interp);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (!nc->spatial) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Quat rot;
|
||||
|
||||
Error err = a->rotation_track_interpolate(i, p_time, &rot);
|
||||
//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);
|
||||
cache_update[cache_update_size++] = nc;
|
||||
nc->accum_pass = accum_pass;
|
||||
nc->loc_accum = nc->init_loc;
|
||||
nc->rot_accum = rot;
|
||||
nc->scale_accum = nc->init_scale;
|
||||
} else {
|
||||
nc->rot_accum = nc->rot_accum.slerp(rot, p_interp);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
if (!nc->spatial) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 scale;
|
||||
|
||||
Error err = a->scale_track_interpolate(i, p_time, &scale);
|
||||
//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);
|
||||
cache_update[cache_update_size++] = nc;
|
||||
nc->accum_pass = accum_pass;
|
||||
nc->loc_accum = nc->init_loc;
|
||||
nc->rot_accum = nc->init_rot;
|
||||
nc->scale_accum = scale;
|
||||
} else {
|
||||
nc->scale_accum = nc->scale_accum.linear_interpolate(scale, p_interp);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
if (!nc->node) {
|
||||
@ -855,14 +1006,43 @@ void AnimationPlayer::_animation_update_transforms() {
|
||||
|
||||
ERR_CONTINUE(nc->accum_pass != accum_pass);
|
||||
|
||||
t.origin = nc->loc_accum;
|
||||
t.basis.set_quat_scale(nc->rot_accum, nc->scale_accum);
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
if (nc->skeleton && nc->bone_idx >= 0) {
|
||||
nc->skeleton->set_bone_pose(nc->bone_idx, t);
|
||||
if (nc->transform_used) {
|
||||
t.origin = nc->loc_accum;
|
||||
t.basis.set_quat_scale(nc->rot_accum, nc->scale_accum);
|
||||
nc->skeleton->set_bone_pose(nc->bone_idx, t);
|
||||
} else {
|
||||
if (nc->loc_used) {
|
||||
nc->skeleton->set_bone_pose_position(nc->bone_idx, nc->loc_accum);
|
||||
}
|
||||
if (nc->rot_used) {
|
||||
nc->skeleton->set_bone_pose_rotation(nc->bone_idx, nc->rot_accum);
|
||||
}
|
||||
if (nc->scale_used) {
|
||||
nc->skeleton->set_bone_pose_scale(nc->bone_idx, nc->scale_accum);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (nc->spatial) {
|
||||
nc->spatial->set_transform(t);
|
||||
if (nc->transform_used) {
|
||||
t.origin = nc->loc_accum;
|
||||
t.basis.set_quat_scale(nc->rot_accum, nc->scale_accum);
|
||||
nc->spatial->set_transform(t);
|
||||
} else {
|
||||
if (nc->loc_used) {
|
||||
nc->spatial->set_translation(nc->loc_accum);
|
||||
}
|
||||
if (nc->rot_used) {
|
||||
nc->spatial->set_rotation(nc->rot_accum.get_euler());
|
||||
}
|
||||
if (nc->scale_used) {
|
||||
nc->spatial->set_scale(nc->scale_accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
}
|
||||
}
|
||||
|
||||
@ -1537,9 +1717,20 @@ Ref<AnimatedValuesBackup> AnimationPlayer::backup_animated_values(Node *p_root_o
|
||||
}
|
||||
|
||||
AnimatedValuesBackup::Entry entry;
|
||||
entry.object = nc->skeleton;
|
||||
entry.bone_idx = nc->bone_idx;
|
||||
entry.value = nc->skeleton->get_bone_pose(nc->bone_idx);
|
||||
|
||||
if (nc->transform_used) {
|
||||
entry.object = nc->skeleton;
|
||||
entry.bone_idx = nc->bone_idx;
|
||||
entry.value = nc->skeleton->get_bone_pose(nc->bone_idx);
|
||||
} else {
|
||||
Array arr;
|
||||
arr.resize(3);
|
||||
arr[0] = nc->skeleton->get_bone_pose_position(nc->bone_idx);
|
||||
arr[1] = nc->skeleton->get_bone_pose_rotation(nc->bone_idx);
|
||||
arr[2] = nc->skeleton->get_bone_pose_scale(nc->bone_idx);
|
||||
entry.value = nc;
|
||||
}
|
||||
|
||||
backup->entries.push_back(entry);
|
||||
} else {
|
||||
if (nc->spatial) {
|
||||
|
@ -93,6 +93,8 @@ private:
|
||||
SP_NODE2D_SCALE,
|
||||
};
|
||||
|
||||
uint32_t setup_pass = 1;
|
||||
|
||||
struct TrackNodeCache {
|
||||
NodePath path;
|
||||
uint32_t id;
|
||||
@ -104,6 +106,14 @@ private:
|
||||
int bone_idx;
|
||||
// accumulated transforms
|
||||
|
||||
bool transform_used;
|
||||
bool loc_used;
|
||||
bool rot_used;
|
||||
bool scale_used;
|
||||
Vector3 init_loc;
|
||||
Quat init_rot;
|
||||
Vector3 init_scale;
|
||||
|
||||
Vector3 loc_accum;
|
||||
Quat rot_accum;
|
||||
Vector3 scale_accum;
|
||||
@ -128,7 +138,8 @@ private:
|
||||
owner(nullptr),
|
||||
special(SP_NONE),
|
||||
object(nullptr),
|
||||
accum_pass(0) {}
|
||||
accum_pass(0) {
|
||||
}
|
||||
};
|
||||
|
||||
Map<StringName, PropertyAnim> property_anim;
|
||||
@ -149,6 +160,8 @@ private:
|
||||
|
||||
Map<StringName, BezierAnim> bezier_anim;
|
||||
|
||||
uint32_t last_setup_pass;
|
||||
|
||||
TrackNodeCache() :
|
||||
id(0),
|
||||
node(nullptr),
|
||||
@ -160,7 +173,16 @@ private:
|
||||
audio_playing(false),
|
||||
audio_start(0.0),
|
||||
audio_len(0.0),
|
||||
animation_playing(false) {}
|
||||
animation_playing(false) {
|
||||
transform_used = false;
|
||||
loc_used = false;
|
||||
rot_used = false;
|
||||
scale_used = false;
|
||||
last_setup_pass = 0;
|
||||
init_loc = Vector3(0, 0, 0);
|
||||
init_rot = Quat(0, 0, 0, 1);
|
||||
init_scale = Vector3(1, 1, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct TrackNodeCacheKey {
|
||||
|
@ -543,13 +543,18 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
||||
NodePath path = anim->track_get_path(i);
|
||||
Animation::TrackType track_type = anim->track_get_type(i);
|
||||
|
||||
Animation::TrackType track_cache_type = track_type;
|
||||
if (track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) {
|
||||
track_cache_type = Animation::TYPE_POSITION_3D; //reference them as position3D tracks, even if they modify rotation or scale
|
||||
}
|
||||
|
||||
TrackCache *track = nullptr;
|
||||
if (track_cache.has(path)) {
|
||||
track = track_cache.get(path);
|
||||
}
|
||||
|
||||
//if not valid, delete track
|
||||
if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == nullptr)) {
|
||||
if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) {
|
||||
playing_caches.erase(track);
|
||||
memdelete(track);
|
||||
track_cache.erase(path);
|
||||
@ -587,6 +592,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
||||
|
||||
} break;
|
||||
case Animation::TYPE_TRANSFORM: {
|
||||
#ifndef _3D_DISABLED
|
||||
Spatial *spatial = Object::cast_to<Spatial>(child);
|
||||
|
||||
if (!spatial) {
|
||||
@ -595,10 +601,46 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
||||
}
|
||||
|
||||
TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
|
||||
track_xform->type = Animation::TYPE_TRANSFORM;
|
||||
|
||||
track_xform->spatial = spatial;
|
||||
track_xform->skeleton = nullptr;
|
||||
track_xform->bone_idx = -1;
|
||||
track_xform->transform_used = true;
|
||||
|
||||
if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {
|
||||
Skeleton *sk = Object::cast_to<Skeleton>(spatial);
|
||||
track_xform->skeleton = sk;
|
||||
int bone_idx = sk->find_bone(path.get_subname(0));
|
||||
if (bone_idx != -1) {
|
||||
track_xform->bone_idx = bone_idx;
|
||||
}
|
||||
}
|
||||
|
||||
track_xform->object = spatial;
|
||||
track_xform->object_id = track_xform->object->get_instance_id();
|
||||
|
||||
track = track_xform;
|
||||
#endif
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
Spatial *spatial = Object::cast_to<Spatial>(child);
|
||||
|
||||
if (!spatial) {
|
||||
ERR_PRINT("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
|
||||
track_xform->type = Animation::TYPE_POSITION_3D;
|
||||
|
||||
track_xform->spatial = spatial;
|
||||
track_xform->skeleton = nullptr;
|
||||
track_xform->bone_idx = -1;
|
||||
track_xform->transform_used = true;
|
||||
|
||||
if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {
|
||||
Skeleton *sk = Object::cast_to<Skeleton>(spatial);
|
||||
@ -614,6 +656,21 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
||||
|
||||
track = track_xform;
|
||||
|
||||
switch (track_type) {
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
track_xform->loc_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
track_xform->rot_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
track_xform->scale_used = true;
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
} break;
|
||||
case Animation::TYPE_METHOD: {
|
||||
TrackCacheMethod *track_method = memnew(TrackCacheMethod);
|
||||
@ -668,6 +725,28 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
|
||||
}
|
||||
|
||||
track_cache[path] = track;
|
||||
} else if (track_cache_type == Animation::TYPE_POSITION_3D) {
|
||||
TrackCacheTransform *track_xform = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (track->setup_pass != setup_pass) {
|
||||
track_xform->loc_used = false;
|
||||
track_xform->rot_used = false;
|
||||
track_xform->scale_used = false;
|
||||
}
|
||||
|
||||
switch (track_type) {
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
track_xform->loc_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
track_xform->rot_used = true;
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
track_xform->scale_used = true;
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
track->setup_pass = setup_pass;
|
||||
@ -831,8 +910,11 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||
ERR_CONTINUE(!track_cache.has(path));
|
||||
|
||||
TrackCache *track = track_cache[path];
|
||||
if (track->type != a->track_get_type(i)) {
|
||||
continue; //may happen should not
|
||||
|
||||
Animation::TrackType ttype = a->track_get_type(i);
|
||||
if (ttype != Animation::TYPE_TRANSFORM && ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
|
||||
//broken animation, but avoid error spamming
|
||||
continue;
|
||||
}
|
||||
|
||||
track->root_motion = root_motion_track == path;
|
||||
@ -850,6 +932,7 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||
|
||||
switch (track->type) {
|
||||
case Animation::TYPE_TRANSFORM: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (track->root_motion) {
|
||||
@ -935,7 +1018,209 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||
}
|
||||
t->scale = t->scale.linear_interpolate(scale, blend);
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (t->process_pass != process_pass) {
|
||||
t->process_pass = process_pass;
|
||||
t->loc = Vector3();
|
||||
t->rot = Quat();
|
||||
t->rot_blend_accum = 0;
|
||||
t->scale = Vector3(1, 1, 1);
|
||||
}
|
||||
|
||||
if (track->root_motion) {
|
||||
double prev_time = time - delta;
|
||||
|
||||
if (prev_time < 0) {
|
||||
if (!a->has_loop()) {
|
||||
prev_time = 0;
|
||||
} else {
|
||||
prev_time = a->get_length() + prev_time;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 loc[2];
|
||||
|
||||
if (prev_time > time) {
|
||||
Error err = a->position_track_interpolate(i, prev_time, &loc[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->position_track_interpolate(i, (double)a->get_length(), &loc[1]);
|
||||
|
||||
t->loc += (loc[1] - loc[0]) * blend;
|
||||
prev_time = 0;
|
||||
}
|
||||
|
||||
Error err = a->position_track_interpolate(i, prev_time, &loc[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->position_track_interpolate(i, time, &loc[1]);
|
||||
|
||||
t->loc += (loc[1] - loc[0]) * blend;
|
||||
|
||||
prev_time = 0;
|
||||
|
||||
} else {
|
||||
Vector3 loc;
|
||||
|
||||
Error err = a->position_track_interpolate(i, time, &loc);
|
||||
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t->loc = t->loc.linear_interpolate(loc, blend);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_ROTATION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (t->process_pass != process_pass) {
|
||||
t->process_pass = process_pass;
|
||||
t->loc = Vector3();
|
||||
t->rot = Quat();
|
||||
t->rot_blend_accum = 0;
|
||||
t->scale = Vector3(1, 1, 1);
|
||||
}
|
||||
|
||||
if (track->root_motion) {
|
||||
double prev_time = time - delta;
|
||||
|
||||
if (prev_time < 0) {
|
||||
if (!a->has_loop()) {
|
||||
prev_time = 0;
|
||||
} else {
|
||||
prev_time = a->get_length() + prev_time;
|
||||
}
|
||||
}
|
||||
|
||||
Quat rot[2];
|
||||
|
||||
if (prev_time > time) {
|
||||
Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
|
||||
|
||||
Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
|
||||
prev_time = 0;
|
||||
}
|
||||
|
||||
Error err = a->rotation_track_interpolate(i, prev_time, &rot[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->rotation_track_interpolate(i, time, &rot[1]);
|
||||
|
||||
Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
|
||||
prev_time = 0;
|
||||
|
||||
} else {
|
||||
Quat rot;
|
||||
|
||||
Error err = a->rotation_track_interpolate(i, time, &rot);
|
||||
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (t->rot_blend_accum == 0) {
|
||||
t->rot = rot;
|
||||
t->rot_blend_accum = blend;
|
||||
} else {
|
||||
real_t rot_total = t->rot_blend_accum + blend;
|
||||
t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized();
|
||||
t->rot_blend_accum = rot_total;
|
||||
}
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_SCALE_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (t->process_pass != process_pass) {
|
||||
t->process_pass = process_pass;
|
||||
t->loc = Vector3();
|
||||
t->rot = Quat();
|
||||
t->rot_blend_accum = 0;
|
||||
t->scale = Vector3(1, 1, 1);
|
||||
}
|
||||
|
||||
if (track->root_motion) {
|
||||
double prev_time = time - delta;
|
||||
|
||||
if (prev_time < 0) {
|
||||
if (!a->has_loop()) {
|
||||
prev_time = 0;
|
||||
} else {
|
||||
prev_time = a->get_length() + prev_time;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 scale[2];
|
||||
|
||||
if (prev_time > time) {
|
||||
Error err = a->scale_track_interpolate(i, prev_time, &scale[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
|
||||
t->scale += (scale[1] - scale[0]) * blend;
|
||||
|
||||
prev_time = 0;
|
||||
}
|
||||
|
||||
Error err = a->scale_track_interpolate(i, prev_time, &scale[0]);
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->scale_track_interpolate(i, time, &scale[1]);
|
||||
|
||||
t->scale += (scale[1] - scale[0]) * blend;
|
||||
|
||||
prev_time = 0;
|
||||
|
||||
} else {
|
||||
Vector3 scale;
|
||||
|
||||
Error err = a->scale_track_interpolate(i, time, &scale);
|
||||
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
|
||||
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t->scale = t->scale.linear_interpolate(scale, blend);
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
|
||||
@ -1198,6 +1483,7 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||
|
||||
switch (track->type) {
|
||||
case Animation::TYPE_TRANSFORM: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
Transform xform;
|
||||
@ -1217,7 +1503,45 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||
} else if (!t->skeleton) {
|
||||
t->spatial->set_transform(xform);
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
#ifndef _3D_DISABLED
|
||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||
|
||||
if (t->root_motion) {
|
||||
Transform xform;
|
||||
xform.origin = t->loc;
|
||||
xform.basis.set_quat_scale(t->rot, t->scale);
|
||||
|
||||
root_motion_transform = xform;
|
||||
|
||||
if (t->skeleton && t->bone_idx >= 0) {
|
||||
root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse();
|
||||
}
|
||||
} else if (t->skeleton && t->bone_idx >= 0) {
|
||||
if (t->loc_used) {
|
||||
t->skeleton->set_bone_pose_position(t->bone_idx, t->loc);
|
||||
}
|
||||
if (t->rot_used) {
|
||||
t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot);
|
||||
}
|
||||
if (t->scale_used) {
|
||||
t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale);
|
||||
}
|
||||
|
||||
} else if (!t->skeleton) {
|
||||
if (t->loc_used) {
|
||||
t->spatial->set_translation(t->loc);
|
||||
}
|
||||
if (t->rot_used) {
|
||||
t->spatial->set_rotation(t->rot.get_euler());
|
||||
}
|
||||
if (t->scale_used) {
|
||||
t->spatial->set_scale(t->scale);
|
||||
}
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
} break;
|
||||
case Animation::TYPE_VALUE: {
|
||||
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
|
||||
|
@ -174,6 +174,7 @@ private:
|
||||
ObjectID object_id;
|
||||
|
||||
TrackCache() {
|
||||
type = Animation::TYPE_ANIMATION;
|
||||
root_motion = false;
|
||||
setup_pass = 0;
|
||||
process_pass = 0;
|
||||
@ -187,16 +188,24 @@ private:
|
||||
Spatial *spatial;
|
||||
Skeleton *skeleton;
|
||||
int bone_idx;
|
||||
bool transform_used;
|
||||
bool loc_used;
|
||||
bool rot_used;
|
||||
bool scale_used;
|
||||
Vector3 loc;
|
||||
Quat rot;
|
||||
float rot_blend_accum;
|
||||
Vector3 scale;
|
||||
|
||||
TrackCacheTransform() {
|
||||
type = Animation::TYPE_TRANSFORM;
|
||||
type = Animation::TYPE_POSITION_3D;
|
||||
spatial = nullptr;
|
||||
bone_idx = -1;
|
||||
skeleton = nullptr;
|
||||
transform_used = false;
|
||||
loc_used = false;
|
||||
rot_used = false;
|
||||
scale_used = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,9 @@ public:
|
||||
enum TrackType {
|
||||
TYPE_VALUE, ///< Set a value in a property, can be interpolated.
|
||||
TYPE_TRANSFORM, ///< Transform a node or a bone.
|
||||
TYPE_POSITION_3D, ///< Position 3D track
|
||||
TYPE_ROTATION_3D, ///< Rotation 3D track
|
||||
TYPE_SCALE_3D, ///< Scale 3D track
|
||||
TYPE_METHOD, ///< Call any method on a specific node.
|
||||
TYPE_BEZIER, ///< Bezier curve
|
||||
TYPE_AUDIO,
|
||||
@ -97,6 +100,30 @@ private:
|
||||
Vector3 scale;
|
||||
};
|
||||
|
||||
/* POSITION TRACK */
|
||||
|
||||
struct PositionTrack : public Track {
|
||||
Vector<TKey<Vector3>> positions;
|
||||
//int32_t compressed_track = -1;
|
||||
PositionTrack() { type = TYPE_POSITION_3D; }
|
||||
};
|
||||
|
||||
/* ROTATION TRACK */
|
||||
|
||||
struct RotationTrack : public Track {
|
||||
Vector<TKey<Quat>> rotations;
|
||||
//int32_t compressed_track = -1;
|
||||
RotationTrack() { type = TYPE_ROTATION_3D; }
|
||||
};
|
||||
|
||||
/* SCALE TRACK */
|
||||
|
||||
struct ScaleTrack : public Track {
|
||||
Vector<TKey<Vector3>> scales;
|
||||
//int32_t compressed_track = -1;
|
||||
ScaleTrack() { type = TYPE_SCALE_3D; }
|
||||
};
|
||||
|
||||
/* TRANSFORM TRACK */
|
||||
|
||||
struct TransformTrack : public Track {
|
||||
@ -255,6 +282,14 @@ private:
|
||||
bool _transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err, float p_alowed_angular_err, float p_max_optimizable_angle, const Vector3 &p_norm);
|
||||
void _transform_track_optimize(int p_idx, float p_allowed_linear_err = 0.05, float p_allowed_angular_err = 0.01, float p_max_optimizable_angle = Math_PI * 0.125);
|
||||
|
||||
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<Quat> &t0, const TKey<Quat> &t1, const TKey<Quat> &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);
|
||||
|
||||
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);
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
@ -299,6 +334,19 @@ public:
|
||||
|
||||
int transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
|
||||
Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const;
|
||||
|
||||
int position_track_insert_key(int p_track, double p_time, const Vector3 &p_position);
|
||||
Error position_track_get_key(int p_track, int p_key, Vector3 *r_position) const;
|
||||
Error position_track_interpolate(int p_track, double p_time, Vector3 *r_interpolation) const;
|
||||
|
||||
int rotation_track_insert_key(int p_track, double p_time, const Quat &p_rotation);
|
||||
Error rotation_track_get_key(int p_track, int p_key, Quat *r_rotation) const;
|
||||
Error rotation_track_interpolate(int p_track, double p_time, Quat *r_interpolation) const;
|
||||
|
||||
int scale_track_insert_key(int p_track, double p_time, const Vector3 &p_scale);
|
||||
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;
|
||||
|
||||
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
|
||||
InterpolationType track_get_interpolation_type(int p_track) const;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user