mirror of https://github.com/godotengine/godot.git
-Add root motion support in AnimationTree.
-Add RootMotionView, to debug root motion in 3D (disabled in runtime)pull/19795/head
parent
f036353b93
commit
c633b770cb
@ -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
|
||||
@ -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
|
||||
Loading…
Reference in New Issue