/*************************************************************************/ /* skeleton_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* PANDEMONIUM ENGINE */ /* https://github.com/Relintai/pandemonium_engine */ /*************************************************************************/ /* Copyright (c) 2022-present Péter Magyar. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "skeleton_2d.h" #include "../resources/skeleton_modification_2d.h" #include "../resources/skeleton_modification_stack_2d.h" #include "core/config/engine.h" #include "core/math/transform_interpolator.h" #include "scene/main/scene_string_names.h" #ifdef TOOLS_ENABLED #include "editor/editor_data.h" #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" #endif //TOOLS_ENABLED bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; if (path.begins_with("auto_calculate_length_and_angle")) { set_autocalculate_length_and_angle(p_value); } else if (path.begins_with("length")) { set_length(p_value); } else if (path.begins_with("bone_angle")) { set_bone_angle(Math::deg2rad(float(p_value))); } else if (path.begins_with("default_length")) { set_length(p_value); } #ifdef TOOLS_ENABLED if (path.begins_with("editor_settings/show_bone_gizmo")) { _editor_set_show_bone_gizmo(p_value); } #endif // TOOLS_ENABLED return true; } bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; if (path.begins_with("auto_calculate_length_and_angle")) { r_ret = get_autocalculate_length_and_angle(); } else if (path.begins_with("length")) { r_ret = get_length(); } else if (path.begins_with("bone_angle")) { r_ret = Math::rad2deg(get_bone_angle()); } else if (path.begins_with("default_length")) { r_ret = get_length(); } #ifdef TOOLS_ENABLED if (path.begins_with("editor_settings/show_bone_gizmo")) { r_ret = _editor_get_show_bone_gizmo(); } #endif // TOOLS_ENABLED return true; } void Bone2D::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_length_and_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (!autocalculate_length_and_angle) { p_list->push_back(PropertyInfo(Variant::REAL, "length", PROPERTY_HINT_RANGE, "1,1024,1", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::REAL, "bone_angle", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); } #ifdef TOOLS_ENABLED p_list->push_back(PropertyInfo(Variant::BOOL, "editor_settings/show_bone_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); #endif // TOOLS_ENABLED } void Bone2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { Node *parent = get_parent(); parent_bone = Object::cast_to(parent); skeleton = nullptr; while (parent) { skeleton = Object::cast_to(parent); if (skeleton) { break; } if (!Object::cast_to(parent)) { break; //skeletons must be chained to Bone2Ds. } parent = parent->get_parent(); } if (skeleton) { Skeleton2D::Bone bone; bone.bone = this; skeleton->bones.push_back(bone); skeleton->_make_bone_setup_dirty(); get_parent()->connect(SceneStringNames::get_singleton()->child_order_changed, skeleton, "_make_bone_setup_dirty", varray(), CONNECT_REFERENCE_COUNTED); } cache_transform = get_transform(); copy_transform_to_cache = true; #ifdef TOOLS_ENABLED // Only draw the gizmo in the editor! if (Engine::get_singleton()->is_editor_hint() == false) { return; } update(); #endif // TOOLS_ENABLED } else if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { if (skeleton) { skeleton->_make_transform_dirty(); } if (copy_transform_to_cache) { cache_transform = get_transform(); } #ifdef TOOLS_ENABLED // Only draw the gizmo in the editor! if (Engine::get_singleton()->is_editor_hint() == false) { return; } update(); if (get_parent()) { Bone2D *parent_bone = Object::cast_to(get_parent()); if (parent_bone) { parent_bone->update(); } } #endif // TOOLS_ENABLED } else if (p_what == NOTIFICATION_EXIT_TREE) { if (skeleton) { for (int i = 0; i < skeleton->bones.size(); i++) { if (skeleton->bones[i].bone == this) { skeleton->bones.remove(i); break; } } skeleton->_make_bone_setup_dirty(); get_parent()->disconnect(SceneStringNames::get_singleton()->child_order_changed, skeleton, "_make_bone_setup_dirty"); skeleton = nullptr; } parent_bone = nullptr; set_transform(cache_transform); } else if (p_what == NOTIFICATION_READY) { if (autocalculate_length_and_angle) { calculate_length_and_rotation(); } } #ifdef TOOLS_ENABLED else if (p_what == NOTIFICATION_EDITOR_PRE_SAVE || p_what == NOTIFICATION_EDITOR_POST_SAVE) { Transform2D tmp_trans = get_transform(); set_transform(cache_transform); cache_transform = tmp_trans; } // Bone2D Editor gizmo drawing: //TODO Bone2D gizmo drawing needs to be moved to an editor plugin else if (p_what == NOTIFICATION_DRAW) { // Only draw the gizmo in the editor! if (Engine::get_singleton()->is_editor_hint() == false) { return; } if (!editor_gizmo_rid.is_valid()) { editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create(); RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item()); RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true); RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10); } RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid); if (!_editor_show_bone_gizmo) { return; } // Undo scaling Transform2D editor_gizmo_trans = Transform2D(); editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale()); RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans); Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1"); Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2"); Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color"); Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color"); bool Bone2D_found = false; for (int i = 0; i < get_child_count(); i++) { Bone2D *child_node = nullptr; child_node = Object::cast_to(get_child(i)); if (!child_node) { continue; } Bone2D_found = true; Vector bone_shape; Vector bone_shape_outline; _editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node); Vector colors; if (has_meta("_local_pose_override_enabled_")) { colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); } else { colors.push_back(bone_color1); colors.push_back(bone_color2); colors.push_back(bone_color1); colors.push_back(bone_color2); } Vector outline_colors; if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); } else { outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); } RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); } if (!Bone2D_found) { Vector bone_shape; Vector bone_shape_outline; _editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr); Vector colors; if (has_meta("_local_pose_override_enabled_")) { colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); colors.push_back(bone_ik_color); } else { colors.push_back(bone_color1); colors.push_back(bone_color2); colors.push_back(bone_color1); colors.push_back(bone_color2); } Vector outline_colors; if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); outline_colors.push_back(bone_selected_color); } else { outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); outline_colors.push_back(bone_outline_color); } RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); } } #endif // TOOLS_ENALBED } #ifdef TOOLS_ENABLED bool Bone2D::_editor_get_bone_shape(Vector *p_shape, Vector *p_outline_shape, Bone2D *p_other_bone) { int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width"); int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size"); if (!is_inside_tree()) { return false; //may have been removed } if (!p_other_bone && length <= 0) { return false; } Vector2 rel; if (p_other_bone) { rel = (p_other_bone->get_global_transform().get_origin() - get_global_transform().get_origin()); rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation } else { float angle_to_use = get_rotation() + bone_angle; rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y)); rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation } Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width; Vector2 reln = rel.normalized(); Vector2 reltn = relt.normalized(); if (p_shape) { p_shape->clear(); p_shape->push_back(Vector2(0, 0)); p_shape->push_back(rel * 0.2 + relt); p_shape->push_back(rel); p_shape->push_back(rel * 0.2 - relt); } if (p_outline_shape) { p_outline_shape->clear(); p_outline_shape->push_back((-reln - reltn) * bone_outline_width); p_outline_shape->push_back((-reln + reltn) * bone_outline_width); p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width); p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width); p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width); p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width); } return true; } void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) { _editor_show_bone_gizmo = p_show_gizmo; update(); } bool Bone2D::_editor_get_show_bone_gizmo() const { return _editor_show_bone_gizmo; } #endif // TOOLS_ENABLED void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest); ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest); ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest); ClassDB::bind_method(D_METHOD("get_skeleton_rest"), &Bone2D::get_skeleton_rest); ClassDB::bind_method(D_METHOD("get_index_in_skeleton"), &Bone2D::get_index_in_skeleton); ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle); ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle); ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length); ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length); ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle); ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); } void Bone2D::set_rest(const Transform2D &p_rest) { rest = p_rest; if (skeleton) { skeleton->_make_bone_setup_dirty(); } update_configuration_warning(); } Transform2D Bone2D::get_rest() const { return rest; } Transform2D Bone2D::get_skeleton_rest() const { if (parent_bone) { return parent_bone->get_skeleton_rest() * rest; } else { return rest; } } void Bone2D::apply_rest() { set_transform(rest); } void Bone2D::set_default_length(float p_length) { WARN_DEPRECATED_MSG("set_default_length is deprecated. Please use set_length instead!"); set_length(p_length); } float Bone2D::get_default_length() const { WARN_DEPRECATED_MSG("get_default_length is deprecated. Please use get_length instead!"); return get_length(); } int Bone2D::get_index_in_skeleton() const { ERR_FAIL_COND_V(!skeleton, -1); skeleton->_update_bone_setup(); return skeleton_index; } String Bone2D::get_configuration_warning() const { String warning = Node2D::get_configuration_warning(); if (!skeleton) { if (warning != String()) { warning += "\n\n"; } if (parent_bone) { warning += TTR("This Bone2D chain should end at a Skeleton2D node."); } else { warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); } } if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { if (warning != String()) { warning += "\n\n"; } warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); } return warning; } void Bone2D::calculate_length_and_rotation() { // if there is at least a single child Bone2D node, we can calculate // the length and direction. We will always just use the first Bone2D for this. bool calculated = false; int child_count = get_child_count(); if (child_count > 0) { for (int i = 0; i < child_count; i++) { Bone2D *child = Object::cast_to(get_child(i)); if (child) { Vector2 child_local_pos = to_local(child->get_global_transform().get_origin()); length = child_local_pos.length(); bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); calculated = true; break; } } } if (calculated) { return; // Finished! } else { WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle"); bone_angle = get_transform().get_rotation(); return; } } void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) { autocalculate_length_and_angle = p_autocalculate; if (autocalculate_length_and_angle) { calculate_length_and_rotation(); } property_list_changed_notify(); } bool Bone2D::get_autocalculate_length_and_angle() const { return autocalculate_length_and_angle; } void Bone2D::set_length(float p_length) { length = p_length; #ifdef TOOLS_ENABLED update(); #endif // TOOLS_ENABLED } float Bone2D::get_length() const { return length; } void Bone2D::set_bone_angle(float p_angle) { bone_angle = p_angle; #ifdef TOOLS_ENABLED update(); #endif // TOOLS_ENABLED } float Bone2D::get_bone_angle() const { return bone_angle; } Bone2D::Bone2D() { skeleton = nullptr; parent_bone = nullptr; skeleton_index = -1; length = 16; bone_angle = 0; autocalculate_length_and_angle = true; set_notify_local_transform(true); //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error. for (int i = 0; i < 3; i++) { rest[i] = Vector2(0, 0); } copy_transform_to_cache = true; } Bone2D::~Bone2D() { #ifdef TOOLS_ENABLED if (editor_gizmo_rid.is_valid()) { RenderingServer::get_singleton()->free(editor_gizmo_rid); } #endif // TOOLS_ENABLED } ////////////////////////////////////// bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; if (path.begins_with("modification_stack")) { set_modification_stack(p_value); return true; } return true; } bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; if (path.begins_with("modification_stack")) { r_ret = get_modification_stack(); return true; } return true; } void Skeleton2D::_get_property_list(List *p_list) const { p_list->push_back( PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); } void Skeleton2D::_make_bone_setup_dirty() { if (bone_setup_dirty) { return; } bone_setup_dirty = true; if (is_inside_tree()) { call_deferred("_update_bone_setup"); } } void Skeleton2D::_update_bone_setup() { if (!bone_setup_dirty) { return; } bone_setup_dirty = false; RS::get_singleton()->skeleton_allocate(skeleton, bones.size(), true); bones.sort(); //sorty so they are always in the same order/index for (int i = 0; i < bones.size(); i++) { bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose bones.write[i].bone->skeleton_index = i; Bone2D *parent_bone = Object::cast_to(bones[i].bone->get_parent()); if (parent_bone) { bones.write[i].parent_index = parent_bone->skeleton_index; } else { bones.write[i].parent_index = -1; } bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest(); } transform_dirty = true; _update_transform(); emit_signal("bone_setup_changed"); } void Skeleton2D::_make_transform_dirty() { if (transform_dirty) { return; } transform_dirty = true; if (is_inside_tree()) { call_deferred("_update_transform"); } } void Skeleton2D::_update_transform() { if (bone_setup_dirty) { _update_bone_setup(); return; //above will update transform anyway } if (!transform_dirty) { return; } transform_dirty = false; for (int i = 0; i < bones.size(); i++) { ERR_CONTINUE(bones[i].parent_index >= i); if (bones[i].parent_index >= 0) { bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); } else { bones.write[i].accum_transform = bones[i].bone->get_transform(); } } for (int i = 0; i < bones.size(); i++) { Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse; RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform); } } int Skeleton2D::get_bone_count() const { ERR_FAIL_COND_V(!is_inside_tree(), 0); if (bone_setup_dirty) { const_cast(this)->_update_bone_setup(); } return bones.size(); } Bone2D *Skeleton2D::get_bone(int p_idx) { ERR_FAIL_COND_V(!is_inside_tree(), nullptr); ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr); return bones[p_idx].bone; } void Skeleton2D::_update_process_mode() { bool process = is_physics_interpolated_and_enabled() && is_visible_in_tree(); set_process_internal(process); set_physics_process_internal(process); } void Skeleton2D::_ensure_update_interpolation_data() { uint64_t tick = Engine::get_singleton()->get_physics_frames(); if (_interpolation_data.last_update_physics_tick != tick) { _interpolation_data.xform_prev = _interpolation_data.xform_curr; _interpolation_data.last_update_physics_tick = tick; } } void Skeleton2D::_physics_interpolated_changed() { _update_process_mode(); } void Skeleton2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { if (bone_setup_dirty) { _update_bone_setup(); } if (transform_dirty) { _update_transform(); } request_ready(); } break; case NOTIFICATION_ENTER_TREE: { _update_process_mode(); if (is_physics_interpolated_and_enabled()) { _interpolation_data.xform_curr = get_global_transform(); _interpolation_data.xform_prev = _interpolation_data.xform_curr; } } break; case NOTIFICATION_TRANSFORM_CHANGED: { if (is_physics_interpolated_and_enabled()) { _ensure_update_interpolation_data(); if (Engine::get_singleton()->is_in_physics_frame()) { _interpolation_data.xform_curr = get_global_transform(); } } else { RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); } } break; case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { _interpolation_data.xform_curr = get_global_transform(); _interpolation_data.xform_prev = _interpolation_data.xform_curr; } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (modification_stack.is_valid()) { execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); } if (is_physics_interpolated_and_enabled()) { _ensure_update_interpolation_data(); _interpolation_data.xform_curr = get_global_transform(); } } break; case NOTIFICATION_INTERNAL_PROCESS: { if (modification_stack.is_valid()) { execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); } if (is_physics_interpolated_and_enabled()) { Transform2D res; TransformInterpolator::interpolate_transform_2d(_interpolation_data.xform_prev, _interpolation_data.xform_curr, res, Engine::get_singleton()->get_physics_interpolation_fraction()); RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, res); } } break; case NOTIFICATION_VISIBILITY_CHANGED: { _update_process_mode(); } break; #ifdef TOOLS_ENABLED case NOTIFICATION_DRAW: { if (Engine::get_singleton()->is_editor_hint()) { if (modification_stack.is_valid()) { modification_stack->draw_editor_gizmos(); } } } break; #endif // TOOLS_ENABLED } } RID Skeleton2D::get_skeleton() const { return skeleton; } void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent) { ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!"); bones.write[p_bone_idx].local_pose_override = p_override; bones.write[p_bone_idx].local_pose_override_amount = p_amount; bones.write[p_bone_idx].local_pose_override_persistent = p_persistent; } Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) { ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!"); return bones[p_bone_idx].local_pose_override; } void Skeleton2D::set_modification_stack(Ref p_stack) { if (modification_stack.is_valid()) { modification_stack->is_setup = false; modification_stack->set_skeleton(nullptr); set_process_internal(false); set_physics_process_internal(false); } modification_stack = p_stack; if (modification_stack.is_valid()) { modification_stack->set_skeleton(this); modification_stack->setup(); set_process_internal(true); set_physics_process_internal(true); #ifdef TOOLS_ENABLED modification_stack->set_editor_gizmos_dirty(true); #endif // TOOLS_ENABLED } } Ref Skeleton2D::get_modification_stack() const { return modification_stack; } void Skeleton2D::execute_modifications(float p_delta, int p_execution_mode) { if (!modification_stack.is_valid()) { return; } // Do not cache the transform changes caused by the modifications! for (int i = 0; i < bones.size(); i++) { bones[i].bone->copy_transform_to_cache = false; } if (modification_stack->skeleton != this) { modification_stack->set_skeleton(this); } modification_stack->execute(p_delta, p_execution_mode); // Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform. if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) { for (int i = 0; i < bones.size(); i++) { if (bones[i].local_pose_override_amount > 0) { bones[i].bone->set_meta("_local_pose_override_enabled_", true); Transform2D final_trans = bones[i].bone->cache_transform; final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount); bones[i].bone->set_transform(final_trans); bones[i].bone->propagate_call("force_update_transform"); if (bones[i].local_pose_override_persistent) { bones.write[i].local_pose_override_amount = 0.0; } } else { // TODO: see if there is a way to undo the override without having to resort to setting every bone's transform. bones[i].bone->remove_meta("_local_pose_override_enabled_"); bones[i].bone->set_transform(bones[i].bone->cache_transform); } } } // Cache any future transform changes for (int i = 0; i < bones.size(); i++) { bones[i].bone->copy_transform_to_cache = true; } #ifdef TOOLS_ENABLED modification_stack->set_editor_gizmos_dirty(true); #endif // TOOLS_ENABLED } void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_bone_setup"), &Skeleton2D::_update_bone_setup); ClassDB::bind_method(D_METHOD("_update_transform"), &Skeleton2D::_update_transform); ClassDB::bind_method(D_METHOD("get_bone_count"), &Skeleton2D::get_bone_count); ClassDB::bind_method(D_METHOD("get_bone", "idx"), &Skeleton2D::get_bone); ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton); ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack); ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack); ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton2D::execute_modifications); ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override); ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override); ClassDB::bind_method(D_METHOD("_make_bone_setup_dirty"), &Skeleton2D::_make_bone_setup_dirty); ADD_SIGNAL(MethodInfo("bone_setup_changed")); } Skeleton2D::Skeleton2D() { bone_setup_dirty = true; transform_dirty = true; skeleton = RID_PRIME(RS::get_singleton()->skeleton_create()); set_notify_transform(true); } Skeleton2D::~Skeleton2D() { RS::get_singleton()->free(skeleton); }