1 | /**************************************************************************/ |
2 | /* skeleton_3d_editor_plugin.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_3d_editor_plugin.h" |
32 | |
33 | #include "core/io/resource_saver.h" |
34 | #include "editor/editor_node.h" |
35 | #include "editor/editor_properties.h" |
36 | #include "editor/editor_properties_vector.h" |
37 | #include "editor/editor_scale.h" |
38 | #include "editor/editor_settings.h" |
39 | #include "editor/editor_string_names.h" |
40 | #include "editor/editor_undo_redo_manager.h" |
41 | #include "editor/plugins/animation_player_editor_plugin.h" |
42 | #include "node_3d_editor_plugin.h" |
43 | #include "scene/3d/collision_shape_3d.h" |
44 | #include "scene/3d/joint_3d.h" |
45 | #include "scene/3d/mesh_instance_3d.h" |
46 | #include "scene/3d/physics_body_3d.h" |
47 | #include "scene/gui/separator.h" |
48 | #include "scene/gui/texture_rect.h" |
49 | #include "scene/resources/capsule_shape_3d.h" |
50 | #include "scene/resources/skeleton_profile.h" |
51 | #include "scene/resources/sphere_shape_3d.h" |
52 | #include "scene/resources/surface_tool.h" |
53 | #include "scene/scene_string_names.h" |
54 | |
55 | void BoneTransformEditor::create_editors() { |
56 | section = memnew(EditorInspectorSection); |
57 | section->setup("trf_properties" , label, this, Color(0.0f, 0.0f, 0.0f), true); |
58 | section->unfold(); |
59 | add_child(section); |
60 | |
61 | enabled_checkbox = memnew(EditorPropertyCheck()); |
62 | enabled_checkbox->set_label("Pose Enabled" ); |
63 | enabled_checkbox->set_selectable(false); |
64 | enabled_checkbox->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
65 | section->get_vbox()->add_child(enabled_checkbox); |
66 | |
67 | // Position property. |
68 | position_property = memnew(EditorPropertyVector3()); |
69 | position_property->setup(-10000, 10000, 0.001f, true); |
70 | position_property->set_label("Position" ); |
71 | position_property->set_selectable(false); |
72 | position_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
73 | position_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
74 | section->get_vbox()->add_child(position_property); |
75 | |
76 | // Rotation property. |
77 | rotation_property = memnew(EditorPropertyQuaternion()); |
78 | rotation_property->setup(-10000, 10000, 0.001f, true); |
79 | rotation_property->set_label("Rotation" ); |
80 | rotation_property->set_selectable(false); |
81 | rotation_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
82 | rotation_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
83 | section->get_vbox()->add_child(rotation_property); |
84 | |
85 | // Scale property. |
86 | scale_property = memnew(EditorPropertyVector3()); |
87 | scale_property->setup(-10000, 10000, 0.001f, true); |
88 | scale_property->set_label("Scale" ); |
89 | scale_property->set_selectable(false); |
90 | scale_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
91 | scale_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
92 | section->get_vbox()->add_child(scale_property); |
93 | |
94 | // Transform/Matrix section. |
95 | rest_section = memnew(EditorInspectorSection); |
96 | rest_section->setup("trf_properties_transform" , "Rest" , this, Color(0.0f, 0.0f, 0.0f), true); |
97 | section->get_vbox()->add_child(rest_section); |
98 | |
99 | // Transform/Matrix property. |
100 | rest_matrix = memnew(EditorPropertyTransform3D()); |
101 | rest_matrix->setup(-10000, 10000, 0.001f, true); |
102 | rest_matrix->set_label("Transform" ); |
103 | rest_matrix->set_selectable(false); |
104 | rest_section->get_vbox()->add_child(rest_matrix); |
105 | } |
106 | |
107 | void BoneTransformEditor::_notification(int p_what) { |
108 | switch (p_what) { |
109 | case NOTIFICATION_THEME_CHANGED: { |
110 | const Color section_color = get_theme_color(SNAME("prop_subsection" ), EditorStringName(Editor)); |
111 | section->set_bg_color(section_color); |
112 | rest_section->set_bg_color(section_color); |
113 | } break; |
114 | } |
115 | } |
116 | |
117 | void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { |
118 | if (updating) { |
119 | return; |
120 | } |
121 | if (skeleton) { |
122 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
123 | undo_redo->create_action(TTR("Set Bone Transform" ), UndoRedo::MERGE_ENDS); |
124 | undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); |
125 | undo_redo->add_do_property(skeleton, p_property, p_value); |
126 | undo_redo->commit_action(); |
127 | } |
128 | } |
129 | |
130 | BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : |
131 | skeleton(p_skeleton) { |
132 | create_editors(); |
133 | } |
134 | |
135 | void BoneTransformEditor::set_keyable(const bool p_keyable) { |
136 | position_property->set_keying(p_keyable); |
137 | rotation_property->set_keying(p_keyable); |
138 | scale_property->set_keying(p_keyable); |
139 | } |
140 | |
141 | void BoneTransformEditor::set_target(const String &p_prop) { |
142 | enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled" ); |
143 | enabled_checkbox->update_property(); |
144 | |
145 | position_property->set_object_and_property(skeleton, p_prop + "position" ); |
146 | position_property->update_property(); |
147 | |
148 | rotation_property->set_object_and_property(skeleton, p_prop + "rotation" ); |
149 | rotation_property->update_property(); |
150 | |
151 | scale_property->set_object_and_property(skeleton, p_prop + "scale" ); |
152 | scale_property->update_property(); |
153 | |
154 | rest_matrix->set_object_and_property(skeleton, p_prop + "rest" ); |
155 | rest_matrix->update_property(); |
156 | } |
157 | |
158 | void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) { |
159 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
160 | if (!te || !te->has_keying()) { |
161 | return; |
162 | } |
163 | te->_clear_selection(); |
164 | PackedStringArray split = p_path.split("/" ); |
165 | if (split.size() == 3 && split[0] == "bones" ) { |
166 | int bone_idx = split[1].to_int(); |
167 | if (split[2] == "position" ) { |
168 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (Vector3)skeleton->get(p_path) / skeleton->get_motion_scale()); |
169 | } |
170 | if (split[2] == "rotation" ) { |
171 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path)); |
172 | } |
173 | if (split[2] == "scale" ) { |
174 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_SCALE_3D, skeleton->get(p_path)); |
175 | } |
176 | } |
177 | } |
178 | |
179 | void BoneTransformEditor::_update_properties() { |
180 | if (!skeleton) { |
181 | return; |
182 | } |
183 | int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); |
184 | List<PropertyInfo> props; |
185 | skeleton->get_property_list(&props); |
186 | for (const PropertyInfo &E : props) { |
187 | PackedStringArray split = E.name.split("/" ); |
188 | if (split.size() == 3 && split[0] == "bones" ) { |
189 | if (split[1].to_int() == selected) { |
190 | if (split[2] == "enabled" ) { |
191 | enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
192 | enabled_checkbox->update_property(); |
193 | enabled_checkbox->queue_redraw(); |
194 | } |
195 | if (split[2] == "position" ) { |
196 | position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
197 | position_property->update_property(); |
198 | position_property->queue_redraw(); |
199 | } |
200 | if (split[2] == "rotation" ) { |
201 | rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
202 | rotation_property->update_property(); |
203 | rotation_property->queue_redraw(); |
204 | } |
205 | if (split[2] == "scale" ) { |
206 | scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
207 | scale_property->update_property(); |
208 | scale_property->queue_redraw(); |
209 | } |
210 | if (split[2] == "rest" ) { |
211 | rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
212 | rest_matrix->update_property(); |
213 | rest_matrix->queue_redraw(); |
214 | } |
215 | } |
216 | } |
217 | } |
218 | } |
219 | |
220 | Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; |
221 | |
222 | void Skeleton3DEditor::set_keyable(const bool p_keyable) { |
223 | keyable = p_keyable; |
224 | if (p_keyable) { |
225 | animation_hb->show(); |
226 | } else { |
227 | animation_hb->hide(); |
228 | } |
229 | }; |
230 | |
231 | void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enabled) { |
232 | skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_RESET_SELECTED_POSES, !p_bone_options_enabled); |
233 | skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled); |
234 | }; |
235 | |
236 | void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { |
237 | if (!skeleton) { |
238 | return; |
239 | } |
240 | |
241 | switch (p_skeleton_option) { |
242 | case SKELETON_OPTION_RESET_ALL_POSES: { |
243 | reset_pose(true); |
244 | break; |
245 | } |
246 | case SKELETON_OPTION_RESET_SELECTED_POSES: { |
247 | reset_pose(false); |
248 | break; |
249 | } |
250 | case SKELETON_OPTION_ALL_POSES_TO_RESTS: { |
251 | pose_to_rest(true); |
252 | break; |
253 | } |
254 | case SKELETON_OPTION_SELECTED_POSES_TO_RESTS: { |
255 | pose_to_rest(false); |
256 | break; |
257 | } |
258 | case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { |
259 | create_physical_skeleton(); |
260 | break; |
261 | } |
262 | case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: { |
263 | export_skeleton_profile(); |
264 | break; |
265 | } |
266 | } |
267 | } |
268 | |
269 | void Skeleton3DEditor::reset_pose(const bool p_all_bones) { |
270 | if (!skeleton) { |
271 | return; |
272 | } |
273 | const int bone_count = skeleton->get_bone_count(); |
274 | if (!bone_count) { |
275 | return; |
276 | } |
277 | |
278 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
279 | ur->create_action(TTR("Set Bone Transform" ), UndoRedo::MERGE_ENDS); |
280 | if (p_all_bones) { |
281 | for (int i = 0; i < bone_count; i++) { |
282 | ur->add_undo_method(skeleton, "set_bone_pose_position" , i, skeleton->get_bone_pose_position(i)); |
283 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , i, skeleton->get_bone_pose_rotation(i)); |
284 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , i, skeleton->get_bone_pose_scale(i)); |
285 | } |
286 | ur->add_do_method(skeleton, "reset_bone_poses" ); |
287 | } else { |
288 | // Todo: Do method with multiple bone selection. |
289 | if (selected_bone == -1) { |
290 | ur->commit_action(); |
291 | return; |
292 | } |
293 | ur->add_undo_method(skeleton, "set_bone_pose_position" , selected_bone, skeleton->get_bone_pose_position(selected_bone)); |
294 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , selected_bone, skeleton->get_bone_pose_rotation(selected_bone)); |
295 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , selected_bone, skeleton->get_bone_pose_scale(selected_bone)); |
296 | ur->add_do_method(skeleton, "reset_bone_pose" , selected_bone); |
297 | } |
298 | ur->commit_action(); |
299 | } |
300 | |
301 | void Skeleton3DEditor::insert_keys(const bool p_all_bones) { |
302 | if (!skeleton) { |
303 | return; |
304 | } |
305 | |
306 | bool pos_enabled = key_loc_button->is_pressed(); |
307 | bool rot_enabled = key_rot_button->is_pressed(); |
308 | bool scl_enabled = key_scale_button->is_pressed(); |
309 | |
310 | int bone_len = skeleton->get_bone_count(); |
311 | Node *root = EditorNode::get_singleton()->get_tree()->get_root(); |
312 | String path = root->get_path_to(skeleton); |
313 | |
314 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
315 | te->make_insert_queue(); |
316 | for (int i = 0; i < bone_len; i++) { |
317 | const String name = skeleton->get_bone_name(i); |
318 | |
319 | if (name.is_empty()) { |
320 | continue; |
321 | } |
322 | |
323 | if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) { |
324 | te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i) / skeleton->get_motion_scale()); |
325 | } |
326 | if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) { |
327 | te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i)); |
328 | } |
329 | if (scl_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_SCALE_3D))) { |
330 | te->insert_transform_key(skeleton, name, Animation::TYPE_SCALE_3D, skeleton->get_bone_pose_scale(i)); |
331 | } |
332 | } |
333 | te->commit_insert_queue(); |
334 | } |
335 | |
336 | void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { |
337 | if (!skeleton) { |
338 | return; |
339 | } |
340 | const int bone_count = skeleton->get_bone_count(); |
341 | if (!bone_count) { |
342 | return; |
343 | } |
344 | |
345 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
346 | ur->create_action(TTR("Set Bone Rest" ), UndoRedo::MERGE_ENDS); |
347 | if (p_all_bones) { |
348 | for (int i = 0; i < bone_count; i++) { |
349 | ur->add_do_method(skeleton, "set_bone_rest" , i, skeleton->get_bone_pose(i)); |
350 | ur->add_undo_method(skeleton, "set_bone_rest" , i, skeleton->get_bone_rest(i)); |
351 | } |
352 | } else { |
353 | // Todo: Do method with multiple bone selection. |
354 | if (selected_bone == -1) { |
355 | ur->commit_action(); |
356 | return; |
357 | } |
358 | ur->add_do_method(skeleton, "set_bone_rest" , selected_bone, skeleton->get_bone_pose(selected_bone)); |
359 | ur->add_undo_method(skeleton, "set_bone_rest" , selected_bone, skeleton->get_bone_rest(selected_bone)); |
360 | } |
361 | ur->commit_action(); |
362 | } |
363 | |
364 | void Skeleton3DEditor::create_physical_skeleton() { |
365 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
366 | ERR_FAIL_COND(!get_tree()); |
367 | Node *owner = get_tree()->get_edited_scene_root(); |
368 | |
369 | const int bone_count = skeleton->get_bone_count(); |
370 | |
371 | if (!bone_count) { |
372 | EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot create a physical skeleton for a Skeleton3D node with no bones." ))); |
373 | return; |
374 | } |
375 | |
376 | Vector<BoneInfo> bones_infos; |
377 | bones_infos.resize(bone_count); |
378 | |
379 | ur->create_action(TTR("Create physical bones" ), UndoRedo::MERGE_ALL); |
380 | for (int bone_id = 0; bone_count > bone_id; ++bone_id) { |
381 | const int parent = skeleton->get_bone_parent(bone_id); |
382 | |
383 | if (parent < 0) { |
384 | bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); |
385 | } else { |
386 | const int parent_parent = skeleton->get_bone_parent(parent); |
387 | |
388 | bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); |
389 | |
390 | // Create physical bone on parent. |
391 | if (!bones_infos[parent].physical_bone) { |
392 | PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos); |
393 | if (physical_bone && physical_bone->get_child(0)) { |
394 | CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0)); |
395 | if (collision_shape) { |
396 | bones_infos.write[parent].physical_bone = physical_bone; |
397 | |
398 | ur->add_do_method(skeleton, "add_child" , physical_bone); |
399 | ur->add_do_method(physical_bone, "set_owner" , owner); |
400 | ur->add_do_method(collision_shape, "set_owner" , owner); |
401 | ur->add_do_property(physical_bone, "bone_name" , skeleton->get_bone_name(parent)); |
402 | |
403 | // Create joint between parent of parent. |
404 | if (parent_parent != -1) { |
405 | ur->add_do_method(physical_bone, "set_joint_type" , PhysicalBone3D::JOINT_TYPE_PIN); |
406 | } |
407 | |
408 | ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone); |
409 | ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape); |
410 | |
411 | ur->add_do_reference(physical_bone); |
412 | ur->add_undo_method(skeleton, "remove_child" , physical_bone); |
413 | } |
414 | } |
415 | } |
416 | } |
417 | } |
418 | ur->commit_action(); |
419 | } |
420 | |
421 | PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { |
422 | const Transform3D child_rest = skeleton->get_bone_rest(bone_child_id); |
423 | |
424 | const real_t half_height(child_rest.origin.length() * 0.5); |
425 | const real_t radius(half_height * 0.2); |
426 | |
427 | CapsuleShape3D *bone_shape_capsule = memnew(CapsuleShape3D); |
428 | bone_shape_capsule->set_height(half_height * 2); |
429 | bone_shape_capsule->set_radius(radius); |
430 | |
431 | CollisionShape3D *bone_shape = memnew(CollisionShape3D); |
432 | bone_shape->set_shape(bone_shape_capsule); |
433 | bone_shape->set_name("CollisionShape3D" ); |
434 | |
435 | Transform3D capsule_transform; |
436 | capsule_transform.basis.rows[0] = Vector3(1, 0, 0); |
437 | capsule_transform.basis.rows[1] = Vector3(0, 0, 1); |
438 | capsule_transform.basis.rows[2] = Vector3(0, -1, 0); |
439 | bone_shape->set_transform(capsule_transform); |
440 | |
441 | /// Get an up vector not collinear with child rest origin |
442 | Vector3 up = Vector3(0, 1, 0); |
443 | if (up.cross(child_rest.origin).is_zero_approx()) { |
444 | up = Vector3(0, 0, 1); |
445 | } |
446 | |
447 | Transform3D body_transform; |
448 | body_transform.basis = Basis::looking_at(child_rest.origin, up); |
449 | body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); |
450 | |
451 | Transform3D joint_transform; |
452 | joint_transform.origin = Vector3(0, 0, half_height); |
453 | |
454 | PhysicalBone3D *physical_bone = memnew(PhysicalBone3D); |
455 | physical_bone->add_child(bone_shape); |
456 | physical_bone->set_name("Physical Bone " + skeleton->get_bone_name(bone_id)); |
457 | physical_bone->set_body_offset(body_transform); |
458 | physical_bone->set_joint_offset(joint_transform); |
459 | return physical_bone; |
460 | } |
461 | |
462 | void Skeleton3DEditor::export_skeleton_profile() { |
463 | if (!skeleton->get_bone_count()) { |
464 | EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot export a SkeletonProfile for a Skeleton3D node with no bones." ))); |
465 | return; |
466 | } |
467 | |
468 | file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
469 | file_dialog->set_title(TTR("Export Skeleton Profile As..." )); |
470 | |
471 | List<String> exts; |
472 | ResourceLoader::get_recognized_extensions_for_type("SkeletonProfile" , &exts); |
473 | file_dialog->clear_filters(); |
474 | for (const String &K : exts) { |
475 | file_dialog->add_filter("*." + K); |
476 | } |
477 | |
478 | file_dialog->popup_file_dialog(); |
479 | } |
480 | |
481 | void Skeleton3DEditor::_file_selected(const String &p_file) { |
482 | // Export SkeletonProfile. |
483 | Ref<SkeletonProfile> sp(memnew(SkeletonProfile)); |
484 | |
485 | // Build SkeletonProfile. |
486 | sp->set_group_size(1); |
487 | |
488 | Vector<Vector2> handle_positions; |
489 | Vector2 position_max; |
490 | Vector2 position_min; |
491 | |
492 | const int bone_count = skeleton->get_bone_count(); |
493 | sp->set_bone_size(bone_count); |
494 | for (int i = 0; i < bone_count; i++) { |
495 | sp->set_bone_name(i, skeleton->get_bone_name(i)); |
496 | int parent = skeleton->get_bone_parent(i); |
497 | if (parent >= 0) { |
498 | sp->set_bone_parent(i, skeleton->get_bone_name(parent)); |
499 | } |
500 | sp->set_reference_pose(i, skeleton->get_bone_rest(i)); |
501 | |
502 | Transform3D grest = skeleton->get_bone_global_rest(i); |
503 | handle_positions.append(Vector2(grest.origin.x, grest.origin.y)); |
504 | if (i == 0) { |
505 | position_max = Vector2(grest.origin.x, grest.origin.y); |
506 | position_min = Vector2(grest.origin.x, grest.origin.y); |
507 | } else { |
508 | position_max.x = MAX(grest.origin.x, position_max.x); |
509 | position_max.y = MAX(grest.origin.y, position_max.y); |
510 | position_min.x = MIN(grest.origin.x, position_min.x); |
511 | position_min.y = MIN(grest.origin.y, position_min.y); |
512 | } |
513 | } |
514 | |
515 | // Layout handles provisionaly. |
516 | Vector2 bound = Vector2(position_max.x - position_min.x, position_max.y - position_min.y); |
517 | Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5); |
518 | float nrm = MAX(bound.x, bound.y); |
519 | if (nrm > 0) { |
520 | for (int i = 0; i < bone_count; i++) { |
521 | handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9; |
522 | sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y)); |
523 | } |
524 | } |
525 | |
526 | Error err = ResourceSaver::save(sp, p_file); |
527 | |
528 | if (err != OK) { |
529 | EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s" ), p_file)); |
530 | return; |
531 | } |
532 | } |
533 | |
534 | Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { |
535 | TreeItem *selected = joint_tree->get_selected(); |
536 | |
537 | if (!selected) { |
538 | return Variant(); |
539 | } |
540 | |
541 | Ref<Texture> icon = selected->get_icon(0); |
542 | |
543 | VBoxContainer *vb = memnew(VBoxContainer); |
544 | HBoxContainer *hb = memnew(HBoxContainer); |
545 | TextureRect *tf = memnew(TextureRect); |
546 | tf->set_texture(icon); |
547 | tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); |
548 | hb->add_child(tf); |
549 | Label *label = memnew(Label(selected->get_text(0))); |
550 | hb->add_child(label); |
551 | vb->add_child(hb); |
552 | hb->set_modulate(Color(1, 1, 1, 1)); |
553 | |
554 | set_drag_preview(vb); |
555 | Dictionary drag_data; |
556 | drag_data["type" ] = "nodes" ; |
557 | drag_data["node" ] = selected; |
558 | |
559 | return drag_data; |
560 | } |
561 | |
562 | bool Skeleton3DEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
563 | TreeItem *target = joint_tree->get_item_at_position(p_point); |
564 | if (!target) { |
565 | return false; |
566 | } |
567 | |
568 | const String path = target->get_metadata(0); |
569 | if (!path.begins_with("bones/" )) { |
570 | return false; |
571 | } |
572 | |
573 | TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node" ]); |
574 | if (target == selected) { |
575 | return false; |
576 | } |
577 | |
578 | const String path2 = target->get_metadata(0); |
579 | if (!path2.begins_with("bones/" )) { |
580 | return false; |
581 | } |
582 | |
583 | return true; |
584 | } |
585 | |
586 | void Skeleton3DEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
587 | if (!can_drop_data_fw(p_point, p_data, p_from)) { |
588 | return; |
589 | } |
590 | |
591 | TreeItem *target = joint_tree->get_item_at_position(p_point); |
592 | TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node" ]); |
593 | |
594 | const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int(); |
595 | const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int(); |
596 | |
597 | move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx); |
598 | } |
599 | |
600 | void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { |
601 | Node *node = get_node_or_null(p_skeleton_path); |
602 | Skeleton3D *skeleton_node = Object::cast_to<Skeleton3D>(node); |
603 | ERR_FAIL_NULL(skeleton_node); |
604 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
605 | ur->create_action(TTR("Set Bone Parentage" )); |
606 | // If the target is a child of ourselves, we move only *us* and not our children. |
607 | if (skeleton_node->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { |
608 | const BoneId parent_idx = skeleton_node->get_bone_parent(p_selected_boneidx); |
609 | const int bone_count = skeleton_node->get_bone_count(); |
610 | for (BoneId i = 0; i < bone_count; ++i) { |
611 | if (skeleton_node->get_bone_parent(i) == p_selected_boneidx) { |
612 | ur->add_undo_method(skeleton_node, "set_bone_parent" , i, skeleton_node->get_bone_parent(i)); |
613 | ur->add_do_method(skeleton_node, "set_bone_parent" , i, parent_idx); |
614 | skeleton_node->set_bone_parent(i, parent_idx); |
615 | } |
616 | } |
617 | } |
618 | ur->add_undo_method(skeleton_node, "set_bone_parent" , p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx)); |
619 | ur->add_do_method(skeleton_node, "set_bone_parent" , p_selected_boneidx, p_target_boneidx); |
620 | skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx); |
621 | |
622 | update_joint_tree(); |
623 | ur->commit_action(); |
624 | } |
625 | |
626 | void Skeleton3DEditor::_joint_tree_selection_changed() { |
627 | TreeItem *selected = joint_tree->get_selected(); |
628 | if (selected) { |
629 | const String path = selected->get_metadata(0); |
630 | if (!path.begins_with("bones/" )) { |
631 | return; |
632 | } |
633 | const int b_idx = path.get_slicec('/', 1).to_int(); |
634 | selected_bone = b_idx; |
635 | if (pose_editor) { |
636 | const String bone_path = "bones/" + itos(b_idx) + "/" ; |
637 | pose_editor->set_target(bone_path); |
638 | pose_editor->set_keyable(keyable); |
639 | } |
640 | } |
641 | |
642 | if (pose_editor && pose_editor->is_inside_tree()) { |
643 | pose_editor->set_visible(selected); |
644 | } |
645 | set_bone_options_enabled(selected); |
646 | |
647 | _update_properties(); |
648 | _update_gizmo_visible(); |
649 | } |
650 | |
651 | // May be not used with single select mode. |
652 | void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { |
653 | } |
654 | |
655 | void Skeleton3DEditor::_update_properties() { |
656 | if (pose_editor) { |
657 | pose_editor->_update_properties(); |
658 | } |
659 | Node3DEditor::get_singleton()->update_transform_gizmo(); |
660 | } |
661 | |
662 | void Skeleton3DEditor::update_joint_tree() { |
663 | joint_tree->clear(); |
664 | |
665 | if (!skeleton) { |
666 | return; |
667 | } |
668 | |
669 | TreeItem *root = joint_tree->create_item(); |
670 | |
671 | HashMap<int, TreeItem *> items; |
672 | |
673 | items.insert(-1, root); |
674 | |
675 | Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("BoneAttachment3D" )); |
676 | |
677 | Vector<int> bones_to_process = skeleton->get_parentless_bones(); |
678 | while (bones_to_process.size() > 0) { |
679 | int current_bone_idx = bones_to_process[0]; |
680 | bones_to_process.erase(current_bone_idx); |
681 | |
682 | const int parent_idx = skeleton->get_bone_parent(current_bone_idx); |
683 | TreeItem *parent_item = items.find(parent_idx)->value; |
684 | |
685 | TreeItem *joint_item = joint_tree->create_item(parent_item); |
686 | items.insert(current_bone_idx, joint_item); |
687 | |
688 | joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx)); |
689 | joint_item->set_icon(0, bone_icon); |
690 | joint_item->set_selectable(0, true); |
691 | joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); |
692 | |
693 | // Add the bone's children to the list of bones to be processed. |
694 | Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); |
695 | int child_bone_size = current_bone_child_bones.size(); |
696 | for (int i = 0; i < child_bone_size; i++) { |
697 | bones_to_process.push_back(current_bone_child_bones[i]); |
698 | } |
699 | } |
700 | } |
701 | |
702 | void Skeleton3DEditor::create_editors() { |
703 | set_h_size_flags(SIZE_EXPAND_FILL); |
704 | set_focus_mode(FOCUS_ALL); |
705 | |
706 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
707 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
708 | |
709 | // Create File dialog. |
710 | file_dialog = memnew(EditorFileDialog); |
711 | file_dialog->connect("file_selected" , callable_mp(this, &Skeleton3DEditor::_file_selected)); |
712 | add_child(file_dialog); |
713 | |
714 | // Create Top Menu Bar. |
715 | HBoxContainer * = memnew(HBoxContainer); |
716 | ne->add_control_to_menu_panel(topmenu_bar); |
717 | |
718 | // Create Skeleton Option in Top Menu Bar. |
719 | skeleton_options = memnew(MenuButton); |
720 | topmenu_bar->add_child(skeleton_options); |
721 | |
722 | skeleton_options->set_text(TTR("Skeleton3D" )); |
723 | |
724 | // Skeleton options. |
725 | PopupMenu *p = skeleton_options->get_popup(); |
726 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_all_poses" , TTR("Reset All Bone Poses" )), SKELETON_OPTION_RESET_ALL_POSES); |
727 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_selected_poses" , TTR("Reset Selected Poses" )), SKELETON_OPTION_RESET_SELECTED_POSES); |
728 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests" , TTR("Apply All Poses to Rests" )), SKELETON_OPTION_ALL_POSES_TO_RESTS); |
729 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests" , TTR("Apply Selected Poses to Rests" )), SKELETON_OPTION_SELECTED_POSES_TO_RESTS); |
730 | p->add_item(TTR("Create Physical Skeleton" ), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); |
731 | p->add_item(TTR("Export Skeleton Profile" ), SKELETON_OPTION_EXPORT_SKELETON_PROFILE); |
732 | |
733 | p->connect("id_pressed" , callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); |
734 | set_bone_options_enabled(false); |
735 | |
736 | Vector<Variant> button_binds; |
737 | button_binds.resize(1); |
738 | |
739 | edit_mode_button = memnew(Button); |
740 | topmenu_bar->add_child(edit_mode_button); |
741 | edit_mode_button->set_flat(true); |
742 | edit_mode_button->set_toggle_mode(true); |
743 | edit_mode_button->set_focus_mode(FOCUS_NONE); |
744 | edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints." )); |
745 | edit_mode_button->connect("toggled" , callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); |
746 | |
747 | edit_mode = false; |
748 | |
749 | if (skeleton) { |
750 | skeleton->add_child(handles_mesh_instance); |
751 | handles_mesh_instance->set_skeleton_path(NodePath("" )); |
752 | } |
753 | |
754 | // Keying buttons. |
755 | animation_hb = memnew(HBoxContainer); |
756 | topmenu_bar->add_child(animation_hb); |
757 | animation_hb->add_child(memnew(VSeparator)); |
758 | animation_hb->hide(); |
759 | |
760 | key_loc_button = memnew(Button); |
761 | key_loc_button->set_flat(true); |
762 | key_loc_button->set_toggle_mode(true); |
763 | key_loc_button->set_pressed(false); |
764 | key_loc_button->set_focus_mode(FOCUS_NONE); |
765 | key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys." )); |
766 | animation_hb->add_child(key_loc_button); |
767 | |
768 | key_rot_button = memnew(Button); |
769 | key_rot_button->set_flat(true); |
770 | key_rot_button->set_toggle_mode(true); |
771 | key_rot_button->set_pressed(true); |
772 | key_rot_button->set_focus_mode(FOCUS_NONE); |
773 | key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys." )); |
774 | animation_hb->add_child(key_rot_button); |
775 | |
776 | key_scale_button = memnew(Button); |
777 | key_scale_button->set_flat(true); |
778 | key_scale_button->set_toggle_mode(true); |
779 | key_scale_button->set_pressed(false); |
780 | key_scale_button->set_focus_mode(FOCUS_NONE); |
781 | key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys." )); |
782 | animation_hb->add_child(key_scale_button); |
783 | |
784 | key_insert_button = memnew(Button); |
785 | key_insert_button->set_flat(true); |
786 | key_insert_button->set_focus_mode(FOCUS_NONE); |
787 | key_insert_button->connect("pressed" , callable_mp(this, &Skeleton3DEditor::insert_keys).bind(false)); |
788 | key_insert_button->set_tooltip_text(TTR("Insert key of bone poses already exist track." )); |
789 | key_insert_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_to_existing_tracks" , TTR("Insert Key (Existing Tracks)" ), Key::INSERT)); |
790 | animation_hb->add_child(key_insert_button); |
791 | |
792 | key_insert_all_button = memnew(Button); |
793 | key_insert_all_button->set_flat(true); |
794 | key_insert_all_button->set_focus_mode(FOCUS_NONE); |
795 | key_insert_all_button->connect("pressed" , callable_mp(this, &Skeleton3DEditor::insert_keys).bind(true)); |
796 | key_insert_all_button->set_tooltip_text(TTR("Insert key of all bone poses." )); |
797 | key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones" , TTR("Insert Key (All Bones)" ), KeyModifierMask::CMD_OR_CTRL + Key::INSERT)); |
798 | animation_hb->add_child(key_insert_all_button); |
799 | |
800 | // Bone tree. |
801 | bones_section = memnew(EditorInspectorSection); |
802 | bones_section->setup("bones" , "Bones" , skeleton, Color(0.0f, 0.0, 0.0f), true); |
803 | add_child(bones_section); |
804 | bones_section->unfold(); |
805 | |
806 | ScrollContainer *s_con = memnew(ScrollContainer); |
807 | s_con->set_h_size_flags(SIZE_EXPAND_FILL); |
808 | s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE); |
809 | bones_section->get_vbox()->add_child(s_con); |
810 | |
811 | joint_tree = memnew(Tree); |
812 | joint_tree->set_columns(1); |
813 | joint_tree->set_focus_mode(Control::FOCUS_NONE); |
814 | joint_tree->set_select_mode(Tree::SELECT_SINGLE); |
815 | joint_tree->set_hide_root(true); |
816 | joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); |
817 | joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); |
818 | joint_tree->set_allow_rmb_select(true); |
819 | SET_DRAG_FORWARDING_GCD(joint_tree, Skeleton3DEditor); |
820 | s_con->add_child(joint_tree); |
821 | |
822 | pose_editor = memnew(BoneTransformEditor(skeleton)); |
823 | pose_editor->set_label(TTR("Bone Transform" )); |
824 | pose_editor->set_visible(false); |
825 | add_child(pose_editor); |
826 | |
827 | set_keyable(te->has_keying()); |
828 | } |
829 | |
830 | void Skeleton3DEditor::_notification(int p_what) { |
831 | switch (p_what) { |
832 | case NOTIFICATION_ENTER_TREE: { |
833 | update_joint_tree(); |
834 | |
835 | joint_tree->connect("item_selected" , callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); |
836 | joint_tree->connect("item_mouse_selected" , callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); |
837 | #ifdef TOOLS_ENABLED |
838 | skeleton->connect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); |
839 | skeleton->connect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_update_properties)); |
840 | skeleton->connect("bone_enabled_changed" , callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); |
841 | skeleton->connect("show_rest_only_changed" , callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); |
842 | #endif |
843 | |
844 | get_tree()->connect("node_removed" , callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONE_SHOT); |
845 | } break; |
846 | case NOTIFICATION_READY: { |
847 | // Will trigger NOTIFICATION_THEME_CHANGED, but won't cause any loops if called here. |
848 | add_theme_constant_override("separation" , 0); |
849 | } break; |
850 | case NOTIFICATION_THEME_CHANGED: { |
851 | skeleton_options->set_icon(get_editor_theme_icon(SNAME("Skeleton3D" ))); |
852 | edit_mode_button->set_icon(get_editor_theme_icon(SNAME("ToolBoneSelect" ))); |
853 | key_loc_button->set_icon(get_editor_theme_icon(SNAME("KeyPosition" ))); |
854 | key_rot_button->set_icon(get_editor_theme_icon(SNAME("KeyRotation" ))); |
855 | key_scale_button->set_icon(get_editor_theme_icon(SNAME("KeyScale" ))); |
856 | key_insert_button->set_icon(get_editor_theme_icon(SNAME("Key" ))); |
857 | key_insert_all_button->set_icon(get_editor_theme_icon(SNAME("NewKey" ))); |
858 | bones_section->set_bg_color(get_theme_color(SNAME("prop_subsection" ), EditorStringName(Editor))); |
859 | |
860 | update_joint_tree(); |
861 | } break; |
862 | case NOTIFICATION_PREDELETE: { |
863 | if (skeleton) { |
864 | select_bone(-1); // Requires that the joint_tree has not been deleted. |
865 | #ifdef TOOLS_ENABLED |
866 | skeleton->disconnect("show_rest_only_changed" , callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); |
867 | skeleton->disconnect("bone_enabled_changed" , callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); |
868 | skeleton->disconnect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); |
869 | skeleton->disconnect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_update_properties)); |
870 | skeleton->set_transform_gizmo_visible(true); |
871 | #endif |
872 | handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); |
873 | } |
874 | edit_mode_toggled(false); |
875 | } break; |
876 | } |
877 | } |
878 | |
879 | void Skeleton3DEditor::_node_removed(Node *p_node) { |
880 | if (skeleton && p_node == skeleton) { |
881 | skeleton = nullptr; |
882 | skeleton_options->hide(); |
883 | } |
884 | |
885 | _update_properties(); |
886 | } |
887 | |
888 | void Skeleton3DEditor::_bind_methods() { |
889 | ClassDB::bind_method(D_METHOD("_node_removed" ), &Skeleton3DEditor::_node_removed); |
890 | ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed" ), &Skeleton3DEditor::_joint_tree_selection_changed); |
891 | ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select" ), &Skeleton3DEditor::_joint_tree_rmb_select); |
892 | ClassDB::bind_method(D_METHOD("_update_properties" ), &Skeleton3DEditor::_update_properties); |
893 | ClassDB::bind_method(D_METHOD("_on_click_skeleton_option" ), &Skeleton3DEditor::_on_click_skeleton_option); |
894 | |
895 | ClassDB::bind_method(D_METHOD("move_skeleton_bone" ), &Skeleton3DEditor::move_skeleton_bone); |
896 | |
897 | ClassDB::bind_method(D_METHOD("_draw_gizmo" ), &Skeleton3DEditor::_draw_gizmo); |
898 | } |
899 | |
900 | void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { |
901 | edit_mode = pressed; |
902 | _update_gizmo_visible(); |
903 | } |
904 | |
905 | Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *p_skeleton) : |
906 | editor_plugin(e_plugin), |
907 | skeleton(p_skeleton) { |
908 | singleton = this; |
909 | |
910 | // Handle. |
911 | handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); |
912 | handle_shader = Ref<Shader>(memnew(Shader)); |
913 | handle_shader->set_code(R"( |
914 | // Skeleton 3D gizmo handle shader. |
915 | |
916 | shader_type spatial; |
917 | render_mode unshaded, shadows_disabled, depth_draw_always; |
918 | uniform sampler2D texture_albedo : source_color; |
919 | uniform float point_size : hint_range(0,128) = 32; |
920 | void vertex() { |
921 | if (!OUTPUT_IS_SRGB) { |
922 | COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); |
923 | } |
924 | VERTEX = VERTEX; |
925 | POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); |
926 | POSITION.z = mix(POSITION.z, 0, 0.999); |
927 | POINT_SIZE = point_size; |
928 | } |
929 | void fragment() { |
930 | vec4 albedo_tex = texture(texture_albedo,POINT_COORD); |
931 | vec3 col = albedo_tex.rgb + COLOR.rgb; |
932 | col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); |
933 | ALBEDO = col; |
934 | if (albedo_tex.a < 0.5) { discard; } |
935 | ALPHA = albedo_tex.a; |
936 | } |
937 | )" ); |
938 | handle_material->set_shader(handle_shader); |
939 | Ref<Texture2D> handle = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorBoneHandle" ), EditorStringName(EditorIcons)); |
940 | handle_material->set_shader_parameter("point_size" , handle->get_width()); |
941 | handle_material->set_shader_parameter("texture_albedo" , handle); |
942 | |
943 | handles_mesh_instance = memnew(MeshInstance3D); |
944 | handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); |
945 | handles_mesh.instantiate(); |
946 | handles_mesh_instance->set_mesh(handles_mesh); |
947 | |
948 | create_editors(); |
949 | } |
950 | |
951 | void Skeleton3DEditor::update_bone_original() { |
952 | if (!skeleton) { |
953 | return; |
954 | } |
955 | if (skeleton->get_bone_count() == 0 || selected_bone == -1) { |
956 | return; |
957 | } |
958 | bone_original_position = skeleton->get_bone_pose_position(selected_bone); |
959 | bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); |
960 | bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); |
961 | } |
962 | |
963 | void Skeleton3DEditor::_hide_handles() { |
964 | handles_mesh_instance->hide(); |
965 | } |
966 | |
967 | void Skeleton3DEditor::_draw_gizmo() { |
968 | if (!skeleton) { |
969 | return; |
970 | } |
971 | |
972 | // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, |
973 | // the skeleton update will be done first and |
974 | // the drawing surface will be interrupted once and an error will occur. |
975 | skeleton->force_update_all_dirty_bones(); |
976 | |
977 | // Handles. |
978 | if (edit_mode) { |
979 | _draw_handles(); |
980 | } else { |
981 | _hide_handles(); |
982 | } |
983 | } |
984 | |
985 | void Skeleton3DEditor::_draw_handles() { |
986 | const int bone_count = skeleton->get_bone_count(); |
987 | |
988 | handles_mesh->clear_surfaces(); |
989 | |
990 | if (bone_count) { |
991 | handles_mesh_instance->show(); |
992 | |
993 | handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); |
994 | |
995 | for (int i = 0; i < bone_count; i++) { |
996 | Color c; |
997 | if (i == selected_bone) { |
998 | c = Color(1, 1, 0); |
999 | } else { |
1000 | c = Color(0.1, 0.25, 0.8); |
1001 | } |
1002 | Vector3 point = skeleton->get_bone_global_pose(i).origin; |
1003 | handles_mesh->surface_set_color(c); |
1004 | handles_mesh->surface_add_vertex(point); |
1005 | } |
1006 | handles_mesh->surface_end(); |
1007 | handles_mesh->surface_set_material(0, handle_material); |
1008 | } else { |
1009 | handles_mesh_instance->hide(); |
1010 | } |
1011 | } |
1012 | |
1013 | TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { |
1014 | if (!p_node) { |
1015 | return nullptr; |
1016 | } |
1017 | |
1018 | NodePath np = p_node->get_metadata(0); |
1019 | if (np == p_path) { |
1020 | return p_node; |
1021 | } |
1022 | |
1023 | TreeItem *children = p_node->get_first_child(); |
1024 | while (children) { |
1025 | TreeItem *n = _find(children, p_path); |
1026 | if (n) { |
1027 | return n; |
1028 | } |
1029 | children = children->get_next(); |
1030 | } |
1031 | |
1032 | return nullptr; |
1033 | } |
1034 | |
1035 | void Skeleton3DEditor::_subgizmo_selection_change() { |
1036 | if (!skeleton) { |
1037 | return; |
1038 | } |
1039 | |
1040 | // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. |
1041 | if (!edit_mode) { |
1042 | skeleton->clear_subgizmo_selection(); |
1043 | return; |
1044 | } |
1045 | |
1046 | int selected = -1; |
1047 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
1048 | if (se) { |
1049 | selected = se->get_selected_bone(); |
1050 | } |
1051 | |
1052 | if (selected >= 0) { |
1053 | Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); |
1054 | for (int i = 0; i < gizmos.size(); i++) { |
1055 | Ref<EditorNode3DGizmo> gizmo = gizmos[i]; |
1056 | if (!gizmo.is_valid()) { |
1057 | continue; |
1058 | } |
1059 | Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); |
1060 | if (!plugin.is_valid()) { |
1061 | continue; |
1062 | } |
1063 | skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); |
1064 | break; |
1065 | } |
1066 | } else { |
1067 | skeleton->clear_subgizmo_selection(); |
1068 | } |
1069 | } |
1070 | |
1071 | void Skeleton3DEditor::select_bone(int p_idx) { |
1072 | if (p_idx >= 0) { |
1073 | TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); |
1074 | if (ti) { |
1075 | // Make visible when it's collapsed. |
1076 | TreeItem *node = ti->get_parent(); |
1077 | while (node && node != joint_tree->get_root()) { |
1078 | node->set_collapsed(false); |
1079 | node = node->get_parent(); |
1080 | } |
1081 | ti->select(0); |
1082 | joint_tree->scroll_to_item(ti); |
1083 | } |
1084 | } else { |
1085 | selected_bone = -1; |
1086 | joint_tree->deselect_all(); |
1087 | _joint_tree_selection_changed(); |
1088 | } |
1089 | } |
1090 | |
1091 | Skeleton3DEditor::~Skeleton3DEditor() { |
1092 | singleton = nullptr; |
1093 | |
1094 | handles_mesh_instance->queue_free(); |
1095 | |
1096 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
1097 | |
1098 | if (animation_hb) { |
1099 | ne->remove_control_from_menu_panel(animation_hb); |
1100 | memdelete(animation_hb); |
1101 | } |
1102 | |
1103 | if (separator) { |
1104 | ne->remove_control_from_menu_panel(separator); |
1105 | memdelete(separator); |
1106 | } |
1107 | |
1108 | if (skeleton_options) { |
1109 | ne->remove_control_from_menu_panel(skeleton_options); |
1110 | memdelete(skeleton_options); |
1111 | } |
1112 | |
1113 | if (edit_mode_button) { |
1114 | ne->remove_control_from_menu_panel(edit_mode_button); |
1115 | memdelete(edit_mode_button); |
1116 | } |
1117 | } |
1118 | |
1119 | bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) { |
1120 | return Object::cast_to<Skeleton3D>(p_object) != nullptr; |
1121 | } |
1122 | |
1123 | void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { |
1124 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); |
1125 | ERR_FAIL_NULL(skeleton); |
1126 | |
1127 | skel_editor = memnew(Skeleton3DEditor(this, skeleton)); |
1128 | add_custom_control(skel_editor); |
1129 | } |
1130 | |
1131 | Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() { |
1132 | skeleton_plugin = memnew(EditorInspectorPluginSkeleton); |
1133 | |
1134 | EditorInspector::add_inspector_plugin(skeleton_plugin); |
1135 | |
1136 | Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); |
1137 | Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); |
1138 | } |
1139 | |
1140 | EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { |
1141 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
1142 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
1143 | if (se && se->is_edit_mode()) { |
1144 | const Ref<InputEventMouseButton> mb = p_event; |
1145 | if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { |
1146 | if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { |
1147 | if (!ne->is_gizmo_visible()) { |
1148 | return EditorPlugin::AFTER_GUI_INPUT_STOP; |
1149 | } |
1150 | } |
1151 | if (mb->is_pressed()) { |
1152 | se->update_bone_original(); |
1153 | } |
1154 | } |
1155 | return EditorPlugin::AFTER_GUI_INPUT_CUSTOM; |
1156 | } |
1157 | return EditorPlugin::AFTER_GUI_INPUT_PASS; |
1158 | } |
1159 | |
1160 | bool Skeleton3DEditorPlugin::handles(Object *p_object) const { |
1161 | return p_object->is_class("Skeleton3D" ); |
1162 | } |
1163 | |
1164 | void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { |
1165 | _update_gizmo_visible(); |
1166 | } |
1167 | |
1168 | void Skeleton3DEditor::_update_gizmo_visible() { |
1169 | _subgizmo_selection_change(); |
1170 | if (edit_mode) { |
1171 | if (selected_bone == -1) { |
1172 | #ifdef TOOLS_ENABLED |
1173 | skeleton->set_transform_gizmo_visible(false); |
1174 | #endif |
1175 | } else { |
1176 | #ifdef TOOLS_ENABLED |
1177 | if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { |
1178 | skeleton->set_transform_gizmo_visible(true); |
1179 | } else { |
1180 | skeleton->set_transform_gizmo_visible(false); |
1181 | } |
1182 | #endif |
1183 | } |
1184 | } else { |
1185 | #ifdef TOOLS_ENABLED |
1186 | skeleton->set_transform_gizmo_visible(true); |
1187 | #endif |
1188 | } |
1189 | _draw_gizmo(); |
1190 | } |
1191 | |
1192 | int Skeleton3DEditor::get_selected_bone() const { |
1193 | return selected_bone; |
1194 | } |
1195 | |
1196 | Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { |
1197 | unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); |
1198 | unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); |
1199 | unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); |
1200 | unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); |
1201 | unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); |
1202 | |
1203 | selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); |
1204 | selected_sh = Ref<Shader>(memnew(Shader)); |
1205 | selected_sh->set_code(R"( |
1206 | // Skeleton 3D gizmo bones shader. |
1207 | |
1208 | shader_type spatial; |
1209 | render_mode unshaded, shadows_disabled; |
1210 | void vertex() { |
1211 | if (!OUTPUT_IS_SRGB) { |
1212 | COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); |
1213 | } |
1214 | VERTEX = VERTEX; |
1215 | POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); |
1216 | POSITION.z = mix(POSITION.z, 0, 0.998); |
1217 | } |
1218 | void fragment() { |
1219 | ALBEDO = COLOR.rgb; |
1220 | ALPHA = COLOR.a; |
1221 | } |
1222 | )" ); |
1223 | selected_mat->set_shader(selected_sh); |
1224 | |
1225 | // Register properties in editor settings. |
1226 | EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton" , Color(1, 0.8, 0.4)); |
1227 | EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone" , Color(0.8, 0.3, 0.0)); |
1228 | EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length" , (float)0.1); |
1229 | EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape" , 1); |
1230 | EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape" , PROPERTY_HINT_ENUM, "Wire,Octahedron" )); |
1231 | } |
1232 | |
1233 | bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { |
1234 | return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; |
1235 | } |
1236 | |
1237 | String Skeleton3DGizmoPlugin::get_gizmo_name() const { |
1238 | return "Skeleton3D" ; |
1239 | } |
1240 | |
1241 | int Skeleton3DGizmoPlugin::get_priority() const { |
1242 | return -1; |
1243 | } |
1244 | |
1245 | int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { |
1246 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
1247 | ERR_FAIL_NULL_V(skeleton, -1); |
1248 | |
1249 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
1250 | |
1251 | if (!se || !se->is_edit_mode()) { |
1252 | return -1; |
1253 | } |
1254 | |
1255 | if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { |
1256 | return -1; |
1257 | } |
1258 | |
1259 | // Select bone. |
1260 | real_t grab_threshold = 4 * EDSCALE; |
1261 | Vector3 ray_from = p_camera->get_global_transform().origin; |
1262 | Transform3D gt = skeleton->get_global_transform(); |
1263 | int closest_idx = -1; |
1264 | real_t closest_dist = 1e10; |
1265 | const int bone_count = skeleton->get_bone_count(); |
1266 | for (int i = 0; i < bone_count; i++) { |
1267 | Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); |
1268 | Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); |
1269 | real_t dist_3d = ray_from.distance_to(joint_pos_3d); |
1270 | real_t dist_2d = p_point.distance_to(joint_pos_2d); |
1271 | if (dist_2d < grab_threshold && dist_3d < closest_dist) { |
1272 | closest_dist = dist_3d; |
1273 | closest_idx = i; |
1274 | } |
1275 | } |
1276 | |
1277 | if (closest_idx >= 0) { |
1278 | se->select_bone(closest_idx); |
1279 | return closest_idx; |
1280 | } |
1281 | |
1282 | se->select_bone(-1); |
1283 | return -1; |
1284 | } |
1285 | |
1286 | Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { |
1287 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
1288 | ERR_FAIL_NULL_V(skeleton, Transform3D()); |
1289 | |
1290 | return skeleton->get_bone_global_pose(p_id); |
1291 | } |
1292 | |
1293 | void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { |
1294 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
1295 | ERR_FAIL_NULL(skeleton); |
1296 | |
1297 | // Prepare for global to local. |
1298 | Transform3D original_to_local; |
1299 | int parent_idx = skeleton->get_bone_parent(p_id); |
1300 | if (parent_idx >= 0) { |
1301 | original_to_local = skeleton->get_bone_global_pose(parent_idx); |
1302 | } |
1303 | Basis to_local = original_to_local.get_basis().inverse(); |
1304 | |
1305 | // Prepare transform. |
1306 | Transform3D t; |
1307 | |
1308 | // Basis. |
1309 | t.basis = to_local * p_transform.get_basis(); |
1310 | |
1311 | // Origin. |
1312 | Vector3 orig = skeleton->get_bone_pose(p_id).origin; |
1313 | Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; |
1314 | t.origin = orig + to_local.xform(sub); |
1315 | |
1316 | // Apply transform. |
1317 | skeleton->set_bone_pose_position(p_id, t.origin); |
1318 | skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); |
1319 | skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); |
1320 | } |
1321 | |
1322 | void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { |
1323 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
1324 | ERR_FAIL_NULL(skeleton); |
1325 | |
1326 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
1327 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
1328 | |
1329 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
1330 | ur->create_action(TTR("Set Bone Transform" )); |
1331 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { |
1332 | for (int i = 0; i < p_ids.size(); i++) { |
1333 | ur->add_do_method(skeleton, "set_bone_pose_position" , p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); |
1334 | ur->add_undo_method(skeleton, "set_bone_pose_position" , p_ids[i], se->get_bone_original_position()); |
1335 | } |
1336 | } |
1337 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { |
1338 | for (int i = 0; i < p_ids.size(); i++) { |
1339 | ur->add_do_method(skeleton, "set_bone_pose_rotation" , p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); |
1340 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , p_ids[i], se->get_bone_original_rotation()); |
1341 | } |
1342 | } |
1343 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { |
1344 | for (int i = 0; i < p_ids.size(); i++) { |
1345 | // If the axis is swapped by scaling, the rotation can be changed. |
1346 | ur->add_do_method(skeleton, "set_bone_pose_rotation" , p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); |
1347 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , p_ids[i], se->get_bone_original_rotation()); |
1348 | ur->add_do_method(skeleton, "set_bone_pose_scale" , p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); |
1349 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , p_ids[i], se->get_bone_original_scale()); |
1350 | } |
1351 | } |
1352 | ur->commit_action(); |
1353 | } |
1354 | |
1355 | void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { |
1356 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
1357 | p_gizmo->clear(); |
1358 | |
1359 | if (!skeleton->get_bone_count()) { |
1360 | return; |
1361 | } |
1362 | |
1363 | int selected = -1; |
1364 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
1365 | if (se) { |
1366 | selected = se->get_selected_bone(); |
1367 | } |
1368 | |
1369 | Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton" ); |
1370 | Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone" ); |
1371 | real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length" ); |
1372 | int bone_shape = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_shape" ); |
1373 | |
1374 | LocalVector<Color> axis_colors; |
1375 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color" ), EditorStringName(Editor))); |
1376 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color" ), EditorStringName(Editor))); |
1377 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color" ), EditorStringName(Editor))); |
1378 | |
1379 | Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); |
1380 | surface_tool->begin(Mesh::PRIMITIVE_LINES); |
1381 | |
1382 | if (p_gizmo->is_selected()) { |
1383 | surface_tool->set_material(selected_mat); |
1384 | } else { |
1385 | unselected_mat->set_albedo(bone_color); |
1386 | surface_tool->set_material(unselected_mat); |
1387 | } |
1388 | |
1389 | LocalVector<int> bones; |
1390 | LocalVector<float> weights; |
1391 | bones.resize(4); |
1392 | weights.resize(4); |
1393 | for (int i = 0; i < 4; i++) { |
1394 | bones[i] = 0; |
1395 | weights[i] = 0; |
1396 | } |
1397 | weights[0] = 1; |
1398 | |
1399 | int current_bone_index = 0; |
1400 | Vector<int> bones_to_process = skeleton->get_parentless_bones(); |
1401 | |
1402 | while (bones_to_process.size() > current_bone_index) { |
1403 | int current_bone_idx = bones_to_process[current_bone_index]; |
1404 | current_bone_index++; |
1405 | |
1406 | Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; |
1407 | |
1408 | Vector<int> child_bones_vector; |
1409 | child_bones_vector = skeleton->get_bone_children(current_bone_idx); |
1410 | int child_bones_size = child_bones_vector.size(); |
1411 | |
1412 | for (int i = 0; i < child_bones_size; i++) { |
1413 | // Something wrong. |
1414 | if (child_bones_vector[i] < 0) { |
1415 | continue; |
1416 | } |
1417 | |
1418 | int child_bone_idx = child_bones_vector[i]; |
1419 | |
1420 | Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; |
1421 | Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; |
1422 | Vector3 d = (v1 - v0).normalized(); |
1423 | real_t dist = v0.distance_to(v1); |
1424 | |
1425 | // Find closest axis. |
1426 | int closest = -1; |
1427 | real_t closest_d = 0.0; |
1428 | for (int j = 0; j < 3; j++) { |
1429 | real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); |
1430 | if (j == 0 || dp > closest_d) { |
1431 | closest = j; |
1432 | } |
1433 | } |
1434 | |
1435 | // Draw bone. |
1436 | switch (bone_shape) { |
1437 | case 0: { // Wire shape. |
1438 | surface_tool->set_color(current_bone_color); |
1439 | bones[0] = current_bone_idx; |
1440 | surface_tool->set_bones(bones); |
1441 | surface_tool->set_weights(weights); |
1442 | surface_tool->add_vertex(v0); |
1443 | bones[0] = child_bone_idx; |
1444 | surface_tool->set_bones(bones); |
1445 | surface_tool->set_weights(weights); |
1446 | surface_tool->add_vertex(v1); |
1447 | } break; |
1448 | |
1449 | case 1: { // Octahedron shape. |
1450 | Vector3 first; |
1451 | Vector3 points[6]; |
1452 | int point_idx = 0; |
1453 | for (int j = 0; j < 3; j++) { |
1454 | Vector3 axis; |
1455 | if (first == Vector3()) { |
1456 | axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); |
1457 | first = axis; |
1458 | } else { |
1459 | axis = d.cross(first).normalized(); |
1460 | } |
1461 | |
1462 | surface_tool->set_color(current_bone_color); |
1463 | for (int k = 0; k < 2; k++) { |
1464 | if (k == 1) { |
1465 | axis = -axis; |
1466 | } |
1467 | Vector3 point = v0 + d * dist * 0.2; |
1468 | point += axis * dist * 0.1; |
1469 | |
1470 | bones[0] = current_bone_idx; |
1471 | surface_tool->set_bones(bones); |
1472 | surface_tool->set_weights(weights); |
1473 | surface_tool->add_vertex(v0); |
1474 | surface_tool->set_bones(bones); |
1475 | surface_tool->set_weights(weights); |
1476 | surface_tool->add_vertex(point); |
1477 | |
1478 | surface_tool->set_bones(bones); |
1479 | surface_tool->set_weights(weights); |
1480 | surface_tool->add_vertex(point); |
1481 | bones[0] = child_bone_idx; |
1482 | surface_tool->set_bones(bones); |
1483 | surface_tool->set_weights(weights); |
1484 | surface_tool->add_vertex(v1); |
1485 | points[point_idx++] = point; |
1486 | } |
1487 | } |
1488 | surface_tool->set_color(current_bone_color); |
1489 | SWAP(points[1], points[2]); |
1490 | bones[0] = current_bone_idx; |
1491 | for (int j = 0; j < 6; j++) { |
1492 | surface_tool->set_bones(bones); |
1493 | surface_tool->set_weights(weights); |
1494 | surface_tool->add_vertex(points[j]); |
1495 | surface_tool->set_bones(bones); |
1496 | surface_tool->set_weights(weights); |
1497 | surface_tool->add_vertex(points[(j + 1) % 6]); |
1498 | } |
1499 | } break; |
1500 | } |
1501 | |
1502 | // Axis as root of the bone. |
1503 | for (int j = 0; j < 3; j++) { |
1504 | bones[0] = current_bone_idx; |
1505 | surface_tool->set_color(axis_colors[j]); |
1506 | surface_tool->set_bones(bones); |
1507 | surface_tool->set_weights(weights); |
1508 | surface_tool->add_vertex(v0); |
1509 | surface_tool->set_bones(bones); |
1510 | surface_tool->set_weights(weights); |
1511 | surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); |
1512 | |
1513 | if (j == closest) { |
1514 | continue; |
1515 | } |
1516 | } |
1517 | |
1518 | // Axis at the end of the bone children. |
1519 | if (i == child_bones_size - 1) { |
1520 | for (int j = 0; j < 3; j++) { |
1521 | bones[0] = child_bone_idx; |
1522 | surface_tool->set_color(axis_colors[j]); |
1523 | surface_tool->set_bones(bones); |
1524 | surface_tool->set_weights(weights); |
1525 | surface_tool->add_vertex(v1); |
1526 | surface_tool->set_bones(bones); |
1527 | surface_tool->set_weights(weights); |
1528 | surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); |
1529 | |
1530 | if (j == closest) { |
1531 | continue; |
1532 | } |
1533 | } |
1534 | } |
1535 | |
1536 | // Add the bone's children to the list of bones to be processed. |
1537 | bones_to_process.push_back(child_bones_vector[i]); |
1538 | } |
1539 | } |
1540 | |
1541 | Ref<ArrayMesh> m = surface_tool->commit(); |
1542 | p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); |
1543 | } |
1544 | |