1 | /**************************************************************************/ |
2 | /* animation_blend_space_1d.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "animation_blend_space_1d.h" |
32 | |
33 | #include "animation_blend_tree.h" |
34 | |
35 | void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const { |
36 | r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position)); |
37 | r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NONE)); |
38 | r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NONE)); |
39 | } |
40 | |
41 | Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const { |
42 | if (p_parameter == closest) { |
43 | return -1; |
44 | } else { |
45 | return 0; |
46 | } |
47 | } |
48 | |
49 | Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) const { |
50 | return get_blend_point_node(p_name.operator String().to_int()); |
51 | } |
52 | |
53 | void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &p_property) const { |
54 | if (p_property.name.begins_with("blend_point_" )) { |
55 | String left = p_property.name.get_slicec('/', 0); |
56 | int idx = left.get_slicec('_', 2).to_int(); |
57 | if (idx >= blend_points_used) { |
58 | p_property.usage = PROPERTY_USAGE_NONE; |
59 | } |
60 | } |
61 | } |
62 | |
63 | void AnimationNodeBlendSpace1D::_tree_changed() { |
64 | AnimationRootNode::_tree_changed(); |
65 | } |
66 | |
67 | void AnimationNodeBlendSpace1D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { |
68 | AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name); |
69 | } |
70 | |
71 | void AnimationNodeBlendSpace1D::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { |
72 | AnimationRootNode::_animation_node_removed(p_oid, p_node); |
73 | } |
74 | |
75 | void AnimationNodeBlendSpace1D::_bind_methods() { |
76 | ClassDB::bind_method(D_METHOD("add_blend_point" , "node" , "pos" , "at_index" ), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1)); |
77 | ClassDB::bind_method(D_METHOD("set_blend_point_position" , "point" , "pos" ), &AnimationNodeBlendSpace1D::set_blend_point_position); |
78 | ClassDB::bind_method(D_METHOD("get_blend_point_position" , "point" ), &AnimationNodeBlendSpace1D::get_blend_point_position); |
79 | ClassDB::bind_method(D_METHOD("set_blend_point_node" , "point" , "node" ), &AnimationNodeBlendSpace1D::set_blend_point_node); |
80 | ClassDB::bind_method(D_METHOD("get_blend_point_node" , "point" ), &AnimationNodeBlendSpace1D::get_blend_point_node); |
81 | ClassDB::bind_method(D_METHOD("remove_blend_point" , "point" ), &AnimationNodeBlendSpace1D::remove_blend_point); |
82 | ClassDB::bind_method(D_METHOD("get_blend_point_count" ), &AnimationNodeBlendSpace1D::get_blend_point_count); |
83 | |
84 | ClassDB::bind_method(D_METHOD("set_min_space" , "min_space" ), &AnimationNodeBlendSpace1D::set_min_space); |
85 | ClassDB::bind_method(D_METHOD("get_min_space" ), &AnimationNodeBlendSpace1D::get_min_space); |
86 | |
87 | ClassDB::bind_method(D_METHOD("set_max_space" , "max_space" ), &AnimationNodeBlendSpace1D::set_max_space); |
88 | ClassDB::bind_method(D_METHOD("get_max_space" ), &AnimationNodeBlendSpace1D::get_max_space); |
89 | |
90 | ClassDB::bind_method(D_METHOD("set_snap" , "snap" ), &AnimationNodeBlendSpace1D::set_snap); |
91 | ClassDB::bind_method(D_METHOD("get_snap" ), &AnimationNodeBlendSpace1D::get_snap); |
92 | |
93 | ClassDB::bind_method(D_METHOD("set_value_label" , "text" ), &AnimationNodeBlendSpace1D::set_value_label); |
94 | ClassDB::bind_method(D_METHOD("get_value_label" ), &AnimationNodeBlendSpace1D::get_value_label); |
95 | |
96 | ClassDB::bind_method(D_METHOD("set_blend_mode" , "mode" ), &AnimationNodeBlendSpace1D::set_blend_mode); |
97 | ClassDB::bind_method(D_METHOD("get_blend_mode" ), &AnimationNodeBlendSpace1D::get_blend_mode); |
98 | |
99 | ClassDB::bind_method(D_METHOD("set_use_sync" , "enable" ), &AnimationNodeBlendSpace1D::set_use_sync); |
100 | ClassDB::bind_method(D_METHOD("is_using_sync" ), &AnimationNodeBlendSpace1D::is_using_sync); |
101 | |
102 | ClassDB::bind_method(D_METHOD("_add_blend_point" , "index" , "node" ), &AnimationNodeBlendSpace1D::_add_blend_point); |
103 | |
104 | for (int i = 0; i < MAX_BLEND_POINTS; i++) { |
105 | ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node" , PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode" , PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point" , "get_blend_point_node" , i); |
106 | ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position" , "get_blend_point_position" , i); |
107 | } |
108 | |
109 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_min_space" , "get_min_space" ); |
110 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_max_space" , "get_max_space" ); |
111 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_snap" , "get_snap" ); |
112 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_value_label" , "get_value_label" ); |
113 | ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode" , PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry" , PROPERTY_USAGE_NO_EDITOR), "set_blend_mode" , "get_blend_mode" ); |
114 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_use_sync" , "is_using_sync" ); |
115 | |
116 | BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED); |
117 | BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE); |
118 | BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY); |
119 | } |
120 | |
121 | void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) { |
122 | for (int i = 0; i < blend_points_used; i++) { |
123 | ChildNode cn; |
124 | cn.name = itos(i); |
125 | cn.node = blend_points[i].node; |
126 | r_child_nodes->push_back(cn); |
127 | } |
128 | } |
129 | |
130 | void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) { |
131 | ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); |
132 | ERR_FAIL_COND(p_node.is_null()); |
133 | |
134 | ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); |
135 | |
136 | if (p_at_index == -1 || p_at_index == blend_points_used) { |
137 | p_at_index = blend_points_used; |
138 | } else { |
139 | for (int i = blend_points_used - 1; i > p_at_index; i--) { |
140 | blend_points[i] = blend_points[i - 1]; |
141 | } |
142 | } |
143 | |
144 | blend_points[p_at_index].node = p_node; |
145 | blend_points[p_at_index].position = p_position; |
146 | |
147 | blend_points[p_at_index].node->connect("tree_changed" , callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED); |
148 | blend_points[p_at_index].node->connect("animation_node_renamed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); |
149 | blend_points[p_at_index].node->connect("animation_node_removed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); |
150 | |
151 | blend_points_used++; |
152 | emit_signal(SNAME("tree_changed" )); |
153 | } |
154 | |
155 | void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) { |
156 | ERR_FAIL_INDEX(p_point, blend_points_used); |
157 | |
158 | blend_points[p_point].position = p_position; |
159 | } |
160 | |
161 | void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) { |
162 | ERR_FAIL_INDEX(p_point, blend_points_used); |
163 | ERR_FAIL_COND(p_node.is_null()); |
164 | |
165 | if (blend_points[p_point].node.is_valid()) { |
166 | blend_points[p_point].node->disconnect("tree_changed" , callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed)); |
167 | blend_points[p_point].node->disconnect("animation_node_renamed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed)); |
168 | blend_points[p_point].node->disconnect("animation_node_removed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed)); |
169 | } |
170 | |
171 | blend_points[p_point].node = p_node; |
172 | blend_points[p_point].node->connect("tree_changed" , callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED); |
173 | blend_points[p_point].node->connect("animation_node_renamed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); |
174 | blend_points[p_point].node->connect("animation_node_removed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED); |
175 | |
176 | emit_signal(SNAME("tree_changed" )); |
177 | } |
178 | |
179 | float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const { |
180 | ERR_FAIL_INDEX_V(p_point, blend_points_used, 0); |
181 | return blend_points[p_point].position; |
182 | } |
183 | |
184 | Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const { |
185 | ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>()); |
186 | return blend_points[p_point].node; |
187 | } |
188 | |
189 | void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { |
190 | ERR_FAIL_INDEX(p_point, blend_points_used); |
191 | |
192 | ERR_FAIL_COND(blend_points[p_point].node.is_null()); |
193 | blend_points[p_point].node->disconnect("tree_changed" , callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed)); |
194 | blend_points[p_point].node->disconnect("animation_node_renamed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed)); |
195 | blend_points[p_point].node->disconnect("animation_node_removed" , callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed)); |
196 | |
197 | for (int i = p_point; i < blend_points_used - 1; i++) { |
198 | blend_points[i] = blend_points[i + 1]; |
199 | } |
200 | |
201 | blend_points_used--; |
202 | |
203 | emit_signal(SNAME("animation_node_removed" ), get_instance_id(), itos(p_point)); |
204 | emit_signal(SNAME("tree_changed" )); |
205 | } |
206 | |
207 | int AnimationNodeBlendSpace1D::get_blend_point_count() const { |
208 | return blend_points_used; |
209 | } |
210 | |
211 | void AnimationNodeBlendSpace1D::set_min_space(float p_min) { |
212 | min_space = p_min; |
213 | |
214 | if (min_space >= max_space) { |
215 | min_space = max_space - 1; |
216 | } |
217 | } |
218 | |
219 | float AnimationNodeBlendSpace1D::get_min_space() const { |
220 | return min_space; |
221 | } |
222 | |
223 | void AnimationNodeBlendSpace1D::set_max_space(float p_max) { |
224 | max_space = p_max; |
225 | |
226 | if (max_space <= min_space) { |
227 | max_space = min_space + 1; |
228 | } |
229 | } |
230 | |
231 | float AnimationNodeBlendSpace1D::get_max_space() const { |
232 | return max_space; |
233 | } |
234 | |
235 | void AnimationNodeBlendSpace1D::set_snap(float p_snap) { |
236 | snap = p_snap; |
237 | } |
238 | |
239 | float AnimationNodeBlendSpace1D::get_snap() const { |
240 | return snap; |
241 | } |
242 | |
243 | void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) { |
244 | value_label = p_label; |
245 | } |
246 | |
247 | String AnimationNodeBlendSpace1D::get_value_label() const { |
248 | return value_label; |
249 | } |
250 | |
251 | void AnimationNodeBlendSpace1D::set_blend_mode(BlendMode p_blend_mode) { |
252 | blend_mode = p_blend_mode; |
253 | } |
254 | |
255 | AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode() const { |
256 | return blend_mode; |
257 | } |
258 | |
259 | void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) { |
260 | sync = p_sync; |
261 | } |
262 | |
263 | bool AnimationNodeBlendSpace1D::is_using_sync() const { |
264 | return sync; |
265 | } |
266 | |
267 | void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { |
268 | if (p_index == blend_points_used) { |
269 | add_blend_point(p_node, 0); |
270 | } else { |
271 | set_blend_point_node(p_index, p_node); |
272 | } |
273 | } |
274 | |
275 | double AnimationNodeBlendSpace1D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { |
276 | if (blend_points_used == 0) { |
277 | return 0.0; |
278 | } |
279 | |
280 | if (blend_points_used == 1) { |
281 | // only one point available, just play that animation |
282 | return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); |
283 | } |
284 | |
285 | double blend_pos = get_parameter(blend_position); |
286 | int cur_closest = get_parameter(closest); |
287 | double cur_length_internal = get_parameter(length_internal); |
288 | double max_time_remaining = 0.0; |
289 | |
290 | if (blend_mode == BLEND_MODE_INTERPOLATED) { |
291 | int point_lower = -1; |
292 | float pos_lower = 0.0; |
293 | int point_higher = -1; |
294 | float pos_higher = 0.0; |
295 | |
296 | // find the closest two points to blend between |
297 | for (int i = 0; i < blend_points_used; i++) { |
298 | float pos = blend_points[i].position; |
299 | |
300 | if (pos <= blend_pos) { |
301 | if (point_lower == -1 || pos > pos_lower) { |
302 | point_lower = i; |
303 | pos_lower = pos; |
304 | } |
305 | } else if (point_higher == -1 || pos < pos_higher) { |
306 | point_higher = i; |
307 | pos_higher = pos; |
308 | } |
309 | } |
310 | |
311 | // fill in weights |
312 | float weights[MAX_BLEND_POINTS] = {}; |
313 | if (point_lower == -1 && point_higher != -1) { |
314 | // we are on the left side, no other point to the left |
315 | // we just play the next point. |
316 | |
317 | weights[point_higher] = 1.0; |
318 | } else if (point_higher == -1) { |
319 | // we are on the right side, no other point to the right |
320 | // we just play the previous point |
321 | |
322 | weights[point_lower] = 1.0; |
323 | } else { |
324 | // we are between two points. |
325 | // figure out weights, then blend the animations |
326 | |
327 | float distance_between_points = pos_higher - pos_lower; |
328 | |
329 | float current_pos_inbetween = blend_pos - pos_lower; |
330 | |
331 | float blend_percentage = current_pos_inbetween / distance_between_points; |
332 | |
333 | float blend_lower = 1.0 - blend_percentage; |
334 | float blend_higher = blend_percentage; |
335 | |
336 | weights[point_lower] = blend_lower; |
337 | weights[point_higher] = blend_higher; |
338 | } |
339 | |
340 | // actually blend the animations now |
341 | |
342 | for (int i = 0; i < blend_points_used; i++) { |
343 | if (i == point_lower || i == point_higher) { |
344 | double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true, p_test_only); |
345 | max_time_remaining = MAX(max_time_remaining, remaining); |
346 | } else if (sync) { |
347 | blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); |
348 | } |
349 | } |
350 | } else { |
351 | int new_closest = -1; |
352 | double new_closest_dist = 1e20; |
353 | |
354 | for (int i = 0; i < blend_points_used; i++) { |
355 | double d = abs(blend_points[i].position - blend_pos); |
356 | if (d < new_closest_dist) { |
357 | new_closest = i; |
358 | new_closest_dist = d; |
359 | } |
360 | } |
361 | |
362 | if (new_closest != cur_closest && new_closest != -1) { |
363 | double from = 0.0; |
364 | if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { |
365 | //for ping-pong loop |
366 | Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node); |
367 | Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node); |
368 | if (!na_c.is_null() && !na_n.is_null()) { |
369 | na_n->set_backward(na_c->is_backward()); |
370 | } |
371 | //see how much animation remains |
372 | from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only); |
373 | } |
374 | |
375 | max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); |
376 | cur_length_internal = from + max_time_remaining; |
377 | |
378 | cur_closest = new_closest; |
379 | |
380 | } else { |
381 | max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); |
382 | } |
383 | |
384 | if (sync) { |
385 | for (int i = 0; i < blend_points_used; i++) { |
386 | if (i != cur_closest) { |
387 | blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); |
388 | } |
389 | } |
390 | } |
391 | } |
392 | |
393 | set_parameter(this->closest, cur_closest); |
394 | set_parameter(this->length_internal, cur_length_internal); |
395 | return max_time_remaining; |
396 | } |
397 | |
398 | String AnimationNodeBlendSpace1D::get_caption() const { |
399 | return "BlendSpace1D" ; |
400 | } |
401 | |
402 | AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() { |
403 | for (int i = 0; i < MAX_BLEND_POINTS; i++) { |
404 | blend_points[i].name = itos(i); |
405 | } |
406 | } |
407 | |
408 | AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() { |
409 | } |
410 | |