Compare commits
4 Commits
537712c806
...
1fca7cfe88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fca7cfe88 | ||
|
|
05c1bae346 | ||
|
|
ae2e2787cd | ||
|
|
8a47bc5508 |
Binary file not shown.
@ -1,8 +1,9 @@
|
|||||||
[gd_scene load_steps=4 format=3 uid="uid://svj53e2xoio"]
|
[gd_scene load_steps=5 format=3 uid="uid://svj53e2xoio"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://d1xcqdqr1qeu6" path="res://assets/MixamoAmy.glb" id="1_0xm2m"]
|
[ext_resource type="PackedScene" uid="uid://d1xcqdqr1qeu6" path="res://assets/MixamoAmy.glb" id="1_0xm2m"]
|
||||||
[ext_resource type="AnimationNodeBlendTree" uid="uid://c7o0gt3li5p4g" path="res://walk_limp_blend_tree.tres" id="2_h2yge"]
|
[ext_resource type="AnimationNodeBlendTree" uid="uid://dbkgln7hoxxc8" path="res://embedded_statemachine.tres" id="2_h2yge"]
|
||||||
[ext_resource type="AnimationLibrary" uid="uid://dwubn740aqx51" path="res://animation_library.res" id="3_h2yge"]
|
[ext_resource type="AnimationLibrary" uid="uid://dwubn740aqx51" path="res://animation_library.res" id="3_h2yge"]
|
||||||
|
[ext_resource type="SyncedBlendTree" uid="uid://cjeho6848x43q" path="res://synced_blend_tree_node.tres" id="4_1bvp3"]
|
||||||
|
|
||||||
[node name="Node3D" type="Node3D"]
|
[node name="Node3D" type="Node3D"]
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ root_node = NodePath("../MixamoAmy")
|
|||||||
tree_root = ExtResource("2_h2yge")
|
tree_root = ExtResource("2_h2yge")
|
||||||
anim_player = NodePath("../MixamoAmy/AnimationPlayer")
|
anim_player = NodePath("../MixamoAmy/AnimationPlayer")
|
||||||
parameters/Blend2/blend_amount = 0.44
|
parameters/Blend2/blend_amount = 0.44
|
||||||
|
"parameters/Embedded StateMachine/conditions/is_healthy" = false
|
||||||
|
"parameters/Embedded StateMachine/conditions/is_limping" = false
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
active = false
|
active = false
|
||||||
@ -23,8 +26,9 @@ libraries = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="."]
|
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="."]
|
||||||
active = false
|
animation_player = NodePath("../AnimationPlayer")
|
||||||
animation_tree = NodePath("../AnimationTree")
|
tree_root = ExtResource("4_1bvp3")
|
||||||
skeleton = NodePath("../MixamoAmy/Armature/Skeleton3D")
|
skeleton = NodePath("../MixamoAmy/Armature/Skeleton3D")
|
||||||
|
parameters/AnimationBlend2Node/blend_amount = 0.24
|
||||||
|
|
||||||
[editable path="MixamoAmy"]
|
[editable path="MixamoAmy"]
|
||||||
|
|||||||
140
doc/design.md
140
doc/design.md
@ -83,8 +83,10 @@ invalid.
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
AnimationB --> TimeScale("TimeScale
|
AnimationB --> TimeScale("TimeScale
|
||||||
----
|
----
|
||||||
*scale*")
|
[ ] scale")
|
||||||
AnimationA --> Blend2
|
AnimationA --> Blend2("Blend2
|
||||||
|
----
|
||||||
|
[ ] blend_amount")
|
||||||
TimeScale --> Blend2
|
TimeScale --> Blend2
|
||||||
Blend2 --> Output
|
Blend2 --> Output
|
||||||
```
|
```
|
||||||
@ -94,11 +96,12 @@ evaluation of the Blend Tree it can be used to retrieve the result (i.e. Animati
|
|||||||
|
|
||||||
Some nodes have special names in the Blend Tree:
|
Some nodes have special names in the Blend Tree:
|
||||||
|
|
||||||
* **Root node** The output node is also called the root node of the graph.
|
* **Root node** The output node is also called the root node of the Blend Tree.
|
||||||
* **Leaf nodes** These are the nodes that have no inputs. In the example these are the nodes AnimationA and AnimationB.
|
* **Leaf nodes** These are the nodes that have no inputs. In the example these are the nodes AnimationA and AnimationB.
|
||||||
* **Parent and child node** For two nodes A and B where B is the node that is connected to the Animation Data output
|
* **Parent and child node** For two nodes A and B where B is the node that is connected to the Animation Data output
|
||||||
port of A
|
port of A
|
||||||
is called the parent node. The output port has no parent and in the example above The Blend2 node is the parent of
|
is called the parent node. The Output node (= Root node) has no parent. In the example above the Blend2 node is the
|
||||||
|
parent of
|
||||||
both AnimationA and TimeScale. Conversely, AnimationA and TimeScale are child nodes of the Blend2 node.
|
both AnimationA and TimeScale. Conversely, AnimationA and TimeScale are child nodes of the Blend2 node.
|
||||||
|
|
||||||
## Blend Tree Evaluation Process
|
## Blend Tree Evaluation Process
|
||||||
@ -125,134 +128,11 @@ Disadvantages:
|
|||||||
Evaluation of the Blend Tree happens in multiple phases to ensure we have syncing dependent timing information available
|
Evaluation of the Blend Tree happens in multiple phases to ensure we have syncing dependent timing information available
|
||||||
before performing the actual evaluation. Essentially the Blend Tree has to call the following function on all nodes:
|
before performing the actual evaluation. Essentially the Blend Tree has to call the following function on all nodes:
|
||||||
|
|
||||||
1. ActivateInputs(): right to left (i.e. from the root node via depth first to the leaf nodes)
|
1. `ActivateInputs(Vector<Node> inputs)`: right to left (i.e. from the root node via depth first to the leaf nodes)
|
||||||
2. CalculateSyncTracks(): left to right (leaf nodes to root node)
|
2. `CalculateSyncTracks(Vector<Node> inputs)`: left to right (leaf nodes to root node)
|
||||||
3. UpdateTime(): right to left
|
3. UpdateTime(): right to left
|
||||||
4. Evaluate(): left to right
|
4. Evaluate(): left to right
|
||||||
|
|
||||||
```c++
|
|
||||||
// BlendTree.h
|
|
||||||
class BlendTree: public SyncedAnimationNode {
|
|
||||||
private:
|
|
||||||
Vector<SyncedAnimationNode*> nodes;
|
|
||||||
Vector<int> node_parent; // node_parent[i] is the index of the parent of node i.
|
|
||||||
Vector<AnimationData*> node_output; // output for each node
|
|
||||||
Vector<Vector<SyncedAnimationNode*>> node_input_nodes;
|
|
||||||
Vector<Vector<AnimationData*>> node_input_data; // list of inputs for all nodes.
|
|
||||||
|
|
||||||
int get_index_for_node(const SyncedAnimationNode& node);
|
|
||||||
};
|
|
||||||
|
|
||||||
// BlendTree.cpp
|
|
||||||
void BlendTree::initialize_tree() {
|
|
||||||
for (int i = 0; ci < num_connections; i++) {
|
|
||||||
const Connection& connection = connections[i];
|
|
||||||
connection.target_node->set_input_node(connection.target_port_name, connection.source_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlendTree::activate_inputs() {
|
|
||||||
nodes[0]->activate_inputs();
|
|
||||||
|
|
||||||
for (int i = 1; i < nodes.size(); i++) {
|
|
||||||
if (nodes[i]->is_active()) {
|
|
||||||
nodes[i]->activate_inputs(node_input_nodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlendTree::calculate_sync_tracks() {
|
|
||||||
for (int i = nodes.size() - 1; i > 0; i--) {
|
|
||||||
if (nodes[i]->is_active()) {
|
|
||||||
nodes[i]->calculate_sync_track();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlendTree::update_time() {
|
|
||||||
for (int i = 1; i < nodes.size(); i++) {
|
|
||||||
if (nodes[i]->is_active()) {
|
|
||||||
if (nodes[i]->is_synced()) {
|
|
||||||
nodes[i]->update_time(node_parents[i]->node_time_info);
|
|
||||||
} else {
|
|
||||||
nodes[i]->update_time(node_parents[i]->node_time_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlendTree::evaluate(GraphEvaluationContext &context, const Vector<AnimationData*>& inputs, AnimationData &output) {
|
|
||||||
for (int i = nodes.size() - 1; i > 0; i--) {
|
|
||||||
if (nodes[i]->is_active()) {
|
|
||||||
node_output[i] = AnimationDataPool::allocate();
|
|
||||||
nodes[i]->evaluate(context, node_inputs[i], node_output[i]);
|
|
||||||
|
|
||||||
// node[i] is done, so we can deallocate the output handles of all input nodes of node[i].
|
|
||||||
for (AnimationGraphnNode& input_node: input_nodes[i]) {
|
|
||||||
AnimationDataPool::deallocate(node_output[input_node.index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes[i]->set_active(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::move(output, nodes[0].output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blend2Node.cpp
|
|
||||||
void Blend2Node::activate_inputs() {
|
|
||||||
input_node_0->set_active(weight < 1.0 - EPS);
|
|
||||||
input_node_1->set_active(weight > EPS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Blend2Node::calculate_sync_track() {
|
|
||||||
if (input_node_0->is_active()) {
|
|
||||||
sync_track = input_node_0->sync_track;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input_node_1->is_active()) {
|
|
||||||
sync_track.blend(input_node_1->sync_track, blend_weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Blend2Node::update_time(SyncedAnimationNode::NodeTimeInfo time_info) {
|
|
||||||
if (!sync_enabled) {
|
|
||||||
node_time_info.position = node_time_info.position + time_info.delta;
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Blend2Node::evaluate(GraphEvaluationContext &context, const Vector<AnimationData*>& inputs, AnimationData &output) {
|
|
||||||
assert(inputs.size() == 2);
|
|
||||||
output = lerp(inputs[0]->get_output(), inputs[1], blend_weight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeScaleNode.cpp
|
|
||||||
void TimeScaleNode::activate_inputs() {
|
|
||||||
input_node_0->set_active(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeScaleNode::calculate_sync_track() {
|
|
||||||
sync_track = input_node_0.sync_track;
|
|
||||||
sync_track.duration *= time_scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeScaleNode::update_time(SyncedAnimationNode::NodeTimeInfo time_info) {
|
|
||||||
if (!sync_enabled) {
|
|
||||||
node_time_info.position = node_time_info.position + time_info.delta;
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeScaleNode::evaluate(GraphEvaluationContext &context, const Vector<AnimationData*>& inputs, AnimationData &output) {
|
|
||||||
assert(inputs.size() == 1);
|
|
||||||
output = inputs[0]->duplicate();
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Machines
|
## State Machines
|
||||||
|
|
||||||
```plantuml
|
```plantuml
|
||||||
@ -320,7 +200,7 @@ input at a laters tage in the graph.
|
|||||||
|
|
||||||
### Effects on graph topology
|
### Effects on graph topology
|
||||||
|
|
||||||
* Increases Node complexity:
|
* Increases Node complexity for handling output ports. Nodes may have the following output ports:
|
||||||
* AnimOutput
|
* AnimOutput
|
||||||
* AnimOutput + Data
|
* AnimOutput + Data
|
||||||
* Data
|
* Data
|
||||||
|
|||||||
@ -20,12 +20,136 @@ void SyncedAnimationGraph::_bind_methods() {
|
|||||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player");
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player");
|
||||||
ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed")));
|
ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed")));
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_tree_root", "animation_node"), &SyncedAnimationGraph::set_root_animation_node);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_tree_root"), &SyncedAnimationGraph::get_root_animation_node);
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "SyncedAnimationNode"), "set_tree_root", "get_tree_root");
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &SyncedAnimationGraph::set_skeleton);
|
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &SyncedAnimationGraph::set_skeleton);
|
||||||
ClassDB::bind_method(D_METHOD("get_skeleton"), &SyncedAnimationGraph::get_skeleton);
|
ClassDB::bind_method(D_METHOD("get_skeleton"), &SyncedAnimationGraph::get_skeleton);
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_skeleton", "get_skeleton");
|
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_skeleton", "get_skeleton");
|
||||||
ADD_SIGNAL(MethodInfo(SNAME("skeleton_changed")));
|
ADD_SIGNAL(MethodInfo(SNAME("skeleton_changed")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::_update_properties_for_node(const String &p_base_path, Ref<SyncedAnimationNode> p_node) const {
|
||||||
|
ERR_FAIL_COND(p_node.is_null());
|
||||||
|
|
||||||
|
List<PropertyInfo> plist;
|
||||||
|
p_node->get_parameter_list(&plist);
|
||||||
|
for (PropertyInfo &pinfo : plist) {
|
||||||
|
StringName key = pinfo.name;
|
||||||
|
|
||||||
|
if (!property_map.has(p_base_path + key)) {
|
||||||
|
Pair<Variant, bool> param;
|
||||||
|
param.first = p_node->get_parameter_default_value(key);
|
||||||
|
param.second = p_node->is_parameter_read_only(key);
|
||||||
|
property_map[p_base_path + key] = param;
|
||||||
|
}
|
||||||
|
|
||||||
|
property_node_map[p_base_path + key] = Pair<Ref<SyncedAnimationNode>, StringName>(p_node, key);
|
||||||
|
|
||||||
|
pinfo.name = p_base_path + key;
|
||||||
|
properties.push_back(pinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Ref<SyncedAnimationNode>> children;
|
||||||
|
p_node->get_child_nodes(&children);
|
||||||
|
|
||||||
|
for (const Ref<SyncedAnimationNode> &child_node : children) {
|
||||||
|
_update_properties_for_node(p_base_path + child_node->name + "/", child_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::_update_properties() const {
|
||||||
|
if (!properties_dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.clear();
|
||||||
|
property_map.clear();
|
||||||
|
property_node_map.clear();
|
||||||
|
|
||||||
|
if (root_animation_node.is_valid()) {
|
||||||
|
_update_properties_for_node(Animation::PARAMETERS_BASE_PATH, root_animation_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
properties_dirty = false;
|
||||||
|
|
||||||
|
const_cast<SyncedAnimationGraph *>(this)->notify_property_list_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncedAnimationGraph::_set(const StringName &p_name, const Variant &p_value) {
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
String name = p_name;
|
||||||
|
if (name == "process_callback") {
|
||||||
|
set_callback_mode_process(static_cast<AnimationMixer::AnimationCallbackModeProcess>((int)p_value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // DISABLE_DEPRECATED
|
||||||
|
if (properties_dirty) {
|
||||||
|
_update_properties();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property_map.has(p_name)) {
|
||||||
|
if (is_inside_tree() && property_map[p_name].second) {
|
||||||
|
return false; // Prevent to set property by user.
|
||||||
|
}
|
||||||
|
Pair<Variant, bool> &prop = property_map[p_name];
|
||||||
|
Variant value = p_value;
|
||||||
|
if (Animation::validate_type_match(prop.first, value)) {
|
||||||
|
Pair<Ref<SyncedAnimationNode>, StringName> property_node = property_node_map[p_name];
|
||||||
|
if (!property_node.first.is_valid()) {
|
||||||
|
print_error(vformat("Cannot set property '%s' node not found.", p_name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
property_node.first->set_parameter(property_node.second, value);
|
||||||
|
|
||||||
|
// also set value in the graph's copy of the value. Should probably be removed at some point...
|
||||||
|
prop.first = value;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncedAnimationGraph::_get(const StringName &p_name, Variant &r_ret) const {
|
||||||
|
#ifndef DISABLE_DEPRECATED
|
||||||
|
if (p_name == "process_callback") {
|
||||||
|
r_ret = get_callback_mode_process();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // DISABLE_DEPRECATED
|
||||||
|
if (properties_dirty) {
|
||||||
|
_update_properties();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property_map.has(p_name)) {
|
||||||
|
r_ret = property_map[p_name].first;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||||
|
if (properties_dirty) {
|
||||||
|
_update_properties();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const PropertyInfo &E : properties) {
|
||||||
|
p_list->push_back(E);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::_tree_changed() {
|
||||||
|
if (properties_dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callable_mp(this, &SyncedAnimationGraph::_update_properties).call_deferred();
|
||||||
|
properties_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::_notification(int p_what) {
|
void SyncedAnimationGraph::_notification(int p_what) {
|
||||||
switch (p_what) {
|
switch (p_what) {
|
||||||
case Node::NOTIFICATION_READY: {
|
case Node::NOTIFICATION_READY: {
|
||||||
@ -132,6 +256,27 @@ NodePath SyncedAnimationGraph::get_animation_player() const {
|
|||||||
return animation_player_path;
|
return animation_player_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::set_root_animation_node(const Ref<SyncedAnimationNode> &p_animation_node) {
|
||||||
|
if (root_animation_node.is_valid()) {
|
||||||
|
root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &SyncedAnimationGraph::_tree_changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
root_animation_node = p_animation_node;
|
||||||
|
|
||||||
|
if (root_animation_node.is_valid()) {
|
||||||
|
_setup_graph();
|
||||||
|
root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &SyncedAnimationGraph::_tree_changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
properties_dirty = true;
|
||||||
|
|
||||||
|
update_configuration_warnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<SyncedAnimationNode> SyncedAnimationGraph::get_root_animation_node() const {
|
||||||
|
return root_animation_node;
|
||||||
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::set_skeleton(const NodePath &p_path) {
|
void SyncedAnimationGraph::set_skeleton(const NodePath &p_path) {
|
||||||
skeleton_path = p_path;
|
skeleton_path = p_path;
|
||||||
if (p_path.is_empty()) {
|
if (p_path.is_empty()) {
|
||||||
@ -152,26 +297,17 @@ NodePath SyncedAnimationGraph::get_skeleton() const {
|
|||||||
return skeleton_path;
|
return skeleton_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::set_graph_root_node(const Ref<SyncedAnimationNode> &p_animation_node) {
|
|
||||||
if (graph_root_node != p_animation_node) {
|
|
||||||
graph_root_node = p_animation_node;
|
|
||||||
_setup_graph();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<SyncedAnimationNode> SyncedAnimationGraph::get_graph_root_node() const {
|
|
||||||
return graph_root_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
||||||
if (!graph_root_node.is_valid()) {
|
if (!root_animation_node.is_valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graph_root_node->activate_inputs(Vector<Ref<SyncedAnimationNode>>());
|
_update_properties();
|
||||||
graph_root_node->calculate_sync_track(Vector<Ref<SyncedAnimationNode>>());
|
|
||||||
graph_root_node->update_time(p_delta);
|
root_animation_node->activate_inputs(Vector<Ref<SyncedAnimationNode>>());
|
||||||
graph_root_node->evaluate(graph_context, LocalVector<AnimationData *>(), graph_output);
|
root_animation_node->calculate_sync_track(Vector<Ref<SyncedAnimationNode>>());
|
||||||
|
root_animation_node->update_time(p_delta);
|
||||||
|
root_animation_node->evaluate(graph_context, LocalVector<AnimationData *>(), graph_output);
|
||||||
|
|
||||||
_apply_animation_data(graph_output);
|
_apply_animation_data(graph_output);
|
||||||
}
|
}
|
||||||
@ -242,11 +378,11 @@ void SyncedAnimationGraph::_cleanup_evaluation_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::_setup_graph() {
|
void SyncedAnimationGraph::_setup_graph() {
|
||||||
if (graph_context.animation_player == nullptr || graph_context.skeleton_3d == nullptr || !graph_root_node.is_valid()) {
|
if (graph_context.animation_player == nullptr || graph_context.skeleton_3d == nullptr || !root_animation_node.is_valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
graph_root_node->initialize(graph_context);
|
root_animation_node->initialize(graph_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncedAnimationGraph::SyncedAnimationGraph() {
|
SyncedAnimationGraph::SyncedAnimationGraph() {
|
||||||
|
|||||||
@ -12,16 +12,31 @@ class SyncedAnimationGraph : public Node {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
NodePath animation_player_path;
|
NodePath animation_player_path;
|
||||||
|
Ref<SyncedAnimationNode> root_animation_node;
|
||||||
NodePath skeleton_path;
|
NodePath skeleton_path;
|
||||||
|
|
||||||
GraphEvaluationContext graph_context = {};
|
GraphEvaluationContext graph_context = {};
|
||||||
Ref<SyncedAnimationNode> graph_root_node = nullptr;
|
|
||||||
AnimationData graph_output;
|
AnimationData graph_output;
|
||||||
|
|
||||||
|
mutable List<PropertyInfo> properties;
|
||||||
|
mutable AHashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
|
||||||
|
mutable AHashMap<StringName, Pair<Ref<SyncedAnimationNode>, StringName>> property_node_map;
|
||||||
|
|
||||||
|
mutable bool properties_dirty = true;
|
||||||
|
|
||||||
|
void _update_properties() const;
|
||||||
|
void _update_properties_for_node(const String &p_base_path, Ref<SyncedAnimationNode> p_node) const;
|
||||||
|
|
||||||
|
void _tree_changed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
|
bool _set(const StringName &p_name, const Variant &p_value);
|
||||||
|
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||||
|
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||||
|
|
||||||
/* ---- General settings for animation ---- */
|
/* ---- General settings for animation ---- */
|
||||||
AnimationMixer::AnimationCallbackModeProcess callback_mode_process = AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_IDLE;
|
AnimationMixer::AnimationCallbackModeProcess callback_mode_process = AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_IDLE;
|
||||||
AnimationMixer::AnimationCallbackModeMethod callback_mode_method = AnimationMixer::ANIMATION_CALLBACK_MODE_METHOD_DEFERRED;
|
AnimationMixer::AnimationCallbackModeMethod callback_mode_method = AnimationMixer::ANIMATION_CALLBACK_MODE_METHOD_DEFERRED;
|
||||||
@ -40,12 +55,12 @@ public:
|
|||||||
void set_animation_player(const NodePath &p_path);
|
void set_animation_player(const NodePath &p_path);
|
||||||
NodePath get_animation_player() const;
|
NodePath get_animation_player() const;
|
||||||
|
|
||||||
|
void set_root_animation_node(const Ref<SyncedAnimationNode> &p_animation_node);
|
||||||
|
Ref<SyncedAnimationNode> get_root_animation_node() const;
|
||||||
|
|
||||||
void set_skeleton(const NodePath &p_path);
|
void set_skeleton(const NodePath &p_path);
|
||||||
NodePath get_skeleton() const;
|
NodePath get_skeleton() const;
|
||||||
|
|
||||||
void set_graph_root_node(const Ref<SyncedAnimationNode> &p_animation_node);
|
|
||||||
Ref<SyncedAnimationNode> get_graph_root_node() const;
|
|
||||||
|
|
||||||
void set_callback_mode_process(AnimationMixer::AnimationCallbackModeProcess p_mode);
|
void set_callback_mode_process(AnimationMixer::AnimationCallbackModeProcess p_mode);
|
||||||
AnimationMixer::AnimationCallbackModeProcess get_callback_mode_process() const;
|
AnimationMixer::AnimationCallbackModeProcess get_callback_mode_process() const;
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,44 @@
|
|||||||
|
|
||||||
#include "synced_animation_node.h"
|
#include "synced_animation_node.h"
|
||||||
|
|
||||||
|
void SyncedAnimationNode::_bind_methods() {
|
||||||
|
ADD_SIGNAL(MethodInfo("tree_changed"));
|
||||||
|
ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
|
||||||
|
ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant SyncedAnimationNode::get_parameter_default_value(const StringName &p_parameter) const {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncedAnimationNode::is_parameter_read_only(const StringName &p_parameter) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant SyncedAnimationNode::get_parameter(const StringName &p_name) const {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationNode::_tree_changed() {
|
||||||
|
emit_signal(SNAME("tree_changed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||||
|
emit_signal(SNAME("animation_node_renamed"), p_oid, p_old_name, p_new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationNode::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) {
|
||||||
|
emit_signal(SNAME("animation_node_removed"), p_oid, p_node);
|
||||||
|
}
|
||||||
|
|
||||||
void SyncedBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
|
void SyncedBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||||
for (const Ref<SyncedAnimationNode> &node : nodes) {
|
for (const Ref<SyncedAnimationNode> &node : tree_graph.nodes) {
|
||||||
String prop_name = node->name;
|
String prop_name = node->name;
|
||||||
if (prop_name != "Output") {
|
if (prop_name != "Output") {
|
||||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
|
p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
|
||||||
@ -25,25 +61,25 @@ bool SyncedBlendTree::_get(const StringName &p_name, Variant &r_value) const {
|
|||||||
|
|
||||||
if (what == "node") {
|
if (what == "node") {
|
||||||
if (node_index != -1) {
|
if (node_index != -1) {
|
||||||
r_value = nodes[node_index];
|
r_value = tree_graph.nodes[node_index];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (what == "position") {
|
if (what == "position") {
|
||||||
if (node_index != -1) {
|
if (node_index != -1) {
|
||||||
r_value = nodes[node_index]->position;
|
r_value = tree_graph.nodes[node_index]->position;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (prop_name == "node_connections") {
|
} else if (prop_name == "node_connections") {
|
||||||
Array conns;
|
Array conns;
|
||||||
conns.resize(tree_builder.connections.size() * 3);
|
conns.resize(tree_graph.connections.size() * 3);
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (const BlendTreeConnection &connection : tree_builder.connections) {
|
for (const BlendTreeConnection &connection : tree_graph.connections) {
|
||||||
conns[idx * 3 + 0] = connection.target_node->name;
|
conns[idx * 3 + 0] = connection.target_node->name;
|
||||||
conns[idx * 3 + 1] = connection.target_node->get_node_input_index(connection.target_port_name);
|
conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name);
|
||||||
conns[idx * 3 + 2] = connection.source_node->name;
|
conns[idx * 3 + 2] = connection.source_node->name;
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
@ -73,7 +109,7 @@ bool SyncedBlendTree::_set(const StringName &p_name, const Variant &p_value) {
|
|||||||
if (what == "position") {
|
if (what == "position") {
|
||||||
int node_index = find_node_index_by_name(node_name);
|
int node_index = find_node_index_by_name(node_name);
|
||||||
if (node_index > -1) {
|
if (node_index > -1) {
|
||||||
tree_builder.nodes[node_index]->position = p_value;
|
tree_graph.nodes[node_index]->position = p_value;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -86,11 +122,11 @@ bool SyncedBlendTree::_set(const StringName &p_name, const Variant &p_value) {
|
|||||||
int target_node_port_index = conns[i + 1];
|
int target_node_port_index = conns[i + 1];
|
||||||
int source_node_index = find_node_index_by_name(conns[i + 2]);
|
int source_node_index = find_node_index_by_name(conns[i + 2]);
|
||||||
|
|
||||||
Ref<SyncedAnimationNode> target_node = tree_builder.nodes[target_node_index];
|
Ref<SyncedAnimationNode> target_node = tree_graph.nodes[target_node_index];
|
||||||
Vector<StringName> target_input_names;
|
Vector<StringName> target_input_names;
|
||||||
target_node->get_input_names(target_input_names);
|
target_node->get_input_names(target_input_names);
|
||||||
|
|
||||||
add_connection(tree_builder.nodes[source_node_index], target_node, target_input_names[target_node_port_index]);
|
add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -159,10 +195,17 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
||||||
animation = context.animation_player->get_animation(animation_name);
|
animation = context.animation_player->get_animation(animation_name);
|
||||||
|
if (!animation.is_valid()) {
|
||||||
|
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
node_time_info.length = animation->get_length();
|
node_time_info.length = animation->get_length();
|
||||||
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
|
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
|
||||||
@ -207,6 +250,28 @@ void AnimationBlend2Node::_bind_methods() {
|
|||||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimationBlend2Node::get_parameter_list(List<PropertyInfo> *p_list) const {
|
||||||
|
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationBlend2Node::set_parameter(const StringName &p_name, const Variant &p_value) {
|
||||||
|
_set(p_name, p_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant AnimationBlend2Node::get_parameter(const StringName &p_name) const {
|
||||||
|
Variant result;
|
||||||
|
_get(p_name, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant AnimationBlend2Node::get_parameter_default_value(const StringName &p_parameter) const {
|
||||||
|
if (p_parameter == blend_amount) {
|
||||||
|
return blend_weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
|
||||||
void AnimationBlend2Node::_get_property_list(List<PropertyInfo> *p_list) const {
|
void AnimationBlend2Node::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||||
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,6 +209,20 @@ class SyncedAnimationNode : public Resource {
|
|||||||
|
|
||||||
friend class SyncedAnimationGraph;
|
friend class SyncedAnimationGraph;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
|
virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
|
||||||
|
virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
|
||||||
|
virtual bool is_parameter_read_only(const StringName &p_parameter) const;
|
||||||
|
|
||||||
|
virtual void set_parameter(const StringName &p_name, const Variant &p_value);
|
||||||
|
virtual Variant get_parameter(const StringName &p_name) const;
|
||||||
|
|
||||||
|
virtual void _tree_changed();
|
||||||
|
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
|
||||||
|
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct NodeTimeInfo {
|
struct NodeTimeInfo {
|
||||||
double length = 0.0;
|
double length = 0.0;
|
||||||
@ -228,7 +242,7 @@ public:
|
|||||||
Vector2 position;
|
Vector2 position;
|
||||||
|
|
||||||
virtual ~SyncedAnimationNode() override = default;
|
virtual ~SyncedAnimationNode() override = default;
|
||||||
virtual void initialize(GraphEvaluationContext &context) {}
|
virtual bool initialize(GraphEvaluationContext &context) { return true; }
|
||||||
|
|
||||||
virtual void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) {
|
virtual void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) {
|
||||||
// By default, all inputs nodes are activated.
|
// By default, all inputs nodes are activated.
|
||||||
@ -275,21 +289,19 @@ public:
|
|||||||
bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node);
|
bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node);
|
||||||
virtual void get_input_names(Vector<StringName> &inputs) const {}
|
virtual void get_input_names(Vector<StringName> &inputs) const {}
|
||||||
|
|
||||||
int get_node_input_index(const StringName &port_name) const {
|
int get_input_index(const StringName &port_name) const {
|
||||||
Vector<StringName> inputs;
|
Vector<StringName> inputs;
|
||||||
get_input_names(inputs);
|
get_input_names(inputs);
|
||||||
return inputs.find(port_name);
|
return inputs.find(port_name);
|
||||||
}
|
}
|
||||||
int get_node_input_count() const {
|
int get_input_count() const {
|
||||||
Vector<StringName> inputs;
|
Vector<StringName> inputs;
|
||||||
get_input_names(inputs);
|
get_input_names(inputs);
|
||||||
return inputs.size();
|
return inputs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected:
|
// Creates a list of nodes nested within the current node. E.g. all nodes within a BlendTree node.
|
||||||
// void _get_property_list(List<PropertyInfo> *p_list) const;
|
virtual void get_child_nodes(List<Ref<SyncedAnimationNode>> *r_child_nodes) const {}
|
||||||
// bool _get(const StringName &p_name, Variant &r_value) const;
|
|
||||||
// bool _set(const StringName &p_name, const Variant &p_value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AnimationSamplerNode : public SyncedAnimationNode {
|
class AnimationSamplerNode : public SyncedAnimationNode {
|
||||||
@ -304,7 +316,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
Ref<Animation> animation;
|
Ref<Animation> animation;
|
||||||
|
|
||||||
void initialize(GraphEvaluationContext &context) override;
|
bool initialize(GraphEvaluationContext &context) override;
|
||||||
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -341,6 +353,11 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
|
void get_parameter_list(List<PropertyInfo> *p_list) const override;
|
||||||
|
Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||||
|
void set_parameter(const StringName &p_name, const Variant &p_value) override;
|
||||||
|
Variant get_parameter(const StringName &p_name) const override;
|
||||||
|
|
||||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||||
bool _get(const StringName &p_name, Variant &r_value) const;
|
bool _get(const StringName &p_name, Variant &r_value) const;
|
||||||
bool _set(const StringName &p_name, const Variant &p_value);
|
bool _set(const StringName &p_name, const Variant &p_value);
|
||||||
@ -353,10 +370,10 @@ struct BlendTreeConnection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class BlendTreeBuilder
|
* @class BlendTreeGraph
|
||||||
* Helper class that is used to build runtime blend trees and also to validate connections.
|
* Helper class that is used to build runtime blend trees and also to validate connections.
|
||||||
*/
|
*/
|
||||||
struct BlendTreeBuilder {
|
struct BlendTreeGraph {
|
||||||
struct NodeConnectionInfo {
|
struct NodeConnectionInfo {
|
||||||
int parent_node_index = -1;
|
int parent_node_index = -1;
|
||||||
HashSet<int> input_subtree_node_indices; // Contains all nodes down to the tree leaves that influence this node.
|
HashSet<int> input_subtree_node_indices; // Contains all nodes down to the tree leaves that influence this node.
|
||||||
@ -366,7 +383,7 @@ struct BlendTreeBuilder {
|
|||||||
|
|
||||||
explicit NodeConnectionInfo(const SyncedAnimationNode *node) {
|
explicit NodeConnectionInfo(const SyncedAnimationNode *node) {
|
||||||
parent_node_index = -1;
|
parent_node_index = -1;
|
||||||
for (int i = 0; i < node->get_node_input_count(); i++) {
|
for (int i = 0; i < node->get_input_count(); i++) {
|
||||||
connected_child_node_index_at_port.push_back(-1);
|
connected_child_node_index_at_port.push_back(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,7 +422,7 @@ struct BlendTreeBuilder {
|
|||||||
LocalVector<NodeConnectionInfo> node_connection_info;
|
LocalVector<NodeConnectionInfo> node_connection_info;
|
||||||
LocalVector<BlendTreeConnection> connections;
|
LocalVector<BlendTreeConnection> connections;
|
||||||
|
|
||||||
BlendTreeBuilder() {
|
BlendTreeGraph() {
|
||||||
Ref<OutputNode> output_node;
|
Ref<OutputNode> output_node;
|
||||||
output_node.instantiate();
|
output_node.instantiate();
|
||||||
output_node->name = "Output";
|
output_node->name = "Output";
|
||||||
@ -512,7 +529,7 @@ struct BlendTreeBuilder {
|
|||||||
|
|
||||||
int source_node_index = find_node_index(source_node);
|
int source_node_index = find_node_index(source_node);
|
||||||
int target_node_index = find_node_index(target_node);
|
int target_node_index = find_node_index(target_node);
|
||||||
int target_input_port_index = target_node->get_node_input_index(target_port_name);
|
int target_input_port_index = target_node->get_input_index(target_port_name);
|
||||||
|
|
||||||
node_connection_info[source_node_index].parent_node_index = target_node_index;
|
node_connection_info[source_node_index].parent_node_index = target_node_index;
|
||||||
node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] = source_node_index;
|
node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] = source_node_index;
|
||||||
@ -549,7 +566,7 @@ struct BlendTreeBuilder {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int target_input_port_index = target_node->get_node_input_index(target_port_name);
|
int target_input_port_index = target_node->get_input_index(target_port_name);
|
||||||
if (node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] != -1) {
|
if (node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] != -1) {
|
||||||
print_error("Cannot connect node: target port already connected");
|
print_error("Cannot connect node: target port already connected");
|
||||||
return false;
|
return false;
|
||||||
@ -567,25 +584,21 @@ struct BlendTreeBuilder {
|
|||||||
class SyncedBlendTree : public SyncedAnimationNode {
|
class SyncedBlendTree : public SyncedAnimationNode {
|
||||||
GDCLASS(SyncedBlendTree, SyncedAnimationNode);
|
GDCLASS(SyncedBlendTree, SyncedAnimationNode);
|
||||||
|
|
||||||
Vector<Ref<SyncedAnimationNode>> nodes;
|
BlendTreeGraph tree_graph;
|
||||||
|
|
||||||
BlendTreeBuilder tree_builder;
|
|
||||||
bool tree_initialized = false;
|
bool tree_initialized = false;
|
||||||
|
|
||||||
void sort_nodes() {
|
void sort_nodes() {
|
||||||
nodes.clear();
|
|
||||||
_node_runtime_data.clear();
|
_node_runtime_data.clear();
|
||||||
tree_builder.sort_nodes_and_references();
|
tree_graph.sort_nodes_and_references();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_runtime_data() {
|
void setup_runtime_data() {
|
||||||
// Add nodes and allocate runtime data
|
// Add nodes and allocate runtime data
|
||||||
for (int i = 0; i < tree_builder.nodes.size(); i++) {
|
for (int i = 0; i < tree_graph.nodes.size(); i++) {
|
||||||
const Ref<SyncedAnimationNode> node = tree_builder.nodes[i];
|
const Ref<SyncedAnimationNode> node = tree_graph.nodes[i];
|
||||||
nodes.push_back(node);
|
|
||||||
|
|
||||||
NodeRuntimeData node_runtime_data;
|
NodeRuntimeData node_runtime_data;
|
||||||
for (int ni = 0; ni < node->get_node_input_count(); ni++) {
|
for (int ni = 0; ni < node->get_input_count(); ni++) {
|
||||||
node_runtime_data.input_data.push_back(nullptr);
|
node_runtime_data.input_data.push_back(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,13 +607,13 @@ class SyncedBlendTree : public SyncedAnimationNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populate runtime data (only now is this.nodes populated to retrieve the nodes)
|
// Populate runtime data (only now is this.nodes populated to retrieve the nodes)
|
||||||
for (int i = 0; i < nodes.size(); i++) {
|
for (int i = 0; i < tree_graph.nodes.size(); i++) {
|
||||||
Ref<SyncedAnimationNode> node = nodes[i];
|
Ref<SyncedAnimationNode> node = tree_graph.nodes[i];
|
||||||
NodeRuntimeData &node_runtime_data = _node_runtime_data[i];
|
NodeRuntimeData &node_runtime_data = _node_runtime_data[i];
|
||||||
|
|
||||||
for (int port_index = 0; port_index < node->get_node_input_count(); port_index++) {
|
for (int port_index = 0; port_index < node->get_input_count(); port_index++) {
|
||||||
const int connected_node_index = tree_builder.node_connection_info[i].connected_child_node_index_at_port[port_index];
|
const int connected_node_index = tree_graph.node_connection_info[i].connected_child_node_index_at_port[port_index];
|
||||||
node_runtime_data.input_nodes.push_back(nodes[connected_node_index]);
|
node_runtime_data.input_nodes.push_back(tree_graph.nodes[connected_node_index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -619,15 +632,15 @@ public:
|
|||||||
LocalVector<NodeRuntimeData> _node_runtime_data;
|
LocalVector<NodeRuntimeData> _node_runtime_data;
|
||||||
|
|
||||||
Ref<SyncedAnimationNode> get_output_node() const {
|
Ref<SyncedAnimationNode> get_output_node() const {
|
||||||
return tree_builder.nodes[0];
|
return tree_graph.nodes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
int find_node_index(const Ref<SyncedAnimationNode> &node) const {
|
int find_node_index(const Ref<SyncedAnimationNode> &node) const {
|
||||||
return tree_builder.find_node_index(node);
|
return tree_graph.find_node_index(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
int find_node_index_by_name(const StringName &name) const {
|
int find_node_index_by_name(const StringName &name) const {
|
||||||
return tree_builder.find_node_index_by_name(name);
|
return tree_graph.find_node_index_by_name(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_node(const Ref<SyncedAnimationNode> &node) {
|
void add_node(const Ref<SyncedAnimationNode> &node) {
|
||||||
@ -636,7 +649,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tree_builder.add_node(node);
|
tree_graph.add_node(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool add_connection(const Ref<SyncedAnimationNode> &source_node, const Ref<SyncedAnimationNode> &target_node, const StringName &target_port_name) {
|
bool add_connection(const Ref<SyncedAnimationNode> &source_node, const Ref<SyncedAnimationNode> &target_node, const StringName &target_port_name) {
|
||||||
@ -645,25 +658,29 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree_builder.add_connection(source_node, target_node, target_port_name);
|
return tree_graph.add_connection(source_node, target_node, target_port_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrides from SyncedAnimationNode
|
// overrides from SyncedAnimationNode
|
||||||
void initialize(GraphEvaluationContext &context) override {
|
bool initialize(GraphEvaluationContext &context) override {
|
||||||
sort_nodes();
|
sort_nodes();
|
||||||
setup_runtime_data();
|
setup_runtime_data();
|
||||||
|
|
||||||
for (Ref<SyncedAnimationNode> node : nodes) {
|
for (const Ref<SyncedAnimationNode> &node : tree_graph.nodes) {
|
||||||
node->initialize(context);
|
if (!node->initialize(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tree_initialized = true;
|
tree_initialized = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||||
nodes[0]->active = true;
|
tree_graph.nodes[0]->active = true;
|
||||||
for (int i = 0; i < nodes.size(); i++) {
|
for (int i = 0; i < tree_graph.nodes.size(); i++) {
|
||||||
Ref<SyncedAnimationNode> node = nodes[i];
|
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||||
|
|
||||||
if (!node->active) {
|
if (!node->active) {
|
||||||
continue;
|
continue;
|
||||||
@ -675,8 +692,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||||
for (int i = nodes.size() - 1; i > 0; i--) {
|
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
|
||||||
Ref<SyncedAnimationNode> node = nodes[i];
|
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||||
|
|
||||||
if (!node->active) {
|
if (!node->active) {
|
||||||
continue;
|
continue;
|
||||||
@ -689,17 +706,17 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update_time(double p_delta) override {
|
void update_time(double p_delta) override {
|
||||||
nodes[0]->node_time_info.delta = p_delta;
|
tree_graph.nodes[0]->node_time_info.delta = p_delta;
|
||||||
nodes[0]->node_time_info.position += p_delta;
|
tree_graph.nodes[0]->node_time_info.position += p_delta;
|
||||||
|
|
||||||
for (int i = 1; i < nodes.size(); i++) {
|
for (int i = 1; i < tree_graph.nodes.size(); i++) {
|
||||||
Ref<SyncedAnimationNode> node = nodes[i];
|
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||||
|
|
||||||
if (!node->active) {
|
if (!node->active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<SyncedAnimationNode> node_parent = nodes[tree_builder.node_connection_info[i].parent_node_index];
|
const Ref<SyncedAnimationNode> &node_parent = tree_graph.nodes[tree_graph.node_connection_info[i].parent_node_index];
|
||||||
|
|
||||||
if (node->node_time_info.is_synced) {
|
if (node->node_time_info.is_synced) {
|
||||||
node->update_time(node_parent->node_time_info.position);
|
node->update_time(node_parent->node_time_info.position);
|
||||||
@ -710,8 +727,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override {
|
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override {
|
||||||
for (int i = nodes.size() - 1; i > 0; i--) {
|
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
|
||||||
const Ref<SyncedAnimationNode> &node = nodes[i];
|
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||||
|
|
||||||
if (!node->active) {
|
if (!node->active) {
|
||||||
continue;
|
continue;
|
||||||
@ -721,7 +738,7 @@ public:
|
|||||||
|
|
||||||
// Populate the inputs
|
// Populate the inputs
|
||||||
for (unsigned int j = 0; j < node_runtime_data.input_data.size(); j++) {
|
for (unsigned int j = 0; j < node_runtime_data.input_data.size(); j++) {
|
||||||
int child_index = tree_builder.node_connection_info[i].connected_child_node_index_at_port[j];
|
int child_index = tree_graph.node_connection_info[i].connected_child_node_index_at_port[j];
|
||||||
node_runtime_data.input_data[j] = _node_runtime_data[child_index].output_data;
|
node_runtime_data.input_data[j] = _node_runtime_data[child_index].output_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,9 +752,15 @@ public:
|
|||||||
node->evaluate(context, node_runtime_data.input_data, *node_runtime_data.output_data);
|
node->evaluate(context, node_runtime_data.input_data, *node_runtime_data.output_data);
|
||||||
|
|
||||||
// All inputs have been consumed and can now be freed.
|
// All inputs have been consumed and can now be freed.
|
||||||
for (int child_index : tree_builder.node_connection_info[i].connected_child_node_index_at_port) {
|
for (const int child_index : tree_graph.node_connection_info[i].connected_child_node_index_at_port) {
|
||||||
memfree(_node_runtime_data[child_index].output_data);
|
memfree(_node_runtime_data[child_index].output_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void get_child_nodes(List<Ref<SyncedAnimationNode>> *r_child_nodes) const override {
|
||||||
|
for (const Ref<SyncedAnimationNode> &node : tree_graph.nodes) {
|
||||||
|
r_child_nodes->push_back(node.ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -71,7 +71,7 @@ struct SyncedAnimationGraphFixture {
|
|||||||
namespace TestSyncedAnimationGraph {
|
namespace TestSyncedAnimationGraph {
|
||||||
|
|
||||||
TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
||||||
BlendTreeBuilder tree_constructor;
|
BlendTreeGraph tree_constructor;
|
||||||
|
|
||||||
Ref<AnimationSamplerNode> animation_sampler_node0;
|
Ref<AnimationSamplerNode> animation_sampler_node0;
|
||||||
animation_sampler_node0.instantiate();
|
animation_sampler_node0.instantiate();
|
||||||
@ -182,7 +182,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
animation_sampler_node.instantiate();
|
animation_sampler_node.instantiate();
|
||||||
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
||||||
|
|
||||||
synced_animation_graph->set_graph_root_node(animation_sampler_node);
|
synced_animation_graph->set_root_animation_node(animation_sampler_node);
|
||||||
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
|
|
||||||
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
||||||
|
|
||||||
synced_animation_graph->set_graph_root_node(synced_blend_tree_node);
|
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||||
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
CHECK(blend2_runtime_data.input_nodes[0] == animation_sampler_node_a);
|
CHECK(blend2_runtime_data.input_nodes[0] == animation_sampler_node_a);
|
||||||
CHECK(blend2_runtime_data.input_nodes[1] == animation_sampler_node_b);
|
CHECK(blend2_runtime_data.input_nodes[1] == animation_sampler_node_b);
|
||||||
|
|
||||||
synced_animation_graph->set_graph_root_node(synced_blend_tree_node);
|
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||||
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
REQUIRE(loaded_synced_blend_tree.is_valid());
|
REQUIRE(loaded_synced_blend_tree.is_valid());
|
||||||
|
|
||||||
loaded_synced_blend_tree->initialize(synced_animation_graph->get_context());
|
loaded_synced_blend_tree->initialize(synced_animation_graph->get_context());
|
||||||
synced_animation_graph->set_graph_root_node(loaded_synced_blend_tree);
|
synced_animation_graph->set_root_animation_node(loaded_synced_blend_tree);
|
||||||
|
|
||||||
// Re-evaluate using a different time. All animation samplers will start again from 0.
|
// Re-evaluate using a different time. All animation samplers will start again from 0.
|
||||||
SceneTree::get_singleton()->process(0.2);
|
SceneTree::get_singleton()->process(0.2);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user