1/**************************************************************************/
2/* skeleton_2d.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 "skeleton_2d.h"
32
33#ifdef TOOLS_ENABLED
34#include "editor/editor_data.h"
35#include "editor/editor_settings.h"
36#include "editor/plugins/canvas_item_editor_plugin.h"
37#endif //TOOLS_ENABLED
38
39bool Bone2D::_set(const StringName &p_path, const Variant &p_value) {
40 String path = p_path;
41
42 if (path.begins_with("auto_calculate_length_and_angle")) {
43 set_autocalculate_length_and_angle(p_value);
44 } else if (path.begins_with("length")) {
45 set_length(p_value);
46 } else if (path.begins_with("bone_angle")) {
47 set_bone_angle(Math::deg_to_rad(real_t(p_value)));
48 } else if (path.begins_with("default_length")) {
49 set_length(p_value);
50 }
51
52#ifdef TOOLS_ENABLED
53 if (path.begins_with("editor_settings/show_bone_gizmo")) {
54 _editor_set_show_bone_gizmo(p_value);
55 }
56#endif // TOOLS_ENABLED
57
58 return true;
59}
60
61bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const {
62 String path = p_path;
63
64 if (path.begins_with("auto_calculate_length_and_angle")) {
65 r_ret = get_autocalculate_length_and_angle();
66 } else if (path.begins_with("length")) {
67 r_ret = get_length();
68 } else if (path.begins_with("bone_angle")) {
69 r_ret = Math::rad_to_deg(get_bone_angle());
70 } else if (path.begins_with("default_length")) {
71 r_ret = get_length();
72 }
73
74#ifdef TOOLS_ENABLED
75 if (path.begins_with("editor_settings/show_bone_gizmo")) {
76 r_ret = _editor_get_show_bone_gizmo();
77 }
78#endif // TOOLS_ENABLED
79
80 return true;
81}
82
83void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const {
84 p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("auto_calculate_length_and_angle"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
85 if (!autocalculate_length_and_angle) {
86 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("length"), PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT));
87 p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("bone_angle"), PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
88 }
89
90#ifdef TOOLS_ENABLED
91 p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("editor_settings/show_bone_gizmo"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
92#endif // TOOLS_ENABLED
93}
94
95void Bone2D::_notification(int p_what) {
96 switch (p_what) {
97 case NOTIFICATION_ENTER_TREE: {
98 Node *parent = get_parent();
99 parent_bone = Object::cast_to<Bone2D>(parent);
100 skeleton = nullptr;
101 while (parent) {
102 skeleton = Object::cast_to<Skeleton2D>(parent);
103 if (skeleton) {
104 break;
105 }
106 if (!Object::cast_to<Bone2D>(parent)) {
107 break; //skeletons must be chained to Bone2Ds.
108 }
109
110 parent = parent->get_parent();
111 }
112
113 if (skeleton) {
114 Skeleton2D::Bone bone;
115 bone.bone = this;
116 skeleton->bones.push_back(bone);
117 skeleton->_make_bone_setup_dirty();
118 get_parent()->connect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty), CONNECT_REFERENCE_COUNTED);
119 }
120
121 cache_transform = get_transform();
122 copy_transform_to_cache = true;
123
124#ifdef TOOLS_ENABLED
125 // Only draw the gizmo in the editor!
126 if (Engine::get_singleton()->is_editor_hint() == false) {
127 return;
128 }
129
130 queue_redraw();
131#endif // TOOLS_ENABLED
132 } break;
133
134 case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
135 if (skeleton) {
136 skeleton->_make_transform_dirty();
137 }
138 if (copy_transform_to_cache) {
139 cache_transform = get_transform();
140 }
141#ifdef TOOLS_ENABLED
142 // Only draw the gizmo in the editor!
143 if (Engine::get_singleton()->is_editor_hint() == false) {
144 return;
145 }
146
147 queue_redraw();
148
149 if (get_parent()) {
150 Bone2D *p_bone = Object::cast_to<Bone2D>(get_parent());
151 if (p_bone) {
152 p_bone->queue_redraw();
153 }
154 }
155#endif // TOOLS_ENABLED
156 } break;
157
158 case NOTIFICATION_EXIT_TREE: {
159 if (skeleton) {
160 for (int i = 0; i < skeleton->bones.size(); i++) {
161 if (skeleton->bones[i].bone == this) {
162 skeleton->bones.remove_at(i);
163 break;
164 }
165 }
166 skeleton->_make_bone_setup_dirty();
167 get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty));
168 }
169 parent_bone = nullptr;
170 set_transform(cache_transform);
171 } break;
172
173 case NOTIFICATION_READY: {
174 if (autocalculate_length_and_angle) {
175 calculate_length_and_rotation();
176 }
177 } break;
178
179#ifdef TOOLS_ENABLED
180 case NOTIFICATION_EDITOR_PRE_SAVE:
181 case NOTIFICATION_EDITOR_POST_SAVE: {
182 Transform2D tmp_trans = get_transform();
183 set_transform(cache_transform);
184 cache_transform = tmp_trans;
185 } break;
186
187 // Bone2D Editor gizmo drawing.
188 // TODO: Bone2D gizmo drawing needs to be moved to an editor plugin.
189 case NOTIFICATION_DRAW: {
190 // Only draw the gizmo in the editor!
191 if (Engine::get_singleton()->is_editor_hint() == false) {
192 return;
193 }
194
195 if (editor_gizmo_rid.is_null()) {
196 editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create();
197 RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item());
198 RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true);
199 RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10);
200 }
201 RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid);
202
203 if (!_editor_show_bone_gizmo) {
204 return;
205 }
206
207 // Undo scaling
208 Transform2D editor_gizmo_trans;
209 editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale());
210 RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans);
211
212 Color bone_color1 = EDITOR_GET("editors/2d/bone_color1");
213 Color bone_color2 = EDITOR_GET("editors/2d/bone_color2");
214 Color bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color");
215 Color bone_outline_color = EDITOR_GET("editors/2d/bone_outline_color");
216 Color bone_selected_color = EDITOR_GET("editors/2d/bone_selected_color");
217
218 bool Bone2D_found = false;
219 for (int i = 0; i < get_child_count(); i++) {
220 Bone2D *child_node = nullptr;
221 child_node = Object::cast_to<Bone2D>(get_child(i));
222 if (!child_node) {
223 continue;
224 }
225 Bone2D_found = true;
226
227 Vector<Vector2> bone_shape;
228 Vector<Vector2> bone_shape_outline;
229
230 _editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node);
231
232 Vector<Color> colors;
233 if (has_meta("_local_pose_override_enabled_")) {
234 colors.push_back(bone_ik_color);
235 colors.push_back(bone_ik_color);
236 colors.push_back(bone_ik_color);
237 colors.push_back(bone_ik_color);
238 } else {
239 colors.push_back(bone_color1);
240 colors.push_back(bone_color2);
241 colors.push_back(bone_color1);
242 colors.push_back(bone_color2);
243 }
244
245 Vector<Color> outline_colors;
246 if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
247 outline_colors.push_back(bone_selected_color);
248 outline_colors.push_back(bone_selected_color);
249 outline_colors.push_back(bone_selected_color);
250 outline_colors.push_back(bone_selected_color);
251 outline_colors.push_back(bone_selected_color);
252 outline_colors.push_back(bone_selected_color);
253 } else {
254 outline_colors.push_back(bone_outline_color);
255 outline_colors.push_back(bone_outline_color);
256 outline_colors.push_back(bone_outline_color);
257 outline_colors.push_back(bone_outline_color);
258 outline_colors.push_back(bone_outline_color);
259 outline_colors.push_back(bone_outline_color);
260 }
261
262 RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
263 RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
264 }
265
266 if (!Bone2D_found) {
267 Vector<Vector2> bone_shape;
268 Vector<Vector2> bone_shape_outline;
269
270 _editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr);
271
272 Vector<Color> colors;
273 if (has_meta("_local_pose_override_enabled_")) {
274 colors.push_back(bone_ik_color);
275 colors.push_back(bone_ik_color);
276 colors.push_back(bone_ik_color);
277 colors.push_back(bone_ik_color);
278 } else {
279 colors.push_back(bone_color1);
280 colors.push_back(bone_color2);
281 colors.push_back(bone_color1);
282 colors.push_back(bone_color2);
283 }
284
285 Vector<Color> outline_colors;
286 if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
287 outline_colors.push_back(bone_selected_color);
288 outline_colors.push_back(bone_selected_color);
289 outline_colors.push_back(bone_selected_color);
290 outline_colors.push_back(bone_selected_color);
291 outline_colors.push_back(bone_selected_color);
292 outline_colors.push_back(bone_selected_color);
293 } else {
294 outline_colors.push_back(bone_outline_color);
295 outline_colors.push_back(bone_outline_color);
296 outline_colors.push_back(bone_outline_color);
297 outline_colors.push_back(bone_outline_color);
298 outline_colors.push_back(bone_outline_color);
299 outline_colors.push_back(bone_outline_color);
300 }
301
302 RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
303 RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
304 }
305 } break;
306#endif // TOOLS_ENABLED
307 }
308}
309
310#ifdef TOOLS_ENABLED
311bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) {
312 float bone_width = EDITOR_GET("editors/2d/bone_width");
313 float bone_outline_width = EDITOR_GET("editors/2d/bone_outline_size");
314
315 if (!is_inside_tree()) {
316 return false; //may have been removed
317 }
318 if (!p_other_bone && length <= 0) {
319 return false;
320 }
321
322 Vector2 rel;
323 if (p_other_bone) {
324 rel = (p_other_bone->get_global_position() - get_global_position());
325 rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
326 } else {
327 real_t angle_to_use = get_rotation() + bone_angle;
328 rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y));
329 rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
330 }
331
332 Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width;
333 Vector2 reln = rel.normalized();
334 Vector2 reltn = relt.normalized();
335
336 if (p_shape) {
337 p_shape->clear();
338 p_shape->push_back(Vector2(0, 0));
339 p_shape->push_back(rel * 0.2 + relt);
340 p_shape->push_back(rel);
341 p_shape->push_back(rel * 0.2 - relt);
342 }
343
344 if (p_outline_shape) {
345 p_outline_shape->clear();
346 p_outline_shape->push_back((-reln - reltn) * bone_outline_width);
347 p_outline_shape->push_back((-reln + reltn) * bone_outline_width);
348 p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width);
349 p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width);
350 p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width);
351 p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width);
352 }
353 return true;
354}
355
356void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) {
357 _editor_show_bone_gizmo = p_show_gizmo;
358 queue_redraw();
359}
360
361bool Bone2D::_editor_get_show_bone_gizmo() const {
362 return _editor_show_bone_gizmo;
363}
364#endif // TOOLS_ENABLED
365
366void Bone2D::_bind_methods() {
367 ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest);
368 ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest);
369 ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest);
370 ClassDB::bind_method(D_METHOD("get_skeleton_rest"), &Bone2D::get_skeleton_rest);
371 ClassDB::bind_method(D_METHOD("get_index_in_skeleton"), &Bone2D::get_index_in_skeleton);
372
373 ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle);
374 ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle);
375 ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length);
376 ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length);
377 ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle);
378 ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle);
379
380 ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest", PROPERTY_HINT_NONE, "suffix:px"), "set_rest", "get_rest");
381}
382
383void Bone2D::set_rest(const Transform2D &p_rest) {
384 rest = p_rest;
385 if (skeleton) {
386 skeleton->_make_bone_setup_dirty();
387 }
388
389 update_configuration_warnings();
390}
391
392Transform2D Bone2D::get_rest() const {
393 return rest;
394}
395
396Transform2D Bone2D::get_skeleton_rest() const {
397 if (parent_bone) {
398 return parent_bone->get_skeleton_rest() * rest;
399 } else {
400 return rest;
401 }
402}
403
404void Bone2D::apply_rest() {
405 set_transform(rest);
406}
407
408int Bone2D::get_index_in_skeleton() const {
409 ERR_FAIL_NULL_V(skeleton, -1);
410 skeleton->_update_bone_setup();
411 return skeleton_index;
412}
413
414PackedStringArray Bone2D::get_configuration_warnings() const {
415 PackedStringArray warnings = Node::get_configuration_warnings();
416 if (!skeleton) {
417 if (parent_bone) {
418 warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node."));
419 } else {
420 warnings.push_back(RTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."));
421 }
422 }
423
424 if (rest == Transform2D(0, 0, 0, 0, 0, 0)) {
425 warnings.push_back(RTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."));
426 }
427
428 return warnings;
429}
430
431void Bone2D::calculate_length_and_rotation() {
432 // if there is at least a single child Bone2D node, we can calculate
433 // the length and direction. We will always just use the first Bone2D for this.
434 bool calculated = false;
435 int child_count = get_child_count();
436 if (child_count > 0) {
437 for (int i = 0; i < child_count; i++) {
438 Bone2D *child = Object::cast_to<Bone2D>(get_child(i));
439 if (child) {
440 Vector2 child_local_pos = to_local(child->get_global_position());
441 length = child_local_pos.length();
442 bone_angle = child_local_pos.normalized().angle();
443 calculated = true;
444 break;
445 }
446 }
447 }
448 if (calculated) {
449 return; // Finished!
450 } else {
451 WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle");
452 bone_angle = get_transform().get_rotation();
453 return;
454 }
455}
456
457void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) {
458 autocalculate_length_and_angle = p_autocalculate;
459 if (autocalculate_length_and_angle) {
460 calculate_length_and_rotation();
461 }
462 notify_property_list_changed();
463}
464
465bool Bone2D::get_autocalculate_length_and_angle() const {
466 return autocalculate_length_and_angle;
467}
468
469void Bone2D::set_length(real_t p_length) {
470 length = p_length;
471
472#ifdef TOOLS_ENABLED
473 queue_redraw();
474#endif // TOOLS_ENABLED
475}
476
477real_t Bone2D::get_length() const {
478 return length;
479}
480
481void Bone2D::set_bone_angle(real_t p_angle) {
482 bone_angle = p_angle;
483
484#ifdef TOOLS_ENABLED
485 queue_redraw();
486#endif // TOOLS_ENABLED
487}
488
489real_t Bone2D::get_bone_angle() const {
490 return bone_angle;
491}
492
493Bone2D::Bone2D() {
494 skeleton = nullptr;
495 parent_bone = nullptr;
496 skeleton_index = -1;
497 length = 16;
498 bone_angle = 0;
499 autocalculate_length_and_angle = true;
500 set_notify_local_transform(true);
501 set_hide_clip_children(true);
502 //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error.
503 for (int i = 0; i < 3; i++) {
504 rest[i] = Vector2(0, 0);
505 }
506 copy_transform_to_cache = true;
507}
508
509Bone2D::~Bone2D() {
510#ifdef TOOLS_ENABLED
511 if (!editor_gizmo_rid.is_null()) {
512 ERR_FAIL_NULL(RenderingServer::get_singleton());
513 RenderingServer::get_singleton()->free(editor_gizmo_rid);
514 }
515#endif // TOOLS_ENABLED
516}
517
518//////////////////////////////////////
519
520bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) {
521 String path = p_path;
522
523 if (path.begins_with("modification_stack")) {
524 set_modification_stack(p_value);
525 return true;
526 }
527 return true;
528}
529
530bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const {
531 String path = p_path;
532
533 if (path.begins_with("modification_stack")) {
534 r_ret = get_modification_stack();
535 return true;
536 }
537 return true;
538}
539
540void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const {
541 p_list->push_back(
542 PropertyInfo(Variant::OBJECT, PNAME("modification_stack"),
543 PROPERTY_HINT_RESOURCE_TYPE,
544 "SkeletonModificationStack2D",
545 PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_ALWAYS_DUPLICATE));
546}
547
548void Skeleton2D::_make_bone_setup_dirty() {
549 if (bone_setup_dirty) {
550 return;
551 }
552 bone_setup_dirty = true;
553 if (is_inside_tree()) {
554 call_deferred(SNAME("_update_bone_setup"));
555 }
556}
557
558void Skeleton2D::_update_bone_setup() {
559 if (!bone_setup_dirty) {
560 return;
561 }
562
563 bone_setup_dirty = false;
564 RS::get_singleton()->skeleton_allocate_data(skeleton, bones.size(), true);
565
566 bones.sort(); //sorting so that they are always in the same order/index
567
568 for (int i = 0; i < bones.size(); i++) {
569 bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
570 bones.write[i].bone->skeleton_index = i;
571 Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
572 if (parent_bone) {
573 bones.write[i].parent_index = parent_bone->skeleton_index;
574 } else {
575 bones.write[i].parent_index = -1;
576 }
577
578 bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest();
579 }
580
581 transform_dirty = true;
582 _update_transform();
583 emit_signal(SNAME("bone_setup_changed"));
584}
585
586void Skeleton2D::_make_transform_dirty() {
587 if (transform_dirty) {
588 return;
589 }
590 transform_dirty = true;
591 if (is_inside_tree()) {
592 call_deferred(SNAME("_update_transform"));
593 }
594}
595
596void Skeleton2D::_update_transform() {
597 if (bone_setup_dirty) {
598 _update_bone_setup();
599 return; //above will update transform anyway
600 }
601 if (!transform_dirty) {
602 return;
603 }
604
605 transform_dirty = false;
606
607 for (int i = 0; i < bones.size(); i++) {
608 ERR_CONTINUE(bones[i].parent_index >= i);
609 if (bones[i].parent_index >= 0) {
610 bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
611 } else {
612 bones.write[i].accum_transform = bones[i].bone->get_transform();
613 }
614 }
615
616 for (int i = 0; i < bones.size(); i++) {
617 Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse;
618 RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform);
619 }
620}
621
622int Skeleton2D::get_bone_count() const {
623 ERR_FAIL_COND_V(!is_inside_tree(), 0);
624
625 if (bone_setup_dirty) {
626 const_cast<Skeleton2D *>(this)->_update_bone_setup();
627 }
628
629 return bones.size();
630}
631
632Bone2D *Skeleton2D::get_bone(int p_idx) {
633 ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
634 ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr);
635
636 return bones[p_idx].bone;
637}
638
639void Skeleton2D::_notification(int p_what) {
640 if (p_what == NOTIFICATION_READY) {
641 if (bone_setup_dirty) {
642 _update_bone_setup();
643 }
644 if (transform_dirty) {
645 _update_transform();
646 }
647 request_ready();
648 }
649
650 if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
651 RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
652 } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
653 if (modification_stack.is_valid()) {
654 execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process);
655 }
656 } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
657 if (modification_stack.is_valid()) {
658 execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process);
659 }
660 }
661#ifdef TOOLS_ENABLED
662 else if (p_what == NOTIFICATION_DRAW) {
663 if (Engine::get_singleton()->is_editor_hint()) {
664 if (modification_stack.is_valid()) {
665 modification_stack->draw_editor_gizmos();
666 }
667 }
668 }
669#endif // TOOLS_ENABLED
670}
671
672RID Skeleton2D::get_skeleton() const {
673 return skeleton;
674}
675
676void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) {
677 ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!");
678 bones.write[p_bone_idx].local_pose_override = p_override;
679 bones.write[p_bone_idx].local_pose_override_amount = p_amount;
680 bones.write[p_bone_idx].local_pose_override_persistent = p_persistent;
681}
682
683Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) {
684 ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!");
685 return bones[p_bone_idx].local_pose_override;
686}
687
688void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack) {
689 if (modification_stack.is_valid()) {
690 modification_stack->is_setup = false;
691 modification_stack->set_skeleton(nullptr);
692
693 set_process_internal(false);
694 set_physics_process_internal(false);
695 }
696 modification_stack = p_stack;
697 if (modification_stack.is_valid()) {
698 modification_stack->set_skeleton(this);
699 modification_stack->setup();
700
701 set_process_internal(true);
702 set_physics_process_internal(true);
703
704#ifdef TOOLS_ENABLED
705 modification_stack->set_editor_gizmos_dirty(true);
706#endif // TOOLS_ENABLED
707 }
708}
709
710Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const {
711 return modification_stack;
712}
713
714void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
715 if (!modification_stack.is_valid()) {
716 return;
717 }
718
719 // Do not cache the transform changes caused by the modifications!
720 for (int i = 0; i < bones.size(); i++) {
721 bones[i].bone->copy_transform_to_cache = false;
722 }
723
724 if (modification_stack->skeleton != this) {
725 modification_stack->set_skeleton(this);
726 }
727
728 modification_stack->execute(p_delta, p_execution_mode);
729
730 // Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform.
731 if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) {
732 for (int i = 0; i < bones.size(); i++) {
733 if (bones[i].local_pose_override_amount > 0) {
734 bones[i].bone->set_meta("_local_pose_override_enabled_", true);
735
736 Transform2D final_trans = bones[i].bone->cache_transform;
737 final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount);
738 bones[i].bone->set_transform(final_trans);
739 bones[i].bone->propagate_call("force_update_transform");
740
741 if (bones[i].local_pose_override_persistent) {
742 bones.write[i].local_pose_override_amount = 0.0;
743 }
744 } else {
745 // TODO: see if there is a way to undo the override without having to resort to setting every bone's transform.
746 bones[i].bone->remove_meta("_local_pose_override_enabled_");
747 bones[i].bone->set_transform(bones[i].bone->cache_transform);
748 }
749 }
750 }
751
752 // Cache any future transform changes
753 for (int i = 0; i < bones.size(); i++) {
754 bones[i].bone->copy_transform_to_cache = true;
755 }
756
757#ifdef TOOLS_ENABLED
758 modification_stack->set_editor_gizmos_dirty(true);
759#endif // TOOLS_ENABLED
760}
761
762void Skeleton2D::_bind_methods() {
763 ClassDB::bind_method(D_METHOD("_update_bone_setup"), &Skeleton2D::_update_bone_setup);
764 ClassDB::bind_method(D_METHOD("_update_transform"), &Skeleton2D::_update_transform);
765
766 ClassDB::bind_method(D_METHOD("get_bone_count"), &Skeleton2D::get_bone_count);
767 ClassDB::bind_method(D_METHOD("get_bone", "idx"), &Skeleton2D::get_bone);
768
769 ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton);
770
771 ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack);
772 ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack);
773 ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton2D::execute_modifications);
774
775 ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override);
776 ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override);
777
778 ADD_SIGNAL(MethodInfo("bone_setup_changed"));
779}
780
781Skeleton2D::Skeleton2D() {
782 skeleton = RS::get_singleton()->skeleton_create();
783 set_notify_transform(true);
784 set_hide_clip_children(true);
785}
786
787Skeleton2D::~Skeleton2D() {
788 ERR_FAIL_NULL(RenderingServer::get_singleton());
789 RS::get_singleton()->free(skeleton);
790}
791