diff --git a/game/addons/.gitignore b/game/addons/.gitignore index 08530628..230d2f1d 100644 --- a/game/addons/.gitignore +++ b/game/addons/.gitignore @@ -9,5 +9,7 @@ !gdpose/** !mesh_data_resource_editor !mesh_data_resource_editor/** +!bone_editor +!bone_editor/** !addon_versions diff --git a/game/addons/bone_editor/BoneDock.tscn b/game/addons/bone_editor/BoneDock.tscn new file mode 100644 index 00000000..31624d75 --- /dev/null +++ b/game/addons/bone_editor/BoneDock.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=2 format=2] + +[sub_resource type="GDScript" id=1] +script/source = "tool +extends CenterContainer + +var bone_editor + +func _on_Button_pressed( ): + if self.bone_editor == null: + return + + self.bone_editor.save_poses( ) + +func _on_Button2_pressed(): + if self.bone_editor == null: + return + + self.bone_editor.init_poses( ) + +func _on_Button3_pressed(): + if self.bone_editor == null: + return + + self.bone_editor.load_poses( ) + +" + +[node name="CenterContainer" type="CenterContainer"] +margin_right = 40.0 +margin_bottom = 40.0 +script = SubResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +margin_left = 44.0 +margin_top = 10.0 +margin_right = 324.0 +margin_bottom = 30.0 + +[node name="Button" type="Button" parent="HBoxContainer"] +margin_right = 84.0 +margin_bottom = 20.0 +text = "Insert Keys" +flat = true + +[node name="Button3" type="Button" parent="HBoxContainer"] +margin_left = 88.0 +margin_right = 198.0 +margin_bottom = 20.0 +text = "Load from Keys" +flat = true + +[node name="Button2" type="Button" parent="HBoxContainer"] +margin_left = 202.0 +margin_right = 280.0 +margin_bottom = 20.0 +text = "Init Bones" +flat = true +[connection signal="pressed" from="HBoxContainer/Button" to="." method="_on_Button_pressed"] +[connection signal="pressed" from="HBoxContainer/Button3" to="." method="_on_Button3_pressed"] +[connection signal="pressed" from="HBoxContainer/Button2" to="." method="_on_Button2_pressed"] diff --git a/game/addons/bone_editor/BoneEditor.gd b/game/addons/bone_editor/BoneEditor.gd new file mode 100644 index 00000000..25c1a30a --- /dev/null +++ b/game/addons/bone_editor/BoneEditor.gd @@ -0,0 +1,168 @@ +tool +extends Spatial + +export(NodePath) var control_skeleton:NodePath setget set_control_skeleton +export(NodePath) var edit_animation_player:NodePath setget set_edit_animation_player +export(bool) var enabled:bool = true +var skeleton:Skeleton = null +var animation_player:AnimationPlayer = null +var first_call:bool = true +var bone_handle_nodes:Array = [] + +func _ready( ): + pass + +func set_control_skeleton( path:NodePath ): + control_skeleton = path + + if self.first_call: + return + + var node:Node = self.get_node( control_skeleton ) + if node is Skeleton: + self.skeleton = node + else: + self.skeleton = null + push_error( str(path) + " does not Skeleton!" ) + + self._generate_bone_handles( ) + +func set_edit_animation_player( path:NodePath ): + edit_animation_player = path + + if self.first_call: + return + + var node:Node = self.get_node( edit_animation_player ) + if node is AnimationPlayer: + self.animation_player = node + else: + self.animation_player = null + push_error( str(path) + " does not Animation Player!" ) + +func _generate_bone_handles( ): + for child in self.get_children( ): + self.remove_child( child ) + + if self.skeleton == null: + return + + self.bone_handle_nodes = [] + var bone_handle: = preload( "BoneHandle.tscn" ) + for bone_id in range( self.skeleton.get_bone_count( ) ): + var bone_name:String = self.skeleton.get_bone_name( bone_id ) + var bone_handle_node:BoneHandle = bone_handle.instance( ) + + bone_handle_node.bone_editor = self + bone_handle_node.name = bone_name + bone_handle_node.skeleton = self.skeleton + bone_handle_node.bone_id = bone_id + bone_handle_node.bone_name = bone_name + + var parent_bone_id:int = self.skeleton.get_bone_parent( bone_id ) + if parent_bone_id == -1: + self.add_child( bone_handle_node ) + else: + self.bone_handle_nodes[parent_bone_id].add_child( bone_handle_node ) + + if Engine.editor_hint == true: + var tree:SceneTree = self.get_tree( ) + if tree != null: + if tree.edited_scene_root != null: + bone_handle_node.set_owner( tree.edited_scene_root ) + + self.bone_handle_nodes.append( bone_handle_node ) + +func _process( delta:float ): + if self.first_call: + self.first_call = false + self.set_control_skeleton( self.control_skeleton ) + self.set_edit_animation_player( self.edit_animation_player ) + + for handle_bone in self.bone_handle_nodes: + handle_bone.enabled = self.enabled + +func init_poses( ): + for handle_bone in self.bone_handle_nodes: + handle_bone.init_pose( ) + +func load_poses( ): + if self.animation_player == null: + push_error( "Animation player is null." ) + return + + var assigned_animation:String = self.animation_player.assigned_animation + var animation:Animation = self.animation_player.get_animation( assigned_animation ) + + var time:float = self.animation_player.current_animation_position + + for track_idx in animation.get_track_count( ): + var target_bone:BoneHandle = null + for handle_bone in self.bone_handle_nodes: + var path:NodePath = NodePath( "%s:%s" % [ self.get_node( self.animation_player.root_node ).get_path_to( self.skeleton ), handle_bone.name ] ) + if animation.track_get_path( track_idx ) == path: + target_bone = handle_bone + break + if target_bone == null: + continue + + var bone:Array = animation.transform_track_interpolate( track_idx, time ) + target_bone.set_pose( Basis( bone[1] ), bone[0] ) + +func save_poses( ): + if self.animation_player == null: + push_error( "Animation player is null." ) + return + + var assigned_animation:String = self.animation_player.assigned_animation + var animation:Animation = self.animation_player.get_animation( assigned_animation ) + + if animation == null: + push_error( "animation does not selected on Animation player." ) + return + + # 足りないボーンがあれば追加する + self._add_bone_tracks( animation ) + # ポーズを保存 + self._save_poses_to_animation( animation ) + +func _add_bone_tracks( animation:Animation ): + var founded_bone_tracks:Array = [] + + for track_idx in animation.get_track_count( ): + founded_bone_tracks.append( animation.track_get_path( track_idx ) ) + + for handle_bone in self.bone_handle_nodes: + var path:NodePath = NodePath( "%s:%s" % [ self.get_node( self.animation_player.root_node ).get_path_to( self.skeleton ), handle_bone.name ] ) + if founded_bone_tracks.find( path ) == -1: + var new_track_idx:int = animation.add_track( Animation.TYPE_TRANSFORM ) + animation.track_set_path( new_track_idx, path ) + print( "added new track for ", path ) + +func _save_poses_to_animation( animation:Animation ): + var time:float = self.animation_player.current_animation_position + + for track_idx in animation.get_track_count( ): + var target_bone:BoneHandle = null + for handle_bone in self.bone_handle_nodes: + var path:NodePath = NodePath( "%s:%s" % [ self.get_node( self.animation_player.root_node ).get_path_to( self.skeleton ), handle_bone.name ] ) + if animation.track_get_path( track_idx ) == path: + target_bone = handle_bone + break + if target_bone == null: + continue + + # すでに同じ位置にモノが存在するなら削除する + for key_idx in animation.track_get_key_count( track_idx ): + if animation.track_get_key_time( track_idx, key_idx ) == time: + animation.track_remove_key( track_idx, key_idx ) + break + + animation.transform_track_insert_key( + track_idx, + time, + target_bone.pose.origin, + target_bone.pose.basis.get_rotation_quat( ), + target_bone.pose.basis.get_scale( ) + ) + print( "* added new key for ", target_bone.name ) diff --git a/game/addons/bone_editor/BoneHandle.gd b/game/addons/bone_editor/BoneHandle.gd new file mode 100644 index 00000000..7497ac70 --- /dev/null +++ b/game/addons/bone_editor/BoneHandle.gd @@ -0,0 +1,48 @@ +tool +extends Spatial + +class_name BoneHandle + +var bone_editor +var skeleton:Skeleton = null +var bone_id:int = -1 +var bone_name:String = "" +var original_rest:Transform +var original_parent_rest:Transform +var original_global_rest_origin:Vector3 +var enabled:bool = true + +var pose:Transform + +func _ready( ): + if self.skeleton == null or self.bone_id == -1: + return + + self.original_rest = self.skeleton.get_bone_rest( self.bone_id ) + var parent_bone_id:int = self.skeleton.get_bone_parent( self.bone_id ) + if parent_bone_id != -1: + self.original_parent_rest = self.skeleton.get_bone_global_pose( parent_bone_id ) + + self.transform.basis = Basis( ) + self.transform.origin = self.original_parent_rest.basis.xform( self.original_rest.origin ) + self.original_global_rest_origin = self.transform.origin + +func init_pose( ): + self.transform.basis = Basis( ) + self.transform.origin = self.original_parent_rest.basis.xform( self.original_rest.origin ) + +func set_pose( basis:Basis, origin:Vector3 ): + self.transform.basis = basis + self.transform.origin = self.original_global_rest_origin + self.original_parent_rest.basis.xform( origin ) + # printt( self.bone_name, basis, origin ) + +func _process( delta:float ): + if self.skeleton == null or self.bone_id == -1: + return + + if self.enabled: + var t:Transform = Transform( ) + t.basis = self.transform.basis + t.origin = self.original_parent_rest.basis.xform_inv( self.transform.origin - self.original_global_rest_origin ) + self.skeleton.set_bone_pose( self.bone_id, t ) + self.pose = t diff --git a/game/addons/bone_editor/BoneHandle.tscn b/game/addons/bone_editor/BoneHandle.tscn new file mode 100644 index 00000000..f202b5ba --- /dev/null +++ b/game/addons/bone_editor/BoneHandle.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/bone_editor/BoneHandle.gd" type="Script" id=1] + +[node name="BoneHandle" type="Spatial"] +script = ExtResource( 1 ) diff --git a/game/addons/bone_editor/LICENSE.txt b/game/addons/bone_editor/LICENSE.txt new file mode 100644 index 00000000..5c3b0b92 --- /dev/null +++ b/game/addons/bone_editor/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2020 Yui Kinomoto + +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. diff --git a/game/addons/bone_editor/icon.png b/game/addons/bone_editor/icon.png new file mode 100644 index 00000000..1122f72c Binary files /dev/null and b/game/addons/bone_editor/icon.png differ diff --git a/game/addons/bone_editor/icon.png.import b/game/addons/bone_editor/icon.png.import new file mode 100644 index 00000000..a507ff17 --- /dev/null +++ b/game/addons/bone_editor/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-98dd89b353351f2740f1a21c6c925c32.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/bone_editor/icon.png" +dest_files=[ "res://.import/icon.png-98dd89b353351f2740f1a21c6c925c32.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=1 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=false +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/game/addons/bone_editor/plugin.cfg b/game/addons/bone_editor/plugin.cfg new file mode 100644 index 00000000..3834f862 --- /dev/null +++ b/game/addons/bone_editor/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="bone-editor" +description="A bone editor on the editor for Godot Engine 3.2 later" +author="arlez80" +version="0.0.1" +script="plugin.gd" diff --git a/game/addons/bone_editor/plugin.gd b/game/addons/bone_editor/plugin.gd new file mode 100644 index 00000000..51a29952 --- /dev/null +++ b/game/addons/bone_editor/plugin.gd @@ -0,0 +1,41 @@ +tool +extends EditorPlugin + +const BoneDock = preload( "BoneDock.tscn" ) + +var bone_dock_instance +var bone_editor + +func _enter_tree( ): + self.bone_dock_instance = BoneDock.instance( ) + self.add_control_to_container( CONTAINER_SPATIAL_EDITOR_MENU, self.bone_dock_instance ) + self.add_custom_type( "BoneEditor", "Spatial", preload("BoneEditor.gd"), preload("icon.png") ) + +func _exit_tree( ): + self.remove_custom_type( "BoneEditor" ) + self.remove_control_from_container( CONTAINER_SPATIAL_EDITOR_MENU, self.bone_dock_instance ) + self.bone_dock_instance.queue_free( ) + +func handles( obj ): + if obj is preload("BoneEditor.gd"): + self.bone_editor = obj + if self.bone_dock_instance != null: + self.bone_dock_instance.bone_editor = obj + self.bone_dock_instance.visible = true + return true + elif obj is preload("BoneHandle.gd"): + self.bone_editor = obj.bone_editor + if self.bone_dock_instance != null: + self.bone_dock_instance.bone_editor = obj.bone_editor + self.bone_dock_instance.visible = true + return true + + self.bone_editor = null + if self.bone_dock_instance != null: + self.bone_dock_instance.bone_editor = null + self.bone_dock_instance.visible = false + + return false + +func get_plugin_name( ): + return "Bone Editor" diff --git a/game/project.godot b/game/project.godot index 87981842..198b1fbc 100644 --- a/game/project.godot +++ b/game/project.godot @@ -14,6 +14,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://scripts/auras/aura_script.gd" }, { +"base": "Spatial", +"class": "BoneHandle", +"language": "GDScript", +"path": "res://addons/bone_editor/BoneHandle.gd" +}, { "base": "Reference", "class": "BrushPrefabs", "language": "GDScript", @@ -221,6 +226,7 @@ _global_script_classes=[ { } ] _global_script_class_icons={ "AuraGD": "", +"BoneHandle": "", "BrushPrefabs": "", "CharacterSkeketonAttachPoint": "", "DisplayPlayerGD": "", @@ -305,7 +311,7 @@ window/size/ui_scale_touch=1.0 [editor_plugins] -enabled=PoolStringArray( "res://addons/Godoxel/plugin.cfg", "res://addons/mesh_data_resource_editor/plugin.cfg" ) +enabled=PoolStringArray( "res://addons/Godoxel/plugin.cfg", "res://addons/bone_editor/plugin.cfg", "res://addons/mesh_data_resource_editor/plugin.cfg" ) [ess]