-Add root motion support in AnimationTree.
-Add RootMotionView, to debug root motion in 3D (disabled in runtime)
This commit is contained in:
parent
f036353b93
commit
c633b770cb
|
@ -1464,7 +1464,8 @@ void EditorInspector::update_tree() {
|
||||||
#endif
|
#endif
|
||||||
for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) {
|
for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) {
|
||||||
Ref<EditorInspectorPlugin> ped = E->get();
|
Ref<EditorInspectorPlugin> ped = E->get();
|
||||||
ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage);
|
bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage);
|
||||||
|
|
||||||
List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector
|
List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector
|
||||||
ped->added_editors.clear();
|
ped->added_editors.clear();
|
||||||
|
|
||||||
|
@ -1532,6 +1533,10 @@ void EditorInspector::update_tree() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exclusive) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
#include "editor/plugins/physical_bone_plugin.h"
|
#include "editor/plugins/physical_bone_plugin.h"
|
||||||
#include "editor/plugins/polygon_2d_editor_plugin.h"
|
#include "editor/plugins/polygon_2d_editor_plugin.h"
|
||||||
#include "editor/plugins/resource_preloader_editor_plugin.h"
|
#include "editor/plugins/resource_preloader_editor_plugin.h"
|
||||||
|
#include "editor/plugins/root_motion_editor_plugin.h"
|
||||||
#include "editor/plugins/script_editor_plugin.h"
|
#include "editor/plugins/script_editor_plugin.h"
|
||||||
#include "editor/plugins/script_text_editor.h"
|
#include "editor/plugins/script_text_editor.h"
|
||||||
#include "editor/plugins/shader_editor_plugin.h"
|
#include "editor/plugins/shader_editor_plugin.h"
|
||||||
|
@ -4635,6 +4636,10 @@ EditorNode::EditorNode() {
|
||||||
Ref<EditorInspectorDefaultPlugin> eidp;
|
Ref<EditorInspectorDefaultPlugin> eidp;
|
||||||
eidp.instance();
|
eidp.instance();
|
||||||
EditorInspector::add_inspector_plugin(eidp);
|
EditorInspector::add_inspector_plugin(eidp);
|
||||||
|
|
||||||
|
Ref<EditorInspectorRootMotionPlugin> rmp;
|
||||||
|
rmp.instance();
|
||||||
|
EditorInspector::add_inspector_plugin(rmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pvrtc_register_compressors();
|
_pvrtc_register_compressors();
|
||||||
|
@ -4660,9 +4665,7 @@ EditorNode::EditorNode() {
|
||||||
|
|
||||||
GLOBAL_DEF("editor/main_run_args", "");
|
GLOBAL_DEF("editor/main_run_args", "");
|
||||||
|
|
||||||
ClassDB::set_class_enabled("CollisionShape", true);
|
ClassDB::set_class_enabled("RootMotionView", true);
|
||||||
ClassDB::set_class_enabled("CollisionShape2D", true);
|
|
||||||
ClassDB::set_class_enabled("CollisionPolygon2D", true);
|
|
||||||
|
|
||||||
//defs here, use EDITOR_GET in logic
|
//defs here, use EDITOR_GET in logic
|
||||||
EDITOR_DEF("interface/scene_tabs/always_show_close_button", false);
|
EDITOR_DEF("interface/scene_tabs/always_show_close_button", false);
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
#include "root_motion_editor_plugin.h"
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "scene/main/viewport.h"
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::_confirmed() {
|
||||||
|
|
||||||
|
TreeItem *ti = filters->get_selected();
|
||||||
|
if (!ti)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NodePath path = ti->get_metadata(0);
|
||||||
|
emit_signal("property_changed", get_edited_property(), path);
|
||||||
|
update_property();
|
||||||
|
filter_dialog->hide(); //may come from activated
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::_node_assign() {
|
||||||
|
|
||||||
|
NodePath current = get_edited_object()->get(get_edited_property());
|
||||||
|
|
||||||
|
AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object());
|
||||||
|
if (!atree->has_node(atree->get_animation_player())) {
|
||||||
|
EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player()));
|
||||||
|
if (!player) {
|
||||||
|
EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *base = player->get_node(player->get_root());
|
||||||
|
|
||||||
|
if (!base) {
|
||||||
|
EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> paths;
|
||||||
|
{
|
||||||
|
List<StringName> animations;
|
||||||
|
player->get_animation_list(&animations);
|
||||||
|
|
||||||
|
for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
|
||||||
|
|
||||||
|
Ref<Animation> anim = player->get_animation(E->get());
|
||||||
|
for (int i = 0; i < anim->get_track_count(); i++) {
|
||||||
|
paths.insert(anim->track_get_path(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filters->clear();
|
||||||
|
TreeItem *root = filters->create_item();
|
||||||
|
|
||||||
|
Map<String, TreeItem *> parenthood;
|
||||||
|
|
||||||
|
for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
|
||||||
|
|
||||||
|
NodePath path = E->get();
|
||||||
|
TreeItem *ti = NULL;
|
||||||
|
String accum;
|
||||||
|
for (int i = 0; i < path.get_name_count(); i++) {
|
||||||
|
String name = path.get_name(i);
|
||||||
|
if (accum != String()) {
|
||||||
|
accum += "/";
|
||||||
|
}
|
||||||
|
accum += name;
|
||||||
|
if (!parenthood.has(accum)) {
|
||||||
|
if (ti) {
|
||||||
|
ti = filters->create_item(ti);
|
||||||
|
} else {
|
||||||
|
ti = filters->create_item(root);
|
||||||
|
}
|
||||||
|
parenthood[accum] = ti;
|
||||||
|
ti->set_text(0, name);
|
||||||
|
ti->set_selectable(0, false);
|
||||||
|
ti->set_editable(0, false);
|
||||||
|
|
||||||
|
if (base->has_node(accum)) {
|
||||||
|
Node *node = base->get_node(accum);
|
||||||
|
if (has_icon(node->get_class(), "EditorIcons")) {
|
||||||
|
ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
|
||||||
|
} else {
|
||||||
|
ti->set_icon(0, get_icon("Node", "EditorIcons"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ti = parenthood[accum];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *node = NULL;
|
||||||
|
if (base->has_node(accum)) {
|
||||||
|
node = base->get_node(accum);
|
||||||
|
}
|
||||||
|
if (!node)
|
||||||
|
continue; //no node, cant edit
|
||||||
|
|
||||||
|
if (path.get_subname_count()) {
|
||||||
|
|
||||||
|
String concat = path.get_concatenated_subnames();
|
||||||
|
|
||||||
|
Skeleton *skeleton = Object::cast_to<Skeleton>(node);
|
||||||
|
if (skeleton && skeleton->find_bone(concat) != -1) {
|
||||||
|
//path in skeleton
|
||||||
|
String bone = concat;
|
||||||
|
int idx = skeleton->find_bone(bone);
|
||||||
|
List<String> bone_path;
|
||||||
|
while (idx != -1) {
|
||||||
|
bone_path.push_front(skeleton->get_bone_name(idx));
|
||||||
|
idx = skeleton->get_bone_parent(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
accum += ":";
|
||||||
|
for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
|
||||||
|
if (F != bone_path.front()) {
|
||||||
|
accum += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
accum += F->get();
|
||||||
|
if (!parenthood.has(accum)) {
|
||||||
|
ti = filters->create_item(ti);
|
||||||
|
parenthood[accum] = ti;
|
||||||
|
ti->set_text(0, F->get());
|
||||||
|
ti->set_selectable(0, false);
|
||||||
|
ti->set_editable(0, false);
|
||||||
|
ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
|
||||||
|
} else {
|
||||||
|
ti = parenthood[accum];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ti->set_selectable(0, true);
|
||||||
|
ti->set_text(0, concat);
|
||||||
|
ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
|
||||||
|
ti->set_metadata(0, path);
|
||||||
|
if (path == current) {
|
||||||
|
ti->select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//just a property
|
||||||
|
ti = filters->create_item(ti);
|
||||||
|
ti->set_text(0, concat);
|
||||||
|
ti->set_selectable(0, true);
|
||||||
|
ti->set_metadata(0, path);
|
||||||
|
if (path == current) {
|
||||||
|
ti->select(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ti) {
|
||||||
|
//just a node, likely call or animation track
|
||||||
|
ti->set_selectable(0, true);
|
||||||
|
ti->set_metadata(0, path);
|
||||||
|
if (path == current) {
|
||||||
|
ti->select(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filters->ensure_cursor_is_visible();
|
||||||
|
filter_dialog->popup_centered_ratio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::_node_clear() {
|
||||||
|
|
||||||
|
emit_signal("property_changed", get_edited_property(), NodePath());
|
||||||
|
update_property();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::update_property() {
|
||||||
|
|
||||||
|
NodePath p = get_edited_object()->get(get_edited_property());
|
||||||
|
|
||||||
|
assign->set_tooltip(p);
|
||||||
|
if (p == NodePath()) {
|
||||||
|
assign->set_icon(Ref<Texture>());
|
||||||
|
assign->set_text(TTR("Assign.."));
|
||||||
|
assign->set_flat(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assign->set_flat(true);
|
||||||
|
|
||||||
|
Node *base_node = NULL;
|
||||||
|
if (base_hint != NodePath()) {
|
||||||
|
if (get_tree()->get_root()->has_node(base_hint)) {
|
||||||
|
base_node = get_tree()->get_root()->get_node(base_hint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
base_node = Object::cast_to<Node>(get_edited_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base_node || !base_node->has_node(p)) {
|
||||||
|
assign->set_icon(Ref<Texture>());
|
||||||
|
assign->set_text(p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *target_node = base_node->get_node(p);
|
||||||
|
ERR_FAIL_COND(!target_node);
|
||||||
|
|
||||||
|
assign->set_text(target_node->get_name());
|
||||||
|
|
||||||
|
Ref<Texture> icon;
|
||||||
|
if (has_icon(target_node->get_class(), "EditorIcons"))
|
||||||
|
icon = get_icon(target_node->get_class(), "EditorIcons");
|
||||||
|
else
|
||||||
|
icon = get_icon("Node", "EditorIcons");
|
||||||
|
|
||||||
|
assign->set_icon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) {
|
||||||
|
|
||||||
|
base_hint = p_base_hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::_notification(int p_what) {
|
||||||
|
|
||||||
|
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
|
||||||
|
Ref<Texture> t = get_icon("Clear", "EditorIcons");
|
||||||
|
clear->set_icon(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyRootMotion::_bind_methods() {
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("_confirmed"), &EditorPropertyRootMotion::_confirmed);
|
||||||
|
ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyRootMotion::_node_assign);
|
||||||
|
ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyRootMotion::_node_clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorPropertyRootMotion::EditorPropertyRootMotion() {
|
||||||
|
|
||||||
|
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||||
|
add_child(hbc);
|
||||||
|
assign = memnew(Button);
|
||||||
|
assign->set_flat(true);
|
||||||
|
assign->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
assign->set_clip_text(true);
|
||||||
|
assign->connect("pressed", this, "_node_assign");
|
||||||
|
hbc->add_child(assign);
|
||||||
|
|
||||||
|
clear = memnew(Button);
|
||||||
|
clear->set_flat(true);
|
||||||
|
clear->connect("pressed", this, "_node_clear");
|
||||||
|
hbc->add_child(clear);
|
||||||
|
|
||||||
|
filter_dialog = memnew(ConfirmationDialog);
|
||||||
|
add_child(filter_dialog);
|
||||||
|
filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
|
||||||
|
filter_dialog->connect("confirmed", this, "_confirmed");
|
||||||
|
|
||||||
|
filters = memnew(Tree);
|
||||||
|
filter_dialog->add_child(filters);
|
||||||
|
filters->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
filters->set_hide_root(true);
|
||||||
|
filters->connect("item_activated", this, "_confirmed");
|
||||||
|
//filters->connect("item_edited", this, "_filter_edited");
|
||||||
|
}
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) {
|
||||||
|
return true; //can handle everything
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) {
|
||||||
|
//do none
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) {
|
||||||
|
|
||||||
|
if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) {
|
||||||
|
print_line("use custom!");
|
||||||
|
EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);
|
||||||
|
if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) {
|
||||||
|
editor->setup(p_hint_text);
|
||||||
|
}
|
||||||
|
add_property_editor(p_path, editor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; //can be overriden, although it will most likely be last anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorInspectorRootMotionPlugin::parse_end() {
|
||||||
|
//do none
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef ROOT_MOTION_EDITOR_PLUGIN_H
|
||||||
|
#define ROOT_MOTION_EDITOR_PLUGIN_H
|
||||||
|
|
||||||
|
#include "editor/editor_inspector.h"
|
||||||
|
#include "editor/editor_spin_slider.h"
|
||||||
|
#include "editor/property_selector.h"
|
||||||
|
#include "scene/animation/animation_tree.h"
|
||||||
|
|
||||||
|
class EditorPropertyRootMotion : public EditorProperty {
|
||||||
|
GDCLASS(EditorPropertyRootMotion, EditorProperty)
|
||||||
|
Button *assign;
|
||||||
|
Button *clear;
|
||||||
|
NodePath base_hint;
|
||||||
|
|
||||||
|
ConfirmationDialog *filter_dialog;
|
||||||
|
Tree *filters;
|
||||||
|
|
||||||
|
void _confirmed();
|
||||||
|
void _node_assign();
|
||||||
|
void _node_clear();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
void _notification(int p_what);
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void update_property();
|
||||||
|
void setup(const NodePath &p_base_hint);
|
||||||
|
EditorPropertyRootMotion();
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin {
|
||||||
|
GDCLASS(EditorInspectorRootMotionPlugin, EditorInspectorPlugin)
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual bool can_handle(Object *p_object);
|
||||||
|
virtual void parse_begin(Object *p_object);
|
||||||
|
virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage);
|
||||||
|
virtual void parse_end();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ROOT_MOTION_EDITOR_PLUGIN_H
|
|
@ -681,6 +681,7 @@ void AnimationTree::_clear_caches() {
|
||||||
void AnimationTree::_process_graph(float p_delta) {
|
void AnimationTree::_process_graph(float p_delta) {
|
||||||
|
|
||||||
//check all tracks, see if they need modification
|
//check all tracks, see if they need modification
|
||||||
|
root_motion_transform = Transform();
|
||||||
|
|
||||||
if (!root.is_valid()) {
|
if (!root.is_valid()) {
|
||||||
ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback.");
|
ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback.");
|
||||||
|
@ -713,6 +714,7 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
|
|
||||||
{ //setup
|
{ //setup
|
||||||
|
|
||||||
|
|
||||||
process_pass++;
|
process_pass++;
|
||||||
|
|
||||||
state.valid = true;
|
state.valid = true;
|
||||||
|
@ -757,6 +759,7 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
|
|
||||||
const AnimationNode::AnimationState &as = E->get();
|
const AnimationNode::AnimationState &as = E->get();
|
||||||
|
|
||||||
|
|
||||||
Ref<Animation> a = as.animation;
|
Ref<Animation> a = as.animation;
|
||||||
float time = as.time;
|
float time = as.time;
|
||||||
float delta = as.delta;
|
float delta = as.delta;
|
||||||
|
@ -770,6 +773,8 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
continue; //may happen should not
|
continue; //may happen should not
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track->root_motion = root_motion_track == path;
|
||||||
|
|
||||||
ERR_CONTINUE(!state.track_map.has(path));
|
ERR_CONTINUE(!state.track_map.has(path));
|
||||||
int blend_idx = state.track_map[path];
|
int blend_idx = state.track_map[path];
|
||||||
|
|
||||||
|
@ -786,18 +791,6 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
|
|
||||||
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
|
||||||
|
|
||||||
Vector3 loc;
|
|
||||||
Quat rot;
|
|
||||||
Vector3 scale;
|
|
||||||
|
|
||||||
Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale);
|
|
||||||
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
|
|
||||||
|
|
||||||
scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
|
|
||||||
|
|
||||||
if (err != OK)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (t->process_pass != process_pass) {
|
if (t->process_pass != process_pass) {
|
||||||
|
|
||||||
t->process_pass = process_pass;
|
t->process_pass = process_pass;
|
||||||
|
@ -806,9 +799,71 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
t->scale = Vector3();
|
t->scale = Vector3();
|
||||||
}
|
}
|
||||||
|
|
||||||
t->loc = t->loc.linear_interpolate(loc, blend);
|
if (track->root_motion) {
|
||||||
t->rot = t->rot.slerp(rot, blend);
|
|
||||||
t->scale = t->scale.linear_interpolate(scale, blend);
|
float 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];
|
||||||
|
Quat rot[2];
|
||||||
|
Vector3 scale[2];
|
||||||
|
|
||||||
|
if (prev_time > time) {
|
||||||
|
|
||||||
|
Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||||
|
if (err != OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]);
|
||||||
|
|
||||||
|
t->loc += (loc[1] - loc[0]) * blend;
|
||||||
|
t->scale += (scale[1] - scale[0]) * blend;
|
||||||
|
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->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||||
|
if (err != OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]);
|
||||||
|
|
||||||
|
t->loc += (loc[1] - loc[0]) * blend;
|
||||||
|
t->scale += (scale[1] - scale[0]) * blend;
|
||||||
|
Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(),blend).normalized();
|
||||||
|
t->rot = (t->rot * q).normalized();
|
||||||
|
|
||||||
|
prev_time = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Vector3 loc;
|
||||||
|
Quat rot;
|
||||||
|
Vector3 scale;
|
||||||
|
|
||||||
|
Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale);
|
||||||
|
//ERR_CONTINUE(err!=OK); //used for testing, should be removed
|
||||||
|
|
||||||
|
scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
|
||||||
|
|
||||||
|
if (err != OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
t->loc = t->loc.linear_interpolate(loc, blend);
|
||||||
|
t->rot = t->rot.slerp(rot, blend);
|
||||||
|
t->scale = t->scale.linear_interpolate(scale, blend);
|
||||||
|
}
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
case Animation::TYPE_VALUE: {
|
case Animation::TYPE_VALUE: {
|
||||||
|
@ -1059,11 +1114,18 @@ void AnimationTree::_process_graph(float p_delta) {
|
||||||
Transform xform;
|
Transform xform;
|
||||||
xform.origin = t->loc;
|
xform.origin = t->loc;
|
||||||
|
|
||||||
t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
|
t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes and root motion
|
||||||
|
|
||||||
xform.basis.set_quat_scale(t->rot, t->scale);
|
xform.basis.set_quat_scale(t->rot, t->scale);
|
||||||
|
|
||||||
if (t->skeleton && t->bone_idx >= 0) {
|
if (t->root_motion) {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
t->skeleton->set_bone_pose(t->bone_idx, xform);
|
t->skeleton->set_bone_pose(t->bone_idx, xform);
|
||||||
|
|
||||||
|
@ -1174,6 +1236,19 @@ String AnimationTree::get_configuration_warning() const {
|
||||||
return warning;
|
return warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimationTree::set_root_motion_track(const NodePath& p_track) {
|
||||||
|
root_motion_track=p_track;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePath AnimationTree::get_root_motion_track() const {
|
||||||
|
return root_motion_track;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Transform AnimationTree::get_root_motion_transform() const {
|
||||||
|
return root_motion_transform;
|
||||||
|
}
|
||||||
|
|
||||||
void AnimationTree::_bind_methods() {
|
void AnimationTree::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active);
|
ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active);
|
||||||
ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active);
|
ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active);
|
||||||
|
@ -1187,12 +1262,19 @@ void AnimationTree::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player);
|
ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player);
|
||||||
ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player);
|
ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed);
|
ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed);
|
||||||
|
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_graph_root", "get_graph_root");
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_graph_root", "get_graph_root");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player"), "set_animation_player", "get_animation_player");
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player"), "set_animation_player", "get_animation_player");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode");
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode");
|
||||||
|
ADD_GROUP("Root Motion","root_motion_");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"),"set_root_motion_track", "get_root_motion_track");
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationTree::AnimationTree() {
|
AnimationTree::AnimationTree() {
|
||||||
|
|
|
@ -135,6 +135,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct TrackCache {
|
struct TrackCache {
|
||||||
|
|
||||||
|
bool root_motion;
|
||||||
uint64_t setup_pass;
|
uint64_t setup_pass;
|
||||||
uint64_t process_pass;
|
uint64_t process_pass;
|
||||||
Animation::TrackType type;
|
Animation::TrackType type;
|
||||||
|
@ -142,6 +144,7 @@ private:
|
||||||
ObjectID object_id;
|
ObjectID object_id;
|
||||||
|
|
||||||
TrackCache() {
|
TrackCache() {
|
||||||
|
root_motion = false;
|
||||||
setup_pass = 0;
|
setup_pass = 0;
|
||||||
process_pass = 0;
|
process_pass = 0;
|
||||||
object = NULL;
|
object = NULL;
|
||||||
|
@ -235,6 +238,9 @@ private:
|
||||||
|
|
||||||
bool started;
|
bool started;
|
||||||
|
|
||||||
|
NodePath root_motion_track;
|
||||||
|
Transform root_motion_transform;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
@ -257,6 +263,11 @@ public:
|
||||||
bool is_state_invalid() const;
|
bool is_state_invalid() const;
|
||||||
String get_invalid_state_reason() const;
|
String get_invalid_state_reason() const;
|
||||||
|
|
||||||
|
void set_root_motion_track(const NodePath &p_track);
|
||||||
|
NodePath get_root_motion_track() const;
|
||||||
|
|
||||||
|
Transform get_root_motion_transform() const;
|
||||||
|
|
||||||
uint64_t get_last_process_pass() const;
|
uint64_t get_last_process_pass() const;
|
||||||
AnimationTree();
|
AnimationTree();
|
||||||
~AnimationTree();
|
~AnimationTree();
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
#include "root_motion_view.h"
|
||||||
|
#include "scene/animation/animation_tree.h"
|
||||||
|
#include "scene/resources/material.h"
|
||||||
|
void RootMotionView::set_animation_path(const NodePath &p_path) {
|
||||||
|
path = p_path;
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NodePath RootMotionView::get_animation_path() const {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootMotionView::set_color(const Color &p_color) {
|
||||||
|
color = p_color;
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color RootMotionView::get_color() const {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootMotionView::set_cell_size(float p_size) {
|
||||||
|
cell_size = p_size;
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float RootMotionView::get_cell_size() const {
|
||||||
|
return cell_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootMotionView::set_radius(float p_radius) {
|
||||||
|
radius = p_radius;
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float RootMotionView::get_radius() const {
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootMotionView::_notification(int p_what) {
|
||||||
|
|
||||||
|
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_set_material(immediate, SpatialMaterial::get_material_rid_for_2d(false, true, false, false, false));
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
|
||||||
|
Transform transform;
|
||||||
|
|
||||||
|
if (has_node(path)) {
|
||||||
|
|
||||||
|
Node *node = get_node(path);
|
||||||
|
|
||||||
|
AnimationTree *tree = Object::cast_to<AnimationTree>(node);
|
||||||
|
if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) {
|
||||||
|
if (is_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_PHYSICS) {
|
||||||
|
set_process_internal(false);
|
||||||
|
set_physics_process_internal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_physics_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_IDLE) {
|
||||||
|
set_process_internal(true);
|
||||||
|
set_physics_process_internal(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
transform = tree->get_root_motion_transform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first && transform == Transform()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
transform.orthonormalize(); //dont want scale, too imprecise
|
||||||
|
transform.affine_invert();
|
||||||
|
|
||||||
|
accumulated = accumulated * transform;
|
||||||
|
accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size);
|
||||||
|
accumulated.origin.y = Math::fposmod(accumulated.origin.y, cell_size);
|
||||||
|
accumulated.origin.z = Math::fposmod(accumulated.origin.z, cell_size);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_clear(immediate);
|
||||||
|
|
||||||
|
int cells_in_radius = int((radius / cell_size) + 1.0);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_LINES);
|
||||||
|
for (int i = -cells_in_radius; i < cells_in_radius; i++) {
|
||||||
|
for (int j = -cells_in_radius; j < cells_in_radius; j++) {
|
||||||
|
|
||||||
|
Vector3 from(i * cell_size, 0, j * cell_size);
|
||||||
|
Vector3 from_i((i + 1) * cell_size, 0, j * cell_size);
|
||||||
|
Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size);
|
||||||
|
from = accumulated.xform(from);
|
||||||
|
from_i = accumulated.xform(from_i);
|
||||||
|
from_j = accumulated.xform(from_j);
|
||||||
|
|
||||||
|
Color c = color, c_i = color, c_j = color;
|
||||||
|
c.a *= MAX(0, 1.0 - from.length() / radius);
|
||||||
|
c_i.a *= MAX(0, 1.0 - from_i.length() / radius);
|
||||||
|
c_j.a *= MAX(0, 1.0 - from_j.length() / radius);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_color(immediate, c);
|
||||||
|
VS::get_singleton()->immediate_vertex(immediate, from);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_color(immediate, c_i);
|
||||||
|
VS::get_singleton()->immediate_vertex(immediate, from_i);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_color(immediate, c);
|
||||||
|
VS::get_singleton()->immediate_vertex(immediate, from);
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_color(immediate, c_j);
|
||||||
|
VS::get_singleton()->immediate_vertex(immediate, from_j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VS::get_singleton()->immediate_end(immediate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AABB RootMotionView::get_aabb() const {
|
||||||
|
|
||||||
|
return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2));
|
||||||
|
}
|
||||||
|
PoolVector<Face3> RootMotionView::get_faces(uint32_t p_usage_flags) const {
|
||||||
|
return PoolVector<Face3>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootMotionView::_bind_methods() {
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RootMotionView::set_cell_size);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_cell_size"), &RootMotionView::get_cell_size);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_radius", "size"), &RootMotionView::set_radius);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_radius"), &RootMotionView::get_radius);
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path"), "set_animation_path", "get_animation_path");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius");
|
||||||
|
}
|
||||||
|
|
||||||
|
RootMotionView::RootMotionView() {
|
||||||
|
radius = 10;
|
||||||
|
cell_size = 1;
|
||||||
|
set_process_internal(true);
|
||||||
|
immediate = VisualServer::get_singleton()->immediate_create();
|
||||||
|
set_base(immediate);
|
||||||
|
color = Color(0.5, 0.5, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootMotionView::~RootMotionView() {
|
||||||
|
set_base(RID());
|
||||||
|
VisualServer::get_singleton()->free(immediate);
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef ROOT_MOTION_VIEW_H
|
||||||
|
#define ROOT_MOTION_VIEW_H
|
||||||
|
|
||||||
|
#include "scene/3d/visual_instance.h"
|
||||||
|
|
||||||
|
class RootMotionView : public VisualInstance {
|
||||||
|
GDCLASS(RootMotionView, VisualInstance)
|
||||||
|
public:
|
||||||
|
RID immediate;
|
||||||
|
NodePath path;
|
||||||
|
float cell_size;
|
||||||
|
float radius;
|
||||||
|
bool use_in_game;
|
||||||
|
Color color;
|
||||||
|
bool first;
|
||||||
|
|
||||||
|
Transform accumulated;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _notification(int p_what);
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void set_animation_path(const NodePath &p_path);
|
||||||
|
NodePath get_animation_path() const;
|
||||||
|
|
||||||
|
void set_color(const Color &p_path);
|
||||||
|
Color get_color() const;
|
||||||
|
|
||||||
|
void set_cell_size(float p_size);
|
||||||
|
float get_cell_size() const;
|
||||||
|
|
||||||
|
void set_radius(float p_radius);
|
||||||
|
float get_radius() const;
|
||||||
|
|
||||||
|
virtual AABB get_aabb() const;
|
||||||
|
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
|
||||||
|
|
||||||
|
RootMotionView();
|
||||||
|
~RootMotionView();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ROOT_MOTION_VIEW_H
|
|
@ -66,10 +66,11 @@
|
||||||
#include "scene/animation/animation_blend_space_1d.h"
|
#include "scene/animation/animation_blend_space_1d.h"
|
||||||
#include "scene/animation/animation_blend_space_2d.h"
|
#include "scene/animation/animation_blend_space_2d.h"
|
||||||
#include "scene/animation/animation_blend_tree.h"
|
#include "scene/animation/animation_blend_tree.h"
|
||||||
#include "scene/animation/animation_tree.h"
|
|
||||||
#include "scene/animation/animation_node_state_machine.h"
|
#include "scene/animation/animation_node_state_machine.h"
|
||||||
#include "scene/animation/animation_player.h"
|
#include "scene/animation/animation_player.h"
|
||||||
|
#include "scene/animation/animation_tree.h"
|
||||||
#include "scene/animation/animation_tree_player.h"
|
#include "scene/animation/animation_tree_player.h"
|
||||||
|
#include "scene/animation/root_motion_view.h"
|
||||||
#include "scene/animation/tween.h"
|
#include "scene/animation/tween.h"
|
||||||
#include "scene/audio/audio_player.h"
|
#include "scene/audio/audio_player.h"
|
||||||
#include "scene/gui/box_container.h"
|
#include "scene/gui/box_container.h"
|
||||||
|
@ -387,6 +388,9 @@ void register_scene_types() {
|
||||||
ClassDB::register_class<NavigationMesh>();
|
ClassDB::register_class<NavigationMesh>();
|
||||||
ClassDB::register_class<Navigation>();
|
ClassDB::register_class<Navigation>();
|
||||||
|
|
||||||
|
ClassDB::register_class<RootMotionView>();
|
||||||
|
ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor
|
||||||
|
|
||||||
ClassDB::register_class<AnimationTree>();
|
ClassDB::register_class<AnimationTree>();
|
||||||
ClassDB::register_class<AnimationNode>();
|
ClassDB::register_class<AnimationNode>();
|
||||||
ClassDB::register_class<AnimationRootNode>();
|
ClassDB::register_class<AnimationRootNode>();
|
||||||
|
|
Loading…
Reference in New Issue