diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5969b61..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,23 +0,0 @@ -## Contributing to TODO Manager -Firstly, thank you for being interested in contributing to the Godot TODO Manager plugin! -TODO Manager has benefitted greatly from enthusiastic users who have suggested new features, noticed bugs, and contributed code to the plugin. - -### Code Style Guide -For the sake of clarity, TODO Manager takes advantage of GDScripts optional static typing in most circumstances. -In particular, when declaring variables use colons to infer the type where possible: - -`todo := "#TODO"` - -If the type is not obvious then explicit typing is desirable: - -`items : PoolStringArray = todo.split()` - -Typed arguments and return values for functions are required: -``` -func example(name: String, amount: int) -> Array: - # code - return array_of_names -``` - -For more info on static typing in Godot please refer to the documentation. -https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/static_typing.html diff --git a/README.md b/README.md deleted file mode 100644 index b723248..0000000 --- a/README.md +++ /dev/null @@ -1,60 +0,0 @@ - -### Localised READMEs - - [简体中文](READMECN.md) (Simplified Chinese) - - -# TODO Manager - -![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png) - -## Simple and flexible - -- Supports GDScript, C# and GDNative -- Seamlessly integrated into the Godot dock -- Lenient syntax. Write TODOs that suit your style -- Quickly jump to lines and launch external editors - -## Customizable - -![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png) - -- Add your own RegEx patterns -- Set colours to your liking - -## Installation - -### Method 1 (Godot Asset Library) - -The most simple way to get started using TODO Manager is to use Godot's inbuilt Asset Library to install the plugin into your project. - -#### Step 1 - -Find TODO Manager in the Godot Asset Library. -![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png) - -#### Step 2 - -Install the package. You may want to untick the /doc folder at this point as it is not necessary for the functions of the plugin. -![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png) - -#### Step 4 - -Enable the plugin in the project settings. -![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) - -### Method 2 (GitHub) - -#### Step 1 - -Click Download ZIP from the 'Code' dropdown. -![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png) - -#### Step 2 - -- Unzip the file and add it into your project folder. Make sure 'addons' is a subdirectory of res:// -- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings. - -#### Step 3 - -Enable the plugin in the project settings. -![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) diff --git a/READMECN.md b/READMECN.md deleted file mode 100644 index 7a248c8..0000000 --- a/READMECN.md +++ /dev/null @@ -1,56 +0,0 @@ -# TODO Manager - - ![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png) - -## 简单而灵活 - -- 支持 GDScript,C# 和 GDNative。 -- 无缝集成到 Godot dock 栏。 -- 宽松的语法,用适合你自己的风格写TODOs。 -- 快速跳转到某一行并启用外部编辑器。 - -## 可定制 - -![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png) - -- 添加你自己的正则表达式。 -- 设置你喜欢的颜色。 - -## 安装 - -### 方法一 (Godot Asset Library) - -最简单的使用 TODO Manager 的方法,使用 Godot 内置的资源商店(Asset Library)来安装这个插件到你的项目。 - -#### 第一步 - -在资源商店搜索 TODO Manager。 -![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png) - -#### 第二步 - -安装下载的插件,你可能需要取消勾选 /doc 文件夹,因为插件的功能不需要。 -![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png) - -#### 第三步 - -在项目设置里启用插件。 -![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) - -### 方法二 (GitHub) - -#### 第一步 - -点击 Download ZIP。 -![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png) - -#### 第二步 - -- 解压文件并且放到你的项目文件夹。确保 “addons” 是 res:// 的子文件夹。 -- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings. -- 不要更改 “addons” 或 “Todo_Manager” 文件夹的名称,因为这会打破预设的保存和加载。 - -#### 第三步 - -在项目设置里启用这个插件。 -![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) diff --git a/addons/cyclops_level_builder/LICENSE.md b/addons/cyclops_level_builder/LICENSE.md new file mode 100644 index 0000000..17f9cf9 --- /dev/null +++ b/addons/cyclops_level_builder/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2023 Mark McKay + +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. \ No newline at end of file diff --git a/addons/cyclops_level_builder/actions/action_convert_to_mesh.gd b/addons/cyclops_level_builder/actions/action_convert_to_mesh.gd new file mode 100644 index 0000000..89ceabe --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_convert_to_mesh.gd @@ -0,0 +1,138 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionConvertToMesh +extends CyclopsAction + + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Convert To Godot Mesh") + +func _execute(): + var root:Node = plugin.get_editor_interface().get_edited_scene_root() + + #var ed_sel:EditorSelection = plugin.get_editor_interface().get_selection() + #var sel_nodes:Array[Node] = ed_sel.get_selected_nodes() + # + #if sel_nodes.is_empty(): + #return + + #var branch_to_clone:Node = sel_nodes[0] + #var root = branch_to_clone.get_parent() + + var converted_branch:Node3D = clone_branch(root) + converted_branch.name = GeneralUtil.find_unique_name(root, "converted_blocks") + root.add_child(converted_branch) + + set_owner_recursive(converted_branch, plugin.get_editor_interface().get_edited_scene_root()) + + pass + +func set_owner_recursive(node:Node3D, new_owner): + node.owner = new_owner + for child in node.get_children(): + if child is Node3D: + set_owner_recursive(child, new_owner) + +func clone_branch(node:Node3D)->Node3D: + if node is CyclopsBlock: + var block:CyclopsBlock = node + var name_root:String = block.name + + var new_node:Node3D = Node3D.new() + new_node.name = name_root + new_node.transform = node.transform + new_node.set_meta("_edit_group_", true) +# new_node.owner = plugin.get_editor_interface().get_edited_scene_root() + + var new_mesh_node:MeshInstance3D = block.mesh_instance.duplicate() + new_mesh_node.name = name_root + "_mesh" +# new_mesh_node.owner = plugin.get_editor_interface().get_edited_scene_root() + new_node.add_child(new_mesh_node) + + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_convex_block_data(block.block_data) + + + var collision_body:PhysicsBody3D + + match block.collision_type: + Collision.Type.STATIC: + collision_body = StaticBody3D.new() + Collision.Type.KINEMATIC: + collision_body = CharacterBody3D.new() + Collision.Type.RIGID: + collision_body = RigidBody3D.new() + + if collision_body: + +# collision_body.owner = plugin.get_editor_interface().get_edited_scene_root() + collision_body.name = name_root + "_col" + collision_body.collision_layer = block.collision_layer + collision_body.collision_mask = block.collision_mask + new_node.add_child(collision_body) + + var collision_shape:CollisionShape3D = CollisionShape3D.new() +# collision_shape.owner = plugin.get_editor_interface().get_edited_scene_root() + collision_body.add_child(collision_shape) + collision_shape.name = name_root + "_col_shp" + + var shape:ConvexPolygonShape3D = ConvexPolygonShape3D.new() + shape.points = vol.get_points() + collision_shape.shape = shape + + #var occluder:OccluderInstance3D = OccluderInstance3D.new() + #occluder.name = name_root + "_occ" +## occluder.owner = plugin.get_editor_interface().get_edited_scene_root() + #new_node.add_child(occluder) + # + #var occluder_object:ArrayOccluder3D = ArrayOccluder3D.new() + #occluder.name = name_root + "_occ" + #occluder_object.vertices = vol.get_points() + #occluder_object.indices = vol.get_trimesh_indices() + #occluder.occluder = occluder_object + + return new_node + + else: + var new_node:Node3D = Node3D.new() +# new_node.owner = plugin.get_editor_interface().get_edited_scene_root() + new_node.transform = node.transform + new_node.name = node.name + for child in node.get_children(): + if branch_is_valid(child): + new_node.add_child(clone_branch(child)) + return new_node + +func branch_is_valid(node:Node)->bool: + if node is CyclopsBlock: + return true + + for child in node.get_children(): + if child is Node3D and branch_is_valid(child): + return true + + return false diff --git a/addons/cyclops_level_builder/actions/action_delete_selected_blocks.gd b/addons/cyclops_level_builder/actions/action_delete_selected_blocks.gd new file mode 100644 index 0000000..36270bb --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_delete_selected_blocks.gd @@ -0,0 +1,46 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionDeleteSelectedBlocks +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Delete Selected Blocks") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var cmd:CommandDeleteBlocks = CommandDeleteBlocks.new() + cmd.builder = plugin + + for block in blocks: + cmd.block_paths.append(block.get_path()) + + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) + diff --git a/addons/cyclops_level_builder/actions/action_duplicate_selected_blocks.gd b/addons/cyclops_level_builder/actions/action_duplicate_selected_blocks.gd new file mode 100644 index 0000000..2a11599 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_duplicate_selected_blocks.gd @@ -0,0 +1,45 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionDuplicateSelectedBlocks +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Duplicate Selected Blocks") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var cmd:CommandDuplicateBlocks = CommandDuplicateBlocks.new() + cmd.builder = plugin + + for block in blocks: + cmd.blocks_to_duplicate.append(block.get_path()) + + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_intersect_block.gd b/addons/cyclops_level_builder/actions/action_intersect_block.gd new file mode 100644 index 0000000..22f081c --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_intersect_block.gd @@ -0,0 +1,57 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionIntersectBlock +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Intersect Blocks") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.size() < 2: + plugin.log("Not enough objects selected") + return + + var active:CyclopsBlock = plugin.get_active_block() + if !active: + plugin.log("No active object selected") + return + + var cmd:CommandIntersectBlock = CommandIntersectBlock.new() + cmd.builder = plugin + + for block in blocks: + if plugin.is_active_block(block): + cmd.main_block_path = block.get_path() + else: + cmd.block_paths.append(block.get_path()) + + if cmd.main_block_path.is_empty(): + return + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_merge_selected_blocks.gd b/addons/cyclops_level_builder/actions/action_merge_selected_blocks.gd new file mode 100644 index 0000000..01a7354 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_merge_selected_blocks.gd @@ -0,0 +1,45 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionMergeSelectedBlocks +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Merge Selected Blocks") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var cmd:CommandMergeBlocks = CommandMergeBlocks.new() + cmd.builder = plugin + + for block in blocks: + cmd.block_paths.append(block.get_path()) + + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_merge_vertices_center.gd b/addons/cyclops_level_builder/actions/action_merge_vertices_center.gd new file mode 100644 index 0000000..9b75fac --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_merge_vertices_center.gd @@ -0,0 +1,56 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionMergeVerticesCenter +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Merge Vertices Center") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var cmd:CommandMergeVertices = CommandMergeVertices.new() + cmd.builder = plugin + + for block in blocks: + var sel_vec:DataVector = block.mesh_vector_data.get_vertex_data(MeshVectorData.V_SELECTED) + + if sel_vec.size() < 2: + continue + + var indices:Array[int] + #print("sel vert bytes ", block.block_data.vertex_selected) + for idx in sel_vec.size(): + if sel_vec.get_value(idx): + indices.append(idx) + cmd.add_vertices(block.get_path(), indices) + + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_mirror_selection_x.gd b/addons/cyclops_level_builder/actions/action_mirror_selection_x.gd new file mode 100644 index 0000000..5d94e1a --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_mirror_selection_x.gd @@ -0,0 +1,30 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionMirrorSelectionX2 +extends ActionScaleSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Mirror Selection X") + scale = Vector3(-1, 1, 1) diff --git a/addons/cyclops_level_builder/actions/action_mirror_selection_y.gd b/addons/cyclops_level_builder/actions/action_mirror_selection_y.gd new file mode 100644 index 0000000..aa34414 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_mirror_selection_y.gd @@ -0,0 +1,30 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionMirrorSelectionY2 +extends ActionScaleSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Mirror Selection Y") + scale = Vector3(1, -1, 1) diff --git a/addons/cyclops_level_builder/actions/action_mirror_selection_z.gd b/addons/cyclops_level_builder/actions/action_mirror_selection_z.gd new file mode 100644 index 0000000..2f919e6 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_mirror_selection_z.gd @@ -0,0 +1,30 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionMirrorSelectionZ +extends ActionScaleSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Mirror Selection Z") + scale = Vector3(1, 1, -1) diff --git a/addons/cyclops_level_builder/actions/action_rotate_selection.gd b/addons/cyclops_level_builder/actions/action_rotate_selection.gd new file mode 100644 index 0000000..a99a5ad --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_selection.gd @@ -0,0 +1,55 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateSelection +extends CyclopsAction + +var rotation_axis:Vector3 = Vector3.ONE +var rotation_angle:float + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, name, accellerator) + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var pivot:Vector3 = calc_pivot_of_blocks(blocks) + + var cmd:CommandTransformVertices = CommandTransformVertices.new() + cmd.builder = plugin + + for block in blocks: + cmd.add_block(block.get_path()) + + var xform:Transform3D = Transform3D.IDENTITY + xform = xform.translated_local(pivot) + xform = xform.rotated_local(rotation_axis, rotation_angle) + xform = xform.translated_local(-pivot) + cmd.transform = xform + #print("cform %s" % xform) + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_rotate_x_180.gd b/addons/cyclops_level_builder/actions/action_rotate_x_180.gd new file mode 100644 index 0000000..5f48b60 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_x_180.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateX180 +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 180 X") + rotation_axis = Vector3(1, 0, 0) + rotation_angle = deg_to_rad(180) diff --git a/addons/cyclops_level_builder/actions/action_rotate_x_90_ccw.gd b/addons/cyclops_level_builder/actions/action_rotate_x_90_ccw.gd new file mode 100644 index 0000000..9bec89a --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_x_90_ccw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateX90Ccw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Ccw X") + rotation_axis = Vector3(1, 0, 0) + rotation_angle = deg_to_rad(90) diff --git a/addons/cyclops_level_builder/actions/action_rotate_x_90_cw.gd b/addons/cyclops_level_builder/actions/action_rotate_x_90_cw.gd new file mode 100644 index 0000000..7fe123a --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_x_90_cw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateX90Cw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Cw X") + rotation_axis = Vector3(1, 0, 0) + rotation_angle = deg_to_rad(-90) diff --git a/addons/cyclops_level_builder/actions/action_rotate_y_180.gd b/addons/cyclops_level_builder/actions/action_rotate_y_180.gd new file mode 100644 index 0000000..64f6fb9 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_y_180.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateY180 +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 180 Y") + rotation_axis = Vector3(0, 1, 0) + rotation_angle = deg_to_rad(180) diff --git a/addons/cyclops_level_builder/actions/action_rotate_y_90_ccw.gd b/addons/cyclops_level_builder/actions/action_rotate_y_90_ccw.gd new file mode 100644 index 0000000..5d736a4 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_y_90_ccw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateY90Ccw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Ccw Y") + rotation_axis = Vector3(0, 1, 0) + rotation_angle = deg_to_rad(90) diff --git a/addons/cyclops_level_builder/actions/action_rotate_y_90_cw.gd b/addons/cyclops_level_builder/actions/action_rotate_y_90_cw.gd new file mode 100644 index 0000000..ea29d28 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_y_90_cw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateY90Cw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Cw Y") + rotation_axis = Vector3(0, 1, 0) + rotation_angle = deg_to_rad(-90) diff --git a/addons/cyclops_level_builder/actions/action_rotate_z_180.gd b/addons/cyclops_level_builder/actions/action_rotate_z_180.gd new file mode 100644 index 0000000..9884000 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_z_180.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateZ180 +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 180 Z") + rotation_axis = Vector3(0, 0, 1) + rotation_angle = deg_to_rad(180) diff --git a/addons/cyclops_level_builder/actions/action_rotate_z_90_ccw.gd b/addons/cyclops_level_builder/actions/action_rotate_z_90_ccw.gd new file mode 100644 index 0000000..5e26b48 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_z_90_ccw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateZ90Ccw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Ccw Z") + rotation_axis = Vector3(0, 0, 1) + rotation_angle = deg_to_rad(90) diff --git a/addons/cyclops_level_builder/actions/action_rotate_z_90_cw.gd b/addons/cyclops_level_builder/actions/action_rotate_z_90_cw.gd new file mode 100644 index 0000000..f8f4576 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_rotate_z_90_cw.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionRotateZ90Cw +extends ActionRotateSelection + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Rotate 90 Cw Z") + rotation_axis = Vector3(0, 0, 1) + rotation_angle = deg_to_rad(-90) diff --git a/addons/cyclops_level_builder/actions/action_scale_selection.gd b/addons/cyclops_level_builder/actions/action_scale_selection.gd new file mode 100644 index 0000000..a3d7ea1 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_scale_selection.gd @@ -0,0 +1,54 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionScaleSelection +extends CyclopsAction + +var scale:Vector3 = Vector3.ONE + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, name, accellerator) + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var pivot:Vector3 = calc_pivot_of_blocks(blocks) + + var cmd:CommandTransformVertices = CommandTransformVertices.new() + cmd.builder = plugin + + for block in blocks: + cmd.add_block(block.get_path()) + + var xform:Transform3D = Transform3D.IDENTITY + xform = xform.translated_local(pivot) + xform = xform.scaled_local(scale) + xform = xform.translated_local(-pivot) + cmd.transform = xform + #print("cform %s" % xform) + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_snap_to_grid.gd b/addons/cyclops_level_builder/actions/action_snap_to_grid.gd new file mode 100644 index 0000000..4e7b46f --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_snap_to_grid.gd @@ -0,0 +1,57 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionSnapToGrid +extends CyclopsAction + +func _init(plugin:CyclopsLevelBuilder): + super._init(plugin, "Snap to grid") + + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.is_empty(): + return + + var pivot:Vector3 = calc_pivot_of_blocks(blocks) + + var cmd:CommandSnapToGrid = CommandSnapToGrid.new() + cmd.builder = plugin + + for block in blocks: + cmd.add_block(block.get_path()) + + + #cmd.grid_size = pow(2, plugin.get_global_scene().grid_size) + #var snap_to_grid_util:SnapToGridUtil = CyclopsAutoload.calc_snap_to_grid_util() + #print("snap_to_grid_util %s" % snap_to_grid_util) + #cmd.snap_to_grid_util = snap_to_grid_util + + + #print("cform %s" % xform) + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) + + diff --git a/addons/cyclops_level_builder/actions/action_subtract_block.gd b/addons/cyclops_level_builder/actions/action_subtract_block.gd new file mode 100644 index 0000000..2a93eb2 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_subtract_block.gd @@ -0,0 +1,57 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionSubtractBlock +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Subtract Block") + +func _execute(): + var blocks:Array[CyclopsBlock] = plugin.get_selected_blocks() + if blocks.size() < 2: + plugin.log("Not enough objects selected") + return + + var active:CyclopsBlock = plugin.get_active_block() + if !active: + plugin.log("No active object selected") + return + + var cmd:CommandSubtractBlock = CommandSubtractBlock.new() + cmd.builder = plugin + + for block in blocks: + if plugin.is_active_block(block): + cmd.block_to_subtract_path = block.get_path() + else: + cmd.block_paths.append(block.get_path()) + + if cmd.block_to_subtract_path.is_empty(): + return + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/actions/action_tool_duplicate.gd b/addons/cyclops_level_builder/actions/action_tool_duplicate.gd new file mode 100644 index 0000000..9ec1a52 --- /dev/null +++ b/addons/cyclops_level_builder/actions/action_tool_duplicate.gd @@ -0,0 +1,33 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionToolDuplicate +extends CyclopsAction + + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Duplicate Selected Blocks") + +func _execute(): + plugin.switch_to_tool(ToolDuplicate.new()) diff --git a/addons/cyclops_level_builder/actions/cyclops_action.gd b/addons/cyclops_level_builder/actions/cyclops_action.gd new file mode 100644 index 0000000..5b111e9 --- /dev/null +++ b/addons/cyclops_level_builder/actions/cyclops_action.gd @@ -0,0 +1,53 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CyclopsAction +extends RefCounted + +var plugin:CyclopsLevelBuilder + +var name:String = "" +var accellerator:Key = KEY_NONE + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + self.plugin = plugin + self.name= name + self.accellerator = accellerator + +func _execute(): + pass + +func calc_pivot_of_blocks(blocks:Array[CyclopsBlock])->Vector3: + var snap_to_grid_util:SnapToGridUtil = CyclopsAutoload.calc_snap_to_grid_util() + + var bounds:AABB = blocks[0].control_mesh.bounds + for idx in range(1, blocks.size()): + var block:CyclopsBlock = blocks[idx] + bounds = bounds.merge(block.control_mesh.bounds) + + var center:Vector3 = bounds.get_center() + center = snap_to_grid_util.snap_point(center) + + return center + diff --git a/addons/cyclops_level_builder/actions/io/action_export_as_cyclops.gd b/addons/cyclops_level_builder/actions/io/action_export_as_cyclops.gd new file mode 100644 index 0000000..cff5a56 --- /dev/null +++ b/addons/cyclops_level_builder/actions/io/action_export_as_cyclops.gd @@ -0,0 +1,44 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionExportAsCyclops +extends CyclopsAction + +var wizard:ExporterCyclopsWizard = preload("res://addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.tscn").instantiate() + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Export As Cyclops File...") + +func _execute(): + if !wizard.get_parent(): + var base_control:Node = plugin.get_editor_interface().get_base_control() + base_control.add_child(wizard) + + wizard.plugin = plugin + wizard.popup_centered() + + + + + diff --git a/addons/cyclops_level_builder/actions/io/action_export_as_gltf.gd b/addons/cyclops_level_builder/actions/io/action_export_as_gltf.gd new file mode 100644 index 0000000..5c6ef37 --- /dev/null +++ b/addons/cyclops_level_builder/actions/io/action_export_as_gltf.gd @@ -0,0 +1,49 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionExportAsGltf +extends CyclopsAction + +var wizard:ExporterGltfWizard = preload("res://addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.tscn").instantiate() + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Export As Gltf...") + +func _execute(): + if !wizard.get_parent(): + var base_control:Node = plugin.get_editor_interface().get_base_control() + base_control.add_child(wizard) + + wizard.plugin = plugin + wizard.popup_centered() + + #await base_control.get_tree().process_frame + +# wizard.popup_hide.connect(func(): wizard.queue_free() ) + + #wizard.popup_centered() + + + + diff --git a/addons/cyclops_level_builder/actions/io/action_export_as_godot_scene.gd b/addons/cyclops_level_builder/actions/io/action_export_as_godot_scene.gd new file mode 100644 index 0000000..79dd875 --- /dev/null +++ b/addons/cyclops_level_builder/actions/io/action_export_as_godot_scene.gd @@ -0,0 +1,42 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionExportAsGodotScene +extends CyclopsAction + +var wizard:ExporterGodotSceneWizard = preload("res://addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.tscn").instantiate() + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Export As Godot Scene...") + +func _execute(): + if !wizard.get_parent(): + var base_control:Node = plugin.get_editor_interface().get_base_control() + base_control.add_child(wizard) + + wizard.plugin = plugin + wizard.popup_centered() + + + diff --git a/addons/cyclops_level_builder/actions/io/action_import_cyclops_file.gd b/addons/cyclops_level_builder/actions/io/action_import_cyclops_file.gd new file mode 100644 index 0000000..3b1e1ef --- /dev/null +++ b/addons/cyclops_level_builder/actions/io/action_import_cyclops_file.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionImportCyclopsFile +extends CyclopsAction + +var wizard:ImporterCyclopsFileWizard = preload("res://addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.tscn").instantiate() + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Import Cyclops File...") + +func _execute(): + if !wizard.get_parent(): + var base_control:Node = plugin.get_editor_interface().get_base_control() + base_control.add_child(wizard) + + wizard.plugin = plugin + wizard.popup_centered() diff --git a/addons/cyclops_level_builder/actions/io/action_import_mesh_instance.gd b/addons/cyclops_level_builder/actions/io/action_import_mesh_instance.gd new file mode 100644 index 0000000..67eb83f --- /dev/null +++ b/addons/cyclops_level_builder/actions/io/action_import_mesh_instance.gd @@ -0,0 +1,66 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ActionImportMeshInstance +extends CyclopsAction + +func _init(plugin:CyclopsLevelBuilder, name:String = "", accellerator:Key = KEY_NONE): + super._init(plugin, "Import Godot MeshInstance...") + +func _execute(): + var nodes:Array[Node] = plugin.get_editor_interface().get_selection().get_selected_nodes() + + if nodes.is_empty(): + return + + if !(nodes[-1] is Node3D): + return + + var tgt_parent:Node3D = nodes[-1] + if tgt_parent is MeshInstance3D: + tgt_parent = tgt_parent.get_parent() + + var cmd:CommandImportGodotMeshes = CommandImportGodotMeshes.new() + cmd.builder = plugin + cmd.target_parent = tgt_parent.get_path() + #print("parent ", tgt_parent.get_path()) + + for node in nodes: + import_branch_recursive(node, cmd) + + if !cmd.will_change_anything(): + return + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) + +func import_branch_recursive(node:Node3D, cmd:CommandImportGodotMeshes): + if node is MeshInstance3D: + cmd.source_nodes.append(node.get_path()) + #print("src ", node.get_path()) + + for child in node.get_children(): + import_branch_recursive(child, cmd) + + diff --git a/addons/cyclops_level_builder/art/cyclops.svg b/addons/cyclops_level_builder/art/cyclops.svg new file mode 100644 index 0000000..a2fc7e1 --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/cyclops.svg.import b/addons/cyclops_level_builder/art/cyclops.svg.import new file mode 100644 index 0000000..fbb9647 --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmbwskudf7ldr" +path="res://.godot/imported/cyclops.svg-62ab1cb5293c5a489284f34e0c642019.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops.svg" +dest_files=["res://.godot/imported/cyclops.svg-62ab1cb5293c5a489284f34e0c642019.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/cyclops1.png b/addons/cyclops_level_builder/art/cyclops1.png new file mode 100644 index 0000000..aa0202b Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops1.png differ diff --git a/build/web/Test Project.png.import b/addons/cyclops_level_builder/art/cyclops1.png.import similarity index 67% rename from build/web/Test Project.png.import rename to addons/cyclops_level_builder/art/cyclops1.png.import index a4e39f8..881ae35 100644 --- a/build/web/Test Project.png.import +++ b/addons/cyclops_level_builder/art/cyclops1.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://d3eyhvfobywi1" -path="res://.godot/imported/Test Project.png-7caed56d1488c410323711de0c53c191.ctex" +uid="uid://l4qj0lbj3ioa" +path="res://.godot/imported/cyclops1.png-6f459321d21304ca30333893d06ffb09.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://build/web/Test Project.png" -dest_files=["res://.godot/imported/Test Project.png-7caed56d1488c410323711de0c53c191.ctex"] +source_file="res://addons/cyclops_level_builder/art/cyclops1.png" +dest_files=["res://.godot/imported/cyclops1.png-6f459321d21304ca30333893d06ffb09.ctex"] [params] diff --git a/addons/cyclops_level_builder/art/cyclops2.png b/addons/cyclops_level_builder/art/cyclops2.png new file mode 100644 index 0000000..bebba72 Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops2.png differ diff --git a/build/web/Test Project.icon.png.import b/addons/cyclops_level_builder/art/cyclops2.png.import similarity index 66% rename from build/web/Test Project.icon.png.import rename to addons/cyclops_level_builder/art/cyclops2.png.import index 3f84565..8333681 100644 --- a/build/web/Test Project.icon.png.import +++ b/addons/cyclops_level_builder/art/cyclops2.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://vsu5i43afibf" -path="res://.godot/imported/Test Project.icon.png-e81507e635b461cf9c1422d681220a19.ctex" +uid="uid://oxrgrpeaamq3" +path="res://.godot/imported/cyclops2.png-510e6418526608a41b6474466da5ef6e.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://build/web/Test Project.icon.png" -dest_files=["res://.godot/imported/Test Project.icon.png-e81507e635b461cf9c1422d681220a19.ctex"] +source_file="res://addons/cyclops_level_builder/art/cyclops2.png" +dest_files=["res://.godot/imported/cyclops2.png-510e6418526608a41b6474466da5ef6e.ctex"] [params] diff --git a/addons/cyclops_level_builder/art/cyclops_16.png b/addons/cyclops_level_builder/art/cyclops_16.png new file mode 100644 index 0000000..835067b Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_16.png differ diff --git a/build/web/Test Project.apple-touch-icon.png.import b/addons/cyclops_level_builder/art/cyclops_16.png.import similarity index 63% rename from build/web/Test Project.apple-touch-icon.png.import rename to addons/cyclops_level_builder/art/cyclops_16.png.import index ae4cb37..0c2777f 100644 --- a/build/web/Test Project.apple-touch-icon.png.import +++ b/addons/cyclops_level_builder/art/cyclops_16.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://hsjifuwo1hbi" -path="res://.godot/imported/Test Project.apple-touch-icon.png-105239907a9c8bfb65ea35a1d1d25b31.ctex" +uid="uid://df0a5uffpuqg3" +path="res://.godot/imported/cyclops_16.png-f07e4f06ecdf5a5bd3bf65311c5a89e7.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://build/web/Test Project.apple-touch-icon.png" -dest_files=["res://.godot/imported/Test Project.apple-touch-icon.png-105239907a9c8bfb65ea35a1d1d25b31.ctex"] +source_file="res://addons/cyclops_level_builder/art/cyclops_16.png" +dest_files=["res://.godot/imported/cyclops_16.png-f07e4f06ecdf5a5bd3bf65311c5a89e7.ctex"] [params] diff --git a/addons/cyclops_level_builder/art/cyclops_17.png b/addons/cyclops_level_builder/art/cyclops_17.png new file mode 100644 index 0000000..51d5ebc Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_17.png differ diff --git a/addons/cyclops_level_builder/art/cyclops_17.png.import b/addons/cyclops_level_builder/art/cyclops_17.png.import new file mode 100644 index 0000000..c60a735 --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops_17.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ynphxd22kahd" +path="res://.godot/imported/cyclops_17.png-c21222671fab466b90ffb528051b4f8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops_17.png" +dest_files=["res://.godot/imported/cyclops_17.png-c21222671fab466b90ffb528051b4f8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/cyclops_3.png b/addons/cyclops_level_builder/art/cyclops_3.png new file mode 100644 index 0000000..eb139e0 Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_3.png differ diff --git a/addons/cyclops_level_builder/art/cyclops_3.png.import b/addons/cyclops_level_builder/art/cyclops_3.png.import new file mode 100644 index 0000000..f473f9b --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops_3.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0krdms4l6ns4" +path="res://.godot/imported/cyclops_3.png-6415cec0c5295619847f207c8fb5e88e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops_3.png" +dest_files=["res://.godot/imported/cyclops_3.png-6415cec0c5295619847f207c8fb5e88e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/cyclops_4.png b/addons/cyclops_level_builder/art/cyclops_4.png new file mode 100644 index 0000000..1d4a3ed Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_4.png differ diff --git a/addons/cyclops_level_builder/art/cyclops_4.png.import b/addons/cyclops_level_builder/art/cyclops_4.png.import new file mode 100644 index 0000000..1bb0fc1 --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops_4.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cg3yjatinkymb" +path="res://.godot/imported/cyclops_4.png-e03a17198c56b52428203cf1953feb8c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops_4.png" +dest_files=["res://.godot/imported/cyclops_4.png-e03a17198c56b52428203cf1953feb8c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/cyclops_closed.png b/addons/cyclops_level_builder/art/cyclops_closed.png new file mode 100644 index 0000000..84ea43f Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_closed.png differ diff --git a/addons/cyclops_level_builder/art/cyclops_closed.png.import b/addons/cyclops_level_builder/art/cyclops_closed.png.import new file mode 100644 index 0000000..d4091e1 --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops_closed.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd8xjcq5k2kia" +path="res://.godot/imported/cyclops_closed.png-8f54cf7552f8e17ac6aa0c8d6378241b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops_closed.png" +dest_files=["res://.godot/imported/cyclops_closed.png-8f54cf7552f8e17ac6aa0c8d6378241b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/cyclops_open.png b/addons/cyclops_level_builder/art/cyclops_open.png new file mode 100644 index 0000000..e80d75b Binary files /dev/null and b/addons/cyclops_level_builder/art/cyclops_open.png differ diff --git a/addons/cyclops_level_builder/art/cyclops_open.png.import b/addons/cyclops_level_builder/art/cyclops_open.png.import new file mode 100644 index 0000000..a71ecaa --- /dev/null +++ b/addons/cyclops_level_builder/art/cyclops_open.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dv78ucvwmycdh" +path="res://.godot/imported/cyclops_open.png-44d55f3db65056f90cc00f3559bf1cb3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/cyclops_open.png" +dest_files=["res://.godot/imported/cyclops_open.png-44d55f3db65056f90cc00f3559bf1cb3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/LICENSE.txt b/addons/cyclops_level_builder/art/fonts/Roboto/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf new file mode 100644 index 0000000..0112e7d Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf.import new file mode 100644 index 0000000..a16f167 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://yhi2wxrd3vdl" +path="res://.godot/imported/Roboto-Black.ttf-bf53e1e350116bdc6b4fb4a05660f669.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Black.ttf" +dest_files=["res://.godot/imported/Roboto-Black.ttf-bf53e1e350116bdc6b4fb4a05660f669.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..b2c6aca Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf.import new file mode 100644 index 0000000..a6e8d6e --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://du5gqvb4yc1dj" +path="res://.godot/imported/Roboto-BlackItalic.ttf-bf8690d15b3cf8cd5d6a2b817379e64e.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BlackItalic.ttf" +dest_files=["res://.godot/imported/Roboto-BlackItalic.ttf-bf8690d15b3cf8cd5d6a2b817379e64e.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 0000000..43da14d Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf.import new file mode 100644 index 0000000..38254eb --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://c38q3pk6fpcof" +path="res://.godot/imported/Roboto-Bold.ttf-df81bc6c67726596bfdfbb0c232daa07.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Bold.ttf" +dest_files=["res://.godot/imported/Roboto-Bold.ttf-df81bc6c67726596bfdfbb0c232daa07.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..bcfdab4 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf.import new file mode 100644 index 0000000..773809f --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://c3x3cd47jhq2" +path="res://.godot/imported/Roboto-BoldItalic.ttf-a6c74f542f3344ae20ca33a53a2a23a5.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-BoldItalic.ttf" +dest_files=["res://.godot/imported/Roboto-BoldItalic.ttf-a6c74f542f3344ae20ca33a53a2a23a5.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf new file mode 100644 index 0000000..1b5eaa3 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf.import new file mode 100644 index 0000000..d8349b8 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://b7wdow3eps416" +path="res://.godot/imported/Roboto-Italic.ttf-16e369b2270cffe6321292c293333ba2.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Italic.ttf" +dest_files=["res://.godot/imported/Roboto-Italic.ttf-16e369b2270cffe6321292c293333ba2.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf new file mode 100644 index 0000000..e7307e7 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf.import new file mode 100644 index 0000000..f44861a --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://r0x2ql45ygwl" +path="res://.godot/imported/Roboto-Light.ttf-32e2bbbc31f4a36b674db4f07c8dde61.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Light.ttf" +dest_files=["res://.godot/imported/Roboto-Light.ttf-32e2bbbc31f4a36b674db4f07c8dde61.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf new file mode 100644 index 0000000..2d277af Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf.import new file mode 100644 index 0000000..b4c6d6c --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bhhr80f3d5x6w" +path="res://.godot/imported/Roboto-LightItalic.ttf-df475dd7032319cbfacce9bda56b5428.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-LightItalic.ttf" +dest_files=["res://.godot/imported/Roboto-LightItalic.ttf-df475dd7032319cbfacce9bda56b5428.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf new file mode 100644 index 0000000..ac0f908 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf.import new file mode 100644 index 0000000..0a853a3 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://c8a6t7tcg764a" +path="res://.godot/imported/Roboto-Medium.ttf-1aacbf46d243718027ec489d66f94134.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Medium.ttf" +dest_files=["res://.godot/imported/Roboto-Medium.ttf-1aacbf46d243718027ec489d66f94134.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..fc36a47 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf.import new file mode 100644 index 0000000..4539967 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://2tbkytso6fih" +path="res://.godot/imported/Roboto-MediumItalic.ttf-a8b1a5abefd7e766b592e7aae3a010dd.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-MediumItalic.ttf" +dest_files=["res://.godot/imported/Roboto-MediumItalic.ttf-a8b1a5abefd7e766b592e7aae3a010dd.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf.import new file mode 100644 index 0000000..3b5a623 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://dejaio63tyi02" +path="res://.godot/imported/Roboto-Regular.ttf-2dd2d3db031bed92eb84483dd4615adb.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf" +dest_files=["res://.godot/imported/Roboto-Regular.ttf-2dd2d3db031bed92eb84483dd4615adb.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf new file mode 100644 index 0000000..2e0dee6 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf.import new file mode 100644 index 0000000..117723e --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bci3pwej3h1vb" +path="res://.godot/imported/Roboto-Thin.ttf-be0d3f6dcdbb5e1125fcb2eb5b3585da.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Thin.ttf" +dest_files=["res://.godot/imported/Roboto-Thin.ttf-be0d3f6dcdbb5e1125fcb2eb5b3585da.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..084f9c0 Binary files /dev/null and b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf differ diff --git a/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf.import b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf.import new file mode 100644 index 0000000..1a89de0 --- /dev/null +++ b/addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://cgr0ds5yvswhi" +path="res://.godot/imported/Roboto-ThinItalic.ttf-73ae85a31d5331b097d04b58d0a0ed22.fontdata" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-ThinItalic.ttf" +dest_files=["res://.godot/imported/Roboto-ThinItalic.ttf-73ae85a31d5331b097d04b58d0a0ed22.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb new file mode 100644 index 0000000..6009a33 Binary files /dev/null and b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb differ diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb.import b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb.import new file mode 100644 index 0000000..3ce294c --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb.import @@ -0,0 +1,36 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://p26cj0m5amq0" +path="res://.godot/imported/gizmo_rotate.glb-2e5427bc458d00aede170e5b0ed6cee0.scn" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb" +dest_files=["res://.godot/imported/gizmo_rotate.glb-2e5427bc458d00aede170e5b0ed6cee0.scn"] + +[params] + +nodes/root_type="Node3D" +nodes/root_name="Scene Root" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=false +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +gltf/naming_version=0 +gltf/embedded_image_handling=1 diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.obj.import b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.obj.import new file mode 100644 index 0000000..c200206 --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_rotate.obj.import @@ -0,0 +1,22 @@ +[remap] + +importer="wavefront_obj" +importer_version=1 +type="Mesh" +uid="uid://dc41qtx1s2lbw" +path="res://.godot/imported/gizmo_rotate.obj-083ea63ef4767449d33eb052210ae2e8.mesh" + +[deps] + +files=["res://.godot/imported/gizmo_rotate.obj-083ea63ef4767449d33eb052210ae2e8.mesh"] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_rotate.obj" +dest_files=["res://.godot/imported/gizmo_rotate.obj-083ea63ef4767449d33eb052210ae2e8.mesh", "res://.godot/imported/gizmo_rotate.obj-083ea63ef4767449d33eb052210ae2e8.mesh"] + +[params] + +generate_tangents=true +scale_mesh=Vector3(1, 1, 1) +offset_mesh=Vector3(0, 0, 0) +optimize_mesh=true +force_disable_mesh_compression=false diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb new file mode 100644 index 0000000..e898f39 Binary files /dev/null and b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb differ diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb.import b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb.import new file mode 100644 index 0000000..584f5aa --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb.import @@ -0,0 +1,36 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://boc4o8oikx7bd" +path="res://.godot/imported/gizmo_scale.glb-b94a0e3ad2db5ab9510faa04f0b770ed.scn" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_scale.glb" +dest_files=["res://.godot/imported/gizmo_scale.glb-b94a0e3ad2db5ab9510faa04f0b770ed.scn"] + +[params] + +nodes/root_type="Node3D" +nodes/root_name="Scene Root" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +gltf/naming_version=0 +gltf/embedded_image_handling=1 diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_scale.obj.import b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.obj.import new file mode 100644 index 0000000..54d8be4 --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_scale.obj.import @@ -0,0 +1,22 @@ +[remap] + +importer="wavefront_obj" +importer_version=1 +type="Mesh" +uid="uid://dm6efs8isals0" +path="res://.godot/imported/gizmo_scale.obj-a2bbb5796f0120e00deceb29269481c4.mesh" + +[deps] + +files=["res://.godot/imported/gizmo_scale.obj-a2bbb5796f0120e00deceb29269481c4.mesh"] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_scale.obj" +dest_files=["res://.godot/imported/gizmo_scale.obj-a2bbb5796f0120e00deceb29269481c4.mesh", "res://.godot/imported/gizmo_scale.obj-a2bbb5796f0120e00deceb29269481c4.mesh"] + +[params] + +generate_tangents=true +scale_mesh=Vector3(1, 1, 1) +offset_mesh=Vector3(0, 0, 0) +optimize_mesh=true +force_disable_mesh_compression=false diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb new file mode 100644 index 0000000..967e126 Binary files /dev/null and b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb differ diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb.import b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb.import new file mode 100644 index 0000000..d3bcfb1 --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb.import @@ -0,0 +1,36 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://ujq3kes2sdfu" +path="res://.godot/imported/gizmo_translate.glb-b25182ebac6173efa72020211f0823b4.scn" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb" +dest_files=["res://.godot/imported/gizmo_translate.glb-b25182ebac6173efa72020211f0823b4.scn"] + +[params] + +nodes/root_type="Node3D" +nodes/root_name="Scene Root" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=false +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +gltf/naming_version=0 +gltf/embedded_image_handling=1 diff --git a/addons/cyclops_level_builder/art/gizmos/gizmo_translate.obj.import b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.obj.import new file mode 100644 index 0000000..25b2cec --- /dev/null +++ b/addons/cyclops_level_builder/art/gizmos/gizmo_translate.obj.import @@ -0,0 +1,22 @@ +[remap] + +importer="wavefront_obj" +importer_version=1 +type="Mesh" +uid="uid://cuuxumssbvx" +path="res://.godot/imported/gizmo_translate.obj-dfe1041d0008a76a601d3c2537af97e5.mesh" + +[deps] + +files=["res://.godot/imported/gizmo_translate.obj-dfe1041d0008a76a601d3c2537af97e5.mesh"] + +source_file="res://addons/cyclops_level_builder/art/gizmos/gizmo_translate.obj" +dest_files=["res://.godot/imported/gizmo_translate.obj-dfe1041d0008a76a601d3c2537af97e5.mesh", "res://.godot/imported/gizmo_translate.obj-dfe1041d0008a76a601d3c2537af97e5.mesh"] + +[params] + +generate_tangents=true +scale_mesh=Vector3(1, 1, 1) +offset_mesh=Vector3(0, 0, 0) +optimize_mesh=true +force_disable_mesh_compression=false diff --git a/addons/cyclops_level_builder/art/icons/arrow_down.svg b/addons/cyclops_level_builder/art/icons/arrow_down.svg new file mode 100644 index 0000000..4dc25ca --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_down.svg @@ -0,0 +1,67 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/arrow_down.svg.import b/addons/cyclops_level_builder/art/icons/arrow_down.svg.import new file mode 100644 index 0000000..ec23d2f --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_down.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bor2x3t7fiqc2" +path.s3tc="res://.godot/imported/arrow_down.svg-2e1ff08c057ea7461c9327204e454db0.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/arrow_down.svg" +dest_files=["res://.godot/imported/arrow_down.svg-2e1ff08c057ea7461c9327204e454db0.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/arrow_left.svg b/addons/cyclops_level_builder/art/icons/arrow_left.svg new file mode 100644 index 0000000..136187a --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_left.svg @@ -0,0 +1,67 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/arrow_left.svg.import b/addons/cyclops_level_builder/art/icons/arrow_left.svg.import new file mode 100644 index 0000000..2165223 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_left.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ghfvfty2oswu" +path="res://.godot/imported/arrow_left.svg-014ed9bdeef2dfa2058baab18d11c5cd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/arrow_left.svg" +dest_files=["res://.godot/imported/arrow_left.svg-014ed9bdeef2dfa2058baab18d11c5cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/arrow_right.svg b/addons/cyclops_level_builder/art/icons/arrow_right.svg new file mode 100644 index 0000000..5e3e3a9 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_right.svg @@ -0,0 +1,67 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/arrow_right.svg.import b/addons/cyclops_level_builder/art/icons/arrow_right.svg.import new file mode 100644 index 0000000..9eeb534 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_right.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7c2vg6lbhmfn" +path="res://.godot/imported/arrow_right.svg-487571a4e582c53960841d4b8d93eafd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/arrow_right.svg" +dest_files=["res://.godot/imported/arrow_right.svg-487571a4e582c53960841d4b8d93eafd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/arrow_up.svg b/addons/cyclops_level_builder/art/icons/arrow_up.svg new file mode 100644 index 0000000..1ab600c --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_up.svg @@ -0,0 +1,67 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/arrow_up.svg.import b/addons/cyclops_level_builder/art/icons/arrow_up.svg.import new file mode 100644 index 0000000..a199ce9 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/arrow_up.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gb1w6xbrq5q" +path="res://.godot/imported/arrow_up.svg-98c02f3791a0716945de0c384c85f807.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/arrow_up.svg" +dest_files=["res://.godot/imported/arrow_up.svg-98c02f3791a0716945de0c384c85f807.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/block.svg b/addons/cyclops_level_builder/art/icons/block.svg new file mode 100644 index 0000000..6d02611 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/block.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/block.svg.import b/addons/cyclops_level_builder/art/icons/block.svg.import new file mode 100644 index 0000000..912a7c3 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/block.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwasqbq4iqkn6" +path="res://.godot/imported/block.svg-764d2bd43d9fe0588da4c013aa3df07b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/block.svg" +dest_files=["res://.godot/imported/block.svg-764d2bd43d9fe0588da4c013aa3df07b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/create_cylinder.svg b/addons/cyclops_level_builder/art/icons/create_cylinder.svg new file mode 100644 index 0000000..70bf780 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_cylinder.svg @@ -0,0 +1,50 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/create_cylinder.svg.import b/addons/cyclops_level_builder/art/icons/create_cylinder.svg.import new file mode 100644 index 0000000..21512c8 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_cylinder.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0vye3ue3ayvf" +path.s3tc="res://.godot/imported/create_cylinder.svg-476a4bc6152ac152d747a20ef15c4e74.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/create_cylinder.svg" +dest_files=["res://.godot/imported/create_cylinder.svg-476a4bc6152ac152d747a20ef15c4e74.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/create_prism.svg b/addons/cyclops_level_builder/art/icons/create_prism.svg new file mode 100644 index 0000000..782f75c --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_prism.svg @@ -0,0 +1,50 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/create_prism.svg.import b/addons/cyclops_level_builder/art/icons/create_prism.svg.import new file mode 100644 index 0000000..b68d4aa --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_prism.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbmwkjbju75er" +path="res://.godot/imported/create_prism.svg-c58e90aecfc90ad3bbb2700eb92eba47.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/create_prism.svg" +dest_files=["res://.godot/imported/create_prism.svg-c58e90aecfc90ad3bbb2700eb92eba47.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/create_stairs.svg b/addons/cyclops_level_builder/art/icons/create_stairs.svg new file mode 100644 index 0000000..e598319 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_stairs.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/create_stairs.svg.import b/addons/cyclops_level_builder/art/icons/create_stairs.svg.import new file mode 100644 index 0000000..53de7b9 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/create_stairs.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwq4w4vf8um1f" +path="res://.godot/imported/create_stairs.svg-d5c3678feb9fe435beb3be7ad9e116de.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/create_stairs.svg" +dest_files=["res://.godot/imported/create_stairs.svg-d5c3678feb9fe435beb3be7ad9e116de.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/edit_clip.svg b/addons/cyclops_level_builder/art/icons/edit_clip.svg new file mode 100644 index 0000000..5f5fb58 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/edit_clip.svg @@ -0,0 +1,59 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/edit_clip.svg.import b/addons/cyclops_level_builder/art/icons/edit_clip.svg.import new file mode 100644 index 0000000..fc9b49f --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/edit_clip.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bos2j51dp4j1s" +path="res://.godot/imported/edit_clip.svg-cb82adafad7dd137e6bfab5345612057.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/edit_clip.svg" +dest_files=["res://.godot/imported/edit_clip.svg-cb82adafad7dd137e6bfab5345612057.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/eye_closed.svg b/addons/cyclops_level_builder/art/icons/eye_closed.svg new file mode 100644 index 0000000..456f992 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/eye_closed.svg @@ -0,0 +1,79 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/eye_closed.svg.import b/addons/cyclops_level_builder/art/icons/eye_closed.svg.import new file mode 100644 index 0000000..a5452b1 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/eye_closed.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlaqhkooso2ef" +path="res://.godot/imported/eye_closed.svg-dd0fa412d337f7ed88eb934f289592d2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/eye_closed.svg" +dest_files=["res://.godot/imported/eye_closed.svg-dd0fa412d337f7ed88eb934f289592d2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/eye_open.svg b/addons/cyclops_level_builder/art/icons/eye_open.svg new file mode 100644 index 0000000..ad91ab0 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/eye_open.svg @@ -0,0 +1,94 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/eye_open.svg.import b/addons/cyclops_level_builder/art/icons/eye_open.svg.import new file mode 100644 index 0000000..c2d8020 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/eye_open.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmu0ivr826rwb" +path="res://.godot/imported/eye_open.svg-c6da53c32ed6c8a148f0ba4dc1173f28.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/eye_open.svg" +dest_files=["res://.godot/imported/eye_open.svg-c6da53c32ed6c8a148f0ba4dc1173f28.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/material_brush.svg b/addons/cyclops_level_builder/art/icons/material_brush.svg new file mode 100644 index 0000000..7cefff0 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/material_brush.svg @@ -0,0 +1,53 @@ + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/material_brush.svg.import b/addons/cyclops_level_builder/art/icons/material_brush.svg.import new file mode 100644 index 0000000..181a8a8 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/material_brush.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw8s7hrmnu34j" +path="res://.godot/imported/material_brush.svg-d1bd5b1c19cc756ce31fd5eaeedcb121.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/material_brush.svg" +dest_files=["res://.godot/imported/material_brush.svg-d1bd5b1c19cc756ce31fd5eaeedcb121.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/move.svg b/addons/cyclops_level_builder/art/icons/move.svg new file mode 100644 index 0000000..a42b891 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/move.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/addons/cyclops_level_builder/art/icons/move.svg.import b/addons/cyclops_level_builder/art/icons/move.svg.import new file mode 100644 index 0000000..4a42b41 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/move.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqy2x1s41ypbt" +path.s3tc="res://.godot/imported/move.svg-ed763a44f715fed1ba9cd0ebb407610b.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/move.svg" +dest_files=["res://.godot/imported/move.svg-ed763a44f715fed1ba9cd0ebb407610b.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/rotate.svg b/addons/cyclops_level_builder/art/icons/rotate.svg new file mode 100644 index 0000000..e58de62 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/rotate.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/rotate.svg.import b/addons/cyclops_level_builder/art/icons/rotate.svg.import new file mode 100644 index 0000000..dfb3444 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/rotate.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1hu5mqwbm55w" +path="res://.godot/imported/rotate.svg-1a301defe8a7754cea1ea5ac0034fd8a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/rotate.svg" +dest_files=["res://.godot/imported/rotate.svg-1a301defe8a7754cea1ea5ac0034fd8a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/select_edge.svg b/addons/cyclops_level_builder/art/icons/select_edge.svg new file mode 100644 index 0000000..f7bf14b --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_edge.svg @@ -0,0 +1,53 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/select_edge.svg.import b/addons/cyclops_level_builder/art/icons/select_edge.svg.import new file mode 100644 index 0000000..f2d0da5 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_edge.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2da2j8ve48rt" +path="res://.godot/imported/select_edge.svg-740cff471ac1df58ecc957f9e83b9e91.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/select_edge.svg" +dest_files=["res://.godot/imported/select_edge.svg-740cff471ac1df58ecc957f9e83b9e91.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/select_face.svg b/addons/cyclops_level_builder/art/icons/select_face.svg new file mode 100644 index 0000000..af6128b --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_face.svg @@ -0,0 +1,55 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/select_face.svg.import b/addons/cyclops_level_builder/art/icons/select_face.svg.import new file mode 100644 index 0000000..b39d9d8 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_face.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bi27fw31w4ssi" +path="res://.godot/imported/select_face.svg-3dcc771c6179443bd23c42d8d78eed16.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/select_face.svg" +dest_files=["res://.godot/imported/select_face.svg-3dcc771c6179443bd23c42d8d78eed16.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/select_vertex.svg b/addons/cyclops_level_builder/art/icons/select_vertex.svg new file mode 100644 index 0000000..d1ec765 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_vertex.svg @@ -0,0 +1,54 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/select_vertex.svg.import b/addons/cyclops_level_builder/art/icons/select_vertex.svg.import new file mode 100644 index 0000000..8c93226 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/select_vertex.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwn58lev5oopd" +path="res://.godot/imported/select_vertex.svg-d1eacdb77752f52ac8784bfc74ae896a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/select_vertex.svg" +dest_files=["res://.godot/imported/select_vertex.svg-d1eacdb77752f52ac8784bfc74ae896a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/snap.svg b/addons/cyclops_level_builder/art/icons/snap.svg new file mode 100644 index 0000000..feda5b5 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap.svg @@ -0,0 +1,54 @@ + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/snap.svg.import b/addons/cyclops_level_builder/art/icons/snap.svg.import new file mode 100644 index 0000000..4f1d7bc --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dloyvoq8piwx0" +path="res://.godot/imported/snap.svg-39023523aa8158257ae64cb4fbbc5761.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/snap.svg" +dest_files=["res://.godot/imported/snap.svg-39023523aa8158257ae64cb4fbbc5761.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/snap_grid.svg b/addons/cyclops_level_builder/art/icons/snap_grid.svg new file mode 100644 index 0000000..2edf8a8 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap_grid.svg @@ -0,0 +1,54 @@ + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/snap_grid.svg.import b/addons/cyclops_level_builder/art/icons/snap_grid.svg.import new file mode 100644 index 0000000..36070ea --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap_grid.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6mucdu7wcbkm" +path.s3tc="res://.godot/imported/snap_grid.svg-12ab1553ba1ac0d6d8b56aa201eba887.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/snap_grid.svg" +dest_files=["res://.godot/imported/snap_grid.svg-12ab1553ba1ac0d6d8b56aa201eba887.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/snap_vertex.svg b/addons/cyclops_level_builder/art/icons/snap_vertex.svg new file mode 100644 index 0000000..0fe7426 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap_vertex.svg @@ -0,0 +1,52 @@ + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/snap_vertex.svg.import b/addons/cyclops_level_builder/art/icons/snap_vertex.svg.import new file mode 100644 index 0000000..ac9e235 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/snap_vertex.svg.import @@ -0,0 +1,38 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0x011okomj8n" +path.s3tc="res://.godot/imported/snap_vertex.svg-0a9ae24d62bafce943c0c8ecb890f083.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/snap_vertex.svg" +dest_files=["res://.godot/imported/snap_vertex.svg-0a9ae24d62bafce943c0c8ecb890f083.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/uv_lock.svg b/addons/cyclops_level_builder/art/icons/uv_lock.svg new file mode 100644 index 0000000..80d93f1 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/uv_lock.svg @@ -0,0 +1,62 @@ + + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/uv_lock.svg.import b/addons/cyclops_level_builder/art/icons/uv_lock.svg.import new file mode 100644 index 0000000..7eb9d42 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/uv_lock.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmj6dd7haiga4" +path="res://.godot/imported/uv_lock.svg-ce9938594c32c381355d5711b9169a38.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/uv_lock.svg" +dest_files=["res://.godot/imported/uv_lock.svg-ce9938594c32c381355d5711b9169a38.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg b/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg new file mode 100644 index 0000000..3a9c075 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + diff --git a/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg.import b/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg.import new file mode 100644 index 0000000..42b3b56 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/vertex_color_brush.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://be3f2j6mnl1yb" +path="res://.godot/imported/vertex_color_brush.svg-654b161cf9f3eb1f136399d25f961329.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/vertex_color_brush.svg" +dest_files=["res://.godot/imported/vertex_color_brush.svg-654b161cf9f3eb1f136399d25f961329.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/icons/xray_normal.svg b/addons/cyclops_level_builder/art/icons/xray_normal.svg new file mode 100644 index 0000000..1bf36c7 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/xray_normal.svg @@ -0,0 +1,108 @@ + + + +image/svg+xml diff --git a/addons/cyclops_level_builder/art/icons/xray_normal.svg.import b/addons/cyclops_level_builder/art/icons/xray_normal.svg.import new file mode 100644 index 0000000..ad4e9c4 --- /dev/null +++ b/addons/cyclops_level_builder/art/icons/xray_normal.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bs54uhn80ykrr" +path="res://.godot/imported/xray_normal.svg-01f10482e0b64ab4c53e5074b26f3f91.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/icons/xray_normal.svg" +dest_files=["res://.godot/imported/xray_normal.svg-01f10482e0b64ab4c53e5074b26f3f91.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.5 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/art/materialTest.glb b/addons/cyclops_level_builder/art/materialTest.glb new file mode 100644 index 0000000..8f993f0 Binary files /dev/null and b/addons/cyclops_level_builder/art/materialTest.glb differ diff --git a/addons/cyclops_level_builder/art/materialTest.glb.import b/addons/cyclops_level_builder/art/materialTest.glb.import new file mode 100644 index 0000000..810b1a5 --- /dev/null +++ b/addons/cyclops_level_builder/art/materialTest.glb.import @@ -0,0 +1,36 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dxpve684yme21" +path="res://.godot/imported/materialTest.glb-89a5487d9d67cb795bea9d0f40563ce7.scn" + +[deps] + +source_file="res://addons/cyclops_level_builder/art/materialTest.glb" +dest_files=["res://.godot/imported/materialTest.glb-89a5487d9d67cb795bea9d0f40563ce7.scn"] + +[params] + +nodes/root_type="Node3D" +nodes/root_name="Scene Root" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +gltf/naming_version=0 +gltf/embedded_image_handling=1 diff --git a/addons/cyclops_level_builder/art/textures/checkerboard.png b/addons/cyclops_level_builder/art/textures/checkerboard.png new file mode 100644 index 0000000..393ca46 Binary files /dev/null and b/addons/cyclops_level_builder/art/textures/checkerboard.png differ diff --git a/addons/cyclops_level_builder/art/textures/checkerboard.png.import b/addons/cyclops_level_builder/art/textures/checkerboard.png.import new file mode 100644 index 0000000..f12ecf8 --- /dev/null +++ b/addons/cyclops_level_builder/art/textures/checkerboard.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b78mg60xhic6n" +path.s3tc="res://.godot/imported/checkerboard.png-cfbf91dbc6f1912b9574735ecca5a514.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/textures/checkerboard.png" +dest_files=["res://.godot/imported/checkerboard.png-cfbf91dbc6f1912b9574735ecca5a514.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/addons/cyclops_level_builder/art/textures/checkerboard_colored.png b/addons/cyclops_level_builder/art/textures/checkerboard_colored.png new file mode 100644 index 0000000..d175a4b Binary files /dev/null and b/addons/cyclops_level_builder/art/textures/checkerboard_colored.png differ diff --git a/addons/cyclops_level_builder/art/textures/checkerboard_colored.png.import b/addons/cyclops_level_builder/art/textures/checkerboard_colored.png.import new file mode 100644 index 0000000..b6322eb --- /dev/null +++ b/addons/cyclops_level_builder/art/textures/checkerboard_colored.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvsytb0qkt170" +path="res://.godot/imported/checkerboard_colored.png-4878fcf8791df5b60275c0ab3474b158.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/textures/checkerboard_colored.png" +dest_files=["res://.godot/imported/checkerboard_colored.png-4878fcf8791df5b60275c0ab3474b158.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/art/textures/grid_cell.png b/addons/cyclops_level_builder/art/textures/grid_cell.png new file mode 100644 index 0000000..6e5364b Binary files /dev/null and b/addons/cyclops_level_builder/art/textures/grid_cell.png differ diff --git a/addons/cyclops_level_builder/art/textures/grid_cell.png.import b/addons/cyclops_level_builder/art/textures/grid_cell.png.import new file mode 100644 index 0000000..56f87d2 --- /dev/null +++ b/addons/cyclops_level_builder/art/textures/grid_cell.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpoaquoridpp6" +path.bptc="res://.godot/imported/grid_cell.png-608181fcff7b2e489ee715ed212734c5.bptc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/textures/grid_cell.png" +dest_files=["res://.godot/imported/grid_cell.png-608181fcff7b2e489ee715ed212734c5.bptc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=true +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/addons/cyclops_level_builder/art/textures/grid_cell2.png b/addons/cyclops_level_builder/art/textures/grid_cell2.png new file mode 100644 index 0000000..67a33e3 Binary files /dev/null and b/addons/cyclops_level_builder/art/textures/grid_cell2.png differ diff --git a/addons/cyclops_level_builder/art/textures/grid_cell2.png.import b/addons/cyclops_level_builder/art/textures/grid_cell2.png.import new file mode 100644 index 0000000..37bfc32 --- /dev/null +++ b/addons/cyclops_level_builder/art/textures/grid_cell2.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnlqi20ay4vs1" +path="res://.godot/imported/grid_cell2.png-1f268fac69e50a444ba304e9119a8d5b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/textures/grid_cell2.png" +dest_files=["res://.godot/imported/grid_cell2.png-1f268fac69e50a444ba304e9119a8d5b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/addons/cyclops_level_builder/art/textures/vertex.png b/addons/cyclops_level_builder/art/textures/vertex.png new file mode 100644 index 0000000..c91145e Binary files /dev/null and b/addons/cyclops_level_builder/art/textures/vertex.png differ diff --git a/addons/cyclops_level_builder/art/textures/vertex.png.import b/addons/cyclops_level_builder/art/textures/vertex.png.import new file mode 100644 index 0000000..5da7a33 --- /dev/null +++ b/addons/cyclops_level_builder/art/textures/vertex.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsvcm4kvcqlru" +path.s3tc="res://.godot/imported/vertex.png-5de890d5df12b689ad042d7798719e34.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/cyclops_level_builder/art/textures/vertex.png" +dest_files=["res://.godot/imported/vertex.png-5de890d5df12b689ad042d7798719e34.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/addons/cyclops_level_builder/commands/cmd_add_block.gd b/addons/cyclops_level_builder/commands/cmd_add_block.gd new file mode 100644 index 0000000..5fb4abc --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_add_block.gd @@ -0,0 +1,86 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandAddBlock +extends CyclopsCommand + +#Public data to set before activating command +var blocks_root_path:NodePath +#var origin:Vector3 +var block_name:String +var bounds:AABB +var material_path:String +var uv_transform:Transform2D = Transform2D.IDENTITY +var collision_type:Collision.Type = Collision.Type.STATIC +var collision_layers:int = 1 +var collision_mask:int = 1 + +#Private data +var block_path:NodePath + +func _init(): + command_name = "Add block" + +func do_it(): + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + + #var blocks_root:Node = builder.get_block_add_parent() + var block_parent:Node = builder.get_node(blocks_root_path) + + block_parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = block_name + block.collision_type = collision_type + block.collision_layer = collision_layers + block.collision_mask = collision_mask + + var material_id:int = -1 + if ResourceLoader.exists(material_path): + var mat = load(material_path) + if mat is Material: + material_id = 0 + block.materials.append(mat) + + + #print("Block root %s" % block) + #print("Create bounds %s" % bounds) + #var parent_xform:Transform3D = node_global_transform(block_parent) + #var vol_xform:Transform3D = Transform3D(Basis(), -bounds.position) + + var mesh:ConvexVolume = ConvexVolume.new() + mesh.init_block(bounds, uv_transform, material_id) + mesh.translate(-bounds.position) + + block.mesh_vector_data = mesh.to_mesh_vector_data() +# block.block_data = mesh.to_convex_block_data() + block_path = block.get_path() + block.global_transform = Transform3D(Basis(), bounds.position) + +# print("AddBlockCommand do_it() %s %s" % [block_inst_id, bounds]) + +func undo_it(): + var block:CyclopsBlock = builder.get_node(block_path) + block.queue_free() + +# print("AddBlockCommand undo_it()") diff --git a/addons/cyclops_level_builder/commands/cmd_add_cylinder.gd b/addons/cyclops_level_builder/commands/cmd_add_cylinder.gd new file mode 100644 index 0000000..9582ffa --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_add_cylinder.gd @@ -0,0 +1,115 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandAddCylinder +extends CyclopsCommand + +#Public data to set before activating command +var blocks_root_path:NodePath +#var block_name:String +var block_name_prefix:String = "Block_" +var origin:Vector3 +var axis_normal:Vector3 +var height:float +var radius_inner:float +var radius_outer:float +var segments:int +var tube:bool = false + +var material_path:String +var uv_transform:Transform2D = Transform2D.IDENTITY +var collision_type:Collision.Type = Collision.Type.STATIC +var collision_layers:int = 1 +var collision_mask:int = 1 + +#Private data +var block_paths:Array[NodePath] + +func _init(): + command_name = "Add cylinder" + +func create_block(blocks_root:Node, set_pivot_xform:Transform3D, mat:Material)->CyclopsBlock: + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + blocks_root.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = GeneralUtil.find_unique_name(blocks_root, block_name_prefix) + block.global_transform = set_pivot_xform.affine_inverse() + block.collision_type = collision_type + block.collision_layer = collision_layers + block.collision_mask = collision_mask + + if mat: + block.materials.append(mat) + + return block + + +func do_it(): +# var blocks_root:CyclopsBlocks = builder.get_node(blocks_root_path) + var blocks_root:Node = builder.get_node(blocks_root_path) + + var material:Material + var material_id:int = -1 + if ResourceLoader.exists(material_path): + var mat = load(material_path) + if mat is Material: + material_id = 0 + material = mat + + var set_pivot_xform:Transform3D = Transform3D(Basis.IDENTITY, -origin) + + if tube: + var bounding_points_inner:PackedVector3Array = MathUtil.create_circle_points(origin, axis_normal, radius_inner, segments) + var bounding_points_outer:PackedVector3Array = MathUtil.create_circle_points(origin, axis_normal, radius_outer, segments) + + for p_idx0 in bounding_points_inner.size(): + var p_idx1:int = wrap(p_idx0 + 1, 0, bounding_points_inner.size()) + + var block:CyclopsBlock = create_block(blocks_root, set_pivot_xform, material) + + var mesh:ConvexVolume = ConvexVolume.new() + var base_points:PackedVector3Array = [bounding_points_inner[p_idx0], bounding_points_inner[p_idx1], bounding_points_outer[p_idx1], bounding_points_outer[p_idx0]] + + mesh.init_prism(base_points, axis_normal * height, uv_transform, material_id) + mesh.transform(set_pivot_xform) + +# block.block_data = mesh.to_convex_block_data() + block.mesh_vector_data = mesh.to_mesh_vector_data() + block_paths.append(block.get_path()) + + else: + var block:CyclopsBlock = create_block(blocks_root, set_pivot_xform, material) + + var bounding_points:PackedVector3Array = MathUtil.create_circle_points(origin, axis_normal, radius_outer, segments) + var mesh:ConvexVolume = ConvexVolume.new() + mesh.init_prism(bounding_points, axis_normal * height, uv_transform, material_id) + mesh.transform(set_pivot_xform) + + block.mesh_vector_data = mesh.to_mesh_vector_data() + block_paths.append(block.get_path()) + +func undo_it(): + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + block.queue_free() diff --git a/addons/cyclops_level_builder/commands/cmd_add_prism.gd b/addons/cyclops_level_builder/commands/cmd_add_prism.gd new file mode 100644 index 0000000..0c6ca14 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_add_prism.gd @@ -0,0 +1,80 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandAddPrism +extends CyclopsCommand + +@export var blocks_root_path:NodePath +@export var block_name:String +@export var base_polygon:PackedVector3Array +@export var extrude:Vector3 +#var local_transform:Transform3D +@export var uv_transform:Transform2D +@export var material_path:String +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export var collision_layers:int = 1 +@export var collision_mask:int = 1 + +#Private +var block_path:NodePath + +func _init(): + command_name = "Add prism" + +func do_it(): + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + + var blocks_root:Node = builder.get_node(blocks_root_path) + blocks_root.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = block_name + #block.transform = local_transform + block.collision_type = collision_type + block.collision_layer = collision_layers + block.collision_mask = collision_mask + + var material_id:int = -1 + if ResourceLoader.exists(material_path): + var mat = load(material_path) + if mat is Material: + material_id = 0 + block.materials.append(mat) + + var set_pivot_xform:Transform3D = Transform3D(Basis.IDENTITY, -base_polygon[0]) + + var mesh:ConvexVolume = ConvexVolume.new() + mesh.init_prism(base_polygon, extrude, uv_transform, material_id) + mesh.transform(set_pivot_xform) + + block.mesh_vector_data = mesh.to_mesh_vector_data() + block_path = block.get_path() + + block.global_transform = set_pivot_xform.affine_inverse() +# print("AddBlockCommand do_it() %s %s" % [block_inst_id, bounds]) + +func undo_it(): + var block:CyclopsBlock = builder.get_node(block_path) + block.queue_free() + +# print("AddBlockCommand undo_it()") diff --git a/addons/cyclops_level_builder/commands/cmd_add_stairs.gd b/addons/cyclops_level_builder/commands/cmd_add_stairs.gd new file mode 100644 index 0000000..fdc0f2d --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_add_stairs.gd @@ -0,0 +1,139 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandAddStairs +extends CyclopsCommand + +#var blocks_root_inst_id:int +var blocks_root_path:NodePath +var block_name_prefix:String +var floor_normal:Vector3 +var drag_origin:Vector3 +var base_drag_cur:Vector3 +var block_drag_cur:Vector3 +var step_height:float = .25 +var step_depth:float = .5 +var direction:int = 0 + +var uv_transform:Transform2D +var material_path:String +var collision_type:Collision.Type = Collision.Type.STATIC +var collision_layers:int = 1 +var collision_mask:int = 1 + +#Private data +var block_paths:Array[NodePath] + +func _init(): + command_name = "Add stairs" + +func create_block(blocks_root:Node, mat:Material)->CyclopsBlock: + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + blocks_root.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = GeneralUtil.find_unique_name(blocks_root, block_name_prefix) + block.collision_type = collision_type + block.collision_layer = collision_layers + block.collision_mask = collision_mask + + if mat: + block.materials.append(mat) + + return block + + +func do_it(): + var blocks_root:Node = builder.get_node(blocks_root_path) + + var material:Material + var material_id:int = -1 + if ResourceLoader.exists(material_path): + var mat = load(material_path) + if mat is Material: + material_id = 0 + material = mat + + var tan_bi:Array[Vector3] = MathUtil.get_axis_aligned_tangent_and_binormal(floor_normal) + var u_normal:Vector3 = tan_bi[0] + var v_normal:Vector3 = tan_bi[1] + + #Rotate ccw by 90 degree increments + match direction: + 1: + var tmp:Vector3 = u_normal + u_normal = -v_normal + v_normal = tmp + 2: + u_normal = -u_normal + v_normal = -v_normal + 3: + var tmp:Vector3 = -u_normal + u_normal = v_normal + v_normal = tmp + + var u_span:Vector3 = (base_drag_cur - drag_origin).project(u_normal) + var v_span:Vector3 = (base_drag_cur - drag_origin).project(v_normal) + + var stairs_origin:Vector3 = drag_origin + if u_span.dot(u_normal) < 0: + stairs_origin += u_span + u_span = -u_span + if v_span.dot(v_normal) < 0: + stairs_origin += v_span + v_span = -v_span + + #Stairs should ascend along v axis + var height_offset = block_drag_cur - base_drag_cur + if height_offset.dot(floor_normal) < 0: + return + var num_steps:int = min(v_span.length() / step_depth, height_offset.length() / step_height) + + var max_height:float = floor(height_offset.length() / step_height) * step_height + + var step_span:Vector3 = v_normal * step_depth + for i in num_steps: + var base_points:PackedVector3Array = [stairs_origin + step_span * i, \ + stairs_origin + u_span + step_span * i, \ + stairs_origin + u_span + step_span * (i + 1), \ + stairs_origin + step_span * (i + 1)] + + var pivot_xform:Transform3D = Transform3D(Basis.IDENTITY, -base_points[0]) + + var mesh:ConvexVolume = ConvexVolume.new() + mesh.init_prism(base_points, \ + floor_normal * (max_height - step_height * i), \ + uv_transform, material_id) + mesh.transform(pivot_xform) + + var block:CyclopsBlock = create_block(blocks_root, material) + + block.mesh_vector_data = mesh.to_mesh_vector_data() + block.global_transform = pivot_xform.affine_inverse() + block_paths.append(block.get_path()) + + +func undo_it(): + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + block.queue_free() diff --git a/addons/cyclops_level_builder/commands/cmd_add_vertices.gd b/addons/cyclops_level_builder/commands/cmd_add_vertices.gd new file mode 100644 index 0000000..1f23369 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_add_vertices.gd @@ -0,0 +1,74 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandAddVertices +extends CyclopsCommand + + +#Public +var points_to_add:PackedVector3Array +var block_path:NodePath + +#Private +var tracked_block_data:MeshVectorData +var selected_points:PackedVector3Array + + +func _init(): + command_name = "Add vertices" + +func do_it(): + var block:CyclopsBlock = builder.get_node(block_path) + + if !tracked_block_data: + var tracked_vol:ConvexVolume = block.control_mesh + tracked_block_data = tracked_vol.to_mesh_vector_data() + + for v in tracked_vol.vertices: + if v.selected: + selected_points.append(v.point) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(tracked_block_data) + + var point_list:PackedVector3Array = vol.get_points() + var local_points = block.global_transform.affine_inverse() * points_to_add + point_list.append_array(local_points) + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(point_list) + new_vol.copy_face_attributes(vol) + + + for v_idx in new_vol.vertices.size(): + var v:ConvexVolume.VertexInfo = new_vol.vertices[v_idx] + if selected_points.has(v.point): + v.selected = true + + block.mesh_vector_data = new_vol.to_mesh_vector_data() + + +func undo_it(): + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = tracked_block_data diff --git a/addons/cyclops_level_builder/commands/cmd_clip_block.gd b/addons/cyclops_level_builder/commands/cmd_clip_block.gd new file mode 100644 index 0000000..65d5b49 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_clip_block.gd @@ -0,0 +1,106 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandClipBlock +extends CyclopsCommand + +#Public data to set before activating command +var blocks_root_path:NodePath +var block_path:NodePath +var cut_plane:Plane +var uv_transform:Transform2D = Transform2D.IDENTITY +var material_path:String = "" + +#Private +var block_sibling_name:String +var old_block_data:MeshVectorData +var old_mat_list:Array[Material] +var block_sibling_path:NodePath + +func _init(): + command_name = "Clip block" + +func get_material_index(mat_list:Array[Material], path:String)->int: + if path.is_empty(): + return -1 + for i in mat_list.size(): + var mat:Material = mat_list[i] + if mat != null && mat.resource_path == path: + return i + return -1 + +func do_it(): + var blocks_root:Node = builder.get_node(blocks_root_path) + var block:CyclopsBlock = builder.get_node(block_path) + + old_block_data = block.mesh_vector_data.duplicate() + old_mat_list = block.materials.duplicate() + + var new_mat_list0:Array[Material] = old_mat_list.duplicate() + + var cut_mat_idx = get_material_index(old_mat_list, material_path) + if cut_mat_idx == -1: + var mat = load(material_path) + if mat is Material: + cut_mat_idx = new_mat_list0.size() + new_mat_list0.append(mat) + + + var new_mat_list1:Array[Material] = new_mat_list0.duplicate() + + #var cut_plane_reverse:Plane = Plane(-cut_plane.normal, cut_plane.get_center()) + + var w2l:Transform3D = block.global_transform.affine_inverse() + var cut_plane_local:Plane = w2l * cut_plane + + var vol0:ConvexVolume = block.control_mesh.cut_with_plane(cut_plane_local, uv_transform, cut_mat_idx) + var vol1:ConvexVolume = block.control_mesh.cut_with_plane(MathUtil.flip_plane(cut_plane_local), uv_transform, cut_mat_idx) + + #Set data of existing block + block.mesh_vector_data = vol0.to_mesh_vector_data() + block.materials = new_mat_list0 + + #Create second block + var block_sibling:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + + blocks_root.add_child(block_sibling) + block_sibling.owner = builder.get_editor_interface().get_edited_scene_root() + block_sibling.name = block_sibling_name + block_sibling.global_transform = block.global_transform + #block_sibling.selected = block.selected + block_sibling_path = block_sibling.get_path() + + block_sibling.mesh_vector_data = vol1.to_mesh_vector_data() + block_sibling.materials = new_mat_list1 + + +func undo_it(): + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = old_block_data + block.materials = old_mat_list.duplicate() + + var block_sibling:CyclopsBlock = builder.get_node(block_sibling_path) + block_sibling.queue_free() + + diff --git a/addons/cyclops_level_builder/commands/cmd_delete_blocks.gd b/addons/cyclops_level_builder/commands/cmd_delete_blocks.gd new file mode 100644 index 0000000..104aa35 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_delete_blocks.gd @@ -0,0 +1,82 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandDeleteBlocks +extends CyclopsCommand + +#Public +var block_paths:Array[NodePath] + +#Private +var tracked_blocks:Array[TrackedBlock] + +func _init(): + command_name = "Delete blocks" + +func will_change_anything(): + if !block_paths.is_empty(): + return true + + return false + + +func do_it(): + #print("Delete do_it") + + if tracked_blocks.is_empty(): + var points:PackedVector3Array + + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + var tracker:TrackedBlock = TrackedBlock.new(block) + tracked_blocks.append(tracker) + + #Delete source blocks + for block_path in block_paths: + var del_block:CyclopsBlock = builder.get_node(block_path) + del_block.get_parent().remove_child(del_block) + del_block.queue_free() + + +func undo_it(): + #print("Delete undo_it") + for tracked in tracked_blocks: + var parent = builder.get_node(tracked.path_parent) + + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + block.mesh_vector_data = tracked.data + block.materials = tracked.materials + block.name = tracked.name + #block.selected = tracked.selected + block.collision_type = tracked.collision_type + block.collision_layer = tracked.collision_layers + block.collision_mask = tracked.collision_mask + + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.global_transform = tracked.world_xform + + + + diff --git a/addons/cyclops_level_builder/commands/cmd_duplicate_blocks.gd b/addons/cyclops_level_builder/commands/cmd_duplicate_blocks.gd new file mode 100644 index 0000000..b4d1544 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_duplicate_blocks.gd @@ -0,0 +1,90 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandDuplicateBlocks +extends CyclopsCommand + +#Public +var blocks_root_path:NodePath +var blocks_to_duplicate:Array[NodePath] +var move_offset:Vector3 +var lock_uvs:bool + +#Private +class BlockInfo extends RefCounted: + var new_block:CyclopsBlock + var source_data:MeshVectorData + var source_global_transform:Transform3D + + func _init(new_block:CyclopsBlock, source_data:MeshVectorData, source_global_transform:Transform3D): + self.new_block = new_block + self.source_data = source_data + self.source_global_transform = source_global_transform + +var added_blocks:Array[BlockInfo] + +func will_change_anything(): + return !added_blocks.is_empty() + +func do_it(): + if added_blocks.is_empty(): + + #Create new blocks + for block_path in blocks_to_duplicate: + var new_block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + + var source_block:CyclopsBlock = builder.get_node(block_path) + + var blocks_root:Node = builder.get_node(blocks_root_path) + new_block.name = GeneralUtil.find_unique_name(blocks_root, source_block.name) + blocks_root.add_child(new_block) + new_block.owner = builder.get_editor_interface().get_edited_scene_root() + new_block.global_transform = source_block.global_transform + new_block.mesh_vector_data = source_block.mesh_vector_data.duplicate() + + var info:BlockInfo = BlockInfo.new(new_block, source_block.mesh_vector_data, source_block.global_transform) + new_block.materials = source_block.materials + #new_block.selected = true + + added_blocks.append(info) + + for path in blocks_to_duplicate: + var block:CyclopsBlock = builder.get_node(path) + #block.selected = false + + for info in added_blocks: + + var new_xform:Transform3D = info.source_global_transform.translated(move_offset) + info.new_block.global_transform = new_xform + + +func undo_it(): + for block in added_blocks: + block.new_block.queue_free() + added_blocks = [] + + for path in blocks_to_duplicate: + var block:CyclopsBlock = builder.get_node(path) + #block.selected = true + diff --git a/addons/cyclops_level_builder/commands/cmd_intersect_block.gd b/addons/cyclops_level_builder/commands/cmd_intersect_block.gd new file mode 100644 index 0000000..6aad74b --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_intersect_block.gd @@ -0,0 +1,152 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandIntersectBlock +extends CyclopsCommand + +class NewBlockInfo extends RefCounted: + var data:MeshVectorData + var materials:Array[Material] + var path:NodePath + var xform:Transform3D + #var centroid:Vector3 + +#Public +var block_paths:Array[NodePath] +var main_block_path:NodePath +var block_name_prefix:String = "Block_" + +#Private +var start_blocks:Array[TrackedBlock] +var main_block_cache:TrackedBlock +#var added_blocks:Array[NewBlockInfo] +var added_block:NewBlockInfo + +func _init(): + command_name = "Intersect blocks" + +func restore_tracked_block(tracked:TrackedBlock)->CyclopsBlock: + var parent = builder.get_node(tracked.path_parent) + + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + block.mesh_vector_data = tracked.data + block.materials = tracked.materials + block.name = tracked.name + #block.selected = tracked.selected + block.global_transform = tracked.world_xform + block.collision_type = tracked.collision_type + block.collision_layer = tracked.collision_layers + block.collision_mask = tracked.collision_mask + + parent.add_child(block) + + block.owner = builder.get_editor_interface().get_edited_scene_root() + + if tracked.selected: + var selection:EditorSelection = builder.get_editor_interface().get_selection() + selection.add_node(block) + + return block + +func will_change_anything()->bool: + var main_block:CyclopsBlock = builder.get_node(main_block_path) + var main_vol:ConvexVolume = main_block.control_mesh + main_vol = main_vol.transformed(main_block.global_transform) + + if block_paths.is_empty(): + return false + + for minuend_path in block_paths: + var minuend_block:CyclopsBlock = builder.get_node(minuend_path) + var minuend_vol:ConvexVolume = minuend_block.control_mesh + minuend_vol = minuend_vol.transformed(minuend_block.global_transform) + + if minuend_vol.intersects_convex_volume(main_vol): + return true + + return false + +func do_it(): + var main_block:CyclopsBlock = builder.get_node(main_block_path) + var snap_to_grid_util:SnapToGridUtil = CyclopsAutoload.calc_snap_to_grid_util() + + if start_blocks.is_empty(): + var main_vol:ConvexVolume = main_block.control_mesh + main_block_cache = TrackedBlock.new(main_block) + main_vol = main_vol.transformed(main_block.global_transform) + + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + + var minuend_vol:ConvexVolume = block.control_mesh + minuend_vol = minuend_vol.transformed(block.global_transform) + if !minuend_vol.intersects_convex_volume(main_vol): + continue + + var tracker:TrackedBlock = TrackedBlock.new(block) + start_blocks.append(tracker) + + main_vol = minuend_vol.intersect(main_vol) + + + var block_info:NewBlockInfo = NewBlockInfo.new() + block_info.materials = main_block.materials + var xform_inv:Transform3D = main_block.global_transform.affine_inverse() + main_vol = main_vol.transformed(xform_inv) + block_info.data = main_vol.to_mesh_vector_data() + block_info.xform = main_block.global_transform + added_block = block_info + + #Delete source blocks + for block_info in start_blocks: + var del_block:CyclopsBlock = builder.get_node(block_info.path) + del_block.queue_free() + + main_block.queue_free() + + #Create blocks + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + var parent:Node = builder.get_node(start_blocks[0].path_parent) + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = GeneralUtil.find_unique_name(parent, block_name_prefix) + block.mesh_vector_data = added_block.data + block.materials = added_block.materials + block.global_transform = added_block.xform + + added_block.path = block.get_path() + + + +func undo_it(): + + #for info in added_blocks: + var added_block_node:CyclopsBlock = builder.get_node(added_block.path) + added_block_node.queue_free() + + restore_tracked_block(main_block_cache) + + for tracked in start_blocks: + restore_tracked_block(tracked) + diff --git a/addons/cyclops_level_builder/commands/cmd_merge_blocks.gd b/addons/cyclops_level_builder/commands/cmd_merge_blocks.gd new file mode 100644 index 0000000..842e706 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_merge_blocks.gd @@ -0,0 +1,148 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMergeBlocks +extends CyclopsCommand + +#Public +var block_paths:Array[NodePath] +var block_name_prefix:String = "Block_" + +#Private +var tracked_blocks:Array[TrackedBlock] +var merged_block_data:MeshVectorData +var merged_mat_list:Array[Material] +var merged_block_path:NodePath +var world_pivot:Vector3 + +func _init(): + command_name = "Merge blocks" + +func get_best_face(centroid:Vector3, ref_list:Array[NodePath])->Array: + var best_face:ConvexVolume.FaceInfo + var best_dist:float = INF + var best_block:CyclopsBlock + + for block_path in ref_list: + var block:CyclopsBlock = builder.get_node(block_path) + var vol:ConvexVolume = block.control_mesh + for f in vol.faces: + var face_center:Vector3 = f.get_centroid() + var offset:float = centroid.distance_squared_to(face_center) + if offset < best_dist: + best_dist = offset + best_face = f + best_block = block + + if best_face.material_id == -1: + return [best_face, null] + return [best_face, best_block.materials[best_face.material_id]] + +func copy_face_attributes(target:ConvexVolume, ref_list:Array[NodePath])->Array[Material]: + var mat_list:Array[Material] + + for f in target.faces: + var centroid:Vector3 = f.get_centroid() + var res:Array = get_best_face(centroid, ref_list) + var ref_face:ConvexVolume.FaceInfo = res[0] + var material:Material = res[1] + + var mat_idx:int = -1 + if material != null: + mat_idx = mat_list.find(material) + if mat_idx == -1: + mat_idx = mat_list.size() + mat_list.append(material) + + f.material_id = mat_idx + f.uv_transform = ref_face.uv_transform + f.selected = ref_face.selected + + return mat_list + +func do_it(): + + if tracked_blocks.is_empty(): + var points:PackedVector3Array + + var first_block:CyclopsBlock = builder.get_node(block_paths[0]) + world_pivot = first_block.global_transform.origin + + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + var tracker:TrackedBlock = TrackedBlock.new(block) + tracked_blocks.append(tracker) + + var world_block:ConvexVolume = ConvexVolume.new() + world_block.init_from_mesh_vector_data(block.control_mesh.to_mesh_vector_data()) + world_block.transform(block.global_transform) + points.append_array(world_block.get_points()) + + var merged_vol:ConvexVolume = ConvexVolume.new() + merged_vol.init_from_points(points) + merged_mat_list = copy_face_attributes(merged_vol, block_paths) + merged_vol.translate(-world_pivot) + merged_block_data = merged_vol.to_mesh_vector_data() + + + + #Delete source blocks + for block_path in block_paths: + var del_block:CyclopsBlock = builder.get_node(block_path) + del_block.queue_free() + + #Create block + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + var parent:Node = builder.get_node(tracked_blocks[0].path_parent) + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = GeneralUtil.find_unique_name(parent, block_name_prefix) + block.mesh_vector_data = merged_block_data + block.materials = merged_mat_list + block.global_transform = Transform3D.IDENTITY.translated(world_pivot) + #block.materials + + merged_block_path = block.get_path() + +func undo_it(): +# var blocks_root:CyclopsBlocks = builder.get_node(blocks_root_path) + var merged_block:CyclopsBlock = builder.get_node(merged_block_path) + merged_block.queue_free() + +# for i in blocks_to_merge.size(): + for tracked in tracked_blocks: + var parent = builder.get_node(tracked.path_parent) + + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + block.mesh_vector_data = tracked.data + block.materials = tracked.materials + block.name = tracked.name + #block.selected = tracked.selected + block.global_transform = tracked.world_xform + block.collision_type = tracked.collision_type + block.collision_layer = tracked.collision_layers + block.collision_mask = tracked.collision_mask + + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() diff --git a/addons/cyclops_level_builder/commands/cmd_merge_vertices.gd b/addons/cyclops_level_builder/commands/cmd_merge_vertices.gd new file mode 100644 index 0000000..676ae26 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_merge_vertices.gd @@ -0,0 +1,142 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMergeVertices +extends CyclopsCommand + + +class BlockVertexChanges extends RefCounted: + var block_path:NodePath + var vertex_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Private +var block_map:Dictionary = {} + +#Public +var merge_point:Vector3 + +enum MergeType { POINT, CENTER, FIRST, LAST } +var merge_type:MergeType = MergeType.CENTER + +func add_vertex(block_path:NodePath, index:int): + add_vertices(block_path, [index]) + +func add_vertices(block_path:NodePath, indices:Array[int]): +# print("adding vertex %s %s" % [block_path, indices]) + var changes:BlockVertexChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockVertexChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data.duplicate() + block_map[block_path] = changes + + for index in indices: + if !changes.vertex_indices.has(index): + changes.vertex_indices.append(index) + +func _init(): + command_name = "Move vertices" + + +func do_it(): +# print("move verts do_it") + for block_path in block_map.keys(): + + var block:CyclopsBlock = builder.get_node(block_path) + var w2l:Transform3D = block.global_transform.affine_inverse() + + var rec:BlockVertexChanges = block_map[block_path] + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + var selected_points:PackedVector3Array + var new_points:PackedVector3Array + for v_idx in vol.vertices.size(): + if !rec.vertex_indices.has(v_idx): + var p:Vector3 = vol.vertices[v_idx].point + new_points.append(p) + + var merge_point_local:Vector3 + match merge_type: + MergeType.POINT: + merge_point_local = w2l * merge_point + pass + MergeType.CENTER: + var centroid:Vector3 + var count:int = 0 + for v_idx in vol.vertices.size(): + if rec.vertex_indices.has(v_idx): + var p:Vector3 = vol.vertices[v_idx].point + centroid += p + count += 1 + centroid /= count + merge_point_local = centroid + MergeType.FIRST: + merge_point_local = vol.vertices[rec.vertex_indices[0]].point + MergeType.LAST: + merge_point_local = vol.vertices[rec.vertex_indices[-1]].point + + + new_points.append(merge_point_local) + selected_points.append(merge_point_local) + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(new_points) + + new_vol.copy_face_attributes(vol) + + for v_idx in new_vol.vertices.size(): + var v:ConvexVolume.VertexInfo = new_vol.vertices[v_idx] +# print ("vol point %s " % v.point) + if selected_points.has(v.point): +# print("set sel") + v.selected = true + + block.mesh_vector_data = new_vol.to_mesh_vector_data() + +func undo_it(): +# print("move verts undo_it") + for block_path in block_map.keys(): + var rec:BlockVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + +func will_change_anything()->bool: + for path in block_map: + var rec:BlockVertexChanges = block_map[path] + match merge_type: + MergeType.POINT: + if rec.vertex_indices.size() >= 1: + return true + _: + if rec.vertex_indices.size() >= 2: + return true + + return false + diff --git a/addons/cyclops_level_builder/commands/cmd_move_edges.gd b/addons/cyclops_level_builder/commands/cmd_move_edges.gd new file mode 100644 index 0000000..84f044b --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_move_edges.gd @@ -0,0 +1,150 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMoveEdges +extends CyclopsCommand + + +class BlockEdgeChanges extends RefCounted: + var block_path:NodePath + var edge_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +var move_offset:Vector3 = Vector3.ZERO + +#Private +var block_map:Dictionary = {} + +func add_edge(block_path:NodePath, index:int): + add_edges(block_path, [index]) + +func add_edges(block_path:NodePath, indices:Array[int]): + var changes:BlockEdgeChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockEdgeChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.edge_indices.has(index): + changes.edge_indices.append(index) + +func _init(): + command_name = "Move edges" + + +func do_it(): +# print("cmd move edges- DO IT") + + for block_path in block_map.keys(): + +# print("%s" % block_path) + + var block:CyclopsBlock = builder.get_node(block_path) + var rec:BlockEdgeChanges = block_map[block_path] + +# print("rec %s" % rec) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + +# print("init done") + var w2l:Transform3D = block.global_transform.affine_inverse() + var move_offset_local = w2l.basis * move_offset + #print("move_offset ", move_offset) + #print("move_offset_local ", move_offset_local) + + var vert_indices:PackedInt32Array + for edge_index in rec.edge_indices: + var e:ConvexVolume.EdgeInfo = vol.edges[edge_index] + if !vert_indices.has(e.start_index): + vert_indices.append(e.start_index) + if !vert_indices.has(e.end_index): + vert_indices.append(e.end_index) + + for v_idx in vert_indices: + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + v.point += move_offset_local + + block.mesh_vector_data = vol.to_mesh_vector_data() +#### + #var moved_vert_indices:PackedInt32Array + #var new_points:PackedVector3Array + #var new_sel_centroids:PackedVector3Array + #var moved_indices:Array[int] = [] + #for edge_index in rec.edge_indices: + #var e:ConvexVolume.EdgeInfo = vol.edges[edge_index] + #var v0:ConvexVolume.VertexInfo = vol.vertices[e.start_index] + #var v1:ConvexVolume.VertexInfo = vol.vertices[e.end_index] + #if e.selected: + #new_sel_centroids.append((v0.point + v1.point) / 2 + move_offset_local) + # + #if !moved_indices.has(e.start_index): + #new_points.append(v0.point + move_offset_local) + #moved_indices.append(e.start_index) + #if !moved_indices.has(e.end_index): + #new_points.append(v1.point + move_offset_local) + #moved_indices.append(e.end_index) + #else: + #if !moved_indices.has(e.start_index): + #new_points.append(v0.point + move_offset_local) + #moved_indices.append(e.start_index) + #if !moved_indices.has(e.end_index): + #new_points.append(v1.point + move_offset_local) + #moved_indices.append(e.end_index) + # + #for v_idx in vol.vertices.size(): + #if !moved_indices.has(v_idx): + #new_points.append(vol.vertices[v_idx].point) + ##print("new points_ %s" % new_points) + # + #var new_vol:ConvexVolume = ConvexVolume.new() + #new_vol.init_from_points(new_points) +# + #new_vol.copy_face_attributes(vol) +# + ##print("new init done") + # + ##Copy selection data + #for e_idx in new_vol.edges.size(): + #var e_new:ConvexVolume.EdgeInfo = new_vol.edges[e_idx] + #var centroid:Vector3 = (new_vol.vertices[e_new.start_index].point + new_vol.vertices[e_new.end_index].point) / 2 +## print ("vol point %s " % v1.point) + #if new_sel_centroids.has(centroid): +## print("set sel") + #e_new.selected = true +# + #block.mesh_vector_data = new_vol.to_mesh_vector_data() + + +func undo_it(): + for block_path in block_map.keys(): + var rec:BlockEdgeChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data diff --git a/addons/cyclops_level_builder/commands/cmd_move_face_planar.gd b/addons/cyclops_level_builder/commands/cmd_move_face_planar.gd new file mode 100644 index 0000000..1db1332 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_move_face_planar.gd @@ -0,0 +1,102 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMoveFacePlanar +extends CyclopsCommand + +#Public data to set before activating command +var blocks_root_path:NodePath +var block_path:NodePath +var move_dir_normal:Vector3 +var move_amount:float +var face_index:int +var lock_uvs:bool = false + + +#Private +var block_name:String +var block_selected:bool +var tracked_block_data:MeshVectorData + +var deleted:bool = false + + +func _init(): + command_name = "Move face planar" + +func move_to(offset:Vector3, intermediate:bool): +# print("move_to off %s faceindex %s amount %s movedir %s" % [offset, face_index, move_amount, move_dir_normal]) + if !tracked_block_data: + var block:CyclopsBlock = builder.get_node(block_path) + + block_name = block.name + block_selected = block.selected + tracked_block_data = block.mesh_vector_data + + var ctl_mesh:ConvexVolume = ConvexVolume.new() + ctl_mesh.init_from_mesh_vector_data(tracked_block_data) + var new_mesh:ConvexVolume = ctl_mesh.translate_face_plane(face_index, offset, lock_uvs) + + #print("offset %s" % offset) + #print("ctl_mesh %s" % ctl_mesh.get_points()) + + + var block:CyclopsBlock = builder.get_node(block_path) + + if new_mesh == null || new_mesh.is_empty(): + #print("new_mesh EMPTY") + block.block_data = null + if !intermediate: + block.queue_free() + deleted = true + return + + #print("new_mesh %s" % new_mesh.get_points()) + + var result_data:MeshVectorData = new_mesh.to_mesh_vector_data() + block.mesh_vector_data = result_data + + +func do_it_intermediate(): + move_to(move_dir_normal * move_amount, true) + +func do_it(): + move_to(move_dir_normal * move_amount, false) + +func undo_it(): + if deleted: + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + + var blocks_root:Node = builder.get_node(blocks_root_path) + blocks_root.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.mesh_vector_data = tracked_block_data + block.name = block_name + block.selected = block_selected + + deleted = false + return + + move_to(Vector3.ZERO, false) + diff --git a/addons/cyclops_level_builder/commands/cmd_move_faces.gd b/addons/cyclops_level_builder/commands/cmd_move_faces.gd new file mode 100644 index 0000000..75c9308 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_move_faces.gd @@ -0,0 +1,144 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMoveFaces +extends CyclopsCommand + +class BlockFaceChanges extends RefCounted: + var block_path:NodePath + var face_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +var move_offset:Vector3 = Vector3.ZERO + +#Private +var block_map:Dictionary = {} + + +func add_face(block_path:NodePath, index:int): +# print("Adding face %s %s" % [block_path, index]) + add_faces(block_path, [index]) + +func add_faces(block_path:NodePath, indices:Array[int]): + var changes:BlockFaceChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_indices.has(index): + changes.face_indices.append(index) + + +func _init(): + command_name = "Move faces" + +func do_it(): +# print("cmd move edges- DO IT") + + for block_path in block_map.keys(): + +# print("%s" % block_path) + + var block:CyclopsBlock = builder.get_node(block_path) + var rec:BlockFaceChanges = block_map[block_path] + + var w2l:Transform3D = block.global_transform.affine_inverse() + var move_offset_local:Vector3 = w2l.basis * move_offset +# print("rec %s" % rec) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + var vert_indices:PackedInt32Array + for f_index in rec.face_indices: + var f:ConvexVolume.FaceInfo = vol.faces[f_index] + for v_idx in f.vertex_indices: + if !vert_indices.has(v_idx): + vert_indices.append(v_idx) + + for v_idx in vert_indices: + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + v.point += move_offset_local + + block.mesh_vector_data = vol.to_mesh_vector_data() + +#### +# print("init done") + + #var new_points:PackedVector3Array + #var new_sel_centroids:PackedVector3Array + #var moved_vert_indices:Array[int] = [] + #for face_index in rec.face_indices: + #var f:ConvexVolume.FaceInfo = vol.faces[face_index] + #var centroid:Vector3 = f.get_centroid() +## var v0:ConvexVolume.VertexInfo = vol.vertices[e.start_index] +## var v1:ConvexVolume.VertexInfo = vol.vertices[e.end_index] + #if f.selected: + #new_sel_centroids.append(centroid + move_offset_local) + # + #for v_idx in f.vertex_indices: + #if !moved_vert_indices.has(v_idx): + #new_points.append(vol.vertices[v_idx].point + move_offset_local) + #moved_vert_indices.append(v_idx) + #else: + #for v_idx in f.vertex_indices: + #if !moved_vert_indices.has(v_idx): + #new_points.append(vol.vertices[v_idx].point + move_offset_local) + #moved_vert_indices.append(v_idx) + # + #for v_idx in vol.vertices.size(): + #if !moved_vert_indices.has(v_idx): + #new_points.append(vol.vertices[v_idx].point) + ##print("new points_ %s" % new_points) + # + #var new_vol:ConvexVolume = ConvexVolume.new() + #new_vol.init_from_points(new_points) +# + #new_vol.copy_face_attributes(vol) + ##print("new init done") + # + ##Copy selection data + #for f_idx in new_vol.faces.size(): + #var f_new:ConvexVolume.FaceInfo = new_vol.faces[f_idx] + #var centroid:Vector3 = f_new.get_centroid() +## print ("vol point %s " % v1.point) + #if new_sel_centroids.has(centroid): +## print("set sel") + #f_new.selected = true +# + #block.mesh_vector_data = new_vol.to_mesh_vector_data() + + +func undo_it(): + for block_path in block_map.keys(): + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data diff --git a/addons/cyclops_level_builder/commands/cmd_move_vertices.gd b/addons/cyclops_level_builder/commands/cmd_move_vertices.gd new file mode 100644 index 0000000..b9f9d47 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_move_vertices.gd @@ -0,0 +1,117 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMoveVertices +extends CyclopsCommand + +class BlockVertexChanges extends RefCounted: + var block_path:NodePath + var vertex_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +@export var move_offset:Vector3 = Vector3.ZERO +@export var triplanar_lock_uvs:bool = false + +#Private +var block_map:Dictionary = {} + + +func add_vertex(block_path:NodePath, index:int): + add_vertices(block_path, [index]) + +func add_vertices(block_path:NodePath, indices:Array[int]): +# print("adding vertex %s %s" % [block_path, indices]) + var changes:BlockVertexChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockVertexChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data.duplicate() + block_map[block_path] = changes + + for index in indices: + if !changes.vertex_indices.has(index): + changes.vertex_indices.append(index) + +func _init(): + command_name = "Move vertices" + +func do_it(): +# print("move verts do_it") + for block_path in block_map.keys(): + + var block:CyclopsBlock = builder.get_node(block_path) + var w2l:Transform3D = block.global_transform + w2l = w2l.affine_inverse() + var move_offset_local:Vector3 = w2l.basis * move_offset + + #print("move offset %s" % move_offset) + #print("move offset local %s" % move_offset_local) + + var rec:BlockVertexChanges = block_map[block_path] + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for v_idx in vol.vertices.size(): + if rec.vertex_indices.has(v_idx): + vol.vertices[v_idx].point += move_offset_local + + block.mesh_vector_data = vol.to_mesh_vector_data() +##### + #var selected_points:PackedVector3Array + #var new_points:PackedVector3Array + #for v_idx in vol.vertices.size(): + #if rec.vertex_indices.has(v_idx): + #var p:Vector3 = vol.vertices[v_idx].point + move_offset_local + #new_points.append(p) + #selected_points.append(p) + #else: + #new_points.append(vol.vertices[v_idx].point) + # + # + #var new_vol:ConvexVolume = ConvexVolume.new() + #new_vol.init_from_points(new_points) + # + #new_vol.copy_face_attributes(vol) + # + #for v_idx in new_vol.vertices.size(): + #var v:ConvexVolume.VertexInfo = new_vol.vertices[v_idx] +## print ("vol point %s " % v.point) + #if selected_points.has(v.point): +## print("set sel") + #v.selected = true +# + #block.mesh_vector_data = new_vol.to_mesh_vector_data() + + +func undo_it(): +# print("move verts undo_it") + for block_path in block_map.keys(): + var rec:BlockVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data diff --git a/addons/cyclops_level_builder/commands/cmd_select_blocks.gd b/addons/cyclops_level_builder/commands/cmd_select_blocks.gd new file mode 100644 index 0000000..f605e0b --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_select_blocks.gd @@ -0,0 +1,239 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSelectBlocks +extends CyclopsCommand + +#Public +var selection_type:Selection.Type = Selection.Type.REPLACE + +var block_paths:Array[NodePath] + +#Private +#var tracked_selected_blocks:Array[NodePath] +#var tracked_active_blocks:Array[NodePath] +var cached_selection:Array[NodePath] +var init:bool = false + +func _init(): + command_name = "Select blocks" + +#func will_change_anything()->bool: +# +# var active_path:NodePath +# if !block_paths.is_empty(): +# active_path = block_paths[0] +## print("will change active %s" % active_path) +# +# for child in builder.get_blocks(): +# if child is CyclopsBlock: +# var block:CyclopsBlock = child +# var path:NodePath = block.get_path() +# +# match selection_type: +# Selection.Type.REPLACE: +# if block.selected != block_paths.has(path): +# return true +# +# if block.active != (path == active_path): +# return true +# +# Selection.Type.ADD: +# if block_paths.has(path): +# if !block.selected: +# return true +# if block.active != (path == active_path): +# return true +# +# Selection.Type.SUBTRACT: +# if block_paths.has(path): +# if block.selected: +# return true +# +# Selection.Type.TOGGLE: +# if !block_paths.is_empty(): +# return true +# +## print("will chage anything false") +# return false + +func will_change_anything()->bool: + var selection:EditorSelection = builder.get_editor_interface().get_selection() + + var cur_node_list:Array[Node] = selection.get_selected_nodes() + if !init: + for node in cur_node_list: + cached_selection.append(node.get_path()) + init = true + + var cur_paths:Array[NodePath] + for node in selection.get_selected_nodes(): + cur_paths.append(node.get_path()) + + if selection_type == Selection.Type.REPLACE: + if cur_paths.size() != block_paths.size(): + return true + for i in cur_paths.size(): + if cur_paths[i] != block_paths[i]: + return true + return false + + + elif selection_type == Selection.Type.ADD: + for path in block_paths: + if !cur_paths.has(path): + return true + return false + + elif selection_type == Selection.Type.SUBTRACT: + for path in block_paths: + if cur_paths.has(path): + return true + return false + + elif selection_type == Selection.Type.TOGGLE: + if !block_paths.is_empty(): + return true + return false + + return false + +func do_it(): + var selection:EditorSelection = builder.get_editor_interface().get_selection() + + var cur_node_list:Array[Node] = selection.get_selected_nodes() + if !init: + cached_selection = cur_node_list.duplicate() + init = true + + var cur_paths:Array[NodePath] + for node in selection.get_selected_nodes(): + cur_paths.append(node.get_path()) + + if selection_type == Selection.Type.REPLACE: + selection.clear() + for path in block_paths: + var node:Node = builder.get_node(path) + selection.add_node(node) + + elif selection_type == Selection.Type.ADD: + for path in block_paths: + if !cur_paths.has(path): + var node:Node = builder.get_node(path) + selection.add_node(node) + + elif selection_type == Selection.Type.SUBTRACT: + for path in block_paths: + if cur_paths.has(path): + var node:Node = builder.get_node(path) + selection.remove_node(node) + + elif selection_type == Selection.Type.TOGGLE: + for path in block_paths: + var node:Node = builder.get_node(path) + + if cur_paths.has(path): + selection.remove_node(node) + else: + selection.add_node(node) + + +#func do_it_old(): +## print("sel verts do_it") +# +# #Cache state +# tracked_selected_blocks.clear() +# tracked_active_blocks.clear() +# +# +# var active_block:CyclopsBlock = builder.get_active_block() +# tracked_active_blocks.append(active_block.get_path()) +# +# for child in builder.get_selected_blocks(): +# var block:CyclopsBlock = child +# tracked_selected_blocks.append(block.get_path()) +# +# #Do selection +# var active_path:NodePath +# if !block_paths.is_empty(): +# active_path = block_paths[0] +# +# #print("do_it active %s" % active_path) +## print("Setting active %s" % active_path) +# for child in builder.get_blocks(): +# var block:CyclopsBlock = child +# var path:NodePath = block.get_path() +# +# match selection_type: +# Selection.Type.REPLACE: +# block.selected = block_paths.has(path) +# block.active = path == active_path +# Selection.Type.ADD: +# if block_paths.has(path): +# block.selected = true +# block.active = path == active_path +# Selection.Type.SUBTRACT: +# if block_paths.has(path): +# block.selected = false +# block.active = false +# Selection.Type.TOGGLE: +# #print("Check block %s" % path) +# #print("act %s sel %s" % [block.active, block.selected]) +# if path == active_path: +# #print("Match active") +# if !block.active: +# #print("Setting active %s" % block.name) +# block.active = true +# block.selected = true +# else: +# #print("Clearing active %s" % block.name) +# block.active = false +# block.selected = false +# else: +# if block_paths.has(path): +# #print("Setting sel") +# block.selected = !block.selected +# block.active = false +# +# builder.selection_changed.emit() + +func undo_it(): + var selection:EditorSelection = builder.get_editor_interface().get_selection() + selection.clear() + + for path in cached_selection: + var node:Node = builder.get_node(path) + selection.add_node(node) + +#func undo_it(): +# +# for child in builder.get_blocks(): +# if child is CyclopsBlock: +# var block:CyclopsBlock = child +# var path:NodePath = block.get_path() +# +# block.selected = tracked_selected_blocks.has(path) +# block.active = tracked_active_blocks.has(path) +# +# builder.selection_changed.emit() diff --git a/addons/cyclops_level_builder/commands/cmd_select_edges.gd b/addons/cyclops_level_builder/commands/cmd_select_edges.gd new file mode 100644 index 0000000..6d4c719 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_select_edges.gd @@ -0,0 +1,176 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSelectEdges +extends CyclopsCommand + +class BlockEdgeChanges extends RefCounted: + var block_path:NodePath + var edge_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +var selection_type:Selection.Type = Selection.Type.REPLACE + +#Private +var block_map:Dictionary = {} + + +func add_edge(block_path:NodePath, index:int): + add_edges(block_path, [index]) + +func add_edges(block_path:NodePath, indices:Array[int]): + var changes:BlockEdgeChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockEdgeChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.edge_indices.has(index): + changes.edge_indices.append(index) + + +func _init(): + command_name = "Select edges" + +func will_change_anything()->bool: + for block_path in block_map.keys(): + #print("path %s" % node_path) + + var rec:BlockEdgeChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + if !rec.edge_indices.is_empty(): + if vol.active_edge != rec.edge_indices[0]: + return true + + match selection_type: + Selection.Type.REPLACE: + for e_idx in vol.edges.size(): + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if e.selected != rec.edge_indices.has(e_idx): + #print("will change SREP") + return true + Selection.Type.ADD: + for e_idx in rec.edge_indices: + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if rec.edge_indices.has(e_idx): + if !e.selected: + #print("will change ADD") + return true + Selection.Type.SUBTRACT: + for e_idx in rec.edge_indices: + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if rec.edge_indices.has(e_idx): + if e.selected: + #print("will change SUB") + return true + Selection.Type.TOGGLE: + #print("will change TOG") + return true + + return false + +func do_it(): +# print("sel edges do_it") + + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockEdgeChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + if !rec.edge_indices.is_empty(): + var active_index:int = rec.edge_indices[0] + match selection_type: + Selection.Type.REPLACE: + vol.active_edge = active_index + Selection.Type.ADD: + vol.active_edge = active_index + Selection.Type.SUBTRACT: + if rec.edge_indices.has(vol.active_edge): + vol.active_edge = -1 + Selection.Type.TOGGLE: + if rec.edge_indices.has(vol.active_edge): + vol.active_edge = -1 + elif !vol.edges[active_index].selected: + vol.active_edge = active_index + + match selection_type: + Selection.Type.REPLACE: + for e_idx in vol.edges.size(): + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + e.selected = rec.edge_indices.has(e_idx) + + Selection.Type.ADD: + for e_idx in vol.edges.size(): + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if rec.edge_indices.has(e_idx): + e.selected = true + + Selection.Type.SUBTRACT: + for e_idx in vol.edges.size(): + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if rec.edge_indices.has(e_idx): + e.selected = false + + Selection.Type.TOGGLE: + for e_idx in vol.edges.size(): + var e:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if rec.edge_indices.has(e_idx): + #print("flipping %s" % e.selected) + e.selected = !e.selected + + if vol.active_edge != -1: + if vol.active_edge >= vol.edges.size() || !vol.edges[vol.active_edge].selected: + vol.active_edge = -1 + + block.mesh_vector_data = vol.to_mesh_vector_data() + + builder.selection_changed.emit() + +func undo_it(): +# print("sel verts undo_it") + #print("sel vert undo_it()") + for block_path in block_map.keys(): + var rec:BlockEdgeChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + + + + diff --git a/addons/cyclops_level_builder/commands/cmd_select_faces.gd b/addons/cyclops_level_builder/commands/cmd_select_faces.gd new file mode 100644 index 0000000..8a6b296 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_select_faces.gd @@ -0,0 +1,168 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSelectFaces +extends CyclopsCommand + +class BlockFaceChanges extends RefCounted: + var block_path:NodePath + var face_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +var selection_type:Selection.Type = Selection.Type.REPLACE + +#Private +var block_map:Dictionary = {} + +func add_face(block_path:NodePath, index:int): + add_faces(block_path, [index]) + +func add_faces(block_path:NodePath, indices:Array[int]): + var changes:BlockFaceChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_indices.has(index): + changes.face_indices.append(index) + + +func _init(): + command_name = "Select faces" + +func will_change_anything()->bool: + for block_path in block_map.keys(): + #print("path %s" % node_path) + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + +# var active_idx:int = -1 + if !rec.face_indices.is_empty(): + if vol.active_face != rec.face_indices[0]: + return true + + match selection_type: + Selection.Type.REPLACE: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.selected != rec.face_indices.has(f_idx): + return true + Selection.Type.ADD: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if rec.face_indices.has(f_idx): + if !f.selected: + return true + Selection.Type.SUBTRACT: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if rec.face_indices.has(f_idx): + if f.selected: + return true + Selection.Type.TOGGLE: + return true + + return false + +func do_it(): + #print("sel verts do_it") + #print("sel vert do_it()") + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + if !rec.face_indices.is_empty(): + var active_index:int = rec.face_indices[0] + match selection_type: + Selection.Type.REPLACE: + vol.active_face = active_index + Selection.Type.ADD: + vol.active_face = active_index + Selection.Type.SUBTRACT: + if rec.face_indices.has(vol.active_face): + vol.active_face = -1 + Selection.Type.TOGGLE: + if rec.face_indices.has(vol.active_face): + vol.active_face = -1 + elif !vol.faces[active_index].selected: + vol.active_face = active_index + + +# print("face active index %s" % active_idx) + + match selection_type: + Selection.Type.REPLACE: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + f.selected = rec.face_indices.has(f_idx) + + Selection.Type.ADD: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if rec.face_indices.has(f_idx): + f.selected = true + + Selection.Type.SUBTRACT: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if rec.face_indices.has(f_idx): + f.selected = false + + Selection.Type.TOGGLE: + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if rec.face_indices.has(f_idx): + f.selected = !f.selected + + if vol.active_face != -1: + if vol.active_face >= vol.faces.size() || !vol.faces[vol.active_face].selected: + vol.active_face = -1 + + block.mesh_vector_data = vol.to_mesh_vector_data() + builder.selection_changed.emit() + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cmd_select_vertices.gd b/addons/cyclops_level_builder/commands/cmd_select_vertices.gd new file mode 100644 index 0000000..d128a92 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_select_vertices.gd @@ -0,0 +1,174 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSelectVertices +extends CyclopsCommand + +class BlockVertexChanges extends RefCounted: + var block_path:NodePath + var vertex_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +#Public +var selection_type:Selection.Type = Selection.Type.REPLACE + +#Private +var block_map:Dictionary = {} + + + + +func add_vertex(block_path:NodePath, index:int): + add_vertices(block_path, [index]) + +func add_vertices(block_path:NodePath, indices:Array[int]): + var changes:BlockVertexChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockVertexChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.vertex_indices.has(index): + changes.vertex_indices.append(index) + + +func _init(): + command_name = "Select vertices" + + +func will_change_anything()->bool: + for block_path in block_map.keys(): + #print("path %s" % node_path) + + var rec:BlockVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + if !rec.vertex_indices.is_empty(): + if vol.active_vertex != rec.vertex_indices[0]: + return true + + match selection_type: + Selection.Type.REPLACE: + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if v.selected != rec.vertex_indices.has(v_idx): + return true + Selection.Type.ADD: + for v_idx in rec.vertex_indices: + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if rec.vertex_indices.has(v_idx): + if !v.selected: + return true + Selection.Type.SUBTRACT: + for v_idx in rec.vertex_indices: + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if rec.vertex_indices.has(v_idx): + if v.selected: + return true + Selection.Type.TOGGLE: + return true + + return false + + +func do_it(): +# print("sel verts do_it") + for block_path in block_map.keys(): + #print("path %s" % node_path) + + var rec:BlockVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + if !rec.vertex_indices.is_empty(): + var active_index:int = rec.vertex_indices[0] + #print("active_index ", active_index) + + match selection_type: + Selection.Type.REPLACE: + vol.active_vertex = active_index + Selection.Type.ADD: + vol.active_vertex = active_index + Selection.Type.SUBTRACT: + if rec.vertex_indices.has(vol.active_vertex): + vol.active_vertex = -1 + Selection.Type.TOGGLE: + if rec.vertex_indices.has(vol.active_vertex): + vol.active_vertex = -1 + elif !vol.vertices[active_index].selected: + vol.active_vertex = active_index + else: + if selection_type == Selection.Type.REPLACE: + vol.active_vertex = -1 + + match selection_type: + Selection.Type.REPLACE: + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + v.selected = rec.vertex_indices.has(v_idx) + + Selection.Type.ADD: + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if rec.vertex_indices.has(v_idx): + v.selected = true + + Selection.Type.SUBTRACT: + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if rec.vertex_indices.has(v_idx): + v.selected = false + + Selection.Type.TOGGLE: + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if rec.vertex_indices.has(v_idx): + v.selected = !v.selected + + vol.update_edge_and_face_selection_from_vertices() + #print("vol.active_vertex ", vol.active_vertex) + block.mesh_vector_data = vol.to_mesh_vector_data() + #print("block.mesh_vector_data.active_vertex ", block.mesh_vector_data.active_vertex) + + builder.selection_changed.emit() + +func undo_it(): +# print("sel verts undo_it") + #print("sel vert undo_it()") + for block_path in block_map.keys(): + var rec:BlockVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() diff --git a/addons/cyclops_level_builder/commands/cmd_set_face_color.gd b/addons/cyclops_level_builder/commands/cmd_set_face_color.gd new file mode 100644 index 0000000..140c086 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_set_face_color.gd @@ -0,0 +1,112 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSetFaceColor +extends CyclopsCommand + + +class BlockFaceChanges extends RefCounted: + var block_path:NodePath + var face_indices:Array[int] + var tracked_block_data:MeshVectorData + +var color:Color = Color.WHITE + +#Private +var block_map:Dictionary = {} + +func add_face(block_path:NodePath, index:int): + add_faces(block_path, [index]) + +func add_faces(block_path:NodePath, indices:Array[int]): +# print("adding_face %s %s" % [block_path, indices]) + var changes:BlockFaceChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_indices.has(index): + changes.face_indices.append(index) + + +func _init(): + command_name = "Set Face Color" + +func will_change_anything()->bool: +# print("CommandSetUvTransform will_change_anything") + for block_path in block_map.keys(): + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.color != color: + return true + + return false + + +func do_it(): + #print("sel verts do_it") +# print("sel uv_transform do_it()") + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + +# print("block_path %s" % block_path) + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): +# print("face_idx %s" % f_idx) + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + f.color = color + + block.mesh_vector_data = vol.to_mesh_vector_data() + builder.selection_changed.emit() + + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cmd_set_face_uv_transform.gd b/addons/cyclops_level_builder/commands/cmd_set_face_uv_transform.gd new file mode 100644 index 0000000..3c63748 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_set_face_uv_transform.gd @@ -0,0 +1,114 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSetFaceUvTransform +extends CyclopsCommand + + +class BlockFaceChanges extends RefCounted: + var block_path:NodePath + var face_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +var uv_transform:Transform2D = Transform2D.IDENTITY +var visible:bool = true +var color:Color = Color.WHITE + +#Private +var block_map:Dictionary = {} + +func add_face(block_path:NodePath, index:int): + add_faces(block_path, [index]) + +func add_faces(block_path:NodePath, indices:Array[int]): +# print("adding_face %s %s" % [block_path, indices]) + var changes:BlockFaceChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_indices.has(index): + changes.face_indices.append(index) + + +func _init(): + command_name = "Set Face Uv Transform" + +func will_change_anything()->bool: +# print("CommandSetUvTransform will_change_anything") + for block_path in block_map.keys(): + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.uv_transform != uv_transform: + return true + + return false + + +func do_it(): + #print("sel verts do_it") +# print("sel uv_transform do_it()") + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + +# print("block_path %s" % block_path) + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): +# print("face_idx %s" % f_idx) + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + f.uv_transform = uv_transform + + block.mesh_vector_data = vol.to_mesh_vector_data() + builder.selection_changed.emit() + + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cmd_set_face_vertex_color.gd b/addons/cyclops_level_builder/commands/cmd_set_face_vertex_color.gd new file mode 100644 index 0000000..1a99f68 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_set_face_vertex_color.gd @@ -0,0 +1,121 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSetFaceVertexColor +extends CyclopsCommand + + +class BlockFaceVertexChanges extends RefCounted: + var block_path:NodePath + var face_vert_indices:Array[int] + var tracked_block_data:MeshVectorData + +var color:Color = Color.WHITE +var strength:float = 1 + +#Private +var block_map:Dictionary = {} + +#class StrokePoint: + #var position:Vector3 + #var pressure:float + # +#var stroke_points:Array[StrokePoint] + +func add_face_vertex(block_path:NodePath, index:int): + add_face_vertices(block_path, [index]) + +func add_face_vertices(block_path:NodePath, indices:Array[int]): +# print("adding_face %s %s" % [block_path, indices]) + var changes:BlockFaceVertexChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceVertexChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vertex_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_vert_indices.has(index): + changes.face_vert_indices.append(index) + + +func _init(): + command_name = "Set Face Vertex Color" + +func will_change_anything()->bool: + return block_map.size() > 0 +# print("CommandSetUvTransform will_change_anything") + #for block_path in block_map.keys(): +# + #var rec:BlockFaceVertexChanges = block_map[block_path] + #var block:CyclopsBlock = builder.get_node(block_path) + # + #var vol:ConvexVolume = ConvexVolume.new() + #vol.init_from_convex_block_data(rec.tracked_block_data) +# + #for f_idx in vol.faces.size(): + #if rec.face_indices.has(f_idx): + #var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + #if f.color != color: + #return true +# + #return false + + + +func do_it(): + #print("sel verts do_it") + #print("sel face vert color do_it()") + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockFaceVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + #print("block_path %s" % block_path) + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for fv_idx in vol.face_vertices.size(): + if rec.face_vert_indices.has(fv_idx): + #print("face_v_idx %s" % fv_idx) + var fv:ConvexVolume.FaceVertexInfo = vol.face_vertices[fv_idx] + fv.color = MathUtil.blend_colors_ignore_alpha(color, fv.color, strength) + + block.mesh_vector_data = vol.to_mesh_vector_data() + builder.selection_changed.emit() + + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var rec:BlockFaceVertexChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cmd_set_face_visible.gd b/addons/cyclops_level_builder/commands/cmd_set_face_visible.gd new file mode 100644 index 0000000..478298b --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_set_face_visible.gd @@ -0,0 +1,112 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSetFaceVisible +extends CyclopsCommand + + +class BlockFaceChanges extends RefCounted: + var block_path:NodePath + var face_indices:Array[int] = [] + var tracked_block_data:MeshVectorData + +var visible:bool = true + +#Private +var block_map:Dictionary = {} + +func add_face(block_path:NodePath, index:int): + add_faces(block_path, [index]) + +func add_faces(block_path:NodePath, indices:Array[int]): +# print("adding_face %s %s" % [block_path, indices]) + var changes:BlockFaceChanges + if block_map.has(block_path): + changes = block_map[block_path] + else: + changes = BlockFaceChanges.new() + changes.block_path = block_path + var block:CyclopsBlock = builder.get_node(block_path) + changes.tracked_block_data = block.mesh_vector_data + block_map[block_path] = changes + + for index in indices: + if !changes.face_indices.has(index): + changes.face_indices.append(index) + + +func _init(): + command_name = "Set Face Properties" + +func will_change_anything()->bool: +# print("CommandSetUvTransform will_change_anything") + for block_path in block_map.keys(): + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.visible != visible: + return true + + return false + + +func do_it(): + #print("sel verts do_it") +# print("sel uv_transform do_it()") + for block_path in block_map.keys(): +# print("path %s" % block_path) + + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + +# print("block_path %s" % block_path) + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(rec.tracked_block_data) + + for f_idx in vol.faces.size(): + if rec.face_indices.has(f_idx): +# print("face_idx %s" % f_idx) + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + f.visible = visible + + block.mesh_vector_data = vol.to_mesh_vector_data() + builder.selection_changed.emit() + + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var rec:BlockFaceChanges = block_map[block_path] + var block:CyclopsBlock = builder.get_node(block_path) + block.mesh_vector_data = rec.tracked_block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cmd_set_material.gd b/addons/cyclops_level_builder/commands/cmd_set_material.gd new file mode 100644 index 0000000..e3ad9e3 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_set_material.gd @@ -0,0 +1,158 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSetMaterial +extends CyclopsCommand + +class Target extends RefCounted: + var block_path:NodePath + var face_indices:PackedInt32Array + +class BlockCache extends RefCounted: + var path:NodePath + var data:MeshVectorData + var materials:Array[Material] + +#Public +var setting_material:bool = true +var material_path:String + +var setting_color:bool = false +var color:Color = Color.WHITE + +var setting_visibility:bool = false +var visibility:bool = true + +var painting_uv:bool = false +var uv_matrix:Transform2D = Transform2D.IDENTITY + +#Private +var target_list:Array[Target] = [] + +var cache_list:Array[BlockCache] = [] + +func add_target(block_path:NodePath, face_indices:PackedInt32Array): +# print("add target %s %s" % [block_path.get_name(block_path.get_name_count() - 1), face_indices]) + var target:Target = null + for t in target_list: + if t.block_path == block_path: + target = t + break + + if !target: + target = Target.new() + target.block_path = block_path + target_list.append(target) + + for f_idx in face_indices: + if !target.face_indices.has(f_idx): + target.face_indices.append(f_idx) + + +func make_cache(): + cache_list = [] + + for t in target_list: + var cache:BlockCache = BlockCache.new() + var block:CyclopsBlock = builder.get_node(t.block_path) + + cache.path = block.get_path() + cache.data = block.mesh_vector_data + cache.materials = block.materials.duplicate() + + cache_list.append(cache) + +func will_change_anything()->bool: + return !target_list.is_empty() + +func _init(): + command_name = "Set material" + +func do_it(): + make_cache() + + for tgt in target_list: + var block:CyclopsBlock = builder.get_node(tgt.block_path) + + var data:MeshVectorData = block.mesh_vector_data + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(data) + + if setting_material: + + var target_material:Material = null + if ResourceLoader.exists(material_path, "Material"): + #print("loading material ", material_path) + var mat = load(material_path) + target_material = mat if mat is Material else null + + var mat_reindex:Dictionary + var mat_list_reduced:Array[Material] + + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + + var mat_to_apply:Material + + if tgt.face_indices.has(f_idx): + mat_to_apply = target_material + else: + mat_to_apply = null if f.material_id == -1 else block.materials[f.material_id] + + if !mat_to_apply: + f.material_id = -1 + elif !mat_reindex.has(mat_to_apply): + var new_idx = mat_reindex.size() + mat_reindex[mat_to_apply] = new_idx + mat_list_reduced.append(mat_to_apply) + f.material_id = new_idx + else: + f.material_id = mat_reindex[mat_to_apply] + + block.materials = mat_list_reduced + + #Set other properties + for f_idx in vol.faces.size(): + if tgt.face_indices.has(f_idx): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if setting_color: + f.color = color + for v_idx in f.vertex_indices: + var fv:ConvexVolume.FaceVertexInfo = \ + vol.get_face_vertex(f_idx, v_idx) + fv.color = color + if setting_visibility: + f.visible = visibility + if painting_uv: + f.uv_transform = uv_matrix + + block.mesh_vector_data = vol.to_mesh_vector_data() + + +func undo_it(): + for cache in cache_list: + var block:CyclopsBlock = builder.get_node(cache.path) + block.materials = cache.materials.duplicate() + block.mesh_vector_data = cache.data + diff --git a/addons/cyclops_level_builder/commands/cmd_snap_to_grid.gd b/addons/cyclops_level_builder/commands/cmd_snap_to_grid.gd new file mode 100644 index 0000000..8d9a458 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_snap_to_grid.gd @@ -0,0 +1,84 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSnapToGrid +extends CyclopsCommand + +class TrackedInfo extends RefCounted: + var data:MeshVectorData + + +#Private +var blocks_to_move:Array[NodePath] +var tracked_block_data:Array[TrackedInfo] + + +func _init(): + command_name = "Snap to grid" + + +#Add blocks to be moved here +func add_block(block_path:NodePath): + blocks_to_move.append(block_path) + + var block:CyclopsBlock = builder.get_node(block_path) + #tracked_blocks.append(block) + var info:TrackedInfo = TrackedInfo.new() + info.data = block.mesh_vector_data.duplicate() +# info.materials = block.materials + tracked_block_data.append(info) + + +func do_it(): + + for i in blocks_to_move.size(): + var block:CyclopsBlock = builder.get_node(blocks_to_move[i]) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(tracked_block_data[i].data) + + var points_new:PackedVector3Array + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + var p_snap:Vector3 = builder.get_snapping_manager().snap_point( + block.global_transform * v.point, SnappingQuery.new(null, [])) + points_new.append(p_snap) + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(points_new) + new_vol.transform(block.global_transform.affine_inverse()) + + + new_vol.copy_face_attributes(vol) + + block.mesh_vector_data = new_vol.to_mesh_vector_data() + +func undo_it(): + for i in blocks_to_move.size(): + var block:CyclopsBlock = builder.get_node(blocks_to_move[i]) + + block.mesh_vector_data = tracked_block_data[i].data + + + diff --git a/addons/cyclops_level_builder/commands/cmd_subtract_block.gd b/addons/cyclops_level_builder/commands/cmd_subtract_block.gd new file mode 100644 index 0000000..71b730a --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_subtract_block.gd @@ -0,0 +1,158 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandSubtractBlock +extends CyclopsCommand + +class NewBlockInfo extends RefCounted: + var data:MeshVectorData + var materials:Array[Material] + var path:NodePath + #var centroid:Vector3 + var xform:Transform3D + +#Public +var block_paths:Array[NodePath] +var block_to_subtract_path:NodePath +var block_name_prefix:String = "Block_" + +#Private +var start_blocks:Array[TrackedBlock] +var subtracted_block_cache:TrackedBlock +var added_blocks:Array[NewBlockInfo] + +func _init(): + command_name = "Subtract blocks" + +func restore_tracked_block(tracked:TrackedBlock)->CyclopsBlock: + var parent = builder.get_node(tracked.path_parent) + + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + block.mesh_vector_data = tracked.data + block.materials = tracked.materials + block.name = tracked.name +# block.selected = tracked.selected + block.global_transform = tracked.world_xform + block.collision_type = tracked.collision_type + block.collision_layer = tracked.collision_layers + block.collision_mask = tracked.collision_mask + + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + + if tracked.selected: + var selection:EditorSelection = builder.get_editor_interface().get_selection() + selection.add_node(block) + + return block + +func will_change_anything()->bool: + var subtrahend_block:CyclopsBlock = builder.get_node(block_to_subtract_path) + var subtrahend_vol:ConvexVolume = subtrahend_block.control_mesh + subtrahend_vol = subtrahend_vol.transformed(subtrahend_block.global_transform) + + if block_paths.is_empty(): + return false + + for minuend_path in block_paths: + var minuend_block:CyclopsBlock = builder.get_node(minuend_path) + var minuend_vol:ConvexVolume = minuend_block.control_mesh + minuend_vol = minuend_vol.transformed(minuend_block.global_transform) + + if minuend_vol.intersects_convex_volume(subtrahend_vol): + return true + + return false + +func do_it(): + var subtrahend_block:CyclopsBlock = builder.get_node(block_to_subtract_path) + var snap_to_grid_util:SnapToGridUtil = CyclopsAutoload.calc_snap_to_grid_util() + + if start_blocks.is_empty(): + var subtrahend_vol:ConvexVolume = subtrahend_block.control_mesh + subtracted_block_cache = TrackedBlock.new(subtrahend_block) + subtrahend_vol = subtrahend_vol.transformed(subtrahend_block.global_transform) + var subtra_xform_inv:Transform3D = subtrahend_block.global_transform.affine_inverse() + + for path in block_paths: + var block:CyclopsBlock = builder.get_node(path) + + var minuend_vol:ConvexVolume = block.control_mesh + minuend_vol = minuend_vol.transformed(block.global_transform) + if !minuend_vol.intersects_convex_volume(subtrahend_vol): + continue + + var tracker:TrackedBlock = TrackedBlock.new(block) + start_blocks.append(tracker) + + var fragments:Array[ConvexVolume] = minuend_vol.subtract(subtrahend_vol) + + for f in fragments: + f.copy_face_attributes(minuend_vol) + #var centroid:Vector3 = f.get_centroid() + #centroid = snap_to_grid_util.snap_point(centroid) + #f.translate(-centroid) + f = f.transformed(block.global_transform.affine_inverse()) + + var block_info:NewBlockInfo = NewBlockInfo.new() + block_info.data = f.to_mesh_vector_data() + block_info.materials = block.materials + block_info.xform = block.global_transform + #block_info.centroid = centroid + added_blocks.append(block_info) + + #Delete source blocks + for block_info in start_blocks: + var del_block:CyclopsBlock = builder.get_node(block_info.path) + del_block.queue_free() + + subtrahend_block.queue_free() + + #Create blocks + for info in added_blocks: + var block:CyclopsBlock = preload("../nodes/cyclops_block.gd").new() + var parent:Node = builder.get_node(start_blocks[0].path_parent) + parent.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = GeneralUtil.find_unique_name(parent, block_name_prefix) + block.mesh_vector_data = info.data + block.materials = info.materials +# block.global_transform = Transform3D.IDENTITY.translated(info.centroid) + block.global_transform = info.xform + + info.path = block.get_path() + + + +func undo_it(): + + for info in added_blocks: + var added_block:CyclopsBlock = builder.get_node(info.path) + added_block.queue_free() + + restore_tracked_block(subtracted_block_cache) + + for tracked in start_blocks: + restore_tracked_block(tracked) + diff --git a/addons/cyclops_level_builder/commands/cmd_transform_blocks.gd b/addons/cyclops_level_builder/commands/cmd_transform_blocks.gd new file mode 100644 index 0000000..132fd7d --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_transform_blocks.gd @@ -0,0 +1,76 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandTransformBlocks +extends CyclopsCommand + +#Public data to set before activating command +var transform:Transform3D +var lock_uvs:bool = false + +#Private +var tracked_blocks:Array[TrackedBlock] + +func _init(): + command_name = "Transform blocks" + +#Add blocks to be moved here +func add_block(block_path:NodePath): + + var block:CyclopsBlock = builder.get_node(block_path) + var tracked:TrackedBlock = TrackedBlock.new(block) + tracked_blocks.append(tracked) + +#Moves all blocks from the start position by this amount +func move_to(offset:Vector3): + for tracked in tracked_blocks: + var block:CyclopsBlock = builder.get_node(tracked.path) + var w_init_xform:Transform3D = tracked.world_xform + + var new_w_xform:Transform3D = w_init_xform.translated(offset) + block.global_transform = new_w_xform + + +func do_it(): + for tracked in tracked_blocks: + var block:CyclopsBlock = builder.get_node(tracked.path) + var w_init_xform:Transform3D = tracked.world_xform + + var new_w_xform:Transform3D = transform * w_init_xform + block.global_transform = new_w_xform + + if !lock_uvs: + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(tracked.data) + + var uv_xform:Transform3D = transform.affine_inverse() + vol.transform_uvs(uv_xform) + + block.mesh_vector_data = vol.to_mesh_vector_data() + +func undo_it(): + for tracked in tracked_blocks: + var block:CyclopsBlock = builder.get_node(tracked.path) + block.global_transform = tracked.world_xform + diff --git a/addons/cyclops_level_builder/commands/cmd_transform_vertices.gd b/addons/cyclops_level_builder/commands/cmd_transform_vertices.gd new file mode 100644 index 0000000..c94510b --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_transform_vertices.gd @@ -0,0 +1,73 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +#Applied trnasorm of points in local space + +@tool +class_name CommandTransformVertices +extends CyclopsCommand + +class TrackedInfo extends RefCounted: + var data:MeshVectorData +# var materials:Array[Material] + +#Local space transform of points +var transform:Transform3D +var lock_uvs:bool = false + +#Private +var blocks_to_move:Array[NodePath] +var tracked_block_data:Array[TrackedInfo] + +func _init(): + command_name = "Transform vertices" + +#Add blocks to be moved here +func add_block(block_path:NodePath): + blocks_to_move.append(block_path) + + var block:CyclopsBlock = builder.get_node(block_path) + #tracked_blocks.append(block) + var info:TrackedInfo = TrackedInfo.new() + info.data = block.mesh_vector_data.duplicate() +# info.materials = block.materials + tracked_block_data.append(info) + +#Moves all blocks from the start position by this amount +func apply_transform(xform:Transform3D): + for i in blocks_to_move.size(): + var block:CyclopsBlock = builder.get_node(blocks_to_move[i]) + + var ctl_mesh:ConvexVolume = ConvexVolume.new() + ctl_mesh.init_from_mesh_vector_data(tracked_block_data[i].data) + ctl_mesh.transform(xform, lock_uvs) + var result_data:MeshVectorData = ctl_mesh.to_mesh_vector_data() + block.mesh_vector_data = result_data + + +func do_it(): + apply_transform(transform) + +func undo_it(): + apply_transform(Transform3D.IDENTITY) + diff --git a/addons/cyclops_level_builder/commands/cmd_vertex_paint_stroke.gd b/addons/cyclops_level_builder/commands/cmd_vertex_paint_stroke.gd new file mode 100644 index 0000000..c00e257 --- /dev/null +++ b/addons/cyclops_level_builder/commands/cmd_vertex_paint_stroke.gd @@ -0,0 +1,130 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandVertexPaintStroke +extends CyclopsCommand + +@export var color:Color = Color.WHITE +@export var strength:float = 1 +@export var radius:float = 1 +@export var falloff_curve:Curve + +enum MaskType { NONE, VERTICES, FACES } +@export var mask:MaskType = MaskType.NONE + +#Private +var block_map:Dictionary = {} +#var block_tgt_map:Dictionary = {} + + +var pen_stroke:PenStroke = PenStroke.new() + +func append_block(block_path:NodePath): + if block_map.has(block_path): + return + + var block:CyclopsBlock = builder.get_node(block_path) + + #print("stroing block faces ", block.block_data.face_vertex_face_index) + + block_map[block_path] = block.mesh_vector_data.duplicate(true) + #print("stroing block faces ", block.block_data.face_vertex_face_index) +# block_tgt_map[block_path] = block.block_data.duplicate(true) + +func append_stroke_point(position:Vector3, pressure:float = 1): + pen_stroke.append_stroke_point(position, pressure) + #print("--pen_stroke ", pen_stroke.stroke_points) + +func _init(): + command_name = "Paint Vertex Color Stroke" + +func will_change_anything()->bool: + return !(block_map.is_empty() || pen_stroke.is_empty()) + +func do_it(): + #print("sel verts do_it") +# print("sel uv_transform do_it()") + + #print("stroke pts ", str(pen_stroke.stroke_points)) + var stroke_resamp:PenStroke = pen_stroke.resample_points(radius * .1) + #print("stroke resamp pts ", str(stroke_resamp.stroke_points)) + + for block_path in block_map.keys(): + + var block:CyclopsBlock = builder.get_node(block_path) + var w2l:Transform3D = block.global_transform.affine_inverse() + #print("painting block ", block.name) + + var block_data:MeshVectorData = block_map[block_path] + #print("block_data raw faces ", block_data.face_vertex_face_index) + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(block_data) + + #Apply stroke + for stroke_pt in stroke_resamp.stroke_points: + var pos_local:Vector3 = w2l * stroke_pt.position + for fv in vol.face_vertices: + var v:ConvexVolume.VertexInfo = vol.vertices[fv.vertex_index] + var f:ConvexVolume.FaceInfo = vol.faces[fv.face_index] + + if mask == MaskType.FACES: + if !f.selected: + continue + elif mask == MaskType.VERTICES: + if !v.selected: + continue + + var dist:float = v.point.distance_to(pos_local) + + if dist > radius: + continue + + var falloff_frac:float = 1 - (dist / radius) + var falloff:float = falloff_curve.sample(falloff_frac) \ + if falloff_curve else 1 + + fv.color = MathUtil.blend_colors_ignore_alpha(\ + color, fv.color, strength * stroke_pt.pressure * falloff) + + #print("fv_idx ", fv.index) + #print("fv color ", fv.color) + + var new_block_data:MeshVectorData = vol.to_mesh_vector_data() + #print("new_block_data faces ", block.block_data.face_vertex_face_index) + block.mesh_vector_data = new_block_data + + builder.selection_changed.emit() + +func undo_it(): +# print("undo_it() select faces") + for block_path in block_map.keys(): + var block:CyclopsBlock = builder.get_node(block_path) + + var block_data:MeshVectorData = block_map[block_path] + + block.mesh_vector_data = block_data + + builder.selection_changed.emit() + diff --git a/addons/cyclops_level_builder/commands/cyclops_command.gd b/addons/cyclops_level_builder/commands/cyclops_command.gd new file mode 100644 index 0000000..7d66a6f --- /dev/null +++ b/addons/cyclops_level_builder/commands/cyclops_command.gd @@ -0,0 +1,77 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CyclopsCommand +extends RefCounted + +var command_name:String = "" +var builder:CyclopsLevelBuilder + +class TrackedBlock extends RefCounted: + var path:NodePath + var path_parent:NodePath + var data:MeshVectorData + var world_xform:Transform3D + var materials:Array[Material] + var selected:bool + var name:String + var collision_type:Collision.Type + var collision_layers:int + var collision_mask:int + + func _init(block:CyclopsBlock): + path = block.get_path() + path_parent = block.get_parent().get_path() + name = block.name + data = block.mesh_vector_data.duplicate() + world_xform = block.global_transform + #selected = block.selected + materials = block.materials + collision_type = block.collision_type + collision_layers = block.collision_layer + collision_mask = block.collision_mask + +func add_to_undo_manager(undo_manager:EditorUndoRedoManager): + undo_manager.create_action(command_name, UndoRedo.MERGE_DISABLE) + undo_manager.add_do_method(self, "do_it") + undo_manager.add_undo_method(self, "undo_it") + + undo_manager.commit_action() + +func node_global_transform(node:Node)->Transform3D: + var node_parent:Node3D + while node: + if node is Node3D: + node_parent = node + break + node = node.get_parent() + + return node_parent.global_transform if node_parent else Transform3D.IDENTITY + +func do_it()->void: + pass + +func undo_it()->void: + pass + diff --git a/addons/cyclops_level_builder/commands/io/cmd_import_cyclops_file.gd b/addons/cyclops_level_builder/commands/io/cmd_import_cyclops_file.gd new file mode 100644 index 0000000..3e95d62 --- /dev/null +++ b/addons/cyclops_level_builder/commands/io/cmd_import_cyclops_file.gd @@ -0,0 +1,87 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandImportCyclopsFile +extends CyclopsCommand + +@export var file_path:String +@export var target_parent:NodePath + +var added_blocks:Array[NodePath] + +func _init(): + command_name = "Import Cyclops File" + +func will_change_anything()->bool: + return FileAccess.file_exists(file_path) + +func do_it(): + if !FileAccess.file_exists(file_path): + push_error("No such file: ", file_path) + return + + var source:String = FileAccess.get_file_as_string(file_path) + var raw = JSON.parse_string(source) + if !(raw is Dictionary): + push_error("Invalid file format: ", file_path) + return + + load_file(raw) + + pass + + +func load_file(root:Dictionary): + var loader:CyclopsFileLoader = CyclopsFileLoader.new() + loader.load(root) + + var editor_scene_root:Node = builder.get_editor_interface().get_edited_scene_root() + + + for scene_id in loader.scene_map.keys(): + var root_node_id:int = loader.scene_map[scene_id] + var loaded_scene:Node3D = loader.node_map[root_node_id] + + editor_scene_root.add_child(loaded_scene) + set_owner_recursive(loaded_scene, editor_scene_root) + + added_blocks.append(loaded_scene.get_path()) + + +func undo_it(): + for block_path in added_blocks: + var block:Node3D = builder.get_node(block_path) + block.queue_free() + + added_blocks.clear() + +func set_owner_recursive(loaded_node:Node3D, owner_node:Node3D): + loaded_node.owner = owner_node + if loaded_node is CyclopsBlock: + #Do not set owner of hidden children + return + + for child in loaded_node.get_children(): + set_owner_recursive(child, owner_node) + diff --git a/addons/cyclops_level_builder/commands/io/cmd_import_godot_meshes.gd b/addons/cyclops_level_builder/commands/io/cmd_import_godot_meshes.gd new file mode 100644 index 0000000..64a16d9 --- /dev/null +++ b/addons/cyclops_level_builder/commands/io/cmd_import_godot_meshes.gd @@ -0,0 +1,99 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandImportGodotMeshes +extends CyclopsCommand + +@export var source_nodes:Array[NodePath] +@export var target_parent:NodePath +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export var collision_layers:int = 1 +@export var collision_mask:int = 1 + +var added_blocks:Array[NodePath] + +func _init(): + command_name = "Import Godot Meshes" + +func will_change_anything()->bool: + return !target_parent.is_empty() && !source_nodes.is_empty() + +func do_it(): + var tgt_parent_node:Node = builder.get_node(target_parent) + if !tgt_parent_node || !(tgt_parent_node is Node3D): + return + + for src_path in source_nodes: + var src_node:Node = builder.get_node(src_path) + if !src_node is MeshInstance3D: + continue + + var src_mesh_inst:MeshInstance3D = src_node + if !src_mesh_inst.mesh: + continue + + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + + var blocks_root:Node3D = tgt_parent_node + blocks_root.add_child(block) + block.owner = builder.get_editor_interface().get_edited_scene_root() + block.name = src_node.name + block.global_transform = src_node.global_transform + block.collision_type = collision_type + block.collision_layer = collision_layers + block.collision_mask = collision_mask + + added_blocks.append(block.get_path()) + + var best_mat:Material + var points:PackedVector3Array + for i in src_mesh_inst.mesh.get_surface_count(): + var mat:Material = src_mesh_inst.mesh.surface_get_material(i) + if best_mat != null: + best_mat = mat + + var surface_arrs:Array = src_mesh_inst.mesh.surface_get_arrays(i) + + if surface_arrs[Mesh.ARRAY_INDEX].is_empty(): + for pt in surface_arrs[Mesh.ARRAY_VERTEX]: + points.append(pt) + else: + for idx in surface_arrs[Mesh.ARRAY_INDEX]: + points.append(surface_arrs[Mesh.ARRAY_VERTEX][idx]) + + if best_mat: + block.materials = [best_mat] + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_points(points, Transform2D.IDENTITY, 0 if best_mat else -1) + block.mesh_vector_data = vol.to_mesh_vector_data() + + +func undo_it(): + for block_path in added_blocks: + var block:CyclopsBlock = builder.get_node(block_path) + block.queue_free() + + added_blocks.clear() + diff --git a/addons/cyclops_level_builder/controls/enum_line_edit.gd b/addons/cyclops_level_builder/controls/enum_line_edit.gd new file mode 100644 index 0000000..3bc5a26 --- /dev/null +++ b/addons/cyclops_level_builder/controls/enum_line_edit.gd @@ -0,0 +1,59 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends OptionButton +class_name EnumLineEdit + +signal option_selected(index:int) + +@export var item_list:PackedStringArray: + get: + return item_list + set(value): + item_list = value + dirty = true + +var dirty:bool = true + +# Called when the node enters the scene tree for the first time. +func _ready(): + + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + clear() + + for name in item_list: + add_item(name) + + dirty = false + + + +func _on_item_selected(index): + option_selected.emit(index) + diff --git a/addons/cyclops_level_builder/controls/enum_line_edit.tscn b/addons/cyclops_level_builder/controls/enum_line_edit.tscn new file mode 100644 index 0000000..38d378a --- /dev/null +++ b/addons/cyclops_level_builder/controls/enum_line_edit.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=3 uid="uid://7ur3lovebuua"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/enum_line_edit.gd" id="1_ltl6a"] + +[node name="EnumLineEdit" type="OptionButton"] +script = ExtResource("1_ltl6a") + +[connection signal="item_selected" from="." to="." method="_on_item_selected"] diff --git a/addons/cyclops_level_builder/controls/fold_out_panel.gd b/addons/cyclops_level_builder/controls/fold_out_panel.gd new file mode 100644 index 0000000..4236156 --- /dev/null +++ b/addons/cyclops_level_builder/controls/fold_out_panel.gd @@ -0,0 +1,51 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name FoldOutPanel + +@export var open:bool = true +@export var text:String = "" + +func get_content_area(): + return %ContentArea + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + %HeaderButton.text = text + if open: + %HeaderButton.icon = preload("res://addons/cyclops_level_builder/art/icons/arrow_down.svg") + %ContentArea.visible = true + else: + %HeaderButton.icon = preload("res://addons/cyclops_level_builder/art/icons/arrow_right.svg") + %ContentArea.visible = false + + +func _on_button_pressed(): + open = !open diff --git a/addons/cyclops_level_builder/controls/fold_out_panel.tscn b/addons/cyclops_level_builder/controls/fold_out_panel.tscn new file mode 100644 index 0000000..cd5522a --- /dev/null +++ b/addons/cyclops_level_builder/controls/fold_out_panel.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=3 format=3 uid="uid://bk0eelj64x4fk"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/fold_out_panel.gd" id="1_n3mr0"] +[ext_resource type="Texture2D" uid="uid://c7c2vg6lbhmfn" path="res://addons/cyclops_level_builder/art/icons/arrow_right.svg" id="2_dwm1s"] + +[node name="FoldOutPanel" type="PanelContainer"] +offset_right = 245.0 +offset_bottom = 219.0 +script = ExtResource("1_n3mr0") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HeaderButton" type="Button" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Fold Name" +icon = ExtResource("2_dwm1s") +alignment = 0 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 8 + +[node name="ContentArea" type="PanelContainer" parent="VBoxContainer/PanelContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="pressed" from="VBoxContainer/HeaderButton" to="." method="_on_button_pressed"] diff --git a/addons/cyclops_level_builder/controls/numeric_line_edit.gd b/addons/cyclops_level_builder/controls/numeric_line_edit.gd new file mode 100644 index 0000000..f7eb7d4 --- /dev/null +++ b/addons/cyclops_level_builder/controls/numeric_line_edit.gd @@ -0,0 +1,139 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name NumbericLineEdit + +signal value_changed(value:float) + +@export var value:float: + get: + return value + set(v): + if value == v: + return + value = v + dirty = true + +@export var snap_size:float = 1 + +@export var disabled:bool = false + +var dirty:bool = true + +enum NumEditState{ IDLE, READY, DRAGGING, TEXT_EDIT } +var state:NumEditState = NumEditState.IDLE + +var mouse_down_pos:Vector2 +var drag_start_radius:float = 4 +var value_start_drag:float + +var line_input:LineEdit +var line_display:Label + +# Called when the node enters the scene tree for the first time. +func _ready(): + var foo = $HBoxContainer/line_display + var uuu = get_node("HBoxContainer/line_display") + + line_input = %line_input + line_display = %line_display + + if line_input: + line_input.visible = false + pass + +func _process(delta): + if dirty: +# print("---value " + str(value)) + if line_input: + line_input.text = format_number(value) + line_display.text = format_number(value) + + dirty = false + +func format_number(val:float)->String: + var text:String = "%.5f" % val + var idx:int = text.findn(".") + if idx != -1: + text = text.rstrip("0") + if text.right(1) == ".": + text = text.left(-1) + return text + + +func _gui_input(event): + if event is InputEventMouseButton: + var e:InputEventMouseButton = event + if e.is_pressed(): + if state == NumEditState.IDLE: + mouse_down_pos = e.position + state = NumEditState.READY + else: + if state == NumEditState.READY: + if line_input: + line_input.visible = true + line_display.visible = false + state = NumEditState.TEXT_EDIT + elif state == NumEditState.DRAGGING: + state = NumEditState.IDLE + + + accept_event() + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + if state == NumEditState.READY: + if e.position.distance_to(mouse_down_pos) >= drag_start_radius: + state = NumEditState.DRAGGING + value_start_drag = value + + elif state == NumEditState.DRAGGING: + var offset = e.position.x - mouse_down_pos.x + var new_value = value_start_drag + (offset * snap_size / 20.0) + #print("-new_value %s" % new_value) + new_value = ceil(new_value / snap_size) * snap_size + + #print("new_value %s" % new_value) + + if value != new_value: + value = new_value + value_changed.emit(value) + dirty = true + +func _on_line_edit_text_submitted(new_text): + var regex = RegEx.new() + regex.compile("^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$") + var result:RegExMatch = regex.search(new_text) + if result: +# print("found match") + value = float(new_text) + value_changed.emit(value) + + dirty = true + state = NumEditState.IDLE + if line_input: + line_input.visible = false + line_display.visible = true + diff --git a/addons/cyclops_level_builder/controls/numeric_line_edit.tscn b/addons/cyclops_level_builder/controls/numeric_line_edit.tscn new file mode 100644 index 0000000..4f53e74 --- /dev/null +++ b/addons/cyclops_level_builder/controls/numeric_line_edit.tscn @@ -0,0 +1,44 @@ +[gd_scene load_steps=5 format=3 uid="uid://diibmlqy1mpqb"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.gd" id="1_u8bpo"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o7f15"] +bg_color = Color(0.0627451, 0.0627451, 0.0627451, 1) + +[sub_resource type="Theme" id="Theme_cw2vs"] +Label/styles/normal = SubResource("StyleBoxFlat_o7f15") + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_8gfnv"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 + +[node name="numeric_line_edit" type="PanelContainer"] +offset_right = 476.0 +offset_bottom = 23.0 +script = ExtResource("1_u8bpo") +snap_size = 0.125 + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="line_input" type="LineEdit" parent="HBoxContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +text = "0" +alignment = 2 +select_all_on_focus = true + +[node name="line_display" type="Label" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme = SubResource("Theme_cw2vs") +theme_override_styles/normal = SubResource("StyleBoxEmpty_8gfnv") +text = "0" +horizontal_alignment = 2 + +[connection signal="text_submitted" from="HBoxContainer/line_input" to="." method="_on_line_edit_text_submitted"] diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.gd b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.gd new file mode 100644 index 0000000..0fa7423 --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.gd @@ -0,0 +1,66 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CheckBox +class_name LineEditorBool + +var resource:Resource: + get: + return resource + set(value): + resource = value + dirty = true + +var prop_name:String: + get: + return prop_name + set(value): + prop_name = value + dirty = true + +var dirty = true + +func update_from_resource(): + if resource: + var result = resource.get(prop_name) + if result != null: + button_pressed = result + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + update_from_resource() + dirty = false + + +func _on_toggled(button_pressed): + if resource: +# print("prop_name %s" % prop_name) +# print("button_pressed %s" % button_pressed) + resource.set(prop_name, button_pressed) diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.tscn b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.tscn new file mode 100644 index 0000000..9ece333 --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://dpncabeqiv1xo"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.gd" id="1_sn3qq"] + +[node name="CheckBox" type="CheckBox"] +offset_right = 24.0 +offset_bottom = 24.0 +script = ExtResource("1_sn3qq") + +[connection signal="toggled" from="." to="." method="_on_toggled"] diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.gd b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.gd new file mode 100644 index 0000000..c3eea2e --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.gd @@ -0,0 +1,65 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends SpinBox +class_name LineEditorFloat + + +var resource:Resource: + get: + return resource + set(value): + resource = value + dirty = true + +var prop_name:String: + get: + return prop_name + set(value): + prop_name = value + dirty = true + +var dirty = true + +func update_from_resource(): + if resource: + var result = resource.get(prop_name) + if result != null: + value = result + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + update_from_resource() + dirty = false + + +func _on_value_changed(value): + if resource: + resource.set(prop_name, value) diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.tscn b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.tscn new file mode 100644 index 0000000..2ce2af2 --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://dg45e7tw7ttu3"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.gd" id="1_o1hmb"] + +[node name="SpinBox" type="SpinBox"] +offset_right = 83.0625 +offset_bottom = 31.0 +step = 0.001 +script = ExtResource("1_o1hmb") + +[connection signal="value_changed" from="." to="." method="_on_value_changed"] diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.gd b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.gd new file mode 100644 index 0000000..3ccd0a3 --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.gd @@ -0,0 +1,71 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends SpinBox +class_name LineEditorInt + +var resource:Resource: + get: + return resource + set(value): + resource = value + dirty = true + +var prop_name:String: + get: + return prop_name + set(value): + prop_name = value + dirty = true + +var dirty = true + +func update_from_resource(): + #print("update_from_resource()") + if resource: + #print("resource %s" % resource) + #print("prop_name %s" % prop_name) + var result = resource.get(prop_name) + #print("result %s" % result) + if result != null: + value = result + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + update_from_resource() + dirty = false + + +func _on_value_changed(value): +# print("_on_value_changed(value)") + if resource: +# print("prop_name %s" % prop_name) +# print("value %s" % value) + resource.set(prop_name, value) diff --git a/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.tscn b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.tscn new file mode 100644 index 0000000..35e8ef9 --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://dh6frljlp7oqe"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.gd" id="1_ryygx"] + +[node name="SpinBox" type="SpinBox"] +offset_right = 83.0625 +offset_bottom = 40.0 +script = ExtResource("1_ryygx") + +[connection signal="value_changed" from="." to="." method="_on_value_changed"] diff --git a/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.gd b/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.gd new file mode 100644 index 0000000..16de2ba --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.gd @@ -0,0 +1,86 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name ResourceInspector + +@export var target:Resource: + get: + return target + set(value): + target = value + build() + +func add_label(name:String): + var label:Label = Label.new() + label.text = name + $GridContainer.add_child(label) + +func build(): + for child in $GridContainer.get_children(): + $GridContainer.remove_child(child) + + if !target: + return + + for prop_dict in target.get_property_list(): + var prop_name:String = prop_dict["name"] +# prop_dict["class_name"] + + var type:Variant.Type = prop_dict["type"] + match type: + TYPE_BOOL: + add_label(prop_name) + + var editor:LineEditorBool = preload("res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_bool.tscn").instantiate() + editor.resource = target + editor.prop_name = prop_name + $GridContainer.add_child(editor) + + TYPE_INT: + add_label(prop_name) + + var editor:LineEditorInt = preload("res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_int.tscn").instantiate() + editor.resource = target + editor.prop_name = prop_name + $GridContainer.add_child(editor) + + TYPE_FLOAT: + add_label(prop_name) + + var editor:LineEditorFloat = preload("res://addons/cyclops_level_builder/controls/resource_inspector/line_editor_float.tscn").instantiate() + editor.resource = target + editor.prop_name = prop_name + $GridContainer.add_child(editor) + + pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.tscn b/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.tscn new file mode 100644 index 0000000..043660b --- /dev/null +++ b/addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://c2484sv0ymy2e"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.gd" id="1_m3yhx"] + +[node name="object_inspector" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_m3yhx") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +columns = 2 diff --git a/addons/cyclops_level_builder/controls/test_line_edit.gd b/addons/cyclops_level_builder/controls/test_line_edit.gd new file mode 100644 index 0000000..1a065c3 --- /dev/null +++ b/addons/cyclops_level_builder/controls/test_line_edit.gd @@ -0,0 +1,16 @@ +@tool +extends Control +class_name TestLineEdit + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + #$Label.text = "Bar" + var lab:Label = get_node("Label") + print(lab.text) + pass diff --git a/addons/cyclops_level_builder/controls/test_line_edit.tscn b/addons/cyclops_level_builder/controls/test_line_edit.tscn new file mode 100644 index 0000000..31c90f0 --- /dev/null +++ b/addons/cyclops_level_builder/controls/test_line_edit.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://boco8mwkm8bc3"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/test_line_edit.gd" id="1_i4c5n"] + +[node name="test_line_edit" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_i4c5n") + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 23.0 +text = "Foo" diff --git a/addons/cyclops_level_builder/controls/tree/TreeTextComponent.gd b/addons/cyclops_level_builder/controls/tree/TreeTextComponent.gd new file mode 100644 index 0000000..c4ccc57 --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/TreeTextComponent.gd @@ -0,0 +1,18 @@ +@tool +extends PanelContainer +class_name TreeTextComponent + +@export var text:String +@export var edit_mode:bool = false + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + %Label.text = text + %Label.visible = !edit_mode + %LineEdit.visible = edit_mode + pass diff --git a/addons/cyclops_level_builder/controls/tree/TreeTextComponent.tscn b/addons/cyclops_level_builder/controls/tree/TreeTextComponent.tscn new file mode 100644 index 0000000..0a6e097 --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/TreeTextComponent.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://7xg6fyk4dust"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/tree/TreeTextComponent.gd" id="1_eruho"] + +[node name="TreeTextLine" type="PanelContainer"] +offset_right = 216.0 +offset_bottom = 31.0 +script = ExtResource("1_eruho") + +[node name="Label" type="Label" parent="."] +unique_name_in_owner = true +layout_mode = 2 +text = "text label" + +[node name="LineEdit" type="LineEdit" parent="."] +unique_name_in_owner = true +layout_mode = 2 +text = "line edit" diff --git a/addons/cyclops_level_builder/controls/tree/abstract_cyclops_tree_model.gd b/addons/cyclops_level_builder/controls/tree/abstract_cyclops_tree_model.gd new file mode 100644 index 0000000..af8e62d --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/abstract_cyclops_tree_model.gd @@ -0,0 +1,32 @@ +@tool +class_name AbstractCyclopsTreeModel + +signal tree_nodes_inserted(parent_node:Object, child_nodes:Array[Object], child_node_indices:PackedInt32Array) +signal tree_nodes_removed(parent_node:Object, child_nodes:Array[Object], child_node_indices:PackedInt32Array) + +#Display data of node has changed, but no the child structore +signal value_for_node_changed(old_node:Object, new_node:Object) + +#Rebuild this ode and all children +signal tree_node_changed(node:Object) + +#Entire tree needs to be rebuilt +signal tree_structure_changed() + +class CyclopsTreePath: + var path:Array[Object] + +func get_child(parent:Object, index:int)->Object: + return null + +func get_child_count(parent:Object)->int: + return 0 + +func get_index_of_child(parent:Object, child:Object)->int: + return -1 + +func get_root()->Object: + return null + +func is_leaf(node:Object)->bool: + return true diff --git a/addons/cyclops_level_builder/controls/tree/cyclops_tree.gd b/addons/cyclops_level_builder/controls/tree/cyclops_tree.gd new file mode 100644 index 0000000..f3b12ed --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/cyclops_tree.gd @@ -0,0 +1,65 @@ +@tool +extends PanelContainer +class_name CyclopsTree + +@export var node_display_component:PackedScene + +var model:AbstractCyclopsTreeModel: + get: + return model + set(value): + if model == value: + return + + if model: + model.tree_nodes_inserted.disconnect(on_tree_nodes_inserted) + model.tree_nodes_removed.disconnect(on_tree_nodes_removed) + model.refresh_node.disconnect(on_refresh_node) + model.tree_node_changed.disconnect(on_tree_node_changed) + model.tree_structure_changed.disconnect(on_tree_structure_changed) + + model = value + + if model: + model.tree_nodes_inserted.connect(on_tree_nodes_inserted) + model.tree_nodes_removed.connect(on_tree_nodes_removed) + model.refresh_node.connect(on_refresh_node) + model.tree_node_changed.connect(on_tree_node_changed) + model.tree_structure_changed.connect(on_tree_structure_changed) + + rebuild_tree() + +func on_tree_nodes_inserted(parent_node:Object, child_nodes:Array[Object], child_node_indices:PackedInt32Array): + pass + +func on_tree_nodes_removed(parent_node:Object, child_nodes:Array[Object], child_node_indices:PackedInt32Array): + pass + +func on_refresh_node(old_node:Object, new_node:Object): + pass + +func on_tree_node_changed(node:Object): + pass + +func on_tree_structure_changed(): + rebuild_tree() + +func rebuild_tree(): + for child in get_children(): + remove_child(child) + child.queue_free() + + if !model: + return + + model.get_root() + pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/controls/tree/cyclops_tree.tscn b/addons/cyclops_level_builder/controls/tree/cyclops_tree.tscn new file mode 100644 index 0000000..c663206 --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/cyclops_tree.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=3 uid="uid://cq6olx6nychug"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/tree/cyclops_tree.gd" id="1_5sunq"] + +[node name="CyclopsTree" type="PanelContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_5sunq") diff --git a/addons/cyclops_level_builder/controls/tree/tree_tier_component.tscn b/addons/cyclops_level_builder/controls/tree/tree_tier_component.tscn new file mode 100644 index 0000000..7fe0510 --- /dev/null +++ b/addons/cyclops_level_builder/controls/tree/tree_tier_component.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=3 format=3 uid="uid://bd6lfhom4yxls"] + +[ext_resource type="Texture2D" uid="uid://c7c2vg6lbhmfn" path="res://addons/cyclops_level_builder/art/icons/arrow_right.svg" id="1_rpn77"] +[ext_resource type="Texture2D" uid="uid://bor2x3t7fiqc2" path="res://addons/cyclops_level_builder/art/icons/arrow_down.svg" id="2_58w4p"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 230.0 +offset_bottom = 210.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="bn_expand" type="TextureButton" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +toggle_mode = true +texture_normal = ExtResource("1_rpn77") +texture_pressed = ExtResource("2_58w4p") + +[node name="NodeDisplayArea" type="PanelContainer" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="DropdownArea" type="HBoxContainer" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/DropdownArea"] +layout_mode = 2 +theme_override_constants/margin_left = 8 + +[node name="ChildArea" type="PanelContainer" parent="VBoxContainer/DropdownArea"] +unique_name_in_owner = true +layout_mode = 2 diff --git a/addons/cyclops_level_builder/controls/vector3_edit.gd b/addons/cyclops_level_builder/controls/vector3_edit.gd new file mode 100644 index 0000000..2ffcc4b --- /dev/null +++ b/addons/cyclops_level_builder/controls/vector3_edit.gd @@ -0,0 +1,44 @@ +@tool +extends HBoxContainer +class_name Vector3Edit + +signal value_changed(value:Vector3) + +@export var value:Vector3: + get: + return value + set(v): + if value == v: + return + + value = v + value_changed.emit(v) + dirty = true + +var dirty:bool = true + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + %edit_x.value = value.x + %edit_y.value = value.y + %edit_z.value = value.z + dirty = false + + + +func _on_edit_x_value_changed(v:float): + value = Vector3(v, value.y, value.z) + + +func _on_edit_y_value_changed(v:float): + value = Vector3(value.x, v, value.z) + + +func _on_edit_z_value_changed(v:float): + value = Vector3(value.x, value.y, v) diff --git a/addons/cyclops_level_builder/controls/vector3_edit.tscn b/addons/cyclops_level_builder/controls/vector3_edit.tscn new file mode 100644 index 0000000..528e60f --- /dev/null +++ b/addons/cyclops_level_builder/controls/vector3_edit.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=3 format=3 uid="uid://cphtpklx81l3w"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/vector3_edit.gd" id="1_lnptu"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_wjq53"] + +[node name="vector3_edit" type="HBoxContainer"] +offset_right = 237.0 +offset_bottom = 26.0 +script = ExtResource("1_lnptu") + +[node name="Label" type="Label" parent="."] +layout_mode = 2 +text = "X:" + +[node name="edit_x" parent="." instance=ExtResource("2_wjq53")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="."] +layout_mode = 2 +text = "Y:" + +[node name="edit_y" parent="." instance=ExtResource("2_wjq53")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label3" type="Label" parent="."] +layout_mode = 2 +text = "Z:" + +[node name="edit_z" parent="." instance=ExtResource("2_wjq53")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[connection signal="value_changed" from="edit_x" to="." method="_on_edit_x_value_changed"] +[connection signal="value_changed" from="edit_y" to="." method="_on_edit_y_value_changed"] +[connection signal="value_changed" from="edit_z" to="." method="_on_edit_z_value_changed"] diff --git a/addons/cyclops_level_builder/controls/vertex_billboard.gd b/addons/cyclops_level_builder/controls/vertex_billboard.gd new file mode 100644 index 0000000..3b7ee35 --- /dev/null +++ b/addons/cyclops_level_builder/controls/vertex_billboard.gd @@ -0,0 +1,56 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name VertexBillboard + +@export var radius:float = 4: + get: + return radius + set(value): + radius = value + dirty = true + +@export var color:Color = Color.WHITE: + get: + return color + set(value): + color = value + dirty = true + +var dirty:bool = true + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + var mat:ShaderMaterial = $MeshInstance3D.get_active_material(0) + #print("active mat %s" % mat) + mat.set_shader_parameter("radius", radius) + mat.set_shader_parameter("emission", color) + dirty = false diff --git a/addons/cyclops_level_builder/controls/vertex_billboard.tscn b/addons/cyclops_level_builder/controls/vertex_billboard.tscn new file mode 100644 index 0000000..e6e3d9f --- /dev/null +++ b/addons/cyclops_level_builder/controls/vertex_billboard.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=4 format=3 uid="uid://cuykufmlg2unb"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/controls/vertex_billboard.gd" id="1_cman8"] +[ext_resource type="Material" uid="uid://rtk56g3h03nt" path="res://addons/cyclops_level_builder/materials/vertex_active_material.tres" id="2_ov23w"] + +[sub_resource type="QuadMesh" id="QuadMesh_5jfb0"] + +[node name="vertex_billboard" type="Node3D"] +script = ExtResource("1_cman8") +color = Color(0, 1, 0, 1) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +material_override = ExtResource("2_ov23w") +mesh = SubResource("QuadMesh_5jfb0") diff --git a/addons/cyclops_level_builder/cyclops_global_scene.gd b/addons/cyclops_level_builder/cyclops_global_scene.gd new file mode 100644 index 0000000..c4cd2af --- /dev/null +++ b/addons/cyclops_level_builder/cyclops_global_scene.gd @@ -0,0 +1,365 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name CyclopsGlobalScene + +@export var selection_color:Color = Color(1, .5, .5, 1) +@export var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") +@export var selection_rect_material:Material = preload("res://addons/cyclops_level_builder/materials/selection_rect_material.tres") +@export var tool_edit_active_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_edit_active_material.tres") +@export var tool_edit_active_fill_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_edit_active_fill_material.tres") +@export var tool_edit_selected_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_edit_selected_material.tres") +@export var tool_edit_selected_fill_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_edit_selected_fill_material.tres") +@export var tool_edit_unselected_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_edit_unselected_material.tres") +@export var tool_object_active_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_object_active_material.tres") +@export var tool_object_selected_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_object_selected_material.tres") +@export var vertex_unselected_material:Material = preload("res://addons/cyclops_level_builder/materials/vertex_unselected_material.tres") +@export var vertex_selected_material:Material = preload("res://addons/cyclops_level_builder/materials/vertex_selected_material.tres") +@export var vertex_active_material:Material = preload("res://addons/cyclops_level_builder/materials/vertex_active_material.tres") +@export var vertex_tool_material:Material = preload("res://addons/cyclops_level_builder/materials/vertex_tool_material.tres") +@export var vertex_radius:float = 8 + +@export var tool_material:Material = preload("res://addons/cyclops_level_builder/materials/tool_material.tres") +@export var outline_material:Material = preload("res://addons/cyclops_level_builder/materials/outline_material.tres") +var tool_mesh:ImmediateMesh + +@export var units_font:Font +@export var units_font_size:int = 16 + +#@export var grid_size:int = 0 +@export var drag_angle_limit:float = deg_to_rad(5) + +const SNAPPING_ENABLED:String = "snapping/enabled" +const SNAPPING_GRID_UNIT_SIZE:String = "snapping/grid/unit_size" +const SNAPPING_GRID_USE_SUBDIVISIONS:String = "snapping/grid/use_subdivisions" +const SNAPPING_GRID_SUBDIVISIONS:String = "snapping/grid/subdivisions" +const SNAPPING_GRID_POWER_OF_TWO_SCALE:String = "snapping/grid/power_of_two_scale" +const SNAPPING_GRID_TRANSFORM:String = "snapping/grid/transform" +const SNAPPING_GRID_ANGLE:String = "snapping/grid/angle" + +@export_file("*.config") var settings_file:String = "cyclops_settings.config" +var settings:CyclopsSettings = CyclopsSettings.new() + +signal xray_mode_changed(value:bool) + +@export var xray_mode:bool = false: + get: + return xray_mode + set(value): + if xray_mode != value: + xray_mode = value + xray_mode_changed.emit(value) + +var unit_sphere:GeometryMesh +var builder:CyclopsLevelBuilder + + +# Called when the node enters the scene tree for the first time. +func _ready(): + init_settings() + + unit_sphere = MathGeometry.unit_sphere() + + tool_mesh = ImmediateMesh.new() + $ToolInstance3D.mesh = tool_mesh + + if FileAccess.file_exists(settings_file): + settings.load_from_file(settings_file) + +func init_settings(): + settings.add_setting(SNAPPING_ENABLED, true, TYPE_BOOL) + settings.add_setting(SNAPPING_GRID_UNIT_SIZE, 1, TYPE_FLOAT) + settings.add_setting(SNAPPING_GRID_POWER_OF_TWO_SCALE, 0, TYPE_INT) + settings.add_setting(SNAPPING_GRID_USE_SUBDIVISIONS, false, TYPE_BOOL) + settings.add_setting(SNAPPING_GRID_SUBDIVISIONS, 10, TYPE_INT) + settings.add_setting(SNAPPING_GRID_TRANSFORM, Transform3D.IDENTITY, TYPE_TRANSFORM3D) + settings.add_setting(SNAPPING_GRID_ANGLE, 15, TYPE_FLOAT) + +func save_settings(): + #print("saving ", settings_file) + settings.save_to_file(settings_file) + +func calc_snap_to_grid_util(): + var snap_to_grid_util:SnapToGridUtil = SnapToGridUtil.new() + #print("calc_snap_to_grid_util") + snap_to_grid_util.unit_size = settings.get_property(SNAPPING_GRID_UNIT_SIZE) + #print("unit_size ", snap_to_grid_util.unit_size) + snap_to_grid_util.power_of_two_scale = settings.get_property(SNAPPING_GRID_POWER_OF_TWO_SCALE) + #print("power_of_two_scale ", snap_to_grid_util.power_of_two_scale) + snap_to_grid_util.use_subdivisions = settings.get_property(SNAPPING_GRID_USE_SUBDIVISIONS) + snap_to_grid_util.grid_subdivisions = settings.get_property(SNAPPING_GRID_SUBDIVISIONS) + snap_to_grid_util.grid_transform = settings.get_property(SNAPPING_GRID_TRANSFORM) + return snap_to_grid_util + +#Called by CyclopsLevelBuilder to draw 2D components +func draw_over_viewport(overlay:Control): + pass + +func draw_line(p0:Vector3, p1:Vector3, mat:Material): + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINES, mat) + + tool_mesh.surface_add_vertex(p0) + tool_mesh.surface_add_vertex(p1) + + tool_mesh.surface_end() + +func draw_loop(points:PackedVector3Array, closed:bool = true, mat:Material = null): + if points.is_empty(): + return + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, mat) + + for p in points: + tool_mesh.surface_add_vertex(p) + + if closed: + tool_mesh.surface_add_vertex(points[0]) + + tool_mesh.surface_end() + + +func draw_wireframe(points:PackedVector3Array, edges:PackedInt32Array, mat:Material = null, vertex_mat = null): + for p in points: + draw_vertex(p, vertex_mat) + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, mat) + + for e_idx in edges: + tool_mesh.surface_add_vertex(points[e_idx]) + + tool_mesh.surface_end() + + +func draw_prism(points:PackedVector3Array, extrude:Vector3, mat:Material = null, vertex_mat = null): + for p in points: + draw_vertex(p, vertex_mat) + draw_vertex(p + extrude, vertex_mat) + + #Bottom loop + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, mat) + + for p in points: + tool_mesh.surface_add_vertex(p) + + tool_mesh.surface_add_vertex(points[0]) + + tool_mesh.surface_end() + + #Top loop + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, mat) + + for p in points: + tool_mesh.surface_add_vertex(p + extrude) + + tool_mesh.surface_add_vertex(points[0] + extrude) + + tool_mesh.surface_end() + + #Sides + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINES, mat) + + for p in points: + tool_mesh.surface_add_vertex(p) + tool_mesh.surface_add_vertex(p + extrude) + + tool_mesh.surface_end() + + +func draw_triangles(tri_points:PackedVector3Array, mat:Material = null): + tool_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES, mat) + + for p in tri_points: + tool_mesh.surface_add_vertex(p) + + tool_mesh.surface_end() + +func draw_rect(start:Vector3, end:Vector3, mat:Material = null, vertex_mat:Material = null): + + var p0:Vector3 = start + var p2:Vector3 = end + var p1:Vector3 = Vector3(p0.x, p0.y, p2.z) + var p3:Vector3 = Vector3(p2.x, p0.y, p0.z) + + draw_vertex(p0, vertex_mat) + draw_vertex(p1, vertex_mat) + draw_vertex(p2, vertex_mat) + draw_vertex(p3, vertex_mat) + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, mat) + + tool_mesh.surface_add_vertex(p0) + tool_mesh.surface_add_vertex(p1) + tool_mesh.surface_add_vertex(p2) + tool_mesh.surface_add_vertex(p3) + tool_mesh.surface_add_vertex(p0) + + tool_mesh.surface_end() + +func clear_tool_mesh(): + #tool_mesh = ImmediateMesh.new() + #$ToolInstance3D.mesh = tool_mesh + tool_mesh.clear_surfaces() + + for child in %VertexGroup.get_children(): + %VertexGroup.remove_child(child) + child.queue_free() + #print("clear") + %cyclops_overlay.clear() + +func draw_text(text:String, pos:Vector2, font:Font, font_size:float): + %cyclops_overlay.draw_text(text, pos, font, font_size) + +# Draws the bounding box for the points [p0, p1, p2] +func draw_cube(p0:Vector3, p1:Vector3, p2:Vector3, mat:Material = null, vertex_mat:Material = null): +# print ("draw_cube %s %s %s" % [p0, p1, p2]) + + var bounds:AABB = AABB(p0, Vector3.ZERO) + bounds = bounds.expand(p1) + bounds = bounds.expand(p2) + + var p000:Vector3 = bounds.position + var p111:Vector3 = bounds.end + var p001:Vector3 = Vector3(p000.x, p000.y, p111.z) + var p010:Vector3 = Vector3(p000.x, p111.y, p000.z) + var p011:Vector3 = Vector3(p000.x, p111.y, p111.z) + var p100:Vector3 = Vector3(p111.x, p000.y, p000.z) + var p101:Vector3 = Vector3(p111.x, p000.y, p111.z) + var p110:Vector3 = Vector3(p111.x, p111.y, p000.z) + + draw_vertex(p000, vertex_mat) + draw_vertex(p001, vertex_mat) + draw_vertex(p010, vertex_mat) + draw_vertex(p011, vertex_mat) + draw_vertex(p100, vertex_mat) + draw_vertex(p101, vertex_mat) + draw_vertex(p110, vertex_mat) + draw_vertex(p111, vertex_mat) + + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINES, mat) + + tool_mesh.surface_add_vertex(p000) + tool_mesh.surface_add_vertex(p001) + tool_mesh.surface_add_vertex(p000) + tool_mesh.surface_add_vertex(p100) + tool_mesh.surface_add_vertex(p101) + tool_mesh.surface_add_vertex(p001) + tool_mesh.surface_add_vertex(p101) + tool_mesh.surface_add_vertex(p100) + + tool_mesh.surface_add_vertex(p010) + tool_mesh.surface_add_vertex(p011) + tool_mesh.surface_add_vertex(p010) + tool_mesh.surface_add_vertex(p110) + tool_mesh.surface_add_vertex(p111) + tool_mesh.surface_add_vertex(p011) + tool_mesh.surface_add_vertex(p111) + tool_mesh.surface_add_vertex(p110) + + tool_mesh.surface_add_vertex(p000) + tool_mesh.surface_add_vertex(p010) + tool_mesh.surface_add_vertex(p100) + tool_mesh.surface_add_vertex(p110) + tool_mesh.surface_add_vertex(p101) + tool_mesh.surface_add_vertex(p111) + tool_mesh.surface_add_vertex(p001) + tool_mesh.surface_add_vertex(p011) + + tool_mesh.surface_end() + + #$ToolInstance3D.mesh = mesh + +func draw_points(points:PackedVector3Array, vertex_mat:Material = null): + draw_vertices(points, vertex_mat) + +func draw_vertex(position:Vector3, mat:Material = null): + draw_vertices([position], mat) + +func draw_vertices(vertices:PackedVector3Array, mat:Material = null): + var arr_mesh = ArrayMesh.new() + var arrays = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = vertices + + arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_POINTS, arrays) + var mesh_inst = MeshInstance3D.new() + mesh_inst.mesh = arr_mesh + + mesh_inst.material_override = mat + + %VertexGroup.add_child(mesh_inst) + + + +func draw_sphere(xform:Transform3D = Transform3D.IDENTITY, material:Material = null, segs_lat:int = 6, segs_long:int = 8): + unit_sphere.append_to_immediate_mesh(tool_mesh, material, xform) + + +func draw_selected_blocks(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_node("/root/CyclopsAutoload") + + var blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + var active_block:CyclopsBlock = builder.get_active_block() + for block in blocks: + var active:bool = block == active_block + var mat:Material = global_scene.tool_object_active_material if active else global_scene.tool_object_selected_material + + #Selection highlight outline + block.append_mesh_outline(tool_mesh, viewport_camera, block.global_transform, mat) + + #block.draw_unit_labels(viewport_camera, block.global_transform) + + +func draw_screen_rect(viewport_camera:Camera3D, p00:Vector2, p11:Vector2, material:Material): + var global_scene:CyclopsGlobalScene = builder.get_node("/root/CyclopsAutoload") + + var p01:Vector2 = Vector2(p00.x, p11.y) + var p10:Vector2 = Vector2(p11.x, p00.y) + var z_pos:float = (viewport_camera.near + viewport_camera.far) / 2 + + tool_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, material) + + for p in [p00, p01, p11, p10, p00]: + var p_proj:Vector3 = viewport_camera.project_position(p, z_pos) +# print("p_proj %s" % p_proj) + + tool_mesh.surface_add_vertex(p_proj) + + tool_mesh.surface_end() + +func set_custom_gizmo(gizmo:Node3D): + for child in %GizmoControl.get_children(): + %GizmoControl.remove_child(child) + + if gizmo: +# print("Setting gizmo") + %GizmoControl.add_child(gizmo) + + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/cyclops_global_scene.tscn b/addons/cyclops_level_builder/cyclops_global_scene.tscn new file mode 100644 index 0000000..b18ccea --- /dev/null +++ b/addons/cyclops_level_builder/cyclops_global_scene.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=6 format=3 uid="uid://4siqre3jhe80"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/cyclops_global_scene.gd" id="1_nu1d3"] +[ext_resource type="FontFile" uid="uid://dejaio63tyi02" path="res://addons/cyclops_level_builder/art/fonts/Roboto/Roboto-Regular.ttf" id="2_savc6"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/cyclops_overlay.gd" id="3_uf260"] + +[sub_resource type="PlaneMesh" id="PlaneMesh_sl0cw"] + +[sub_resource type="ImmediateMesh" id="ImmediateMesh_7cfi3"] + +[node name="CyclopsGlobals" type="Node3D"] +script = ExtResource("1_nu1d3") +units_font = ExtResource("2_savc6") + +[node name="ControlMesh" type="MeshInstance3D" parent="."] +visible = false +mesh = SubResource("PlaneMesh_sl0cw") + +[node name="ToolInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("ImmediateMesh_7cfi3") + +[node name="VertexGroup" type="Node3D" parent="."] +unique_name_in_owner = true + +[node name="GizmoControl" type="Node3D" parent="."] +unique_name_in_owner = true + +[node name="cyclops_overlay" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 3 +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("3_uf260") diff --git a/addons/cyclops_level_builder/cyclops_level_builder.gd b/addons/cyclops_level_builder/cyclops_level_builder.gd new file mode 100644 index 0000000..bbd4507 --- /dev/null +++ b/addons/cyclops_level_builder/cyclops_level_builder.gd @@ -0,0 +1,462 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends EditorPlugin +class_name CyclopsLevelBuilder + +signal active_node_changed +signal selection_changed +signal snapping_tool_changed + +const AUTOLOAD_NAME = "CyclopsAutoload" +const CYCLOPS_HUD_NAME = "CyclopsGlobalHud" + +var config:CyclopsConfig = preload("res://addons/cyclops_level_builder/data/configuration.tres") + +var logger:CyclopsLogger = CyclopsLogger.new() + +var material_dock:MaterialPaletteViewport +var convex_face_editor_dock:ConvexFaceEdtiorViewport +var tool_properties_dock:ToolPropertiesDock +var snapping_properties_dock:SnappingPropertiesDock +var cyclops_console_dock:CyclopsConsole +var main_toolbar:MainToolbar +var editor_toolbar:EditorToolbar +var upgrade_cyclops_blocks_toolbar:UpgradeCyclopsBlocksToolbar +var activated:bool = false + +var always_on:bool = false: + get: + return always_on + set(value): + always_on = value + #print("always_on %s" % always_on) + update_activation() + +var block_create_distance:float = 10 +var tool:CyclopsTool = null +var snapping_system:CyclopsSnappingSystem = null +var lock_uvs:bool = false +var tool_overlay_extrude:float = .01 + +var tool_uv_transform:Transform2D +var tool_material_path:String + +var handle_screen_radius:float = 6 + +var drag_start_radius:float = 6 + +enum Mode { OBJECT, EDIT } +var mode:Mode = Mode.OBJECT +enum EditMode { VERTEX, EDGE, FACE } +var edit_mode:CyclopsLevelBuilder.EditMode = CyclopsLevelBuilder.EditMode.VERTEX + +var display_mode:DisplayMode.Type = DisplayMode.Type.MATERIAL + +var cached_viewport_camera:Camera3D + +var editor_cache:Dictionary +var editor_cache_file:String = "user://cyclops_editor_cache.json" + +func get_snapping_manager()->SnappingManager: + var mgr:SnappingManager = SnappingManager.new() + mgr.snap_enabled = CyclopsAutoload.settings.get_property(CyclopsGlobalScene.SNAPPING_ENABLED) + mgr.snap_tool = snapping_system + + return mgr + +func _get_plugin_name()->String: + return "CyclopsLevelBuilder" + +func _get_plugin_icon()->Texture2D: + return preload("res://addons/cyclops_level_builder/art/cyclops.svg") + +func _enter_tree(): + if FileAccess.file_exists(editor_cache_file): + #print(">> _enter_tree") + var text:String = FileAccess.get_file_as_string(editor_cache_file) + #print("load text:", text) + editor_cache = JSON.parse_string(text) + + add_custom_type("CyclopsScene", "Node3D", preload("nodes/cyclops_scene.gd"), preload("nodes/cyclops_blocks_icon.png")) + + add_custom_type("CyclopsBlock", "Node3D", preload("nodes/cyclops_block.gd"), preload("nodes/cyclops_blocks_icon.png")) + add_custom_type("CyclopsBlocks", "Node3D", preload("nodes/cyclops_blocks.gd"), preload("nodes/cyclops_blocks_icon.png")) + add_custom_type("CyclopsConvexBlock", "Node", preload("nodes/cyclops_convex_block.gd"), preload("nodes/cyclops_blocks_icon.png")) + add_custom_type("CyclopsConvexBlockBody", "Node", preload("nodes/cyclops_convex_block_body.gd"), preload("nodes/cyclops_blocks_icon.png")) + + add_autoload_singleton(AUTOLOAD_NAME, "res://addons/cyclops_level_builder/cyclops_global_scene.tscn") + #add_autoload_singleton(CYCLOPS_HUD_NAME, "res://addons/cyclops_level_builder/cyclops_global_hud.tscn") + + material_dock = preload("res://addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.tscn").instantiate() + material_dock.builder = self + + convex_face_editor_dock = preload("res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.tscn").instantiate() + convex_face_editor_dock.builder = self + + tool_properties_dock = preload("res://addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.tscn").instantiate() + tool_properties_dock.builder = self + + snapping_properties_dock = preload("res://addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.tscn").instantiate() + snapping_properties_dock.builder = self + + cyclops_console_dock = preload("res://addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.tscn").instantiate() + cyclops_console_dock.editor_plugin = self + + main_toolbar = preload("menu/main_toolbar.tscn").instantiate() + main_toolbar.editor_plugin = self + + editor_toolbar = preload("menu/editor_toolbar.tscn").instantiate() + editor_toolbar.editor_plugin = self + + upgrade_cyclops_blocks_toolbar = preload("res://addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.tscn").instantiate() + upgrade_cyclops_blocks_toolbar.editor_plugin = self + + add_control_to_bottom_panel(cyclops_console_dock, "Cyclops") + + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, main_toolbar) + + var editor:EditorInterface = get_editor_interface() + var selection:EditorSelection = editor.get_selection() + selection.selection_changed.connect(on_selection_changed) + + update_activation() + + + #Wait until everything is loaded + await get_tree().process_frame + + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + global_scene.builder = self + + switch_to_snapping_system(SnappingSystemGrid.new()) + switch_to_tool(ToolBlock.new()) + + +func _exit_tree(): + var file:FileAccess = FileAccess.open(editor_cache_file, FileAccess.WRITE) + #var text:String = JSON.stringify(editor_cache, " ") + #print("saving cache:", text) + file.store_string(JSON.stringify(editor_cache, " ")) + file.close() + + # Clean-up of the plugin goes here. + remove_autoload_singleton(AUTOLOAD_NAME) + #remove_autoload_singleton(CYCLOPS_HUD_NAME) + + remove_custom_type("CyclopsScene") + + remove_custom_type("CyclopsBlock") + remove_custom_type("CyclopsBlocks") + remove_custom_type("CyclopsConvexBlock") + remove_custom_type("CyclopsConvexBlockBody") + + remove_control_from_bottom_panel(cyclops_console_dock) + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, main_toolbar) + + if activated: + remove_control_from_docks(material_dock) + remove_control_from_docks(convex_face_editor_dock) + remove_control_from_docks(tool_properties_dock) + remove_control_from_docks(snapping_properties_dock) + remove_control_from_docks(cyclops_console_dock) + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, editor_toolbar) + + if upgrade_cyclops_blocks_toolbar.activated: + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, upgrade_cyclops_blocks_toolbar) + + material_dock.queue_free() + convex_face_editor_dock.queue_free() + tool_properties_dock.queue_free() + snapping_properties_dock.queue_free() + cyclops_console_dock.queue_free() + main_toolbar.queue_free() + editor_toolbar.queue_free() + upgrade_cyclops_blocks_toolbar.queue_free() + +func log(message:String, level:CyclopsLogger.LogLevel = CyclopsLogger.LogLevel.ERROR): + logger.log(message, level) + +func get_blocks()->Array[CyclopsBlock]: + return get_blocks_recursive(get_editor_interface().get_edited_scene_root()) + +func get_blocks_recursive(node:Node)->Array[CyclopsBlock]: + var result:Array[CyclopsBlock] + + if node is CyclopsBlock: + result.append(node) + for child in node.get_children(): + result.append_array(get_blocks_recursive(child)) + return result + +func is_selected(node:Node)->bool: + var selection:EditorSelection = get_editor_interface().get_selection() + for n in selection.get_selected_nodes(): + if n == node: + return true + return false + + +func is_active_block(block:CyclopsBlock)->bool: + var selection:EditorSelection = get_editor_interface().get_selection() + var nodes:Array[Node] = selection.get_selected_nodes() + + return !nodes.is_empty() && nodes.back() == block + +func get_active_block()->CyclopsBlock: + var selection:EditorSelection = get_editor_interface().get_selection() + var nodes:Array[Node] = selection.get_selected_nodes() + + var back:Node = nodes.back() + if back is CyclopsBlock: + return back + return null + + +#Blocks listed in order of selection with last block being the most recent (ie, active) one +func get_selected_blocks()->Array[CyclopsBlock]: + var result:Array[CyclopsBlock] + + var selection:EditorSelection = get_editor_interface().get_selection() + for node in selection.get_selected_nodes(): + if node is CyclopsBlock: + result.append(node) + + return result + +func get_block_add_parent()->Node: + var selection:EditorSelection = get_editor_interface().get_selection() + var nodes:Array = selection.get_selected_nodes() + if nodes.is_empty(): + return get_editor_interface().get_edited_scene_root() + + if nodes[0] is CyclopsBlock: + #print("getting parent of ", nodes[0].name) + return nodes[0].get_parent() + return nodes[0] + +func update_activation(): + var editor:EditorInterface = get_editor_interface() + var selection:EditorSelection = editor.get_selection() + var nodes:Array[Node] = selection.get_selected_nodes() + + #Node list ordered in order of selection with most recently sdelected at end + var node:Node = null + if !nodes.is_empty(): + node = nodes[0] + + if node is CyclopsBlock || always_on: + #print("updarting activation") + if !activated: + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, editor_toolbar) + add_control_to_bottom_panel(material_dock, "Materials") + add_control_to_dock(DOCK_SLOT_RIGHT_BL, convex_face_editor_dock) + add_control_to_dock(DOCK_SLOT_RIGHT_BL, tool_properties_dock) + add_control_to_dock(DOCK_SLOT_RIGHT_BL, snapping_properties_dock) + activated = true + else: + if activated: + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, editor_toolbar) + remove_control_from_bottom_panel(material_dock) + remove_control_from_docks(convex_face_editor_dock) + remove_control_from_docks(tool_properties_dock) + remove_control_from_docks(snapping_properties_dock) + activated = false + + if node is CyclopsBlocks: + if !upgrade_cyclops_blocks_toolbar.activated: + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, upgrade_cyclops_blocks_toolbar) + upgrade_cyclops_blocks_toolbar.activated = true + else: + if upgrade_cyclops_blocks_toolbar.activated: + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, upgrade_cyclops_blocks_toolbar) + upgrade_cyclops_blocks_toolbar.activated = false + +func on_selection_changed(): + update_activation() + + if cached_viewport_camera: + tool._draw_tool(cached_viewport_camera) + +func _handles(object:Object): +# return object is CyclopsBlocks or object is CyclopsConvexBlock + return object is CyclopsBlock or object is CyclopsBlocks or always_on + +func _forward_3d_draw_over_viewport(viewport_control:Control): + var global_scene:CyclopsGlobalScene = get_global_scene() + global_scene.draw_over_viewport(viewport_control) + #Draw on top of viweport here + +func _forward_3d_gui_input(viewport_camera:Camera3D, event:InputEvent): + #print("plugin: " + event.as_text()) + cached_viewport_camera = viewport_camera + + if tool: + var result:bool = tool._gui_input(viewport_camera, event) + tool._draw_tool(viewport_camera) + return EditorPlugin.AFTER_GUI_INPUT_STOP if result else EditorPlugin.AFTER_GUI_INPUT_PASS + + return EditorPlugin.AFTER_GUI_INPUT_PASS + +func _get_state()->Dictionary: + var state:Dictionary = {} + + #print("ed cache ", str(editor_cache)) + #state["editor_cache"] = editor_cache.duplicate() + + material_dock.save_state(state) + convex_face_editor_dock.save_state(state) + tool_properties_dock.save_state(state) + snapping_properties_dock.save_state(state) + cyclops_console_dock.save_state(state) + + return state + +func _set_state(state): + #print("ed set_state ", str(state)) + + #editor_cache = state.get("editor_cache", {}).duplicate() + + material_dock.load_state(state) + convex_face_editor_dock.load_state(state) + tool_properties_dock.load_state(state) + snapping_properties_dock.load_state(state) + cyclops_console_dock.load_state(state) + + +func get_tool_cache(tool_id:String): + if !editor_cache.has("tool"): + return {} + + if !editor_cache.tool.has(tool_id): + return {} + + return editor_cache.tool[tool_id] + +func set_tool_cache(tool_id:String, cache:Dictionary): + if !editor_cache.has("tool"): + editor_cache["tool"] = {} + + editor_cache.tool[tool_id] = cache + +func get_snapping_cache(tool_id:String): + if !editor_cache.has("snapping"): + return {} + + if !editor_cache.snapping.has(tool_id): + return {} + + return editor_cache.snapping[tool_id] + +func set_snapping_cache(tool_id:String, cache:Dictionary): + if !editor_cache.has("snapping"): + editor_cache["snapping"] = {} + + editor_cache.snapping[tool_id] = cache + +func switch_to_tool(_tool:CyclopsTool): + #print(">> switch to tool") + + if tool: + tool._deactivate() + + tool = _tool + + if tool: + tool._activate(self) + var control:Control = tool._get_tool_properties_editor() + tool_properties_dock.set_editor(control) + +func switch_to_snapping_system(_snapping_system:CyclopsSnappingSystem): + if snapping_system: + snapping_system._deactivate() + + snapping_system = _snapping_system + + if snapping_system: + snapping_system._activate(self) + var control:Control = snapping_system._get_properties_editor() + snapping_properties_dock.set_editor(control) + + snapping_tool_changed.emit() + +func get_global_scene()->CyclopsGlobalScene: + var scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + return scene + + + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + var best_result:IntersectResults + + var blocks:Array[CyclopsBlock] = get_blocks() + + for block in blocks: + if !block.is_visible_in_tree(): + continue + + var result:IntersectResults = block.intersect_ray_closest(origin, dir) +# print("isect %s %s" % [node.name, result]) + if result: + if !best_result or result.distance_squared < best_result.distance_squared: +# print("setting best result %s" % node.name) + best_result = result +# print("best_result %s" % ray_best_result) + +# print("returning best result %s" % ray_best_result) + return best_result + +func intersect_ray_closest_selected_only(origin:Vector3, dir:Vector3)->IntersectResults: + var best_result:IntersectResults + + var blocks:Array[CyclopsBlock] = get_selected_blocks() + for block in blocks: + var result:IntersectResults = block.intersect_ray_closest(origin, dir) + if result: + if !best_result or result.distance_squared < best_result.distance_squared: + best_result = result + + return best_result + + +func intersect_frustum_all(frustum:Array[Plane])->Array[CyclopsBlock]: + var result:Array[CyclopsBlock] = [] + + var blocks:Array[CyclopsBlock] = get_blocks() + for block in blocks: + var xform:Transform3D = block.global_transform.affine_inverse() + + var frustum_local:Array[Plane] + for p in frustum: + frustum_local.append(xform * p) + + #print("intersect_frustum_all block %s" % block.get_path()) + var vol:ConvexVolume = block.control_mesh +# if !vol: +# print("nil vol %s" % block.get_path()) + if vol && vol.intersects_frustum(frustum_local): + result.append(block) + + return result + diff --git a/addons/cyclops_level_builder/cyclops_overlay.gd b/addons/cyclops_level_builder/cyclops_overlay.gd new file mode 100644 index 0000000..0c58456 --- /dev/null +++ b/addons/cyclops_level_builder/cyclops_overlay.gd @@ -0,0 +1,69 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name CyclopsOverlay + + +class TextLabel extends Resource: + var text:String + var pos:Vector2 + var font:Font + var font_size:float + +var text_labels:Array[TextLabel] + +func draw_text(text:String, pos:Vector2, font:Font, font_size:float): + #print("draw_Text") + var label:TextLabel = TextLabel.new() + label.text = text + label.pos = pos + label.font = font + label.font_size = font_size + + text_labels.append(label) + queue_redraw() + +#func add_label(label:TextLabel): +# text_labels.append(label) +# queue_redraw() + +func clear(): + text_labels.clear() + queue_redraw() + +func _draw(): + for label in text_labels: + draw_string(label.font, label.pos, \ + label.text, HORIZONTAL_ALIGNMENT_CENTER, -1, \ + label.font_size) + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/data/configuration.tres b/addons/cyclops_level_builder/data/configuration.tres new file mode 100644 index 0000000..d499d89 --- /dev/null +++ b/addons/cyclops_level_builder/data/configuration.tres @@ -0,0 +1,22 @@ +[gd_resource type="Resource" script_class="CyclopsConfig" load_steps=16 format=3 uid="uid://b54ok0creqhnb"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/cyclops_config.gd" id="1_jrivp"] +[ext_resource type="Resource" uid="uid://dwxpsgoxb60yp" path="res://addons/cyclops_level_builder/data/snapping_tags/snap_tag_grid.tres" id="2_8mhpe"] +[ext_resource type="Resource" uid="uid://rskdanqaqt1y" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_move.tres" id="2_mkeje"] +[ext_resource type="Resource" uid="uid://beqrq2vlgidpe" path="res://addons/cyclops_level_builder/data/snapping_tags/snap_tag_vertex.tres" id="3_bw3pn"] +[ext_resource type="Resource" uid="uid://c648hs1r46mat" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_create_block.tres" id="3_fxuvh"] +[ext_resource type="Resource" uid="uid://cihxgriu32oxb" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_create_prism.tres" id="4_utowl"] +[ext_resource type="Resource" uid="uid://p0ucaj6w232i" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_rotate.tres" id="5_lrya4"] +[ext_resource type="Resource" uid="uid://wm5lu7jdndym" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_create_cylinder.tres" id="5_yyi6p"] +[ext_resource type="Resource" uid="uid://16f1nwimgn0p" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_create_stairs.tres" id="6_pr2fs"] +[ext_resource type="Resource" uid="uid://buod6sdg7c12l" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_clip.tres" id="7_pbu8d"] +[ext_resource type="Resource" uid="uid://ctj35x0jfg7ej" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_vertex.tres" id="8_m71cc"] +[ext_resource type="Resource" uid="uid://dub6oyal5fxly" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_edge.tres" id="9_hqyx3"] +[ext_resource type="Resource" uid="uid://cmdgd8wmfdo4a" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_face.tres" id="10_j16ya"] +[ext_resource type="Resource" uid="uid://bjmuechy70058" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_material_brush.tres" id="11_ew3jv"] +[ext_resource type="Resource" uid="uid://b1a71dvqwi4h1" path="res://addons/cyclops_level_builder/data/tool_tags/tool_tag_vertex_color_brush.tres" id="15_u2xjc"] + +[resource] +script = ExtResource("1_jrivp") +tool_tags = Array[Resource("res://addons/cyclops_level_builder/resources/tool_tag.gd")]([ExtResource("2_mkeje"), ExtResource("5_lrya4"), ExtResource("3_fxuvh"), ExtResource("4_utowl"), ExtResource("5_yyi6p"), ExtResource("6_pr2fs"), ExtResource("7_pbu8d"), ExtResource("8_m71cc"), ExtResource("9_hqyx3"), ExtResource("10_j16ya"), ExtResource("11_ew3jv"), ExtResource("15_u2xjc")]) +snapping_tags = Array[Resource("res://addons/cyclops_level_builder/snapping/snapping_tag.gd")]([ExtResource("2_8mhpe"), ExtResource("3_bw3pn")]) diff --git a/addons/cyclops_level_builder/data/snapping_tags/snap_tag_grid.tres b/addons/cyclops_level_builder/data/snapping_tags/snap_tag_grid.tres new file mode 100644 index 0000000..5f366e4 --- /dev/null +++ b/addons/cyclops_level_builder/data/snapping_tags/snap_tag_grid.tres @@ -0,0 +1,12 @@ +[gd_resource type="Resource" script_class="SnappingTag" load_steps=4 format=3 uid="uid://dwxpsgoxb60yp"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_tag.gd" id="1_jbxkl"] +[ext_resource type="Texture2D" uid="uid://c6mucdu7wcbkm" path="res://addons/cyclops_level_builder/art/icons/snap_grid.svg" id="1_ngui8"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_system_grid.gd" id="2_thx8k"] + +[resource] +script = ExtResource("1_jbxkl") +name = "Grid" +icon = ExtResource("1_ngui8") +tooltip = "Snap to grid points" +snapping_script = ExtResource("2_thx8k") diff --git a/addons/cyclops_level_builder/data/snapping_tags/snap_tag_vertex.tres b/addons/cyclops_level_builder/data/snapping_tags/snap_tag_vertex.tres new file mode 100644 index 0000000..d3329c6 --- /dev/null +++ b/addons/cyclops_level_builder/data/snapping_tags/snap_tag_vertex.tres @@ -0,0 +1,12 @@ +[gd_resource type="Resource" script_class="SnappingTag" load_steps=4 format=3 uid="uid://beqrq2vlgidpe"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_tag.gd" id="1_jlotd"] +[ext_resource type="Texture2D" uid="uid://c0x011okomj8n" path="res://addons/cyclops_level_builder/art/icons/snap_vertex.svg" id="1_jmnf8"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_system_vertex.gd" id="2_c0g2w"] + +[resource] +script = ExtResource("1_jlotd") +name = "Vertex" +icon = ExtResource("1_jmnf8") +tooltip = "Snap to vertices" +snapping_script = ExtResource("2_c0g2w") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_clip.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_clip.tres new file mode 100644 index 0000000..f165c22 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_clip.tres @@ -0,0 +1,31 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://buod6sdg7c12l"] + +[ext_resource type="Texture2D" uid="uid://bos2j51dp4j1s" path="res://addons/cyclops_level_builder/art/icons/edit_clip.svg" id="1_oaury"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_4grct"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_clip.gd" id="3_xt387"] + +[sub_resource type="InputEventKey" id="InputEventKey_hd0cm"] +device = -1 +pressed = true +keycode = 67 +unicode = 99 + +[resource] +script = ExtResource("2_4grct") +id = "clip" +name = "Clip" +input_events = Array[InputEvent]([SubResource("InputEventKey_hd0cm")]) +input_events_override = false +tooltip = "Clip + +Click on surface of block to place first cutting point. + +Click again to place second cutting point. This will define the plane block will be cut along. + +If you press Enter at this point, the block will be cut. The cutting plane will be defined by the cutting line you've drawn and the normal of the plane it is on. + +You can optionally place a third cutting point. If you do, the three placed points will define the cutting plane when you press Enter. + +Press Backspace to delete the last cutting point you placed." +icon = ExtResource("1_oaury") +tool_script = ExtResource("3_xt387") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_block.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_block.tres new file mode 100644 index 0000000..dcb6ac4 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_block.tres @@ -0,0 +1,33 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://c648hs1r46mat"] + +[ext_resource type="Texture2D" uid="uid://bwasqbq4iqkn6" path="res://addons/cyclops_level_builder/art/icons/block.svg" id="1_qojcl"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_vgbvo"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_block.gd" id="3_52q4h"] + +[sub_resource type="InputEventKey" id="InputEventKey_vdxf5"] +device = -1 +pressed = true +keycode = 82 +unicode = 114 + +[resource] +script = ExtResource("1_vgbvo") +id = "block" +name = "Block" +input_events = Array[InputEvent]([SubResource("InputEventKey_vdxf5")]) +input_events_override = true +tooltip = "Block + +Click and drag in empty space or on unselected block to create a new block. + +Click and drag on a selected block to move it in the XZ plane. Hold Alt to drag along the Y axis. + +Ctrl-click and drag on the face of a block to move the face along its normal. + +Escape or right click to cancel drawing the block. + +Click on block to select it. Shift-Click toggles, Ctrl-Click adds and Shift-Ctrl Click subtracts. + +Click in empty space to clear selection." +icon = ExtResource("1_qojcl") +tool_script = ExtResource("3_52q4h") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_cylinder.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_cylinder.tres new file mode 100644 index 0000000..6106a0e --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_cylinder.tres @@ -0,0 +1,27 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://wm5lu7jdndym"] + +[ext_resource type="Texture2D" uid="uid://0vye3ue3ayvf" path="res://addons/cyclops_level_builder/art/icons/create_cylinder.svg" id="1_a3871"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_8mpiw"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_cylinder.gd" id="3_2hl37"] + +[sub_resource type="InputEventKey" id="InputEventKey_qk3nx"] +device = -1 +shift_pressed = true +keycode = 67 +unicode = 67 + +[resource] +script = ExtResource("2_8mpiw") +id = "cylinder" +name = "Cylinder" +input_events = Array[InputEvent]([SubResource("InputEventKey_qk3nx")]) +input_events_override = false +tooltip = "Cylinder + +Click on surface of block or in empty space to begin creating base of a cylinder. + +Release the mouse to enter height drawing mode. If you have the tube option selected, you will draw the second ring instead. + +Use the mouse wheel to change the number of sides of the cylinder while drawing." +icon = ExtResource("1_a3871") +tool_script = ExtResource("3_2hl37") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_prism.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_prism.tres new file mode 100644 index 0000000..991f3ce --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_prism.tres @@ -0,0 +1,29 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://cihxgriu32oxb"] + +[ext_resource type="Texture2D" uid="uid://cbmwkjbju75er" path="res://addons/cyclops_level_builder/art/icons/create_prism.svg" id="1_gxivr"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_oalyb"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_prism.gd" id="3_oagna"] + +[sub_resource type="InputEventKey" id="InputEventKey_ral6n"] +device = -1 +shift_pressed = true +keycode = 84 +unicode = 84 + +[resource] +script = ExtResource("1_oalyb") +id = "prism" +name = "Prism" +input_events = Array[InputEvent]([SubResource("InputEventKey_ral6n")]) +input_events_override = false +tooltip = "Prism + +Click on surface of block or in empty space to begin creating base of a prism. + +Click to add new point. Backspace to remove the last point you added. You can also right click on a point to remove it. + +Press Enter to extrude base. + +Press Enter again to finish extruding and create block." +icon = ExtResource("1_gxivr") +tool_script = ExtResource("3_oagna") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_stairs.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_stairs.tres new file mode 100644 index 0000000..810a858 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_create_stairs.tres @@ -0,0 +1,26 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://16f1nwimgn0p"] + +[ext_resource type="Texture2D" uid="uid://bwq4w4vf8um1f" path="res://addons/cyclops_level_builder/art/icons/create_stairs.svg" id="1_4iod6"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_kdc1t"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_stairs.gd" id="3_5ju43"] + +[sub_resource type="InputEventKey" id="InputEventKey_bydj5"] +device = -1 +shift_pressed = true +pressed = true +keycode = 69 +unicode = 69 + +[resource] +script = ExtResource("1_kdc1t") +id = "stairs" +name = "Stairs" +input_events = Array[InputEvent]([SubResource("InputEventKey_bydj5")]) +input_events_override = false +tooltip = "Click on surface of block or in empty space to begin creating base of a stair case. + +Release mouse button and drag upwards to adjust the height of the stairs. + +Use the mouse wheel to change the direction the stairs face. Ctrl-Wheel to change the height of each step, Ctrl-Shift-Wheel to change the depth of each step." +icon = ExtResource("1_4iod6") +tool_script = ExtResource("3_5ju43") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_duplicate.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_duplicate.tres new file mode 100644 index 0000000..24c991b --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_duplicate.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=3 format=3 uid="uid://ryja8b4fr8bb"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_cii1q"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_duplicate.gd" id="2_ml6st"] + +[resource] +script = ExtResource("1_cii1q") +id = "duplicate" +name = "Duplicate" +tooltip = "" +tool_script = ExtResource("2_ml6st") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_edge.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_edge.tres new file mode 100644 index 0000000..1713990 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_edge.tres @@ -0,0 +1,27 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://dub6oyal5fxly"] + +[ext_resource type="Texture2D" uid="uid://d2da2j8ve48rt" path="res://addons/cyclops_level_builder/art/icons/select_edge.svg" id="1_6o0d8"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_w2hsk"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_edge.gd" id="3_6rili"] + +[sub_resource type="InputEventKey" id="InputEventKey_k6fuk"] +device = -1 +pressed = true +keycode = 50 +unicode = 50 + +[resource] +script = ExtResource("1_w2hsk") +id = "edit_edge" +name = "Edge" +input_events = Array[InputEvent]([SubResource("InputEventKey_k6fuk")]) +input_events_override = false +tooltip = "Edge + +Click on an edge to select it. Shift Click to toggle selecton, Ctrl Click to add to selection, Shift-Ctrl click to subtract from selection. + +Click and drag to move edge in XZ plane. Hold Alt to drag along Y axis. Click and drag on a selected edge to move all selected edges. + +Hover the mouse over a different block and press Alt-Q to switch to editing that block." +icon = ExtResource("1_6o0d8") +tool_script = ExtResource("3_6rili") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_face.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_face.tres new file mode 100644 index 0000000..421bdef --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_face.tres @@ -0,0 +1,31 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=6 format=3 uid="uid://cmdgd8wmfdo4a"] + +[ext_resource type="Texture2D" uid="uid://bi27fw31w4ssi" path="res://addons/cyclops_level_builder/art/icons/select_face.svg" id="1_s64xo"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_qfyqw"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_face.gd" id="3_y22x5"] + +[sub_resource type="InputEventKey" id="InputEventKey_xxi5p"] +pressed = true +keycode = 51 + +[sub_resource type="InputEventKey" id="InputEventKey_1smmt"] +device = -1 +pressed = true +keycode = 52 +unicode = 52 + +[resource] +script = ExtResource("2_qfyqw") +id = "edit_face" +name = "Face" +input_events = Array[InputEvent]([SubResource("InputEventKey_xxi5p"), SubResource("InputEventKey_1smmt")]) +input_events_override = false +tooltip = "Face + +Click on a face to select it. Shift Click to toggle selecton, Ctrl Click to add to selection, Shift-Ctrl click to subtract from selection. + +Click and drag to move face in XZ plane. Hold Alt to drag along Y axis. Click and drag on a selected face to move all selected faces. + +Hover the mouse over a different block and press Alt-Q to switch to editing that block." +icon = ExtResource("1_s64xo") +tool_script = ExtResource("3_y22x5") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_vertex.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_vertex.tres new file mode 100644 index 0000000..ef03c6f --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_edit_vertex.tres @@ -0,0 +1,27 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://ctj35x0jfg7ej"] + +[ext_resource type="Texture2D" uid="uid://cwn58lev5oopd" path="res://addons/cyclops_level_builder/art/icons/select_vertex.svg" id="1_i5cb7"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_yi1sl"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_vertex.gd" id="3_3wsge"] + +[sub_resource type="InputEventKey" id="InputEventKey_11fdh"] +device = -1 +pressed = true +keycode = 49 +unicode = 49 + +[resource] +script = ExtResource("2_yi1sl") +id = "edit_vertex" +name = "Vertex" +input_events = Array[InputEvent]([SubResource("InputEventKey_11fdh")]) +input_events_override = false +tooltip = "Vertex + +Click on a vertex to select it. Shift Click to toggle selecton, Ctrl Click to add to selection, Shift-Ctrl click to subtract from selection. + +Click and drag to move vertex in XZ plane. Hold Alt to drag along Y axis. Click and drag on a selected vertex to move all selected vertices. + +Hover the mouse over a different block and press Alt-Q to switch to editing that block." +icon = ExtResource("1_i5cb7") +tool_script = ExtResource("3_3wsge") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_material_brush.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_material_brush.tres new file mode 100644 index 0000000..ee25517 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_material_brush.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://bjmuechy70058"] + +[ext_resource type="Texture2D" uid="uid://dw8s7hrmnu34j" path="res://addons/cyclops_level_builder/art/icons/material_brush.svg" id="1_hjh4j"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_ooato"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_material_brush.gd" id="3_1e4l3"] + +[sub_resource type="InputEventKey" id="InputEventKey_enkb2"] +device = -1 +keycode = 66 +unicode = 98 + +[resource] +script = ExtResource("2_ooato") +id = "material_brush" +name = "Material Brush" +input_events = Array[InputEvent]([SubResource("InputEventKey_enkb2")]) +input_events_override = false +tooltip = "Material Brush + +Click and drag on surfaces to apply the currently selected material. + +Shift-X will sample the properties of the face under the brush cursor." +icon = ExtResource("1_hjh4j") +tool_script = ExtResource("3_1e4l3") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_move.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_move.tres new file mode 100644 index 0000000..497a1e5 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_move.tres @@ -0,0 +1,37 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=6 format=3 uid="uid://rskdanqaqt1y"] + +[ext_resource type="Texture2D" uid="uid://cqy2x1s41ypbt" path="res://addons/cyclops_level_builder/art/icons/move.svg" id="1_g0ofo"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="1_skypg"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_move.gd" id="2_16d6f"] + +[sub_resource type="InputEventKey" id="InputEventKey_0yb0p"] +device = -1 +pressed = true +keycode = 81 +unicode = 113 + +[sub_resource type="InputEventKey" id="InputEventKey_ygw0u"] +device = -1 +pressed = true +keycode = 87 +unicode = 119 + +[resource] +script = ExtResource("1_skypg") +id = "move" +name = "Move" +input_events = Array[InputEvent]([SubResource("InputEventKey_0yb0p"), SubResource("InputEventKey_ygw0u")]) +input_events_override = true +tooltip = "Move + +Click and drag on a selected block to move it in the XZ plane. Hold Alt to drag along the Y axis. + +Click and drag anywhere else to drag a rectangular selection region. + +Escape or right click to cancel movement. + +Click on a block to select it. Shift-Click toggles, Ctrl-Click adds and Shift-Ctrl Click subtracts. + +Click in empty space to clear selection." +icon = ExtResource("1_g0ofo") +tool_script = ExtResource("2_16d6f") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_rotate.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_rotate.tres new file mode 100644 index 0000000..109f438 --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_rotate.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://p0ucaj6w232i"] + +[ext_resource type="Texture2D" uid="uid://1hu5mqwbm55w" path="res://addons/cyclops_level_builder/art/icons/rotate.svg" id="1_12a61"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_10xto"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_rotate.gd" id="3_nykm1"] + +[sub_resource type="InputEventKey" id="InputEventKey_dvj1l"] +device = -1 +pressed = true +keycode = 69 +unicode = 101 + +[resource] +script = ExtResource("2_10xto") +id = "rotate" +name = "Rotate" +input_events = Array[InputEvent]([SubResource("InputEventKey_dvj1l")]) +input_events_override = true +tooltip = "Rotate + +Click and drag the circle gizmo to rotate around the plane the circle lies in." +icon = ExtResource("1_12a61") +tool_script = ExtResource("3_nykm1") diff --git a/addons/cyclops_level_builder/data/tool_tags/tool_tag_vertex_color_brush.tres b/addons/cyclops_level_builder/data/tool_tags/tool_tag_vertex_color_brush.tres new file mode 100644 index 0000000..868224f --- /dev/null +++ b/addons/cyclops_level_builder/data/tool_tags/tool_tag_vertex_color_brush.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="ToolTag" load_steps=5 format=3 uid="uid://b1a71dvqwi4h1"] + +[ext_resource type="Texture2D" uid="uid://be3f2j6mnl1yb" path="res://addons/cyclops_level_builder/art/icons/vertex_color_brush.svg" id="1_e5tvl"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/tool_tag.gd" id="2_pcbhj"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_vertex_color_brush.gd" id="3_12f6u"] + +[sub_resource type="InputEventKey" id="InputEventKey_v56kt"] +device = -1 +keycode = 86 +unicode = 118 + +[resource] +script = ExtResource("2_pcbhj") +id = "vertex_color_brush" +name = "Vertex Color Brush" +input_events = Array[InputEvent]([SubResource("InputEventKey_v56kt")]) +input_events_override = false +tooltip = "Vertex Color Brush + +Click and drag on surfaces to adjust the vertex color. + +Shift-X will sample the color of the closest vertex." +icon = ExtResource("1_e5tvl") +tool_script = ExtResource("3_12f6u") diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.gd b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.gd new file mode 100644 index 0000000..9bf82ac --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.gd @@ -0,0 +1,77 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends SubViewport +class_name ConvexFaceEditorPreview + + + +@export var target_material:Material: + get: + return target_material + set(value): + target_material = value + dirty = true + +@export var uv_transform:Transform2D = Transform2D.IDENTITY: + get: + return uv_transform + set(value): + if value == uv_transform: + return + uv_transform = value + dirty = true + +@export var color:Color = Color.WHITE: + get: + return color + set(value): + if value == color: + return + color = value + dirty = true + +var dirty:bool = true +#var points:PackedVector3Array = [Vector3(0, 0, 0), Vector3(1, 1, 0), Vector3(1, 0, 0), Vector3(0, 1, 0)] + +func take_snapshot()->ImageTexture: + #print ("pre-grabbing image %s" % target_material.resource_path) + await RenderingServer.frame_post_draw + #print ("grabbing image %s" % target_material.resource_path) + var image:Image = get_viewport().get_texture().get_image() + var tex:ImageTexture = ImageTexture.create_from_image(image) + return tex + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func _process(delta): + if dirty: + $UvPreviewStudio.target_material = target_material + $UvPreviewStudio.uv_transform = uv_transform + $UvPreviewStudio.color = color + dirty = false + + diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.tscn b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.tscn new file mode 100644 index 0000000..3cfd6e6 --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://bbfgpupliiqnm"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.gd" id="1_bjhau"] +[ext_resource type="Material" uid="uid://rdhrhgrb78ls" path="res://addons/cyclops_level_builder/materials/grid.tres" id="2_t8xtu"] +[ext_resource type="PackedScene" uid="uid://716oipfa7f5l" path="res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.tscn" id="3_jtspd"] + +[node name="UvPreview" type="SubViewport"] +own_world_3d = true +size = Vector2i(256, 256) +render_target_update_mode = 4 +script = ExtResource("1_bjhau") +target_material = ExtResource("2_t8xtu") +color = null + +[node name="UvPreviewStudio" parent="." instance=ExtResource("3_jtspd")] +target_material = ExtResource("2_t8xtu") diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.gd b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.gd new file mode 100644 index 0000000..28cabad --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.gd @@ -0,0 +1,77 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name ConvexFaceEditorPreviewStudio + + +@export var target_material:Material: + get: + return target_material + set(value): + target_material = value + #$Node3D/MeshInstance3D.material_override = target_material + dirty = true + +@export var uv_transform:Transform2D = Transform2D.IDENTITY: + get: + return uv_transform + set(value): + if value == uv_transform: + return + uv_transform = value + dirty = true + +@export var color:Color = Color.WHITE: + get: + return color + set(value): + if value == color: + return + color = value + dirty = true + +var dirty:bool = true +var points:PackedVector3Array = [Vector3(-1, 1, 0), Vector3(1, 1, 0), Vector3(-1, -1, 0), Vector3(1, -1, 0)] + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): +# print("_process") + if dirty: + var mesh:ImmediateMesh = ImmediateMesh.new() + + mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, target_material) + + mesh.surface_set_normal(Vector3(0, 0, 1)) + for p in points: + mesh.surface_set_uv(uv_transform * Vector2(p.x, -p.y)) + mesh.surface_set_color(color) + mesh.surface_add_vertex(p) + + mesh.surface_end() + +# print("Building preview mesh") + $MeshInstance3D.mesh = mesh + dirty = false diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.tscn b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.tscn new file mode 100644 index 0000000..3c87e91 --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://716oipfa7f5l"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview_studio.gd" id="1_38c8p"] + +[sub_resource type="ImmediateMesh" id="ImmediateMesh_lw55q"] + +[node name="Node3D" type="Node3D"] +script = ExtResource("1_38c8p") +color = null + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1) +projection = 1 + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("ImmediateMesh_lw55q") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.0684279) diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.gd b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.gd new file mode 100644 index 0000000..12f81ac --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.gd @@ -0,0 +1,241 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name ConvexFaceEdtiorViewport + +var material_thumbnail_dirty:bool = true + +var target_material:Material +var empty_material:Material + +var uv_transform:Transform2D = Transform2D.IDENTITY +var color:Color = Color.WHITE + +var builder:CyclopsLevelBuilder: + get: + return builder + set(value): + if builder: + builder.selection_changed.disconnect(on_selection_changed) + + builder = value + + if builder: + builder.selection_changed.connect(on_selection_changed) + +var spin_offset_x:NumbericLineEdit +var spin_offset_y:NumbericLineEdit +var spin_scale_x:NumbericLineEdit +var spin_scale_y:NumbericLineEdit +var spin_rotation:NumbericLineEdit +var spin_skew:NumbericLineEdit + +#var test_slider:EditorSpinSlider + +# Called when the node enters the scene tree for the first time. +func _ready(): + empty_material = StandardMaterial3D.new() + empty_material.albedo_color = Color.BLACK + + spin_offset_x = $VBoxContainer/GridContainer2/HBoxContainer2/offset_x + spin_offset_y = $VBoxContainer/GridContainer2/HBoxContainer/offset_y + spin_scale_x = $VBoxContainer/GridContainer3/HBoxContainer2/scale_x + spin_scale_y = $VBoxContainer/GridContainer3/HBoxContainer/scale_y + spin_rotation = $VBoxContainer/GridContainer4/HBoxContainer2/rotation + spin_skew = $VBoxContainer/GridContainer4/HBoxContainer/skew + +# test_slider = EditorSpinSlider.new() +# test_slider.size_flags_horizontal = Control.SIZE_EXPAND +# $VBoxContainer.add_child(test_slider) + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + + if material_thumbnail_dirty: + material_thumbnail_dirty = false + + $UvPreview.target_material = target_material + $UvPreview.uv_transform = uv_transform + $UvPreview.color = color + + var tex:ImageTexture = await $UvPreview.take_snapshot() + $VBoxContainer/Preview.texture = tex + pass + +func on_selection_changed(): + material_thumbnail_dirty = true + target_material = empty_material + + + var block:CyclopsBlock = builder.get_active_block() + if block: + var vol:ConvexVolume = block.control_mesh + var face_idx = vol.active_face if vol.active_face != -1 else 0 + + var f:ConvexVolume.FaceInfo = vol.get_face(face_idx) + + + spin_offset_x.value = f.uv_transform.origin.x + spin_offset_y.value = f.uv_transform.origin.y + spin_scale_x.value = f.uv_transform.get_scale().x + spin_scale_y.value = f.uv_transform.get_scale().y + spin_rotation.value = rad_to_deg(f.uv_transform.get_rotation()) + spin_skew.value = rad_to_deg(f.uv_transform.get_skew()) + %check_face_visible.button_pressed = f.visible + %color_picker_face.color = f.color + + if f.material_id != -1: + var mat:Material = block.materials[f.material_id] + target_material = mat + else: + target_material = null + + uv_transform = f.uv_transform + + + +func save_state(state:Dictionary): + var substate:Dictionary = {} + state["uv_editor_dock"] = substate + +# substate["materials"] = material_list.duplicate() + +func load_state(state:Dictionary): + if state == null || !state.has("uv_editor_dock"): + return + + var substate:Dictionary = state["uv_editor_dock"] + + +func apply_uv_transform(): + var xform:Transform2D = Transform2D(deg_to_rad(spin_rotation.value), \ + Vector2(spin_scale_x.value, spin_scale_y.value), \ + deg_to_rad(spin_skew.value), \ + Vector2(spin_offset_x.value, spin_offset_y.value)) + + uv_transform = xform + #print("apply_uv_transform ", uv_transform) + + var cmd:CommandSetFaceUvTransform = CommandSetFaceUvTransform.new() + cmd.builder = builder + cmd.uv_transform = xform + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: +# print("sel block %s" % block.name) + + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.selected: + cmd.add_face(block.get_path(), f_idx) + + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + +func apply_visible(): + var face_visible:bool = %check_face_visible.button_pressed + + #print("apply_uv_transform ", uv_transform) + + var cmd:CommandSetFaceVisible = CommandSetFaceVisible.new() + cmd.builder = builder + cmd.visible = face_visible + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: +# print("sel block %s" % block.name) + + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.selected: + cmd.add_face(block.get_path(), f_idx) + + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + +func apply_color(): + var face_color:Color = %color_picker_face.color + color = face_color + + #print("apply_uv_transform ", uv_transform) + + var cmd:CommandSetFaceColor = CommandSetFaceColor.new() + cmd.builder = builder + cmd.color = face_color + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: +# print("sel block %s" % block.name) + + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var f:ConvexVolume.FaceInfo = vol.faces[f_idx] + if f.selected: + cmd.add_face(block.get_path(), f_idx) + + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + +func _on_offset_x_value_changed(value): + apply_uv_transform() + + +func _on_offset_y_value_changed(value): + apply_uv_transform() + + +func _on_scale_x_value_changed(value): + apply_uv_transform() + + +func _on_scale_y_value_changed(value): + apply_uv_transform() + + +func _on_rotation_value_changed(value): + apply_uv_transform() + + +func _on_skew_value_changed(value): + apply_uv_transform() + + + + +func _on_color_picker_face_color_changed(color): + apply_color() + + +func _on_check_face_visible_toggled(button_pressed): + apply_visible() diff --git a/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.tscn b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.tscn new file mode 100644 index 0000000..8435987 --- /dev/null +++ b/addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.tscn @@ -0,0 +1,163 @@ +[gd_scene load_steps=6 format=3 uid="uid://bxcewugh0vbee"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_viewport.gd" id="1_rk116"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_cekit"] +[ext_resource type="PackedScene" uid="uid://bbfgpupliiqnm" path="res://addons/cyclops_level_builder/docks/convex_face_editor/convex_face_editor_preview.tscn" id="2_kpj7h"] + +[sub_resource type="Image" id="Image_a1rps"] +data = { +"data": PackedByteArray(203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203), +"format": "RGB8", +"height": 128, +"mipmaps": false, +"width": 128 +} + +[sub_resource type="ImageTexture" id="ImageTexture_lr775"] +image = SubResource("Image_a1rps") + +[node name="Face Properties" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -697.0 +offset_bottom = -285.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_rk116") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +metadata/_edit_lock_ = true + +[node name="Preview" type="TextureRect" parent="VBoxContainer"] +layout_mode = 2 +texture = SubResource("ImageTexture_lr775") +stretch_mode = 3 + +[node name="GridContainer2" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/GridContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="VBoxContainer/GridContainer2/HBoxContainer2"] +layout_mode = 2 +text = "Offset X +" + +[node name="offset_x" parent="VBoxContainer/GridContainer2/HBoxContainer2" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/GridContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="VBoxContainer/GridContainer2/HBoxContainer"] +layout_mode = 2 +text = "Offset Y +" + +[node name="offset_y" parent="VBoxContainer/GridContainer2/HBoxContainer" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GridContainer3" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/GridContainer3"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="VBoxContainer/GridContainer3/HBoxContainer2"] +layout_mode = 2 +text = "Scale X +" + +[node name="scale_x" parent="VBoxContainer/GridContainer3/HBoxContainer2" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/GridContainer3"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="VBoxContainer/GridContainer3/HBoxContainer"] +layout_mode = 2 +text = "Scale Y" + +[node name="scale_y" parent="VBoxContainer/GridContainer3/HBoxContainer" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="GridContainer4" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/GridContainer4"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="VBoxContainer/GridContainer4/HBoxContainer2"] +layout_mode = 2 +text = "Rotation" + +[node name="rotation" parent="VBoxContainer/GridContainer4/HBoxContainer2" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 +snap_size = 15.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/GridContainer4"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="VBoxContainer/GridContainer4/HBoxContainer"] +layout_mode = 2 +text = "Skew +" + +[node name="skew" parent="VBoxContainer/GridContainer4/HBoxContainer" instance=ExtResource("2_cekit")] +layout_mode = 2 +size_flags_horizontal = 3 +snap_size = 15.0 + +[node name="check_face_visible" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Visible" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Color" + +[node name="color_picker_face" type="ColorPickerButton" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="UvPreview" parent="." instance=ExtResource("2_kpj7h")] +size = Vector2i(128, 128) +target_material = null +color = Color(1, 1, 1, 1) + +[connection signal="value_changed" from="VBoxContainer/GridContainer2/HBoxContainer2/offset_x" to="." method="_on_offset_x_value_changed"] +[connection signal="value_changed" from="VBoxContainer/GridContainer2/HBoxContainer/offset_y" to="." method="_on_offset_y_value_changed"] +[connection signal="value_changed" from="VBoxContainer/GridContainer3/HBoxContainer2/scale_x" to="." method="_on_scale_x_value_changed"] +[connection signal="value_changed" from="VBoxContainer/GridContainer3/HBoxContainer/scale_y" to="." method="_on_scale_y_value_changed"] +[connection signal="value_changed" from="VBoxContainer/GridContainer4/HBoxContainer2/rotation" to="." method="_on_rotation_value_changed"] +[connection signal="value_changed" from="VBoxContainer/GridContainer4/HBoxContainer/skew" to="." method="_on_skew_value_changed"] +[connection signal="toggled" from="VBoxContainer/check_face_visible" to="." method="_on_check_face_visible_toggled"] +[connection signal="color_changed" from="VBoxContainer/HBoxContainer/color_picker_face" to="." method="_on_color_picker_face_color_changed"] diff --git a/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.gd b/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.gd new file mode 100644 index 0000000..f716946 --- /dev/null +++ b/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.gd @@ -0,0 +1,67 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name CyclopsConsole + +var editor_plugin:CyclopsLevelBuilder + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func save_state(state:Dictionary): + var substate:Dictionary = {} + state["cyclops_console"] = substate + + +func load_state(state:Dictionary): + if state == null || !state.has("cyclops_console"): + return + + var substate:Dictionary = state["cyclops_console"] + +func _on_enable_cyclops_toggled(button_pressed): + editor_plugin.always_on = button_pressed + + +func _on_bn_create_block_pressed(): + var cmd:CommandAddBlock = CommandAddBlock.new() + cmd.builder = editor_plugin + + var bounds:AABB = AABB(%block_position.value, %block_size.value) + cmd.bounds = bounds + var scene_root = editor_plugin.get_editor_interface().get_edited_scene_root() + cmd.blocks_root_path = scene_root.get_path() + cmd.block_name = GeneralUtil.find_unique_name(scene_root, "block") + + var undo:EditorUndoRedoManager = editor_plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) + diff --git a/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.tscn b/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.tscn new file mode 100644 index 0000000..bcf69c0 --- /dev/null +++ b/addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=3 format=3 uid="uid://cbo80g1hbom2"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/cyclops_console/cyclops_console.gd" id="1_f4kro"] +[ext_resource type="PackedScene" uid="uid://cphtpklx81l3w" path="res://addons/cyclops_level_builder/controls/vector3_edit.tscn" id="2_qpx41"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 469.0 +offset_bottom = 322.0 +script = ExtResource("1_f4kro") + +[node name="enable_cyclops" type="CheckBox" parent="."] +visible = false +layout_mode = 2 +text = "Enable Cyclops" + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/PanelContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Create Block" + +[node name="GridContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Position" + +[node name="block_position" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer/GridContainer" instance=ExtResource("2_qpx41")] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Size" + +[node name="block_size" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer/GridContainer" instance=ExtResource("2_qpx41")] +unique_name_in_owner = true +layout_mode = 2 +value = Vector3(1, 1, 1) + +[node name="bn_create_block" type="Button" parent="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Create Block" + +[connection signal="toggled" from="enable_cyclops" to="." method="_on_enable_cyclops_toggled"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/PanelContainer/VBoxContainer/bn_create_block" to="." method="_on_bn_create_block_pressed"] diff --git a/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_add_materials.gd b/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_add_materials.gd new file mode 100644 index 0000000..513cb10 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_add_materials.gd @@ -0,0 +1,56 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMaterialDockAddMaterials +extends CyclopsCommand + +#Public +var res_path_list:Array[String] + +#Private +var old_res_path_list:Array[String] + + +func _init(): + command_name = "Add materials" + +func do_it(): +# print("Add Materials do_it") + var mat_dock:MaterialPaletteViewport = builder.material_dock + old_res_path_list = mat_dock.material_list.duplicate() + +# print("old mat list %s" % str(old_res_path_list)) + + var new_list:Array[String] = old_res_path_list.duplicate() + for mat in res_path_list: + if !new_list.has(mat): + new_list.append(mat) + +# print("new mat list %s" % str(new_list)) + + mat_dock.set_materials(new_list) + +func undo_it(): + var mat_dock:MaterialPaletteViewport = builder.material_dock + mat_dock.set_materials(old_res_path_list) diff --git a/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_remove_materials.gd b/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_remove_materials.gd new file mode 100644 index 0000000..771906c --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/commands/cmd_mat_dock_remove_materials.gd @@ -0,0 +1,52 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CommandMaterialDockRemoveMaterials +extends CyclopsCommand + +#Public +var res_path_list:Array[String] + +#Private +var old_res_path_list:Array[String] + + +func _init(): + command_name = "Remove materials" + +func do_it(): +# print("Remove Materials do_it") + + var mat_dock:MaterialPaletteViewport = builder.material_dock + old_res_path_list = mat_dock.material_list.duplicate() + + var new_list:Array[String] = old_res_path_list.duplicate() + for mat in res_path_list: + var idx:int = new_list.find(mat) + new_list.remove_at(idx) + mat_dock.set_materials(new_list) + +func undo_it(): + var mat_dock:MaterialPaletteViewport = builder.material_dock + mat_dock.set_materials(old_res_path_list) diff --git a/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.gd b/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.gd new file mode 100644 index 0000000..040fa49 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.gd @@ -0,0 +1,242 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name MaterialPaletteViewport + +#@export var material_list:Array[String] = [] + +#@export var thumbnail_group:ThumbnailGroup + +var builder:CyclopsLevelBuilder: + get: + return builder + set(value): + if builder == value: + return + + builder = value + + var mv:MaterialViewer = %MaterialViewer + if mv: + mv.builder = builder + + #call_deferred("update_plugin") + #update_plugin() + +#var undo_manager:UndoRedo + +#var has_mouse_focus:bool = false + +#var drag_pressed:bool = false +#var drag_start_pos:Vector2 +#var drag_start_scroll_value_y:float + +#func update_plugin(): + #var mv:MaterialViewer = %MaterialViewer + #if mv: + #mv.builder = builder + + +# Called when the node enters the scene tree for the first time. +func _ready(): +# print("MaterialPaletteViewport") + #undo_manager = UndoRedo.new() + + #update_thumbnails() + + #%MaterialViewer.builder = builder + pass + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + +#func _can_drop_data(at_position:Vector2, data:Variant): +## print("_can_drop_data %s" % data) + #return typeof(data) == TYPE_DICTIONARY and data.has("type") and data["type"] == "files" +# +#func _gui_input(event): + #if event is InputEventMouseButton: + #var e:InputEventMouseButton = event + # + #if e.button_index == MOUSE_BUTTON_MIDDLE: + #var v_scroll:VScrollBar = %ScrollContainer.get_v_scroll_bar() + # + #drag_pressed = e.pressed + #drag_start_pos = e.position + #drag_start_scroll_value_y = v_scroll.value +# + #elif event is InputEventMouseMotion: + #if drag_pressed: + #var e:InputEventMouseMotion = event + #var offset:Vector2 = e.position - drag_start_pos + # + #var win_size:Vector2 = %ScrollContainer.size + # + #var v_scroll:VScrollBar = %ScrollContainer.get_v_scroll_bar() + #v_scroll.value = clamp(drag_start_scroll_value_y - (offset.y / win_size.y) * v_scroll.max_value, v_scroll.min_value, v_scroll.max_value) +## print("v min max %s %s" % [v_scroll.min_value, v_scroll.max_value]) + + + +#func _unhandled_input(event): + #if !has_mouse_focus: + #return + # + #if event is InputEventKey: + ##print("key event %s" % str(event)) + #var e:InputEventKey = event +## if e.keycode == KEY_DELETE: + #if e.keycode == KEY_X: + #if e.pressed: +## print("mat pal X") + #remove_selected_material() +# + #accept_event() +# +#func remove_selected_material(): + #var cmd:CommandMaterialDockRemoveMaterials = CommandMaterialDockRemoveMaterials.new() + #cmd.builder = builder + # + #for child in %HFlowContainer.get_children(): + #if child.selected: + #cmd.res_path_list.append(child.material_path) +# + #var undo_manager:EditorUndoRedoManager = builder.get_undo_redo() + #cmd.add_to_undo_manager(undo_manager) + +#func set_materials(res_path_list:Array[String]): + #material_list = res_path_list +## print("set mat list %s" % str(material_list)) + #update_thumbnails() + # +# +func save_state(state:Dictionary): + var substate:Dictionary = {} + state["material_palette"] = substate + #substate["materials"] = material_list.duplicate() + +func load_state(state:Dictionary): + if state == null || !state.has("material_palette"): + return + + var substate:Dictionary = state["material_palette"] +# +## print("load_state()") + #material_list = [] + #if substate.has("materials"): + #for mat_path in substate["materials"]: + #if ResourceLoader.exists(mat_path): + #material_list.append(mat_path) + # + #update_thumbnails() +# +#func _drop_data(at_position, data): + #var files = data["files"] + ##print("--drop") + #var add_list:Array[String] + #for f in files: +## print("Dropping %s" % f) + #var res:Resource = load(f) + #if res is Material: + #if !material_list.has(f): + #add_list.append(f) + # + # + #var cmd:CommandMaterialDockAddMaterials = CommandMaterialDockAddMaterials.new() + #cmd.builder = builder + # + #cmd.res_path_list = add_list +# + #var undo_manager:EditorUndoRedoManager = builder.get_undo_redo() + #cmd.add_to_undo_manager(undo_manager) + # + ##print("drop data clear") + ##material_list.clear() + +#func update_thumbnails(): +## print("update_thumbnails()") + #var cur_sel:String + # + #for child in %HFlowContainer.get_children(): + #if child.selected: + #cur_sel = child.material_path + #break +# + #for child in %HFlowContainer.get_children(): + ##print("removing %s" % child.get_class()) + #child.group = null + #%HFlowContainer.remove_child(child) + #child.queue_free() +# + #for path in material_list: + #var res:Resource = preload("res://addons/cyclops_level_builder/docks/material_palette/material_thumbnail.tscn") + #var thumbnail:MaterialThumbnail = res.instantiate() + #thumbnail.builder = builder + #thumbnail.material_path = path + #thumbnail.group = thumbnail_group +## print("adding mat %s" % path) + # + # + #%HFlowContainer.add_child(thumbnail) + #thumbnail.owner = self + # + #if cur_sel: + #for child in %HFlowContainer.get_children(): + #if child.material_path == cur_sel: + #child.selected = true + #break + + +#func _on_visibility_changed(): + ##Control freezes for some reason when hidden and then shown, so just regenereate it + #if visible: + #update_thumbnails() + + + +#func _on_remove_all_materials_pressed(): + #var cmd:CommandMaterialDockRemoveMaterials = CommandMaterialDockRemoveMaterials.new() + #cmd.builder = builder + # + #cmd.res_path_list = material_list.duplicate() +# + #var undo_manager:EditorUndoRedoManager = builder.get_undo_redo() + #cmd.add_to_undo_manager(undo_manager) + + + +#func _on_remove_sel_pressed(): + #remove_selected_material() +# +# +#func _on_h_flow_container_mouse_entered(): + #has_mouse_focus = true +## print("_on_h_flow_container_mouse_entered()") +# +# +#func _on_h_flow_container_mouse_exited(): + #has_mouse_focus = false +## print("_on_h_flow_container_mouse_exited()") diff --git a/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.tscn b/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.tscn new file mode 100644 index 0000000..31ed4f3 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://o1efx0qxc4n3"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_palette_viewport.gd" id="1_xyxg3"] +[ext_resource type="PackedScene" uid="uid://denc7grw42qsu" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.tscn" id="3_bks23"] + +[node name="Materials" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_xyxg3") +metadata/_edit_lock_ = true + +[node name="MaterialViewer" parent="." instance=ExtResource("3_bks23")] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 0.0 +offset_bottom = 0.0 +grow_horizontal = 2 +grow_vertical = 2 + +[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.gd new file mode 100644 index 0000000..17d025e --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.gd @@ -0,0 +1,124 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Window +class_name CreateMaterialDialog + +signal create_material(params:Dictionary) + +var texture_list:Array[Texture2D] +var parent_dir_path:String + +var plugin:CyclopsLevelBuilder: + get: + return plugin + set(value): + if value == plugin: + return + + plugin = value + #print("CreateMaterialDialog setting plugin") + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_bn_okay_pressed(): + var mat_type:String = "standard" if %radio_stdMat.is_pressed() else "shader" + var tgt_param:String = "albedo_texture" + + + create_material.emit({ + "name": %line_material_name.text, + "material_type" : mat_type, + "shader_res_path" : %line_shader_path.text, + "texture_parameter" : %target_slot.get_item_text(%target_slot.selected), + "uv_parameter" : %uv_slot.get_item_text(%uv_slot.selected), + "uv_type" : "1x1" if %radio_uv_1x1.is_pressed() else "pix_per_game_unit", + "pix_per_game_unit" : %line_pix_per_game_unit.text.to_int(), + "parent_dir" : parent_dir_path, + "textures" : texture_list + #material_type: "" + }) + + hide() + + +func _on_bn_cancel_pressed(): + hide() + + +func _on_bn_browse_shader_pressed(): + %FileDialog.popup_centered() + + +func _on_about_to_popup(): + #print("CreateMaterialDialog about to popup") + + var ed_iface:EditorInterface = plugin.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + + var root_dir:EditorFileSystemDirectory = efs.get_filesystem() + + if !texture_list.is_empty(): + %line_material_name.text = texture_list[0].resource_path.get_file().get_basename() + + +func _on_file_dialog_file_selected(path:String): + var shader:Shader = ResourceLoader.load(path, "Shader") + if !shader: + return + + %line_shader_path.text = path + update_shader_slot_list() + +func update_shader_slot_list(): + var path:String = %line_shader_path.text + var shader:Shader = ResourceLoader.load(path, "Shader") + %target_slot.clear() + %uv_slot.clear() + + #TYPE_VECTOR2 + if shader: + #Array of dictionaries + var params:Array = shader.get_shader_uniform_list() + + for p in params: + #print("shader param ", str(p)) + if p["hint_string"] == "Texture2D": + %target_slot.add_item(p["name"]) + if p["type"] == TYPE_VECTOR2 || p["type"] == TYPE_VECTOR3: + %uv_slot.add_item(p["name"]) + + + + +func _on_line_shader_path_text_changed(new_text): + update_shader_slot_list() diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.tscn b/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.tscn new file mode 100644 index 0000000..639526d --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.tscn @@ -0,0 +1,182 @@ +[gd_scene load_steps=6 format=3 uid="uid://b510d4yme5xtx"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.gd" id="1_ysdvw"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7cu3j"] +bg_color = Color(0.309804, 0.309804, 0.309804, 0) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 + +[sub_resource type="Theme" id="Theme_u2063"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_7cu3j") + +[sub_resource type="ButtonGroup" id="ButtonGroup_hlttb"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_rde8s"] + +[node name="CreateMaterialDialog" type="Window"] +title = "Create Material" +position = Vector2i(0, 36) +size = Vector2i(600, 400) +script = ExtResource("1_ysdvw") + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Material Name:" + +[node name="line_material_name" type="LineEdit" parent="PanelContainer/VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PanelContainer2" type="PanelContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme = SubResource("Theme_u2063") + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer2"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer"] +layout_mode = 2 +text = "Material Type" + +[node name="radio_stdMat" type="CheckBox" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true +button_group = SubResource("ButtonGroup_hlttb") +text = "StandardMaterial3D" + +[node name="radio_shaderMat" type="CheckBox" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_group = SubResource("ButtonGroup_hlttb") +text = "ShaderMaterial" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 64 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Shader:" + +[node name="line_shader_path" type="LineEdit" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_browse_shader" type="Button" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Pick shader to use for material" +text = "..." + +[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Texture Parameter:" + +[node name="target_slot" type="OptionButton" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HBoxContainer3" type="HBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +layout_mode = 2 +text = "UV Parameter:" + +[node name="uv_slot" type="OptionButton" parent="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme = SubResource("Theme_u2063") + +[node name="VBoxContainer2" type="VBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2"] +layout_mode = 2 +text = "UVs" + +[node name="radio_uv_1x1" type="CheckBox" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +button_group = SubResource("ButtonGroup_rde8s") +text = "1 x 1" + +[node name="radio_uv_scale_to_pix" type="CheckBox" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true +button_group = SubResource("ButtonGroup_rde8s") +text = "Scale to pixel size" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2"] +layout_mode = 2 +theme_override_constants/margin_left = 64 + +[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2/MarginContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2/MarginContainer/HBoxContainer2"] +layout_mode = 2 +text = "Pixels per game unit" + +[node name="line_pix_per_game_unit" type="LineEdit" parent="PanelContainer/VBoxContainer/PanelContainer/VBoxContainer2/MarginContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +text = "32" + +[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="bn_okay" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Cancel +" + +[node name="FileDialog" type="FileDialog" parent="."] +unique_name_in_owner = true +title = "Open a File" +size = Vector2i(600, 400) +ok_button_text = "Open" +file_mode = 0 + +[connection signal="about_to_popup" from="." to="." method="_on_about_to_popup"] +[connection signal="text_changed" from="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer/line_shader_path" to="." method="_on_line_shader_path_text_changed"] +[connection signal="pressed" from="PanelContainer/VBoxContainer/PanelContainer2/VBoxContainer/MarginContainer/VBoxContainer/HBoxContainer/bn_browse_shader" to="." method="_on_bn_browse_shader_pressed"] +[connection signal="pressed" from="PanelContainer/VBoxContainer/HBoxContainer/bn_okay" to="." method="_on_bn_okay_pressed"] +[connection signal="pressed" from="PanelContainer/VBoxContainer/HBoxContainer/bn_cancel" to="." method="_on_bn_cancel_pressed"] +[connection signal="file_selected" from="FileDialog" to="." method="_on_file_dialog_file_selected"] diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.gd new file mode 100644 index 0000000..aeaa37b --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.gd @@ -0,0 +1,69 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + + +@tool +extends PopupPanel +class_name LineInput + +@export var text:String: + get: + return text + set(value): + text = value + title = value + #%Label.text = text + +@export var edit_text:String: + get: + return edit_text + set(value): + edit_text = value + %LineEdit.text = text + +signal text_chosen(text:String) + +# Called when the node enters the scene tree for the first time. +func _ready(): + #%Label.text = text + #%LineEdit.text = edit_text + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_line_edit_text_submitted(new_text): + text_chosen.emit(%LineEdit.text) + hide() + + +func _on_bn_accept_pressed(): + text_chosen.emit(%LineEdit.text) + hide() + + +func _on_bn_cancel_pressed(): + hide() diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.tscn b/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.tscn new file mode 100644 index 0000000..b3e09d0 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=2 format=3 uid="uid://tkp4i7e1fs5"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/line_input.gd" id="1_5x46h"] + +[node name="LineInput" type="PopupPanel"] +title = "Input" +size = Vector2i(200, 74) +visible = true +unresizable = false +borderless = false +always_on_top = true +script = ExtResource("1_5x46h") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 4.0 +offset_top = 4.0 +offset_right = 196.0 +offset_bottom = 70.0 + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/CenterContainer"] +layout_mode = 2 + +[node name="bn_accept" type="Button" parent="VBoxContainer/CenterContainer/HBoxContainer"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="VBoxContainer/CenterContainer/HBoxContainer"] +layout_mode = 2 +text = "Cancel" + +[connection signal="text_submitted" from="VBoxContainer/LineEdit" to="." method="_on_line_edit_text_submitted"] +[connection signal="pressed" from="VBoxContainer/CenterContainer/HBoxContainer/bn_accept" to="." method="_on_bn_accept_pressed"] +[connection signal="pressed" from="VBoxContainer/CenterContainer/HBoxContainer/bn_cancel" to="." method="_on_bn_cancel_pressed"] diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_active_theme.tres b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_active_theme.tres new file mode 100644 index 0000000..6068bac --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_active_theme.tres @@ -0,0 +1,16 @@ +[gd_resource type="Theme" load_steps=2 format=3 uid="uid://eajwlh2rlu3a"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_amf1g"] +bg_color = Color(0, 0, 0, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +border_color = Color(1, 0.890196, 0.729412, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[resource] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_amf1g") diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_normal_theme.tres b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_normal_theme.tres new file mode 100644 index 0000000..6446863 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_normal_theme.tres @@ -0,0 +1,17 @@ +[gd_resource type="Theme" load_steps=2 format=3 uid="uid://di7ydnvl4n54r"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_amf1g"] +bg_color = Color(0, 0, 0, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +border_color = Color(0, 0, 0, 1) +border_blend = true +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[resource] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_amf1g") diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_selected_theme.tres b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_selected_theme.tres new file mode 100644 index 0000000..06cc9e5 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_selected_theme.tres @@ -0,0 +1,16 @@ +[gd_resource type="Theme" load_steps=2 format=3 uid="uid://8ufqa1nourhn"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_amf1g"] +bg_color = Color(0, 0, 0, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +border_color = Color(1, 0.533333, 0, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[resource] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_amf1g") diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.gd new file mode 100644 index 0000000..f1fcd0c --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.gd @@ -0,0 +1,154 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name MaterialButton + +signal apply_material(mat_bn:MaterialButton) +signal select_material(mat_bn:MaterialButton, selection_type:SelectionList.Type) + +@export var selected:bool = false: + get: + return selected + set(value): + if selected == value: + return + selected = value + update_border() + +@export var active:bool = false: + get: + return active + set(value): + if active == value: + return + active = value + update_border() + +@export_file("*.tres") var material_path:String: + get: + return material_path + set(value): + if material_path == value: + return + + material_path = value + + dirty = true + + +@export var group:RadioButtonGroup: + get: + return group + set(value): + if group == value: + return + + if group != null: + group.remove_button(self) + + group = value + + if group != null: + group.add_button(self) + +@export var theme_normal:Theme = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_normal_theme.tres") +@export var theme_selected:Theme = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_selected_theme.tres") +@export var theme_active:Theme = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_active_theme.tres") + +var plugin:CyclopsLevelBuilder: + get: + return plugin + set(value): + if value == plugin: + return + + plugin = value + + dirty = true + +var dirty:bool = true + +var material_local:Material + +func rebuild_thumbnail(): + if !plugin: + return + + var rp:EditorResourcePreview = plugin.get_editor_interface().get_resource_previewer() + rp.queue_resource_preview(material_path, self, "resource_preview_callback", null) + + material_local = ResourceLoader.load(material_path, "Material") +# material_local = load(material_path) + %MaterialName.text = GeneralUtil.calc_resource_name(material_local) + tooltip_text = material_path + +func resource_preview_callback(path:String, preview:Texture2D, thumbnail_preview:Texture2D, userdata:Variant): + #print("Set bn tex ", path) + %TextureRect.texture = preview + + +func _gui_input(event:InputEvent): + if event is InputEventMouseButton: + var e:InputEventMouseButton = event + + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.pressed: + if e.double_click: + #apply_material_to_selected() + apply_material.emit(self) + else: + #if group: + #group.select_thumbnail(self) + #else: + #selected = true + + # builder.tool_material_path = material_path + + select_material.emit(self, SelectionList.choose_type(e.shift_pressed, e.ctrl_pressed)) + + get_viewport().set_input_as_handled() + +func update_border(): + if active: + theme = theme_active + elif selected: + theme = theme_selected + else: + theme = theme_normal + + +# Called when the node enters the scene tree for the first time. +func _ready(): + update_border() + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + rebuild_thumbnail() + dirty = false + pass diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.tscn b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.tscn new file mode 100644 index 0000000..cd99104 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=5 format=3 uid="uid://dj3p6dratmybd"] + +[ext_resource type="Theme" uid="uid://di7ydnvl4n54r" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/mat_bn_normal_theme.tres" id="1_t260s"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.gd" id="1_vd4oe"] + +[sub_resource type="Image" id="Image_cy15a"] +data = { +"data": PackedByteArray(158, 156, 158, 163, 159, 163, 158, 156, 158, 163, 159, 163, 158, 156, 158, 163, 159, 163, 158, 156, 158, 163, 159, 163, 158, 156, 158, 163, 159, 163, 158, 156, 158, 163, 160, 163, 158, 156, 158, 163, 160, 163, 158, 156, 158, 163, 160, 163, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 159, 157, 159, 164, 160, 164, 159, 157, 159, 164, 160, 164, 159, 156, 159, 164, 160, 164, 159, 156, 159, 164, 160, 164, 159, 156, 159, 164, 160, 164, 159, 156, 159, 164, 160, 164, 159, 156, 159, 163, 160, 163, 159, 156, 159, 163, 160, 163, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 127, 132, 127, 131, 135, 131, 126, 132, 126, 131, 134, 131, 126, 132, 126, 131, 134, 131, 163, 159, 163, 163, 159, 163, 169, 163, 169, 164, 160, 164, 166, 162, 166, 164, 160, 164, 169, 163, 169, 164, 160, 164, 166, 163, 166, 164, 160, 164, 169, 164, 169, 165, 161, 165, 167, 163, 167, 165, 161, 165, 170, 164, 170, 165, 161, 165, 136, 140, 136, 135, 138, 135, 139, 141, 139, 135, 139, 135, 137, 141, 137, 135, 139, 135, 140, 142, 140, 136, 139, 136, 137, 141, 137, 136, 139, 136, 140, 142, 140, 136, 139, 136, 137, 141, 137, 136, 139, 136, 140, 142, 140, 136, 139, 136, 169, 165, 169, 166, 162, 166, 171, 165, 171, 166, 162, 166, 168, 165, 168, 166, 162, 166, 171, 165, 171, 166, 162, 166, 168, 164, 168, 166, 162, 166, 171, 165, 171, 166, 162, 166, 168, 164, 168, 165, 162, 165, 170, 165, 170, 165, 162, 165, 136, 140, 136, 134, 138, 134, 139, 141, 139, 134, 137, 134, 135, 139, 135, 134, 137, 134, 138, 140, 138, 133, 137, 133, 135, 138, 135, 133, 136, 133, 137, 139, 137, 133, 136, 133, 134, 138, 134, 132, 136, 132, 137, 138, 137, 129, 133, 129, 158, 156, 158, 165, 162, 165, 161, 159, 161, 166, 162, 166, 162, 159, 162, 166, 162, 166, 162, 160, 162, 167, 163, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 167, 164, 167, 163, 161, 163, 168, 164, 168, 163, 161, 163, 168, 164, 168, 133, 138, 133, 138, 141, 138, 134, 139, 134, 138, 142, 138, 134, 139, 134, 138, 142, 138, 134, 140, 134, 139, 143, 139, 135, 140, 135, 139, 143, 139, 135, 140, 135, 139, 143, 139, 135, 140, 135, 139, 143, 139, 135, 141, 135, 139, 143, 139, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 166, 170, 165, 163, 165, 169, 166, 169, 165, 163, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 168, 164, 168, 133, 138, 133, 137, 141, 137, 132, 138, 132, 136, 140, 136, 132, 137, 132, 136, 140, 136, 131, 137, 131, 135, 139, 135, 131, 136, 131, 135, 139, 135, 130, 136, 130, 135, 138, 135, 130, 135, 130, 134, 138, 134, 130, 135, 130, 131, 134, 131, 166, 160, 166, 165, 162, 165, 169, 163, 169, 164, 160, 164, 169, 163, 169, 166, 163, 166, 169, 164, 169, 165, 161, 165, 170, 164, 170, 167, 164, 167, 170, 164, 170, 165, 162, 165, 170, 165, 170, 168, 164, 168, 171, 165, 171, 166, 163, 166, 141, 143, 141, 138, 142, 138, 141, 143, 141, 137, 140, 137, 142, 144, 142, 139, 143, 139, 142, 144, 142, 138, 141, 138, 142, 144, 142, 140, 143, 140, 143, 145, 143, 138, 142, 138, 143, 145, 143, 140, 144, 140, 143, 145, 143, 138, 142, 138, 173, 167, 173, 170, 167, 170, 173, 167, 173, 168, 164, 168, 173, 167, 173, 170, 166, 170, 173, 167, 173, 168, 164, 168, 173, 167, 173, 170, 166, 170, 172, 167, 172, 167, 164, 167, 172, 166, 172, 169, 165, 169, 172, 166, 172, 167, 163, 167, 141, 142, 141, 137, 141, 137, 140, 142, 140, 135, 139, 135, 139, 141, 139, 136, 140, 136, 139, 141, 139, 134, 138, 134, 138, 140, 138, 135, 139, 135, 138, 140, 138, 133, 137, 133, 137, 139, 137, 134, 138, 134, 137, 139, 137, 129, 133, 129, 158, 156, 158, 166, 162, 166, 162, 159, 162, 166, 162, 166, 162, 160, 162, 167, 163, 167, 162, 160, 162, 167, 163, 167, 163, 160, 163, 167, 164, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 168, 165, 168, 164, 162, 164, 169, 165, 169, 134, 139, 134, 139, 142, 139, 135, 140, 135, 139, 143, 139, 135, 140, 135, 140, 143, 140, 136, 141, 136, 140, 144, 140, 136, 141, 136, 140, 144, 140, 136, 141, 136, 141, 144, 141, 136, 142, 136, 141, 144, 141, 137, 142, 137, 141, 145, 141, 166, 164, 166, 171, 167, 171, 166, 164, 166, 171, 167, 171, 166, 164, 166, 170, 167, 170, 166, 164, 166, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 165, 162, 165, 169, 165, 169, 134, 139, 134, 138, 142, 138, 133, 139, 133, 137, 141, 137, 133, 138, 133, 137, 140, 137, 132, 137, 132, 136, 140, 136, 132, 137, 132, 136, 139, 136, 131, 136, 131, 135, 139, 135, 130, 136, 130, 135, 138, 135, 130, 135, 130, 131, 135, 131, 163, 159, 163, 164, 160, 164, 169, 163, 169, 164, 160, 164, 167, 163, 167, 165, 161, 165, 170, 164, 170, 165, 162, 165, 167, 164, 167, 165, 162, 165, 171, 165, 171, 166, 162, 166, 169, 165, 169, 167, 163, 167, 172, 166, 172, 167, 163, 167, 139, 143, 139, 137, 141, 137, 142, 144, 142, 138, 141, 138, 140, 144, 140, 138, 142, 138, 143, 145, 143, 139, 142, 139, 141, 144, 141, 139, 143, 139, 144, 145, 144, 140, 143, 140, 141, 145, 141, 140, 143, 140, 144, 146, 144, 140, 143, 140, 171, 168, 171, 169, 165, 169, 174, 169, 174, 169, 165, 169, 171, 167, 171, 169, 165, 169, 174, 168, 174, 169, 165, 169, 170, 167, 170, 169, 165, 169, 173, 167, 173, 168, 164, 168, 170, 166, 170, 168, 164, 168, 172, 167, 172, 167, 164, 167, 138, 142, 138, 137, 140, 137, 141, 143, 141, 136, 139, 136, 137, 141, 137, 135, 139, 135, 140, 142, 140, 135, 138, 135, 136, 140, 136, 134, 138, 134, 138, 140, 138, 134, 137, 134, 135, 139, 135, 133, 137, 133, 137, 139, 137, 129, 133, 129, 158, 156, 158, 166, 162, 166, 162, 160, 162, 167, 163, 167, 162, 160, 162, 167, 163, 167, 163, 160, 163, 167, 164, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 165, 163, 165, 169, 166, 169, 135, 140, 135, 140, 143, 140, 136, 141, 136, 140, 144, 140, 136, 142, 136, 141, 144, 141, 137, 142, 137, 141, 145, 141, 137, 142, 137, 142, 145, 142, 137, 143, 137, 142, 145, 142, 138, 143, 138, 142, 145, 142, 138, 143, 138, 142, 145, 142, 167, 165, 167, 172, 168, 172, 167, 165, 167, 172, 168, 172, 167, 165, 167, 172, 168, 172, 167, 165, 167, 171, 168, 171, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 166, 164, 166, 170, 167, 170, 165, 163, 165, 170, 166, 170, 135, 140, 135, 139, 142, 139, 134, 139, 134, 138, 142, 138, 133, 139, 133, 137, 141, 137, 133, 138, 133, 137, 140, 137, 132, 137, 132, 136, 140, 136, 132, 137, 132, 136, 139, 136, 131, 136, 131, 135, 139, 135, 130, 136, 130, 131, 135, 131, 166, 160, 166, 166, 162, 166, 169, 164, 169, 165, 161, 165, 170, 164, 170, 167, 164, 167, 170, 165, 170, 166, 162, 166, 171, 165, 171, 168, 165, 168, 171, 165, 171, 167, 163, 167, 172, 166, 172, 169, 165, 169, 172, 167, 172, 168, 164, 168, 143, 145, 143, 140, 144, 140, 143, 145, 143, 139, 143, 139, 144, 145, 144, 141, 145, 141, 145, 146, 145, 140, 144, 140, 145, 146, 145, 142, 146, 142, 145, 147, 145, 141, 144, 141, 145, 147, 145, 143, 146, 143, 145, 147, 145, 141, 145, 141, 175, 170, 175, 172, 169, 172, 175, 170, 175, 170, 167, 170, 175, 169, 175, 172, 169, 172, 174, 169, 174, 170, 166, 170, 174, 169, 174, 172, 168, 172, 174, 169, 174, 169, 165, 169, 173, 168, 173, 170, 167, 170, 173, 167, 173, 168, 164, 168, 142, 144, 142, 139, 143, 139, 142, 144, 142, 137, 140, 137, 141, 143, 141, 138, 142, 138, 140, 142, 140, 136, 139, 136, 140, 142, 140, 136, 140, 136, 139, 141, 139, 134, 138, 134, 138, 140, 138, 135, 139, 135, 138, 140, 138, 129, 133, 129, 158, 156, 158, 166, 163, 166, 162, 160, 162, 167, 163, 167, 163, 160, 163, 167, 164, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 167, 170, 136, 141, 136, 141, 144, 141, 137, 142, 137, 141, 145, 141, 137, 143, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 138, 144, 138, 143, 146, 143, 139, 144, 139, 143, 147, 143, 139, 145, 139, 144, 147, 144, 139, 145, 139, 144, 147, 144, 169, 167, 169, 173, 169, 173, 169, 166, 169, 173, 169, 173, 168, 166, 168, 173, 169, 173, 168, 166, 168, 172, 169, 172, 168, 165, 168, 172, 168, 172, 167, 165, 167, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 136, 141, 136, 140, 143, 140, 135, 140, 135, 139, 143, 139, 134, 140, 134, 138, 142, 138, 134, 139, 134, 137, 141, 137, 133, 138, 133, 137, 140, 137, 132, 137, 132, 136, 140, 136, 132, 137, 132, 135, 139, 135, 131, 136, 131, 131, 135, 131, 163, 159, 163, 164, 160, 164, 170, 164, 170, 165, 161, 165, 167, 164, 167, 165, 162, 165, 171, 165, 171, 166, 162, 166, 169, 165, 169, 167, 163, 167, 172, 166, 172, 167, 164, 167, 170, 166, 170, 168, 164, 168, 173, 167, 173, 169, 165, 169, 141, 145, 141, 140, 143, 140, 145, 146, 145, 140, 144, 140, 142, 146, 142, 141, 145, 141, 145, 147, 145, 142, 145, 142, 144, 147, 144, 142, 145, 142, 146, 148, 146, 143, 146, 143, 144, 148, 144, 143, 146, 143, 147, 149, 147, 143, 146, 143, 173, 170, 173, 172, 168, 172, 176, 171, 176, 172, 168, 172, 173, 170, 173, 171, 167, 171, 176, 170, 176, 171, 167, 171, 173, 169, 173, 170, 167, 170, 175, 169, 175, 170, 166, 170, 172, 168, 172, 169, 166, 169, 174, 168, 174, 169, 165, 169, 140, 144, 140, 138, 142, 138, 143, 145, 143, 138, 141, 138, 139, 143, 139, 137, 140, 137, 141, 143, 141, 136, 140, 136, 137, 141, 137, 135, 139, 135, 140, 142, 140, 135, 138, 135, 136, 140, 136, 134, 138, 134, 138, 140, 138, 129, 133, 129, 158, 156, 158, 167, 163, 167, 162, 160, 162, 167, 164, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 165, 163, 165, 170, 166, 170, 165, 164, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 137, 143, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 139, 144, 139, 143, 147, 143, 139, 145, 139, 144, 147, 144, 140, 145, 140, 145, 148, 145, 140, 145, 140, 145, 148, 145, 141, 146, 141, 145, 148, 145, 141, 146, 141, 145, 149, 145, 170, 168, 170, 174, 170, 174, 170, 168, 170, 174, 170, 174, 169, 167, 169, 174, 170, 174, 169, 167, 169, 173, 170, 173, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 169, 172, 168, 165, 168, 172, 168, 172, 167, 165, 167, 171, 168, 171, 137, 142, 137, 141, 144, 141, 136, 141, 136, 140, 144, 140, 135, 140, 135, 139, 143, 139, 134, 140, 134, 138, 142, 138, 134, 139, 134, 137, 141, 137, 133, 138, 133, 137, 140, 137, 132, 137, 132, 136, 140, 136, 131, 136, 131, 131, 135, 131, 166, 160, 166, 167, 163, 167, 170, 164, 170, 165, 162, 165, 171, 165, 171, 168, 164, 168, 171, 165, 171, 167, 163, 167, 172, 166, 172, 169, 166, 169, 173, 167, 173, 168, 164, 168, 173, 167, 173, 171, 167, 171, 174, 168, 174, 169, 166, 169, 145, 146, 145, 142, 146, 142, 145, 147, 145, 142, 145, 142, 146, 148, 146, 144, 147, 144, 147, 149, 147, 143, 146, 143, 148, 149, 148, 145, 149, 145, 148, 150, 148, 144, 147, 144, 148, 150, 148, 146, 149, 146, 149, 150, 149, 145, 148, 145, 177, 172, 177, 175, 171, 175, 177, 172, 177, 173, 169, 173, 177, 172, 177, 174, 171, 174, 177, 172, 177, 172, 168, 172, 176, 171, 176, 173, 170, 173, 176, 170, 176, 171, 167, 171, 175, 170, 175, 172, 169, 172, 175, 169, 175, 170, 166, 170, 145, 146, 145, 141, 145, 141, 144, 145, 144, 139, 142, 139, 143, 145, 143, 139, 143, 139, 142, 144, 142, 137, 140, 137, 141, 143, 141, 138, 141, 138, 140, 142, 140, 135, 139, 135, 139, 141, 139, 136, 140, 136, 138, 140, 138, 129, 133, 129, 158, 156, 158, 167, 163, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 165, 163, 165, 170, 166, 170, 165, 164, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 168, 172, 138, 144, 138, 143, 146, 143, 139, 145, 139, 144, 147, 144, 140, 145, 140, 145, 148, 145, 141, 146, 141, 145, 149, 145, 142, 146, 142, 146, 149, 146, 142, 147, 142, 146, 150, 146, 143, 147, 143, 146, 150, 146, 143, 148, 143, 147, 150, 147, 171, 169, 171, 175, 172, 175, 171, 169, 171, 175, 172, 175, 171, 169, 171, 175, 172, 175, 170, 169, 170, 174, 171, 174, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 170, 173, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 169, 172, 138, 143, 138, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 140, 144, 140, 135, 140, 135, 139, 143, 139, 134, 140, 134, 138, 142, 138, 133, 139, 133, 137, 141, 137, 133, 138, 133, 136, 140, 136, 131, 137, 131, 131, 135, 131, 163, 160, 163, 165, 161, 165, 170, 165, 170, 166, 162, 166, 168, 165, 168, 167, 163, 167, 172, 166, 172, 167, 164, 167, 170, 166, 170, 168, 164, 168, 173, 167, 173, 169, 165, 169, 171, 167, 171, 169, 166, 169, 174, 169, 174, 170, 167, 170, 143, 146, 143, 142, 145, 142, 147, 148, 147, 143, 146, 143, 145, 148, 145, 144, 147, 144, 148, 150, 148, 145, 148, 145, 146, 150, 146, 145, 148, 145, 150, 151, 150, 145, 149, 145, 147, 150, 147, 146, 149, 146, 150, 152, 150, 146, 149, 146, 176, 173, 176, 174, 170, 174, 179, 173, 179, 174, 170, 174, 176, 172, 176, 174, 170, 174, 178, 173, 178, 173, 170, 173, 175, 171, 175, 173, 169, 173, 177, 172, 177, 172, 169, 172, 174, 170, 174, 171, 168, 171, 176, 170, 176, 170, 167, 170, 143, 146, 143, 141, 144, 141, 145, 146, 145, 140, 143, 140, 141, 144, 141, 139, 142, 139, 143, 145, 143, 138, 141, 138, 139, 143, 139, 137, 140, 137, 141, 143, 141, 136, 139, 136, 137, 141, 137, 135, 139, 135, 139, 141, 139, 129, 133, 129, 158, 156, 158, 167, 164, 167, 163, 161, 163, 168, 164, 168, 164, 162, 164, 169, 165, 169, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 168, 172, 168, 166, 168, 173, 169, 173, 139, 145, 139, 144, 148, 144, 141, 145, 141, 145, 149, 145, 142, 146, 142, 146, 150, 146, 142, 147, 142, 147, 150, 147, 143, 148, 143, 147, 151, 147, 144, 149, 144, 148, 151, 148, 144, 149, 144, 148, 152, 148, 145, 149, 145, 148, 152, 148, 172, 170, 172, 177, 173, 177, 172, 170, 172, 177, 173, 177, 172, 170, 172, 176, 173, 176, 172, 170, 172, 176, 172, 176, 171, 169, 171, 175, 172, 175, 170, 169, 170, 174, 171, 174, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 139, 144, 139, 143, 146, 143, 138, 143, 138, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 140, 143, 140, 135, 140, 135, 139, 142, 139, 134, 139, 134, 138, 142, 138, 133, 138, 133, 137, 141, 137, 132, 137, 132, 131, 135, 131, 166, 160, 166, 167, 164, 167, 171, 165, 171, 166, 163, 166, 172, 166, 172, 169, 165, 169, 172, 167, 172, 168, 164, 168, 173, 167, 173, 170, 167, 170, 174, 168, 174, 169, 166, 169, 174, 169, 174, 172, 169, 172, 175, 170, 175, 171, 167, 171, 147, 149, 147, 145, 148, 145, 148, 150, 148, 144, 147, 144, 149, 150, 149, 147, 150, 147, 150, 151, 150, 146, 149, 146, 150, 152, 150, 148, 151, 148, 151, 153, 151, 147, 150, 147, 151, 153, 151, 149, 152, 149, 152, 153, 152, 148, 151, 148, 180, 175, 180, 177, 174, 177, 180, 174, 180, 175, 172, 175, 180, 174, 180, 177, 173, 177, 179, 174, 179, 174, 171, 174, 179, 173, 179, 176, 172, 176, 178, 173, 178, 173, 169, 173, 177, 172, 177, 174, 171, 174, 177, 171, 177, 172, 168, 172, 146, 148, 146, 143, 147, 143, 145, 147, 145, 141, 144, 141, 145, 146, 145, 141, 145, 141, 143, 145, 143, 138, 142, 138, 142, 144, 142, 139, 143, 139, 141, 143, 141, 136, 140, 136, 141, 142, 141, 137, 141, 137, 139, 141, 139, 129, 133, 129, 127, 132, 127, 136, 140, 136, 133, 138, 133, 138, 142, 138, 134, 139, 134, 139, 143, 139, 135, 140, 135, 140, 144, 140, 136, 141, 136, 141, 145, 141, 137, 143, 137, 142, 145, 142, 138, 144, 138, 143, 146, 143, 139, 145, 139, 144, 148, 144, 170, 167, 170, 174, 171, 174, 170, 169, 170, 175, 172, 175, 171, 169, 171, 176, 172, 176, 172, 170, 172, 177, 173, 177, 173, 171, 173, 177, 174, 177, 173, 171, 173, 178, 174, 178, 174, 172, 174, 178, 174, 178, 174, 172, 174, 178, 174, 178, 146, 151, 146, 150, 153, 150, 146, 151, 146, 150, 153, 150, 145, 150, 145, 149, 153, 149, 145, 150, 145, 149, 152, 149, 144, 149, 144, 148, 151, 148, 144, 148, 144, 147, 150, 147, 142, 147, 142, 146, 149, 146, 141, 146, 141, 145, 148, 145, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 169, 172, 167, 165, 167, 171, 168, 171, 166, 164, 166, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 161, 163, 163, 160, 163, 131, 135, 131, 135, 138, 135, 141, 142, 141, 137, 140, 137, 139, 142, 139, 137, 141, 137, 143, 144, 143, 139, 142, 139, 141, 144, 141, 140, 143, 140, 145, 146, 145, 141, 144, 141, 143, 146, 143, 142, 145, 142, 147, 149, 147, 143, 146, 143, 174, 171, 174, 173, 169, 173, 178, 173, 178, 174, 170, 174, 176, 173, 176, 174, 171, 174, 179, 174, 179, 175, 172, 175, 178, 174, 178, 176, 172, 176, 181, 176, 181, 176, 173, 176, 178, 175, 178, 177, 173, 177, 181, 176, 181, 177, 173, 177, 151, 154, 151, 150, 153, 150, 153, 155, 153, 149, 152, 149, 150, 153, 150, 149, 152, 149, 152, 154, 152, 148, 151, 148, 149, 152, 149, 147, 150, 147, 151, 152, 151, 146, 149, 146, 147, 150, 147, 145, 148, 145, 149, 150, 149, 144, 147, 144, 174, 170, 174, 172, 168, 172, 176, 170, 176, 170, 167, 170, 172, 169, 172, 170, 166, 170, 174, 168, 174, 169, 165, 169, 170, 167, 170, 168, 164, 168, 172, 167, 172, 167, 164, 167, 169, 165, 169, 166, 163, 166, 170, 165, 170, 161, 157, 161, 127, 132, 127, 137, 140, 137, 134, 139, 134, 138, 142, 138, 135, 140, 135, 139, 143, 139, 136, 141, 136, 140, 144, 140, 137, 142, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 139, 145, 139, 144, 147, 144, 141, 145, 141, 145, 149, 145, 170, 169, 170, 175, 172, 175, 172, 169, 172, 176, 173, 176, 172, 170, 172, 177, 173, 177, 173, 171, 173, 178, 174, 178, 174, 172, 174, 178, 175, 178, 174, 173, 174, 179, 176, 179, 175, 173, 175, 179, 176, 179, 175, 173, 175, 179, 176, 179, 148, 153, 148, 152, 155, 152, 148, 153, 148, 151, 155, 151, 147, 152, 147, 151, 154, 151, 147, 151, 147, 150, 153, 150, 146, 150, 146, 149, 153, 149, 145, 150, 145, 148, 151, 148, 144, 149, 144, 147, 150, 147, 143, 147, 143, 146, 149, 146, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 163, 161, 163, 163, 160, 163, 134, 136, 134, 137, 140, 137, 141, 143, 141, 137, 140, 137, 142, 144, 142, 140, 143, 140, 143, 145, 143, 139, 143, 139, 144, 146, 144, 142, 145, 142, 145, 147, 145, 142, 145, 142, 146, 148, 146, 145, 148, 145, 148, 150, 148, 144, 147, 144, 178, 172, 178, 176, 172, 176, 179, 173, 179, 175, 171, 175, 180, 174, 180, 178, 174, 178, 180, 175, 180, 176, 173, 176, 181, 176, 181, 179, 176, 179, 182, 177, 182, 178, 174, 178, 182, 177, 182, 180, 177, 180, 183, 177, 183, 178, 175, 178, 155, 157, 155, 153, 156, 153, 155, 156, 155, 151, 154, 151, 154, 156, 154, 152, 155, 152, 154, 156, 154, 150, 153, 150, 153, 155, 153, 150, 153, 150, 152, 153, 152, 147, 150, 147, 151, 153, 151, 148, 151, 148, 150, 151, 150, 145, 148, 145, 177, 172, 177, 174, 170, 174, 176, 171, 176, 171, 167, 171, 175, 170, 175, 172, 169, 172, 174, 169, 174, 169, 165, 169, 173, 168, 173, 170, 167, 170, 173, 167, 173, 167, 164, 167, 172, 166, 172, 169, 165, 169, 170, 165, 170, 161, 157, 161, 127, 132, 127, 137, 141, 137, 134, 139, 134, 139, 143, 139, 135, 140, 135, 140, 144, 140, 136, 142, 136, 141, 145, 141, 137, 143, 137, 142, 146, 142, 139, 144, 139, 144, 147, 144, 140, 145, 140, 145, 148, 145, 142, 146, 142, 146, 150, 146, 171, 169, 171, 176, 173, 176, 172, 170, 172, 177, 174, 177, 173, 171, 173, 178, 174, 178, 174, 172, 174, 179, 176, 179, 175, 173, 175, 180, 176, 180, 176, 174, 176, 180, 177, 180, 176, 174, 176, 180, 177, 180, 177, 174, 177, 181, 177, 181, 150, 154, 150, 153, 156, 153, 150, 154, 150, 153, 156, 153, 149, 153, 149, 152, 156, 152, 148, 153, 148, 152, 155, 152, 147, 152, 147, 150, 154, 150, 146, 151, 146, 150, 153, 150, 145, 150, 145, 148, 151, 148, 144, 149, 144, 147, 150, 147, 171, 169, 171, 174, 171, 174, 170, 167, 170, 173, 170, 173, 169, 167, 169, 172, 169, 172, 167, 165, 167, 172, 168, 172, 167, 164, 167, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 163, 161, 163, 164, 160, 164, 131, 135, 131, 135, 139, 135, 141, 143, 141, 137, 141, 137, 140, 143, 140, 138, 142, 138, 144, 145, 144, 140, 143, 140, 142, 145, 142, 141, 145, 141, 146, 148, 146, 142, 145, 142, 145, 148, 145, 144, 147, 144, 149, 150, 149, 145, 148, 145, 176, 172, 176, 174, 171, 174, 180, 174, 180, 176, 172, 176, 178, 174, 178, 177, 173, 177, 182, 176, 182, 178, 174, 178, 180, 176, 180, 178, 175, 178, 183, 178, 183, 179, 176, 179, 181, 178, 181, 179, 176, 179, 184, 179, 184, 179, 176, 179, 154, 157, 154, 153, 156, 153, 156, 158, 156, 152, 155, 152, 153, 156, 153, 152, 155, 152, 155, 157, 155, 151, 154, 151, 152, 155, 152, 150, 153, 150, 153, 155, 153, 149, 152, 149, 150, 153, 150, 147, 150, 147, 151, 152, 151, 146, 149, 146, 175, 172, 175, 173, 169, 173, 177, 172, 177, 172, 168, 172, 173, 170, 173, 171, 167, 171, 175, 169, 175, 170, 166, 170, 171, 167, 171, 169, 165, 169, 173, 167, 173, 168, 164, 168, 169, 166, 169, 167, 163, 167, 171, 165, 171, 161, 158, 161, 127, 132, 127, 137, 141, 137, 134, 140, 134, 139, 143, 139, 136, 141, 136, 140, 144, 140, 137, 142, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 139, 145, 139, 144, 148, 144, 141, 146, 141, 145, 149, 145, 142, 147, 142, 147, 150, 147, 172, 170, 172, 177, 173, 177, 173, 171, 173, 178, 174, 178, 174, 172, 174, 179, 176, 179, 175, 173, 175, 180, 177, 180, 176, 174, 176, 181, 177, 181, 177, 175, 177, 181, 178, 181, 177, 176, 177, 182, 178, 182, 178, 176, 178, 182, 179, 182, 151, 156, 151, 155, 158, 155, 151, 156, 151, 154, 158, 154, 150, 155, 150, 154, 157, 154, 150, 154, 150, 153, 156, 153, 149, 153, 149, 152, 155, 152, 147, 152, 147, 150, 154, 150, 146, 151, 146, 149, 153, 149, 145, 150, 145, 148, 151, 148, 172, 169, 172, 175, 172, 175, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 161, 164, 164, 160, 164, 134, 136, 134, 137, 141, 137, 142, 144, 142, 138, 141, 138, 143, 145, 143, 141, 144, 141, 144, 146, 144, 140, 144, 140, 145, 147, 145, 143, 147, 143, 146, 148, 146, 143, 146, 143, 148, 150, 148, 146, 150, 146, 150, 151, 150, 146, 149, 146, 179, 174, 179, 177, 174, 177, 180, 175, 180, 176, 173, 176, 182, 176, 182, 179, 176, 179, 183, 177, 183, 178, 175, 178, 184, 178, 184, 181, 178, 181, 184, 179, 184, 180, 177, 180, 185, 179, 185, 182, 179, 182, 185, 180, 185, 180, 177, 180, 158, 160, 158, 156, 158, 156, 158, 159, 158, 154, 157, 154, 157, 159, 157, 154, 158, 154, 156, 158, 156, 152, 155, 152, 156, 157, 156, 152, 156, 152, 154, 156, 154, 150, 153, 150, 153, 155, 153, 150, 153, 150, 152, 153, 152, 147, 150, 147, 179, 173, 179, 176, 172, 176, 178, 172, 178, 172, 169, 172, 176, 171, 176, 173, 170, 173, 175, 170, 175, 170, 166, 170, 174, 169, 174, 171, 167, 171, 173, 168, 173, 168, 164, 168, 173, 167, 173, 169, 165, 169, 171, 165, 171, 161, 158, 161, 127, 132, 127, 137, 141, 137, 135, 140, 135, 140, 143, 140, 136, 141, 136, 141, 144, 141, 137, 142, 137, 142, 145, 142, 138, 144, 138, 144, 147, 144, 140, 145, 140, 145, 148, 145, 142, 146, 142, 146, 150, 146, 143, 148, 143, 148, 151, 148, 173, 171, 173, 178, 174, 178, 174, 172, 174, 179, 175, 179, 175, 173, 175, 180, 176, 180, 176, 174, 176, 181, 177, 181, 177, 175, 177, 182, 178, 182, 178, 176, 178, 182, 179, 182, 178, 177, 178, 183, 179, 183, 179, 177, 179, 183, 180, 183, 153, 157, 153, 156, 159, 156, 152, 157, 152, 156, 159, 156, 152, 156, 152, 155, 158, 155, 151, 156, 151, 154, 157, 154, 150, 154, 150, 153, 156, 153, 149, 153, 149, 152, 155, 152, 147, 152, 147, 150, 153, 150, 145, 150, 145, 149, 152, 149, 172, 170, 172, 176, 172, 176, 171, 169, 171, 174, 171, 174, 170, 167, 170, 173, 170, 173, 169, 166, 169, 172, 169, 172, 167, 165, 167, 171, 168, 171, 166, 164, 166, 170, 167, 170, 165, 163, 165, 169, 166, 169, 164, 162, 164, 164, 160, 164, 131, 135, 131, 136, 139, 136, 142, 144, 142, 138, 142, 138, 140, 144, 140, 139, 143, 139, 145, 146, 145, 141, 144, 141, 143, 146, 143, 142, 145, 142, 147, 149, 147, 144, 147, 144, 146, 149, 146, 145, 148, 145, 150, 152, 150, 147, 150, 147, 177, 174, 177, 176, 172, 176, 181, 176, 181, 177, 174, 177, 179, 176, 179, 178, 175, 178, 183, 178, 183, 179, 176, 179, 182, 178, 182, 180, 177, 180, 185, 180, 185, 181, 178, 181, 183, 180, 183, 182, 178, 182, 186, 181, 186, 182, 178, 182, 157, 160, 157, 155, 158, 155, 159, 160, 159, 155, 158, 155, 156, 159, 156, 154, 157, 154, 158, 159, 158, 153, 156, 153, 154, 157, 154, 152, 155, 152, 155, 157, 155, 150, 153, 150, 151, 154, 151, 149, 152, 149, 152, 154, 152, 147, 150, 147, 177, 173, 177, 174, 170, 174, 178, 173, 178, 173, 169, 173, 174, 170, 174, 172, 168, 172, 176, 170, 176, 170, 167, 170, 172, 168, 172, 169, 166, 169, 174, 168, 174, 169, 165, 169, 170, 166, 170, 167, 164, 167, 171, 165, 171, 161, 158, 161, 127, 132, 127, 137, 141, 137, 135, 140, 135, 140, 144, 140, 136, 141, 136, 141, 145, 141, 137, 143, 137, 142, 146, 142, 139, 144, 139, 144, 147, 144, 140, 145, 140, 145, 149, 145, 142, 147, 142, 147, 150, 147, 144, 149, 144, 149, 152, 149, 173, 171, 173, 178, 174, 178, 174, 173, 174, 179, 176, 179, 176, 174, 176, 180, 177, 180, 177, 175, 177, 182, 178, 182, 178, 176, 178, 183, 179, 183, 179, 177, 179, 183, 180, 183, 179, 178, 179, 184, 180, 184, 180, 178, 180, 184, 180, 184, 153, 158, 153, 157, 160, 157, 153, 158, 153, 157, 160, 157, 153, 157, 153, 156, 159, 156, 152, 156, 152, 155, 158, 155, 151, 155, 151, 154, 157, 154, 150, 154, 150, 152, 156, 152, 148, 153, 148, 151, 154, 151, 146, 151, 146, 149, 153, 149, 173, 170, 173, 176, 173, 176, 171, 169, 171, 175, 172, 175, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 168, 165, 168, 172, 168, 172, 167, 164, 167, 170, 167, 170, 165, 164, 165, 170, 166, 170, 164, 162, 164, 164, 160, 164, 134, 136, 134, 137, 141, 137, 142, 144, 142, 138, 142, 138, 143, 145, 143, 141, 145, 141, 145, 146, 145, 141, 144, 141, 146, 148, 146, 144, 147, 144, 147, 149, 147, 144, 147, 144, 149, 150, 149, 147, 150, 147, 150, 152, 150, 147, 150, 147, 180, 175, 180, 178, 175, 178, 182, 176, 182, 178, 174, 178, 183, 178, 183, 181, 177, 181, 184, 179, 184, 180, 177, 180, 185, 180, 185, 183, 179, 183, 186, 180, 186, 182, 178, 182, 186, 181, 186, 184, 181, 184, 187, 182, 187, 183, 179, 183, 160, 162, 160, 158, 160, 158, 160, 161, 160, 156, 159, 156, 159, 161, 159, 156, 160, 156, 158, 160, 158, 154, 157, 154, 157, 159, 157, 154, 157, 154, 156, 158, 156, 151, 154, 151, 154, 156, 154, 151, 154, 151, 153, 154, 153, 148, 151, 148, 180, 174, 180, 176, 173, 176, 178, 173, 178, 173, 170, 173, 177, 172, 177, 174, 170, 174, 176, 170, 176, 171, 167, 171, 175, 169, 175, 172, 168, 172, 174, 168, 174, 169, 165, 169, 173, 167, 173, 170, 166, 170, 171, 165, 171, 161, 158, 161, 127, 132, 127, 137, 141, 137, 135, 140, 135, 140, 144, 140, 136, 142, 136, 141, 145, 141, 138, 143, 138, 143, 146, 143, 139, 145, 139, 144, 148, 144, 141, 146, 141, 145, 149, 145, 143, 147, 143, 147, 150, 147, 144, 149, 144, 149, 152, 149, 174, 172, 174, 178, 175, 178, 175, 173, 175, 180, 176, 180, 176, 174, 176, 181, 178, 181, 177, 176, 177, 182, 179, 182, 178, 177, 178, 183, 180, 183, 179, 178, 179, 184, 180, 184, 180, 178, 180, 184, 181, 184, 180, 178, 180, 185, 181, 185, 154, 159, 154, 158, 161, 158, 154, 158, 154, 158, 160, 158, 153, 158, 153, 157, 160, 157, 153, 157, 153, 156, 159, 156, 151, 156, 151, 154, 158, 154, 150, 155, 150, 153, 156, 153, 148, 153, 148, 151, 155, 151, 147, 151, 147, 150, 153, 150, 173, 171, 173, 177, 173, 177, 172, 170, 172, 175, 172, 175, 170, 168, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 166, 170, 164, 162, 164, 164, 160, 164, 131, 135, 131, 136, 139, 136, 142, 144, 142, 138, 142, 138, 141, 144, 141, 140, 143, 140, 145, 146, 145, 141, 145, 141, 144, 147, 144, 143, 146, 143, 148, 150, 148, 144, 147, 144, 146, 150, 146, 146, 149, 146, 151, 153, 151, 148, 151, 148, 178, 174, 178, 177, 173, 177, 182, 177, 182, 178, 174, 178, 180, 177, 180, 179, 176, 179, 184, 179, 184, 180, 177, 180, 183, 179, 183, 182, 178, 182, 186, 181, 186, 182, 179, 182, 184, 181, 184, 183, 179, 183, 187, 182, 187, 183, 179, 183, 158, 161, 158, 157, 160, 157, 160, 162, 160, 156, 159, 156, 157, 160, 157, 156, 158, 156, 159, 160, 159, 155, 158, 155, 155, 158, 155, 153, 156, 153, 156, 158, 156, 152, 155, 152, 152, 156, 152, 150, 153, 150, 153, 155, 153, 149, 152, 149, 177, 174, 177, 175, 171, 175, 179, 173, 179, 173, 170, 173, 175, 171, 175, 172, 169, 172, 176, 171, 176, 171, 167, 171, 172, 169, 172, 170, 166, 170, 174, 169, 174, 169, 165, 169, 170, 167, 170, 168, 164, 168, 171, 165, 171, 161, 158, 161, 127, 132, 127, 138, 141, 138, 135, 141, 135, 140, 144, 140, 137, 142, 137, 141, 145, 141, 138, 143, 138, 143, 146, 143, 139, 145, 139, 144, 148, 144, 141, 146, 141, 146, 149, 146, 143, 148, 143, 147, 151, 147, 145, 149, 145, 149, 153, 149, 174, 172, 174, 179, 175, 179, 175, 173, 175, 180, 177, 180, 177, 174, 177, 181, 178, 181, 178, 176, 178, 183, 179, 183, 179, 177, 179, 183, 180, 183, 180, 178, 180, 184, 181, 184, 180, 178, 180, 185, 182, 185, 181, 179, 181, 185, 182, 185, 155, 159, 155, 158, 161, 158, 155, 159, 155, 158, 161, 158, 154, 158, 154, 157, 160, 157, 153, 158, 153, 156, 159, 156, 152, 156, 152, 155, 158, 155, 150, 155, 150, 153, 156, 153, 149, 153, 149, 152, 155, 152, 147, 152, 147, 150, 153, 150, 173, 171, 173, 177, 173, 177, 172, 170, 172, 175, 172, 175, 170, 169, 170, 174, 170, 174, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 166, 170, 164, 162, 164, 164, 160, 164, 134, 136, 134, 138, 141, 138, 142, 144, 142, 138, 142, 138, 144, 145, 144, 141, 145, 141, 145, 146, 145, 141, 145, 141, 146, 148, 146, 144, 148, 144, 148, 150, 148, 145, 148, 145, 150, 151, 150, 148, 151, 148, 151, 153, 151, 148, 151, 148, 181, 176, 181, 179, 175, 179, 182, 177, 182, 178, 175, 178, 183, 178, 183, 181, 178, 181, 185, 179, 185, 180, 177, 180, 186, 180, 186, 184, 180, 184, 186, 181, 186, 183, 179, 183, 187, 182, 187, 185, 182, 185, 187, 182, 187, 183, 180, 183, 161, 162, 161, 158, 162, 158, 160, 162, 160, 157, 160, 157, 160, 162, 160, 157, 160, 157, 159, 160, 159, 155, 158, 155, 158, 160, 158, 155, 158, 155, 156, 158, 156, 152, 155, 152, 155, 157, 155, 152, 155, 152, 153, 155, 153, 149, 152, 149, 180, 175, 180, 177, 173, 177, 179, 173, 179, 173, 170, 173, 177, 172, 177, 174, 170, 174, 176, 171, 176, 171, 167, 171, 175, 170, 175, 172, 168, 172, 174, 169, 174, 169, 165, 169, 173, 167, 173, 170, 166, 170, 171, 165, 171, 161, 158, 161, 159, 157, 159, 169, 165, 169, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 168, 171, 167, 165, 167, 172, 169, 172, 169, 167, 169, 173, 170, 173, 170, 168, 170, 175, 171, 175, 171, 169, 171, 176, 173, 176, 172, 170, 172, 177, 174, 177, 146, 151, 146, 151, 154, 151, 148, 153, 148, 153, 156, 153, 150, 154, 150, 154, 157, 154, 151, 156, 151, 156, 158, 156, 153, 157, 153, 157, 160, 157, 153, 158, 153, 158, 161, 158, 154, 159, 154, 158, 161, 158, 155, 159, 155, 158, 162, 158, 181, 179, 181, 185, 182, 185, 181, 179, 181, 185, 181, 185, 180, 178, 180, 184, 180, 184, 179, 178, 179, 183, 180, 183, 178, 177, 178, 182, 179, 182, 177, 175, 177, 181, 177, 181, 176, 174, 176, 179, 176, 179, 174, 173, 174, 178, 174, 178, 145, 150, 145, 148, 152, 148, 144, 149, 144, 147, 150, 147, 142, 147, 142, 145, 149, 145, 140, 145, 140, 144, 147, 144, 139, 144, 139, 142, 145, 142, 137, 143, 137, 141, 145, 141, 136, 141, 136, 139, 143, 139, 133, 139, 133, 131, 135, 131, 164, 160, 164, 166, 162, 166, 173, 167, 173, 168, 165, 168, 171, 167, 171, 169, 165, 169, 174, 169, 174, 170, 167, 170, 173, 169, 173, 172, 168, 172, 177, 171, 177, 173, 169, 173, 175, 172, 175, 174, 170, 174, 179, 174, 179, 176, 172, 176, 150, 153, 150, 150, 153, 150, 154, 156, 154, 151, 154, 151, 153, 156, 153, 153, 156, 153, 157, 159, 157, 154, 157, 154, 156, 159, 156, 155, 158, 155, 160, 161, 160, 156, 159, 156, 158, 161, 158, 157, 160, 157, 161, 162, 161, 157, 160, 157, 185, 182, 185, 183, 180, 183, 187, 182, 187, 183, 179, 183, 184, 181, 184, 182, 179, 182, 186, 181, 186, 181, 178, 181, 183, 179, 183, 180, 177, 180, 184, 179, 184, 179, 175, 179, 180, 177, 180, 177, 174, 177, 181, 176, 181, 176, 173, 176, 149, 153, 149, 147, 150, 147, 150, 152, 150, 145, 148, 145, 146, 149, 146, 144, 147, 144, 147, 149, 147, 142, 145, 142, 143, 146, 143, 141, 144, 141, 144, 146, 144, 139, 143, 139, 140, 144, 140, 138, 141, 138, 140, 142, 140, 130, 133, 130, 159, 157, 159, 168, 165, 168, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 169, 172, 169, 166, 169, 173, 170, 173, 170, 168, 170, 175, 171, 175, 171, 169, 171, 176, 172, 176, 172, 170, 172, 177, 174, 177, 146, 151, 146, 151, 154, 151, 148, 153, 148, 152, 156, 152, 150, 154, 150, 154, 157, 154, 151, 156, 151, 155, 158, 155, 152, 157, 152, 156, 160, 156, 153, 158, 153, 157, 160, 157, 154, 158, 154, 158, 161, 158, 155, 159, 155, 158, 161, 158, 181, 179, 181, 185, 182, 185, 180, 179, 180, 184, 181, 184, 180, 178, 180, 184, 180, 184, 179, 177, 179, 183, 179, 183, 178, 176, 178, 182, 178, 182, 177, 175, 177, 180, 177, 180, 176, 174, 176, 179, 176, 179, 174, 172, 174, 178, 174, 178, 145, 150, 145, 148, 152, 148, 144, 148, 144, 146, 150, 146, 142, 147, 142, 145, 148, 145, 140, 145, 140, 144, 147, 144, 139, 144, 139, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 139, 143, 139, 133, 139, 133, 131, 135, 131, 166, 160, 166, 168, 165, 168, 173, 167, 173, 168, 164, 168, 173, 168, 173, 171, 167, 171, 174, 169, 174, 170, 167, 170, 176, 170, 176, 173, 170, 173, 177, 171, 177, 173, 169, 173, 178, 173, 178, 176, 172, 176, 179, 174, 179, 175, 172, 175, 152, 154, 152, 150, 154, 150, 154, 156, 154, 151, 154, 151, 156, 157, 156, 154, 157, 154, 157, 158, 157, 154, 157, 154, 158, 160, 158, 156, 159, 156, 159, 160, 159, 156, 159, 156, 160, 161, 160, 158, 161, 158, 160, 162, 160, 157, 160, 157, 187, 182, 187, 185, 181, 185, 187, 182, 187, 182, 179, 182, 186, 181, 186, 184, 180, 184, 186, 180, 186, 181, 177, 181, 185, 179, 185, 182, 178, 182, 184, 178, 184, 178, 175, 178, 182, 177, 182, 179, 176, 179, 181, 176, 181, 176, 172, 176, 151, 153, 151, 148, 151, 148, 150, 152, 150, 145, 148, 145, 148, 150, 148, 145, 148, 145, 147, 149, 147, 142, 145, 142, 145, 147, 145, 142, 145, 142, 144, 146, 144, 139, 142, 139, 143, 145, 143, 139, 143, 139, 140, 142, 140, 130, 133, 130, 159, 156, 159, 168, 165, 168, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 169, 172, 168, 166, 168, 173, 170, 173, 169, 167, 169, 174, 171, 174, 171, 169, 171, 176, 172, 176, 172, 170, 172, 177, 173, 177, 145, 150, 145, 150, 153, 150, 147, 152, 147, 152, 155, 152, 149, 153, 149, 153, 156, 153, 150, 155, 150, 155, 158, 155, 152, 156, 152, 156, 159, 156, 153, 157, 153, 157, 160, 157, 153, 158, 153, 157, 160, 157, 154, 158, 154, 158, 161, 158, 180, 178, 180, 184, 181, 184, 180, 178, 180, 184, 180, 184, 179, 178, 179, 183, 180, 183, 179, 177, 179, 182, 179, 182, 178, 176, 178, 181, 178, 181, 177, 175, 177, 180, 177, 180, 176, 173, 176, 179, 176, 179, 174, 172, 174, 178, 174, 178, 145, 150, 145, 148, 151, 148, 143, 148, 143, 146, 150, 146, 141, 146, 141, 145, 148, 145, 140, 145, 140, 143, 147, 143, 138, 144, 138, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 139, 143, 139, 133, 139, 133, 131, 135, 131, 164, 160, 164, 166, 162, 166, 172, 167, 172, 168, 164, 168, 170, 167, 170, 169, 165, 169, 174, 169, 174, 170, 166, 170, 173, 169, 173, 171, 167, 171, 176, 171, 176, 172, 169, 172, 175, 172, 175, 174, 170, 174, 179, 173, 179, 175, 171, 175, 149, 153, 149, 149, 152, 149, 153, 155, 153, 150, 153, 150, 152, 156, 152, 152, 155, 152, 156, 158, 156, 153, 156, 153, 155, 158, 155, 154, 157, 154, 158, 160, 158, 155, 158, 155, 157, 160, 157, 156, 158, 156, 160, 161, 160, 156, 159, 156, 184, 180, 184, 182, 179, 182, 186, 181, 186, 182, 178, 182, 183, 180, 183, 181, 178, 181, 185, 180, 185, 180, 177, 180, 182, 178, 182, 179, 176, 179, 183, 178, 183, 178, 174, 178, 179, 176, 179, 177, 173, 177, 181, 176, 181, 176, 172, 176, 149, 152, 149, 146, 150, 146, 150, 151, 150, 145, 148, 145, 145, 149, 145, 143, 146, 143, 146, 148, 146, 142, 145, 142, 142, 146, 142, 140, 144, 140, 144, 145, 144, 139, 142, 139, 140, 144, 140, 138, 141, 138, 140, 142, 140, 130, 133, 130, 159, 156, 159, 168, 164, 168, 165, 163, 165, 170, 166, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 168, 172, 168, 166, 168, 173, 169, 173, 169, 167, 169, 174, 170, 174, 170, 169, 170, 175, 172, 175, 172, 170, 172, 177, 173, 177, 145, 150, 145, 150, 153, 150, 147, 151, 147, 151, 154, 151, 148, 153, 148, 153, 156, 153, 150, 154, 150, 154, 157, 154, 151, 156, 151, 155, 158, 155, 152, 156, 152, 156, 159, 156, 153, 157, 153, 156, 160, 156, 153, 158, 153, 157, 160, 157, 179, 178, 179, 183, 180, 183, 179, 177, 179, 183, 180, 183, 179, 177, 179, 183, 179, 183, 178, 176, 178, 182, 178, 182, 177, 175, 177, 181, 177, 181, 176, 174, 176, 180, 176, 180, 175, 173, 175, 178, 175, 178, 174, 172, 174, 177, 174, 177, 144, 149, 144, 147, 151, 147, 143, 148, 143, 146, 149, 146, 141, 146, 141, 145, 148, 145, 139, 145, 139, 143, 146, 143, 138, 143, 138, 142, 145, 142, 137, 142, 137, 140, 144, 140, 135, 141, 135, 139, 143, 139, 133, 138, 133, 131, 135, 131, 166, 160, 166, 168, 164, 168, 172, 167, 172, 168, 164, 168, 173, 167, 173, 171, 167, 171, 174, 169, 174, 170, 166, 170, 175, 169, 175, 173, 169, 173, 176, 170, 176, 172, 168, 172, 177, 172, 177, 175, 172, 175, 178, 173, 178, 174, 171, 174, 151, 153, 151, 150, 153, 150, 153, 154, 153, 150, 153, 150, 154, 156, 154, 152, 156, 152, 156, 157, 156, 152, 155, 152, 156, 158, 156, 155, 158, 155, 158, 159, 158, 154, 157, 154, 158, 160, 158, 156, 159, 156, 158, 160, 158, 155, 158, 155, 186, 180, 186, 183, 179, 183, 185, 180, 185, 181, 177, 181, 185, 180, 185, 182, 179, 182, 184, 179, 184, 179, 176, 179, 184, 178, 184, 180, 177, 180, 183, 177, 183, 177, 174, 177, 181, 176, 181, 178, 175, 178, 180, 175, 180, 175, 172, 175, 150, 152, 150, 147, 150, 147, 149, 151, 149, 144, 147, 144, 148, 149, 148, 144, 148, 144, 146, 148, 146, 141, 145, 141, 145, 146, 145, 141, 145, 141, 144, 145, 144, 138, 142, 138, 142, 144, 142, 139, 143, 139, 140, 142, 140, 130, 133, 130, 159, 156, 159, 168, 164, 168, 165, 163, 165, 170, 166, 170, 165, 164, 165, 170, 167, 170, 167, 165, 167, 172, 168, 172, 168, 165, 168, 173, 169, 173, 169, 167, 169, 174, 170, 174, 170, 168, 170, 175, 171, 175, 171, 169, 171, 176, 173, 176, 144, 149, 144, 149, 152, 149, 146, 150, 146, 150, 153, 150, 147, 152, 147, 152, 155, 152, 149, 153, 149, 153, 156, 153, 150, 154, 150, 154, 157, 154, 151, 155, 151, 155, 158, 155, 151, 156, 151, 155, 158, 155, 152, 156, 152, 156, 158, 156, 178, 177, 178, 183, 179, 183, 178, 176, 178, 182, 179, 182, 178, 176, 178, 182, 178, 182, 177, 175, 177, 181, 177, 181, 176, 174, 176, 180, 177, 180, 175, 173, 175, 179, 176, 179, 174, 172, 174, 178, 174, 178, 173, 171, 173, 177, 173, 177, 144, 149, 144, 147, 150, 147, 142, 147, 142, 145, 149, 145, 140, 145, 140, 144, 147, 144, 139, 144, 139, 143, 146, 143, 138, 143, 138, 141, 145, 141, 136, 142, 136, 140, 144, 140, 135, 140, 135, 139, 143, 139, 133, 138, 133, 131, 135, 131, 164, 160, 164, 166, 162, 166, 172, 166, 172, 167, 164, 167, 170, 166, 170, 169, 165, 169, 174, 168, 174, 169, 166, 169, 172, 168, 172, 170, 167, 170, 176, 170, 176, 172, 168, 172, 174, 170, 174, 173, 169, 173, 178, 172, 178, 174, 170, 174, 148, 151, 148, 147, 150, 147, 152, 153, 152, 149, 152, 149, 150, 154, 150, 150, 153, 150, 154, 156, 154, 151, 154, 151, 153, 156, 153, 152, 155, 152, 156, 158, 156, 153, 156, 153, 154, 158, 154, 153, 156, 153, 157, 159, 157, 153, 156, 153, 182, 179, 182, 180, 177, 180, 184, 179, 184, 180, 176, 180, 181, 178, 181, 179, 176, 179, 183, 178, 183, 178, 175, 178, 180, 177, 180, 178, 174, 178, 182, 176, 182, 177, 173, 177, 178, 174, 178, 176, 172, 176, 179, 174, 179, 174, 171, 174, 147, 150, 147, 145, 148, 145, 148, 150, 148, 144, 147, 144, 144, 148, 144, 142, 145, 142, 145, 147, 145, 141, 144, 141, 142, 145, 142, 139, 143, 139, 143, 145, 143, 138, 142, 138, 139, 143, 139, 137, 140, 137, 140, 142, 140, 130, 133, 130, 159, 156, 159, 168, 164, 168, 164, 162, 164, 169, 166, 169, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 169, 172, 168, 166, 168, 173, 170, 173, 169, 167, 169, 174, 171, 174, 170, 169, 170, 175, 172, 175, 144, 148, 144, 148, 151, 148, 145, 150, 145, 150, 153, 150, 146, 151, 146, 150, 154, 150, 147, 152, 147, 152, 155, 152, 149, 153, 149, 153, 156, 153, 150, 154, 150, 153, 156, 153, 150, 155, 150, 154, 157, 154, 150, 155, 150, 154, 157, 154, 177, 175, 177, 181, 178, 181, 177, 175, 177, 181, 178, 181, 177, 175, 177, 180, 177, 180, 176, 174, 176, 180, 176, 180, 175, 173, 175, 179, 176, 179, 174, 172, 174, 178, 174, 178, 173, 171, 173, 177, 173, 177, 172, 170, 172, 176, 172, 176, 143, 148, 143, 146, 150, 146, 141, 146, 141, 145, 148, 145, 140, 145, 140, 143, 147, 143, 138, 144, 138, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 140, 143, 140, 135, 140, 135, 138, 142, 138, 133, 138, 133, 131, 135, 131, 166, 160, 166, 168, 164, 168, 172, 166, 172, 167, 164, 167, 173, 167, 173, 170, 166, 170, 173, 168, 173, 169, 165, 169, 174, 169, 174, 172, 168, 172, 175, 170, 175, 171, 167, 171, 176, 171, 176, 174, 170, 174, 177, 172, 177, 173, 169, 173, 150, 151, 150, 147, 151, 147, 151, 153, 151, 147, 150, 147, 152, 153, 152, 150, 153, 150, 153, 155, 153, 150, 153, 150, 154, 156, 154, 152, 155, 152, 155, 156, 155, 151, 154, 151, 156, 157, 156, 153, 156, 153, 156, 157, 156, 152, 155, 152, 183, 178, 183, 181, 177, 181, 183, 178, 183, 178, 175, 178, 183, 178, 183, 180, 177, 180, 182, 177, 182, 177, 174, 177, 182, 176, 182, 178, 175, 178, 180, 175, 180, 176, 172, 176, 180, 174, 180, 177, 173, 177, 179, 173, 179, 174, 170, 174, 149, 150, 149, 145, 149, 145, 148, 149, 148, 143, 146, 143, 146, 148, 146, 143, 146, 143, 145, 147, 145, 140, 144, 140, 144, 145, 144, 140, 144, 140, 143, 145, 143, 138, 141, 138, 142, 144, 142, 138, 142, 138, 140, 142, 140, 130, 133, 130, 159, 156, 159, 168, 164, 168, 164, 162, 164, 169, 165, 169, 165, 163, 165, 170, 166, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 168, 172, 168, 165, 168, 173, 169, 173, 169, 167, 169, 174, 170, 174, 170, 168, 170, 174, 171, 174, 142, 147, 142, 147, 150, 147, 144, 149, 144, 148, 152, 148, 145, 150, 145, 150, 153, 150, 146, 151, 146, 150, 153, 150, 147, 152, 147, 151, 154, 151, 148, 153, 148, 152, 155, 152, 148, 153, 148, 152, 156, 152, 149, 153, 149, 153, 156, 153, 176, 174, 176, 180, 177, 180, 176, 174, 176, 180, 176, 180, 176, 173, 176, 179, 176, 179, 175, 173, 175, 179, 175, 179, 174, 172, 174, 178, 174, 178, 173, 171, 173, 177, 174, 177, 172, 170, 172, 176, 173, 176, 171, 169, 171, 175, 172, 175, 142, 147, 142, 145, 149, 145, 140, 145, 140, 144, 147, 144, 139, 144, 139, 143, 146, 143, 138, 143, 138, 141, 145, 141, 137, 142, 137, 140, 144, 140, 135, 141, 135, 139, 143, 139, 134, 140, 134, 138, 142, 138, 132, 138, 132, 131, 135, 131, 163, 160, 163, 165, 162, 165, 172, 166, 172, 167, 163, 167, 169, 165, 169, 168, 164, 168, 173, 167, 173, 169, 165, 169, 171, 167, 171, 169, 166, 169, 174, 169, 174, 170, 167, 170, 173, 169, 173, 171, 168, 171, 177, 171, 177, 172, 169, 172, 146, 149, 146, 145, 148, 145, 150, 151, 150, 146, 149, 146, 148, 151, 148, 147, 150, 147, 152, 153, 152, 148, 151, 148, 150, 153, 150, 149, 152, 149, 153, 155, 153, 150, 153, 150, 151, 155, 151, 150, 153, 150, 154, 156, 154, 150, 153, 150, 179, 176, 179, 177, 174, 177, 182, 177, 182, 177, 174, 177, 179, 176, 179, 177, 173, 177, 181, 176, 181, 176, 173, 176, 178, 174, 178, 176, 172, 176, 180, 174, 180, 174, 171, 174, 176, 173, 176, 174, 170, 174, 178, 173, 178, 173, 169, 173, 145, 149, 145, 143, 146, 143, 147, 148, 147, 142, 145, 142, 143, 146, 143, 141, 144, 141, 145, 146, 145, 139, 143, 139, 140, 144, 140, 138, 142, 138, 142, 144, 142, 137, 141, 137, 138, 142, 138, 136, 140, 136, 139, 141, 139, 129, 133, 129, 159, 156, 159, 167, 164, 167, 164, 162, 164, 169, 165, 169, 165, 162, 165, 170, 166, 170, 165, 163, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 169, 172, 168, 166, 168, 173, 169, 173, 169, 167, 169, 174, 170, 174, 141, 146, 141, 146, 149, 146, 143, 147, 143, 147, 150, 147, 144, 149, 144, 148, 151, 148, 145, 150, 145, 149, 152, 149, 145, 150, 145, 150, 153, 150, 146, 151, 146, 150, 153, 150, 147, 151, 147, 151, 154, 151, 147, 152, 147, 151, 154, 151, 174, 173, 174, 179, 175, 179, 174, 172, 174, 178, 175, 178, 174, 172, 174, 178, 174, 178, 174, 172, 174, 178, 174, 178, 173, 171, 173, 177, 173, 177, 172, 170, 172, 176, 173, 176, 171, 169, 171, 175, 172, 175, 170, 169, 170, 174, 171, 174, 141, 146, 141, 144, 148, 144, 139, 145, 139, 143, 146, 143, 138, 144, 138, 142, 145, 142, 137, 142, 137, 141, 144, 141, 136, 141, 136, 140, 143, 140, 135, 140, 135, 139, 142, 139, 134, 139, 134, 138, 141, 138, 132, 137, 132, 131, 135, 131, 166, 160, 166, 167, 164, 167, 171, 165, 171, 167, 163, 167, 172, 166, 172, 169, 165, 169, 172, 167, 172, 168, 164, 168, 173, 168, 173, 171, 167, 171, 174, 169, 174, 170, 166, 170, 175, 169, 175, 173, 169, 173, 176, 170, 176, 172, 168, 172, 147, 149, 147, 145, 149, 145, 149, 150, 149, 145, 148, 145, 150, 151, 150, 147, 151, 147, 150, 152, 150, 147, 150, 147, 151, 153, 151, 149, 152, 149, 152, 153, 152, 148, 151, 148, 152, 154, 152, 150, 153, 150, 153, 154, 153, 149, 152, 149, 181, 176, 181, 178, 174, 178, 180, 175, 180, 176, 172, 176, 180, 175, 180, 177, 174, 177, 180, 174, 180, 175, 172, 175, 179, 174, 179, 176, 173, 176, 179, 173, 179, 174, 170, 174, 178, 172, 178, 175, 171, 175, 177, 172, 177, 172, 168, 172, 147, 149, 147, 144, 147, 144, 146, 148, 146, 141, 144, 141, 145, 146, 145, 141, 145, 141, 144, 145, 144, 139, 142, 139, 143, 145, 143, 139, 143, 139, 142, 144, 142, 137, 140, 137, 141, 143, 141, 137, 141, 137, 139, 141, 139, 129, 133, 129, 127, 132, 127, 136, 140, 136, 133, 138, 133, 138, 141, 138, 134, 139, 134, 138, 142, 138, 135, 140, 135, 139, 143, 139, 136, 141, 136, 140, 144, 140, 137, 142, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 139, 144, 139, 144, 147, 144, 169, 167, 169, 174, 170, 174, 170, 168, 170, 174, 171, 174, 171, 169, 171, 175, 172, 175, 172, 169, 172, 176, 173, 176, 172, 170, 172, 177, 173, 177, 173, 170, 173, 177, 173, 177, 173, 171, 173, 177, 174, 177, 173, 171, 173, 177, 174, 177, 145, 150, 145, 149, 153, 149, 145, 150, 145, 149, 152, 149, 145, 150, 145, 149, 152, 149, 144, 149, 144, 148, 151, 148, 144, 149, 144, 147, 150, 147, 143, 148, 143, 146, 150, 146, 142, 147, 142, 145, 149, 145, 141, 146, 141, 144, 148, 144, 169, 167, 169, 173, 169, 173, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 165, 163, 165, 169, 166, 169, 165, 162, 165, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 160, 163, 163, 160, 163, 131, 135, 131, 134, 138, 134, 140, 142, 140, 136, 139, 136, 138, 141, 138, 137, 140, 137, 142, 143, 142, 138, 141, 138, 140, 143, 140, 138, 142, 138, 144, 145, 144, 140, 143, 140, 142, 145, 142, 141, 144, 141, 145, 147, 145, 142, 145, 142, 173, 169, 173, 172, 168, 172, 177, 171, 177, 172, 169, 172, 174, 171, 174, 173, 169, 173, 178, 172, 178, 173, 170, 173, 176, 172, 176, 174, 170, 174, 179, 173, 179, 174, 171, 174, 177, 173, 177, 175, 171, 175, 179, 174, 179, 175, 171, 175, 148, 152, 148, 147, 150, 147, 151, 153, 151, 147, 150, 147, 148, 151, 148, 146, 150, 146, 150, 152, 150, 145, 149, 145, 147, 150, 147, 145, 148, 145, 149, 150, 149, 144, 147, 144, 145, 149, 145, 143, 146, 143, 147, 149, 147, 142, 145, 142, 173, 169, 173, 170, 167, 170, 174, 169, 174, 169, 166, 169, 171, 167, 171, 169, 165, 169, 173, 167, 173, 168, 164, 168, 170, 166, 170, 167, 164, 167, 172, 166, 172, 167, 163, 167, 168, 164, 168, 166, 162, 166, 170, 164, 170, 161, 157, 161, 127, 132, 127, 136, 139, 136, 132, 138, 132, 137, 141, 137, 133, 139, 133, 138, 142, 138, 134, 139, 134, 139, 143, 139, 135, 140, 135, 140, 143, 140, 136, 141, 136, 141, 144, 141, 137, 142, 137, 142, 145, 142, 138, 143, 138, 143, 146, 143, 168, 166, 168, 173, 169, 173, 169, 167, 169, 174, 170, 174, 170, 167, 170, 174, 171, 174, 170, 168, 170, 175, 171, 175, 171, 169, 171, 175, 172, 175, 171, 169, 171, 176, 172, 176, 172, 170, 172, 176, 172, 176, 172, 170, 172, 176, 173, 176, 144, 149, 144, 147, 151, 147, 144, 148, 144, 147, 150, 147, 143, 148, 143, 147, 150, 147, 143, 148, 143, 146, 150, 146, 142, 147, 142, 145, 149, 145, 141, 146, 141, 145, 148, 145, 140, 145, 140, 144, 147, 144, 139, 145, 139, 143, 146, 143, 168, 166, 168, 172, 168, 172, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 168, 165, 168, 164, 161, 164, 168, 164, 168, 162, 160, 162, 163, 160, 163, 134, 136, 134, 136, 139, 136, 139, 141, 139, 135, 139, 135, 140, 142, 140, 138, 141, 138, 141, 143, 141, 137, 140, 137, 142, 144, 142, 139, 143, 139, 143, 145, 143, 139, 142, 139, 144, 145, 144, 141, 145, 141, 145, 146, 145, 141, 144, 141, 175, 169, 175, 173, 169, 173, 176, 170, 176, 171, 167, 171, 176, 171, 176, 174, 170, 174, 177, 171, 177, 172, 169, 172, 177, 172, 177, 175, 171, 175, 178, 172, 178, 173, 170, 173, 178, 173, 178, 175, 172, 175, 178, 173, 178, 173, 170, 173, 150, 151, 150, 147, 150, 147, 149, 151, 149, 145, 148, 145, 149, 150, 149, 146, 150, 146, 149, 150, 149, 144, 147, 144, 148, 150, 148, 145, 148, 145, 147, 149, 147, 143, 146, 143, 146, 148, 146, 144, 147, 144, 146, 147, 146, 141, 144, 141, 174, 169, 174, 172, 168, 172, 174, 168, 174, 169, 165, 169, 173, 167, 173, 170, 166, 170, 173, 167, 173, 167, 164, 167, 172, 166, 172, 169, 165, 169, 171, 165, 171, 166, 162, 166, 170, 165, 170, 167, 164, 167, 170, 164, 170, 161, 157, 161, 127, 132, 127, 135, 139, 135, 132, 137, 132, 137, 140, 137, 133, 138, 133, 137, 141, 137, 133, 139, 133, 138, 142, 138, 134, 140, 134, 139, 143, 139, 135, 140, 135, 140, 144, 140, 136, 141, 136, 141, 144, 141, 137, 142, 137, 142, 145, 142, 167, 165, 167, 172, 169, 172, 168, 166, 168, 173, 169, 173, 169, 167, 169, 173, 170, 173, 169, 167, 169, 174, 170, 174, 170, 167, 170, 174, 170, 174, 170, 168, 170, 174, 171, 174, 170, 168, 170, 175, 171, 175, 170, 169, 170, 175, 171, 175, 142, 147, 142, 146, 149, 146, 142, 147, 142, 145, 149, 145, 141, 146, 141, 145, 149, 145, 141, 146, 141, 145, 148, 145, 140, 145, 140, 144, 148, 144, 140, 145, 140, 144, 147, 144, 139, 144, 139, 143, 146, 143, 138, 144, 138, 142, 145, 142, 167, 165, 167, 171, 167, 171, 166, 164, 166, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 161, 163, 167, 164, 167, 162, 160, 162, 163, 160, 163, 131, 135, 131, 134, 137, 134, 139, 141, 139, 135, 138, 135, 137, 140, 137, 135, 139, 135, 140, 142, 140, 136, 140, 136, 138, 142, 138, 137, 140, 137, 142, 144, 142, 138, 141, 138, 140, 144, 140, 139, 142, 139, 144, 145, 144, 139, 143, 139, 171, 168, 171, 170, 166, 170, 174, 169, 174, 170, 167, 170, 172, 169, 172, 171, 167, 171, 176, 170, 176, 171, 167, 171, 173, 170, 173, 172, 168, 172, 176, 171, 176, 172, 168, 172, 174, 170, 174, 172, 169, 172, 177, 171, 177, 172, 169, 172, 145, 149, 145, 144, 147, 144, 148, 150, 148, 143, 146, 143, 145, 148, 145, 143, 146, 143, 147, 149, 147, 143, 146, 143, 144, 147, 144, 142, 145, 142, 146, 148, 146, 141, 145, 141, 143, 146, 143, 141, 144, 141, 145, 146, 145, 140, 143, 140, 171, 167, 171, 169, 165, 169, 173, 167, 173, 168, 164, 168, 170, 166, 170, 167, 164, 167, 172, 166, 172, 167, 163, 167, 169, 165, 169, 166, 162, 166, 171, 165, 171, 165, 162, 165, 167, 164, 167, 165, 161, 165, 169, 164, 169, 161, 157, 161, 127, 132, 127, 135, 139, 135, 131, 137, 131, 136, 140, 136, 132, 137, 132, 137, 140, 137, 133, 138, 133, 137, 141, 137, 134, 139, 134, 138, 142, 138, 134, 140, 134, 139, 143, 139, 135, 140, 135, 140, 143, 140, 136, 141, 136, 140, 144, 140, 166, 164, 166, 171, 167, 171, 167, 165, 167, 172, 168, 172, 167, 165, 167, 172, 169, 172, 168, 166, 168, 173, 169, 173, 169, 166, 169, 173, 169, 173, 169, 167, 169, 173, 170, 173, 169, 167, 169, 173, 170, 173, 169, 167, 169, 173, 170, 173, 140, 145, 140, 144, 148, 144, 140, 145, 140, 144, 148, 144, 140, 145, 140, 144, 147, 144, 139, 145, 139, 144, 147, 144, 139, 144, 139, 143, 146, 143, 138, 144, 138, 142, 146, 142, 138, 143, 138, 142, 145, 142, 137, 142, 137, 141, 145, 141, 166, 164, 166, 170, 167, 170, 165, 164, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 161, 163, 167, 164, 167, 163, 160, 163, 167, 163, 167, 162, 160, 162, 163, 159, 163, 134, 136, 134, 135, 139, 135, 138, 140, 138, 134, 138, 134, 139, 141, 139, 136, 140, 136, 140, 142, 140, 136, 139, 136, 140, 142, 140, 138, 142, 138, 141, 143, 141, 137, 140, 137, 142, 144, 142, 139, 143, 139, 143, 144, 143, 138, 142, 138, 173, 168, 173, 171, 167, 171, 174, 168, 174, 169, 165, 169, 174, 169, 174, 172, 168, 172, 174, 169, 174, 170, 166, 170, 175, 169, 175, 172, 169, 172, 175, 170, 175, 171, 167, 171, 176, 170, 176, 173, 169, 173, 176, 170, 176, 171, 167, 171, 146, 148, 146, 144, 147, 144, 146, 148, 146, 142, 145, 142, 146, 148, 146, 143, 146, 143, 145, 147, 145, 141, 145, 141, 145, 147, 145, 142, 146, 142, 145, 146, 145, 140, 144, 140, 144, 146, 144, 141, 145, 141, 144, 145, 144, 139, 142, 139, 173, 167, 173, 170, 166, 170, 172, 167, 172, 167, 164, 167, 172, 166, 172, 169, 165, 169, 171, 165, 171, 166, 162, 166, 171, 165, 171, 168, 164, 168, 170, 165, 170, 165, 161, 165, 170, 164, 170, 167, 163, 167, 169, 164, 169, 161, 157, 161, 127, 132, 127, 135, 138, 135, 131, 136, 131, 136, 139, 136, 132, 137, 132, 136, 140, 136, 132, 137, 132, 137, 140, 137, 133, 138, 133, 137, 141, 137, 134, 139, 134, 138, 142, 138, 134, 140, 134, 139, 143, 139, 135, 140, 135, 139, 143, 139, 165, 164, 165, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 164, 167, 171, 167, 171, 167, 165, 167, 172, 168, 172, 167, 165, 167, 172, 168, 172, 167, 165, 167, 172, 169, 172, 168, 166, 168, 172, 169, 172, 168, 166, 168, 172, 169, 172, 139, 144, 139, 143, 146, 143, 139, 144, 139, 143, 146, 143, 138, 144, 138, 142, 146, 142, 138, 143, 138, 142, 145, 142, 138, 143, 138, 142, 145, 142, 137, 142, 137, 141, 145, 141, 137, 142, 137, 140, 144, 140, 136, 141, 136, 140, 144, 140, 165, 163, 165, 170, 166, 170, 165, 163, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 161, 163, 167, 164, 167, 163, 160, 163, 167, 163, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 163, 159, 163, 131, 135, 131, 133, 136, 133, 138, 140, 138, 134, 137, 134, 136, 139, 136, 134, 138, 134, 139, 141, 139, 135, 138, 135, 137, 140, 137, 135, 139, 135, 140, 142, 140, 136, 140, 136, 138, 142, 138, 137, 140, 137, 142, 143, 142, 137, 141, 137, 170, 166, 170, 168, 164, 168, 173, 167, 173, 168, 165, 168, 170, 167, 170, 169, 165, 169, 174, 168, 174, 169, 165, 169, 171, 168, 171, 169, 166, 169, 174, 169, 174, 170, 166, 170, 172, 168, 172, 170, 166, 170, 174, 169, 174, 170, 166, 170, 142, 145, 142, 141, 144, 141, 145, 146, 145, 140, 144, 140, 142, 145, 142, 140, 144, 140, 145, 146, 145, 140, 143, 140, 141, 145, 141, 139, 143, 139, 144, 145, 144, 139, 142, 139, 140, 144, 140, 138, 142, 138, 143, 144, 143, 138, 141, 138, 169, 166, 169, 167, 164, 167, 172, 166, 172, 167, 163, 167, 169, 165, 169, 166, 162, 166, 171, 165, 171, 165, 162, 165, 167, 164, 167, 165, 162, 165, 170, 164, 170, 165, 161, 165, 167, 163, 167, 164, 160, 164, 169, 163, 169, 161, 157, 161, 127, 132, 127, 134, 138, 134, 130, 136, 130, 135, 139, 135, 131, 136, 131, 136, 139, 136, 132, 137, 132, 136, 140, 136, 132, 137, 132, 137, 140, 137, 133, 138, 133, 137, 141, 137, 133, 139, 133, 138, 142, 138, 134, 139, 134, 138, 142, 138, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 166, 170, 165, 164, 165, 170, 167, 170, 166, 164, 166, 170, 167, 170, 166, 164, 166, 171, 167, 171, 167, 164, 167, 171, 167, 171, 167, 165, 167, 171, 167, 171, 167, 165, 167, 171, 168, 171, 137, 143, 137, 141, 145, 141, 137, 142, 137, 141, 145, 141, 137, 142, 137, 141, 145, 141, 137, 142, 137, 141, 144, 141, 136, 142, 136, 140, 144, 140, 136, 141, 136, 140, 144, 140, 135, 141, 135, 139, 143, 139, 135, 140, 135, 139, 143, 139, 165, 162, 165, 169, 165, 169, 164, 162, 164, 169, 165, 169, 164, 162, 164, 168, 164, 168, 163, 161, 163, 167, 164, 167, 163, 160, 163, 167, 163, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 166, 162, 166, 161, 159, 161, 163, 159, 163, 134, 136, 134, 134, 138, 134, 137, 139, 137, 133, 137, 133, 138, 140, 138, 135, 139, 135, 138, 140, 138, 134, 138, 134, 139, 141, 139, 136, 140, 136, 140, 141, 140, 135, 139, 135, 140, 142, 140, 137, 141, 137, 141, 143, 141, 136, 140, 136, 172, 166, 172, 169, 165, 169, 172, 167, 172, 167, 164, 167, 173, 167, 173, 170, 166, 170, 173, 167, 173, 168, 164, 168, 173, 167, 173, 170, 167, 170, 173, 168, 173, 169, 165, 169, 173, 168, 173, 171, 167, 171, 173, 168, 173, 169, 165, 169, 144, 145, 144, 141, 144, 141, 144, 145, 144, 139, 142, 139, 143, 145, 143, 140, 144, 140, 143, 145, 143, 138, 142, 138, 143, 145, 143, 140, 143, 140, 142, 144, 142, 138, 141, 138, 142, 144, 142, 139, 143, 139, 142, 143, 142, 137, 140, 137, 172, 166, 172, 169, 165, 169, 171, 165, 171, 166, 162, 166, 171, 165, 171, 168, 164, 168, 170, 165, 170, 165, 161, 165, 170, 164, 170, 167, 163, 167, 169, 164, 169, 164, 160, 164, 169, 163, 169, 166, 162, 166, 169, 163, 169, 161, 157, 161, 126, 132, 126, 134, 138, 134, 130, 135, 130, 135, 138, 135, 130, 136, 130, 135, 139, 135, 131, 136, 131, 136, 139, 136, 131, 137, 131, 136, 140, 136, 132, 137, 132, 137, 140, 137, 133, 138, 133, 137, 141, 137, 133, 138, 133, 138, 141, 138, 164, 162, 164, 169, 165, 169, 164, 162, 164, 169, 165, 169, 165, 163, 165, 169, 166, 169, 165, 163, 165, 170, 166, 170, 165, 163, 165, 170, 166, 170, 165, 164, 165, 170, 167, 170, 166, 164, 166, 170, 167, 170, 166, 164, 166, 170, 167, 170, 136, 141, 136, 140, 144, 140, 136, 141, 136, 140, 144, 140, 136, 141, 136, 140, 144, 140, 135, 141, 135, 140, 143, 140, 135, 140, 135, 139, 143, 139, 135, 140, 135, 139, 143, 139, 134, 140, 134, 138, 142, 138, 134, 139, 134, 138, 142, 138, 164, 162, 164, 168, 164, 168, 164, 161, 164, 168, 164, 168, 163, 161, 163, 167, 164, 167, 163, 160, 163, 167, 163, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 166, 162, 166, 162, 159, 162, 166, 162, 166, 161, 159, 161, 163, 159, 163, 131, 134, 131, 132, 136, 132, 137, 139, 137, 133, 136, 133, 135, 138, 135, 133, 137, 133, 138, 140, 138, 134, 137, 134, 135, 139, 135, 134, 138, 134, 139, 141, 139, 135, 138, 135, 136, 140, 136, 135, 139, 135, 140, 142, 140, 136, 139, 136, 168, 164, 168, 166, 163, 166, 172, 166, 172, 167, 163, 167, 169, 165, 169, 167, 163, 167, 172, 166, 172, 167, 164, 167, 169, 166, 169, 167, 164, 167, 172, 167, 172, 168, 164, 168, 170, 166, 170, 168, 164, 168, 173, 167, 173, 168, 164, 168, 139, 143, 139, 138, 141, 138, 142, 144, 142, 138, 141, 138, 139, 143, 139, 138, 141, 138, 142, 144, 142, 137, 141, 137, 139, 143, 139, 137, 140, 137, 141, 143, 141, 137, 140, 137, 138, 142, 138, 136, 140, 136, 141, 142, 141, 136, 139, 136, 168, 164, 168, 166, 162, 166, 170, 165, 170, 165, 162, 165, 167, 164, 167, 165, 161, 165, 170, 164, 170, 165, 161, 165, 167, 163, 167, 164, 160, 164, 169, 163, 169, 164, 160, 164, 166, 162, 166, 164, 160, 164, 169, 163, 169, 161, 157, 161, 126, 132, 126, 134, 137, 134, 130, 135, 130, 134, 138, 134, 130, 135, 130, 134, 138, 134, 130, 136, 130, 135, 138, 135, 131, 136, 131, 135, 139, 135, 131, 136, 131, 135, 139, 135, 131, 137, 131, 136, 139, 136, 132, 137, 132, 136, 140, 136, 163, 161, 163, 167, 164, 167, 163, 161, 163, 168, 164, 168, 163, 161, 163, 168, 164, 168, 164, 161, 164, 168, 164, 168, 164, 162, 164, 168, 164, 168, 164, 162, 164, 168, 165, 168, 164, 162, 164, 168, 165, 168, 164, 162, 164, 169, 165, 169, 133, 139, 133, 138, 141, 138, 133, 139, 133, 137, 141, 137, 133, 139, 133, 137, 141, 137, 133, 138, 133, 137, 141, 137, 133, 138, 133, 137, 141, 137, 133, 138, 133, 137, 141, 137, 132, 138, 132, 137, 140, 137, 132, 137, 132, 136, 140, 136, 163, 160, 163, 167, 164, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 167, 163, 167, 162, 160, 162, 166, 163, 166, 162, 160, 162, 166, 162, 166, 161, 159, 161, 166, 162, 166, 161, 159, 161, 165, 162, 165, 161, 158, 161, 163, 159, 163, 134, 136, 134, 131, 134, 131, 134, 136, 134, 129, 133, 129, 134, 136, 134, 131, 135, 131, 134, 136, 134, 129, 133, 129, 134, 136, 134, 131, 135, 131, 134, 136, 134, 129, 133, 129, 134, 136, 134, 131, 135, 131, 134, 136, 134, 129, 133, 129, 166, 160, 166, 163, 160, 163, 166, 160, 166, 161, 157, 161, 166, 160, 166, 164, 160, 164, 166, 160, 166, 161, 158, 161, 166, 160, 166, 164, 160, 164, 166, 160, 166, 161, 158, 161, 166, 160, 166, 164, 160, 164, 166, 160, 166, 161, 158, 161, 134, 136, 134, 131, 135, 131, 134, 136, 134, 130, 133, 130, 134, 136, 134, 131, 135, 131, 134, 136, 134, 130, 133, 130, 134, 136, 134, 131, 135, 131, 134, 136, 134, 130, 133, 130, 134, 136, 134, 131, 135, 131, 134, 136, 134, 129, 133, 129, 166, 160, 166, 163, 160, 163, 166, 160, 166, 161, 157, 161, 166, 160, 166, 163, 159, 163, 166, 160, 166, 161, 157, 161, 166, 160, 166, 163, 159, 163, 166, 160, 166, 161, 157, 161, 166, 160, 166, 163, 159, 163, 166, 160, 166, 161, 157, 161), +"format": "RGB8", +"height": 64, +"mipmaps": false, +"width": 64 +} + +[sub_resource type="ImageTexture" id="ImageTexture_7h7sd"] +image = SubResource("Image_cy15a") + +[node name="MaterialButton" type="PanelContainer"] +offset_right = 89.0 +offset_bottom = 91.0 +mouse_filter = 1 +theme = ExtResource("1_t260s") +script = ExtResource("1_vd4oe") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +metadata/_edit_lock_ = true + +[node name="TextureRect" type="TextureRect" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +texture = SubResource("ImageTexture_7h7sd") +stretch_mode = 3 + +[node name="MaterialName" type="Label" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +clip_text = true diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_group.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_group.gd new file mode 100644 index 0000000..57751e3 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_group.gd @@ -0,0 +1,58 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name MaterialGroup + +class Tier: + var name:String + var children:Array[MaterialGroup] + + func _init(name:String = ""): + self.name = name + + func create_child_with_name(name:String)->Tier: + var child:Tier = Tier.new(name) + children.append(child) + return child + + func get_child_with_name(name:String): + for child in children: + if child.name == name: + return child + return null + + func get_child_index_with_name(name:String)->int: + for i in children.size(): + if children[i].name == name: + return i + return -1 + + func remove_child_with_name(name:String): + var idx:int = get_child_index_with_name(name) + if idx > -1: + children.remove_at(idx) + +var root:Tier = Tier.new("Any") + diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.gd new file mode 100644 index 0000000..e28138b --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.gd @@ -0,0 +1,321 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Tree +class_name MaterialGroupsTree + +signal visiblity_changed + +enum ButtonType { VISIBLE } + +const bn_vis_off = preload("res://addons/cyclops_level_builder/art/icons/eye_closed.svg") +const bn_vis_on = preload("res://addons/cyclops_level_builder/art/icons/eye_open.svg") + +@export var show_unused_dirs:bool = true: + get: + return show_unused_dirs + set(value): + if value == show_unused_dirs: + return + show_unused_dirs = value + + reload_materials() + +var plugin:CyclopsLevelBuilder: + get: + return plugin + set(value): + if value == plugin: + return + + if plugin: + var ed_iface:EditorInterface = plugin.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + efs.filesystem_changed.disconnect(on_filesystem_changed) + efs.resources_reimported.disconnect(on_resources_reimported) + efs.resources_reload.disconnect(on_resources_reload) + + plugin = value + %CreateMaterialDialog.plugin = plugin + + if plugin: + var ed_iface:EditorInterface = plugin.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + efs.filesystem_changed.connect(on_filesystem_changed) + efs.resources_reimported.connect(on_resources_reimported) + efs.resources_reload.connect(on_resources_reload) + + reload_materials() + +var tree_item_to_path_map:Dictionary +var path_to_tree_item_map:Dictionary + + +func reload_materials(): + #print("reload_materials") + clear() + tree_item_to_path_map.clear() + path_to_tree_item_map.clear() + + if !plugin: + return + + var ed_iface:EditorInterface = plugin.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + + var root_dir:EditorFileSystemDirectory = efs.get_filesystem() + + var root_tree_item:TreeItem = create_item() + root_tree_item.set_text(0, root_dir.get_name()) + root_tree_item.set_checked(1, true) + root_tree_item.set_editable(1, true) + + tree_item_to_path_map[root_tree_item] = root_dir.get_path() + path_to_tree_item_map[root_dir.get_path()] = root_tree_item + + build_tree_recursive(root_dir, root_tree_item) + + collapse_unused_dirs() + + +func build_tree_recursive(parent_dir:EditorFileSystemDirectory, tree_item_parent:TreeItem): + #print("par_dir count ", parent_dir.get_path(), parent_dir.get_subdir_count()) + + for i in parent_dir.get_subdir_count(): + var child_dir:EditorFileSystemDirectory = parent_dir.get_subdir(i) + #print("add child ", child_dir.get_path()) + + if !show_unused_dirs && !dir_has_materials_recursive(child_dir): + continue + + var item:TreeItem = create_item(tree_item_parent) + item.set_text(0, child_dir.get_name()) + item.set_checked(1, true) + #item.set_editable(1, true) + item.add_button(1, bn_vis_on, ButtonType.VISIBLE, false, "Visible") + + tree_item_to_path_map[item] = child_dir.get_path() + path_to_tree_item_map[child_dir.get_path()] = item + #print("path ", child_dir.get_path()) + + build_tree_recursive(child_dir, item) + + +func on_filesystem_changed(): + reload_materials() + pass + +func on_resources_reimported(resources: PackedStringArray): + pass + +func on_resources_reload(resources: PackedStringArray): + pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + +func create_new_group(): + pass + +func delete_selected_group(): + pass + +func rename_selected_group(): + pass + +func _on_item_selected(): + var item:TreeItem = get_selected() + item.get_index() + pass # Replace with function body. + + +func _on_item_edited(): + var item:TreeItem = get_edited() + pass # Replace with function body. + + +func _on_popup_menu_id_pressed(id:int): + match id: + 0: + create_new_group() + 1: + delete_selected_group() + 2: + rename_selected_group() + + + +func _on_button_clicked(item:TreeItem, column:int, id:int, mouse_button_index:int): + var checked:bool = !item.is_checked(1) + item.set_checked(1, checked) + item.set_button(1, ButtonType.VISIBLE, bn_vis_on if checked else bn_vis_off) + visiblity_changed.emit() + +func is_path_visible(path:String)->bool: + if !path_to_tree_item_map.has(path): + return false + + var item:TreeItem = path_to_tree_item_map[path] + return item.is_checked(1) + + +func get_hidden_directories()->Array[String]: + var ret_paths:Array[String] + + for path in path_to_tree_item_map.keys(): + var item:TreeItem = path_to_tree_item_map[path] + if !item.is_checked(1): + ret_paths.append(path) + + return ret_paths + +func dir_has_materials(dir:EditorFileSystemDirectory)->bool: + for i in dir.get_file_count(): + var file_type:StringName = dir.get_file_type(i) + + if file_type == "StandardMaterial3D" || file_type == "ORMMaterial3D" || file_type == "ShaderMaterial": + return true + + return false + +func dir_has_materials_recursive(dir:EditorFileSystemDirectory)->bool: + if dir_has_materials(dir): + return true + + for i in dir.get_subdir_count(): + var child_dir:EditorFileSystemDirectory = dir.get_subdir(i) + if dir_has_materials_recursive(child_dir): + return true + + return false + +func collapse_unused_dirs(): + if !plugin: + return + + var ed_iface:EditorInterface = plugin.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + + var root_dir:EditorFileSystemDirectory = efs.get_filesystem() + collapse_unused_dirs_recursive(root_dir) + + +func collapse_unused_dirs_recursive(dir:EditorFileSystemDirectory)->bool: + #print("path ", dir.get_path()) + if !path_to_tree_item_map.has(dir.get_path()): + return false + + var item:TreeItem = path_to_tree_item_map[dir.get_path()] + #print("item ", item.get_text(0)) + var expanded:bool = dir_has_materials(dir) +# item.collapsed = !dir_has_materials(dir) + # + for i in dir.get_subdir_count(): + var child_dir:EditorFileSystemDirectory = dir.get_subdir(i) + var result:bool = collapse_unused_dirs_recursive(child_dir) + if result: + expanded = true + + item.collapsed = !expanded + + return expanded + +func _can_drop_data(at_position:Vector2, data:Variant): +# print("_can_drop_data %s" % data) + return typeof(data) == TYPE_DICTIONARY and data.has("type") and data["type"] == "files" + + +func _drop_data(at_position:Vector2, data:Variant): + var item:TreeItem = get_item_at_position(at_position) + if !item: + return + + var files = data["files"] + #print("--drop") + var texture_list:Array[Texture2D] + for f in files: +# print("Dropping %s" % f) + var res:Resource = load(f) + if res is Texture2D: + #print("Dropping %s" % res.resource_path) + + texture_list.append(res) + + if texture_list.is_empty(): + return + + var parent_dir_path:String = tree_item_to_path_map[item] + + %CreateMaterialDialog.parent_dir_path = parent_dir_path + %CreateMaterialDialog.texture_list = texture_list + %CreateMaterialDialog.popup_centered() + #%CreateMaterialDialog.popup_on_parent() + +func _on_create_material_dialog_create_material(params:Dictionary): + + #Prepare texture + var target_texture:Texture2D + + var tex_list:Array = params["textures"] + if tex_list.size() == 1: + target_texture = tex_list[0] + elif tex_list.size() > 1: + var anim_tex:AnimatedTexture = AnimatedTexture.new() + anim_tex.frames = tex_list.size() + for i in tex_list.size(): + anim_tex.set_frame_texture(i, tex_list[i]) + + target_texture = anim_tex + + #Create material + if params["material_type"] == "standard": + var new_mat:StandardMaterial3D = StandardMaterial3D.new() + new_mat.albedo_texture = target_texture + + if params["uv_type"] == "pix_per_game_unit": + var ppgu:int = params["pix_per_game_unit"] + new_mat.uv1_scale = Vector3(tex_list[0].get_width() / ppgu, tex_list[0].get_height() / ppgu, 1) + + ResourceSaver.save(new_mat, params["parent_dir"] + "/" + params["name"] + ".tres") + + elif params["material_type"] == "shader": + var new_mat:ShaderMaterial = ShaderMaterial.new() + new_mat.shader = ResourceLoader.load(params["shader_res_path"], "Shader") + + #print("tex param ", params["texture_parameter"]) + new_mat.set_shader_parameter(params["texture_parameter"], target_texture) + + if params["uv_type"] == "pix_per_game_unit": + var ppgu:float = params["pix_per_game_unit"] + new_mat.set_shader_parameter(params["uv_parameter"], Vector3(tex_list[0].get_width() / ppgu, tex_list[0].get_height() / ppgu, 1)) + + ResourceSaver.save(new_mat, params["parent_dir"] + "/" + params["name"] + ".tres") + + pass # Replace with function body. diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.tscn b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.tscn new file mode 100644 index 0000000..89c9265 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.tscn @@ -0,0 +1,29 @@ +[gd_scene load_steps=3 format=3 uid="uid://cchlfqbh0djdn"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.gd" id="1_u4dlj"] +[ext_resource type="PackedScene" uid="uid://b510d4yme5xtx" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/create_material_dialog.tscn" id="2_adfk6"] + +[node name="MatGroupTree" type="Tree"] +unique_name_in_owner = true +columns = 2 +script = ExtResource("1_u4dlj") + +[node name="PopupMenu" type="PopupMenu" parent="."] +unique_name_in_owner = true +item_count = 3 +item_0/text = "New Group" +item_0/id = 0 +item_1/text = "Delete Group" +item_1/id = 1 +item_2/text = "Rename" +item_2/id = 2 + +[node name="CreateMaterialDialog" parent="." instance=ExtResource("2_adfk6")] +unique_name_in_owner = true +visible = false + +[connection signal="button_clicked" from="." to="." method="_on_button_clicked"] +[connection signal="item_edited" from="." to="." method="_on_item_edited"] +[connection signal="item_selected" from="." to="." method="_on_item_selected"] +[connection signal="id_pressed" from="PopupMenu" to="." method="_on_popup_menu_id_pressed"] +[connection signal="create_material" from="CreateMaterialDialog" to="." method="_on_create_material_dialog_create_material"] diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.gd new file mode 100644 index 0000000..4e2573e --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.gd @@ -0,0 +1,240 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name MaterialViewer + +#var button_group:RadioButtonGroup = RadioButtonGroup.new() + +var builder:CyclopsLevelBuilder: + get: + return builder + set(value): + if value == builder: + return + + if builder: + var ed_iface:EditorInterface = builder.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + efs.filesystem_changed.disconnect(on_filesystem_changed) + efs.resources_reimported.disconnect(on_resources_reimported) + efs.resources_reload.disconnect(on_resources_reload) + + builder = value + %MatGroupTree.plugin = builder + + if builder: + var ed_iface:EditorInterface = builder.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + efs.filesystem_changed.connect(on_filesystem_changed) + efs.resources_reimported.connect(on_resources_reimported) + efs.resources_reload.connect(on_resources_reload) + + reload_materials() + + +var material_groups:MaterialGroup + +var selected_material_paths:Array[String] +var material_viewer_state:MaterialViewerState = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state_res.tres") + +func on_filesystem_changed(): + #print("on_filesystem_changed") + reload_materials() + pass + +func on_resources_reimported(resources:PackedStringArray): + #print("on_resources_reimported ", resources) + pass + +func on_resources_reload(resources:PackedStringArray): + #print("on_resources_reload ", resources) + pass + +func reload_materials(): + #return + + for child in %ButtonArea.get_children(): + %ButtonArea.remove_child(child) + child.queue_free() + + if !builder: + return + + var ed_iface:EditorInterface = builder.get_editor_interface() + var efs:EditorFileSystem = ed_iface.get_resource_filesystem() + + var efsd:EditorFileSystemDirectory = efs.get_filesystem() + reload_materials_recursive(efsd) + pass + +func reload_materials_recursive(dir:EditorFileSystemDirectory): + var mat_name_filter:String = %lineEd_filter.text + + if !%MatGroupTree.is_path_visible(dir.get_path()): + return + #var vis = %MatGroupTree.is_path_visible(dir.get_path()) + #print("reload check path ", dir.get_path(), " vis ", vis) + #get_hidden_directories() + + var ed_iface:EditorInterface = builder.get_editor_interface() + var res_prev:EditorResourcePreview = ed_iface.get_resource_previewer() + + for i in dir.get_file_count(): +# dir.get_file(i) + var type:String = dir.get_file_type(i) + #"StandardMaterial3D" + if type == "StandardMaterial3D" || type == "ShaderMaterial" || type == "ORMMaterial3D": + var path:String = dir.get_file_path(i) + + if !mat_name_filter.is_empty() && !path.contains(mat_name_filter): + continue + + #print("path %s type %s" % [path, type]) + + #res_prev.queue_resource_preview(path, self, "resource_preview_callback", null) + + var bn:MaterialButton = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_button.tscn").instantiate() + bn.material_path = path + bn.plugin = builder + bn.selected = selected_material_paths.has(path) + bn.active = !selected_material_paths.is_empty() && path == selected_material_paths[-1] + #button_group.add_button(bn) + bn.apply_material.connect(func(mat_bn:MaterialButton): apply_material(mat_bn)) + bn.select_material.connect(func(mat_bn:MaterialButton, type:SelectionList.Type): select_material(mat_bn, type)) + + %ButtonArea.add_child(bn) + pass + + for i in dir.get_subdir_count(): + reload_materials_recursive(dir.get_subdir(i)) + +func apply_material(mat_bn:MaterialButton): + var cmd:CommandSetMaterial = CommandSetMaterial.new() + cmd.builder = builder + cmd.material_path = mat_bn.material_path + + var is_obj_mode:bool = builder.mode == CyclopsLevelBuilder.Mode.OBJECT + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + if is_obj_mode: + cmd.add_target(block.get_path(), block.control_mesh.get_face_indices()) + else: + var face_indices:PackedInt32Array = block.control_mesh.get_face_indices(true) + if !face_indices.is_empty(): + cmd.add_target(block.get_path(), face_indices) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + +func is_active_material(path:String): + return !selected_material_paths.is_empty() && path == selected_material_paths[-1] + +func select_material(mat_bn:MaterialButton, sel_type:SelectionList.Type): + match sel_type: + SelectionList.Type.REPLACE: + selected_material_paths = [mat_bn.material_path] + SelectionList.Type.TOGGLE: + var idx:int = selected_material_paths.find(mat_bn.material_path) + if idx >= 0: + selected_material_paths.remove_at(idx) + else: + selected_material_paths.append(mat_bn.material_path) + SelectionList.Type.RANGE: + var bn_list = %ButtonArea.get_children() + var range_from_idx:int = -1 + var range_to_idx:int = -1 + for i in bn_list.size(): + if bn_list[i] == mat_bn: + range_to_idx = i + if is_active_material(bn_list[i].material_path): + range_from_idx = i + + for i in range(range_from_idx, range_to_idx + (1 if range_from_idx < range_to_idx else -1), 1 if range_from_idx < range_to_idx else -1): + var path = bn_list[i].material_path + if selected_material_paths.has(path): + selected_material_paths.erase(path) + selected_material_paths.append(path) + + material_viewer_state.active_material_path = \ + "" if selected_material_paths.is_empty() else selected_material_paths[-1] + + #print("set sel mat: ", material_viewer_state.active_material_path) + #print("sel mat list: ", selected_material_paths) + + for bn in %ButtonArea.get_children(): + var mat_idx:int = selected_material_paths.find(bn.material_path) + if mat_idx >= 0: + if mat_idx == selected_material_paths.size() - 1: + bn.active = true + else: + bn.active = false + + bn.selected = true + + else: + bn.active = false + bn.selected = false + + + +#func resource_preview_callback(path:String, preview:Texture2D, userdata:Variant): + #pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + #material_groups = MaterialGroup.new("All") + # + #reload_materials() + + + #var root:TreeItem = %Tree.create_item() + #var child1:TreeItem = %Tree.create_item(root) + #var child2:TreeItem = %Tree.create_item(root) + #var subchild1:TreeItem = %Tree.create_item(child1) + #subchild1.set_text(0, "Subchild1") + + pass + + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_line_ed_filter_text_changed(new_text): + reload_materials() + + +func _on_mat_group_tree_visiblity_changed(): + reload_materials() + + + +func _on_bn_show_unused_dirs_toggled(toggled_on): + %MatGroupTree.show_unused_dirs = toggled_on diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.tscn b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.tscn new file mode 100644 index 0000000..8c9df53 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=3 format=3 uid="uid://denc7grw42qsu"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer.gd" id="1_nrjye"] +[ext_resource type="PackedScene" uid="uid://cchlfqbh0djdn" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_groups_tree.tscn" id="2_8hnut"] + +[node name="MaterialViewer" type="PanelContainer"] +offset_right = 523.0 +offset_bottom = 350.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_nrjye") + +[node name="HSplitContainer" type="HSplitContainer" parent="."] +layout_mode = 2 +split_offset = 240 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HSplitContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HSplitContainer/VBoxContainer2"] +layout_mode = 2 + +[node name="bn_show_unused_dirs" type="Button" parent="HSplitContainer/VBoxContainer2/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Show unused directories" +toggle_mode = true +button_pressed = true +text = "Show unused" + +[node name="MatGroupTree" parent="HSplitContainer/VBoxContainer2" instance=ExtResource("2_8hnut")] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HSplitContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Filter:" + +[node name="lineEd_filter" type="LineEdit" parent="HSplitContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Filter materials" + +[node name="PanelContainer" type="PanelContainer" parent="HSplitContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="HSplitContainer/VBoxContainer/PanelContainer"] +layout_mode = 2 + +[node name="ButtonArea" type="HFlowContainer" parent="HSplitContainer/VBoxContainer/PanelContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[connection signal="toggled" from="HSplitContainer/VBoxContainer2/HBoxContainer/bn_show_unused_dirs" to="." method="_on_bn_show_unused_dirs_toggled"] +[connection signal="visiblity_changed" from="HSplitContainer/VBoxContainer2/MatGroupTree" to="." method="_on_mat_group_tree_visiblity_changed"] +[connection signal="text_changed" from="HSplitContainer/VBoxContainer/HBoxContainer/lineEd_filter" to="." method="_on_line_ed_filter_text_changed"] diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state.gd new file mode 100644 index 0000000..116cb7b --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state.gd @@ -0,0 +1,32 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name MaterialViewerState + +@export var active_material_path:String: + set(value): + if active_material_path != value: + active_material_path = value + emit_changed() diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state_res.tres b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state_res.tres new file mode 100644 index 0000000..79f7112 --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state_res.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="MaterialViewerState" load_steps=2 format=3 uid="uid://cwq6b2p7f631n"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state.gd" id="1_3sifp"] + +[resource] +script = ExtResource("1_3sifp") +active_material_path = "res://materials/tiles/groundcover_green.tres" diff --git a/addons/cyclops_level_builder/docks/material_palette/material_viewer/radio_button_group.gd b/addons/cyclops_level_builder/docks/material_palette/material_viewer/radio_button_group.gd new file mode 100644 index 0000000..d74161d --- /dev/null +++ b/addons/cyclops_level_builder/docks/material_palette/material_viewer/radio_button_group.gd @@ -0,0 +1,40 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name RadioButtonGroup + +var buttons:Array + +func select_button(button): + for t in buttons: + t.selected = t == button + +func add_button(button): + buttons.append(button) + +func remove_button(button): + buttons.remove_at(buttons.find(button)) + + diff --git a/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.gd b/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.gd new file mode 100644 index 0000000..ee254c0 --- /dev/null +++ b/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.gd @@ -0,0 +1,93 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name SnappingPropertiesDock + + +var builder:CyclopsLevelBuilder: + get: + return builder + set(value): + if builder == value: + return + + if builder: + builder.snapping_tool_changed.disconnect(on_snapping_tool_changed) + + builder = value + + if builder: + builder.snapping_tool_changed.connect(on_snapping_tool_changed) + +func on_snapping_tool_changed(): + update_ui() + +func update_ui(): + if builder: + var snap_tool:CyclopsSnappingSystem = builder.snapping_system + + var ed = snap_tool._get_properties_editor() + + #print("Clearing editor") + + for child in %ScrollContainer.get_children(): + %ScrollContainer.remove_child(child) + child.queue_free() + + #print("Setting editor") + if ed: + %ScrollContainer.add_child(ed) + pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + update_ui() + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func set_editor(control:Control): + + for child in $ScrollContainer.get_children(): + $ScrollContainer.remove_child(child) + + if control: + $ScrollContainer.add_child(control) + +func save_state(state:Dictionary): + var substate:Dictionary = {} + state["snapping_properties"] = substate + + #substate["materials"] = material_list.duplicate() + +func load_state(state:Dictionary): + if state == null || !state.has("snapping_properties"): + return + + var substate:Dictionary = state["snapping_properties"] diff --git a/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.tscn b/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.tscn new file mode 100644 index 0000000..a36927b --- /dev/null +++ b/addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=2 format=3 uid="uid://cu2hyc0pa1nh3"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/snapping_properties/snapping_properties_dock.gd" id="1_rymgk"] + +[node name="Snapping" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_rymgk") + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.gd b/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.gd new file mode 100644 index 0000000..dfa856a --- /dev/null +++ b/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.gd @@ -0,0 +1,56 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Control +class_name ToolPropertiesDock + +var builder:CyclopsLevelBuilder + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + +func set_editor(control:Control): + for child in $ScrollContainer.get_children(): + $ScrollContainer.remove_child(child) + + if control: + $ScrollContainer.add_child(control) + +func save_state(state:Dictionary): + var substate:Dictionary = {} + state["tool_properties_dock"] = substate + + +func load_state(state:Dictionary): + if state == null || !state.has("tool_properties_dock"): + return + + var substate:Dictionary = state["tool_properties_dock"] diff --git a/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.tscn b/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.tscn new file mode 100644 index 0000000..3c2a250 --- /dev/null +++ b/addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=2 format=3 uid="uid://caoy37s0y5a8y"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/docks/tool_properties/tool_properties_dock.gd" id="1_7262j"] + +[node name="Tool Properties" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_7262j") + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/addons/cyclops_level_builder/handles/handle_edge.gd b/addons/cyclops_level_builder/handles/handle_edge.gd new file mode 100644 index 0000000..86da2e8 --- /dev/null +++ b/addons/cyclops_level_builder/handles/handle_edge.gd @@ -0,0 +1,40 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name HandleEdge + +var edge_index:int +#var p0:Vector3 +#var p1:Vector3 +#var p0_init:Vector3 +#var p1_init:Vector3 +#var p_ref:Vector3 #Centroid +#var p_ref_init:Vector3 +var block_path:NodePath + + +func _to_string(): +# return "%s init pos %s %s pos %s %s" % [block_path, initial_p0, initial_p1, p0, p1] + return "edge %s e_idx:%s " % [block_path, edge_index] diff --git a/addons/cyclops_level_builder/handles/handle_face.gd b/addons/cyclops_level_builder/handles/handle_face.gd new file mode 100644 index 0000000..f6f0ae9 --- /dev/null +++ b/addons/cyclops_level_builder/handles/handle_face.gd @@ -0,0 +1,38 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name HandleFace + +var face_index:int +#var face_id:int +#var p_ref:Vector3 #Centroid +#var p_ref_init:Vector3 +var p_center:Vector3 +var block_path:NodePath + + +func _to_string(): +# return "%s init pos %s %s pos %s %s" % [block_path, initial_p0, initial_p1, p0, p1] + return "face %s idx:%s center %s" % [block_path.get_name(block_path.get_name_count() - 1), face_index, p_center] diff --git a/addons/cyclops_level_builder/handles/handle_vertex.gd b/addons/cyclops_level_builder/handles/handle_vertex.gd new file mode 100644 index 0000000..af89b77 --- /dev/null +++ b/addons/cyclops_level_builder/handles/handle_vertex.gd @@ -0,0 +1,36 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name HandleVertex + +var vertex_index:int +var position:Vector3 +#var id:int #Label to link this vertex back to whatever is being tracked +var initial_position:Vector3 +var block_path:NodePath + + +func _to_string(): + return "%s init pos %s pos %s" % [block_path, initial_position, position] diff --git a/addons/cyclops_level_builder/io/cyclops_io/buffer_archive.gd b/addons/cyclops_level_builder/io/cyclops_io/buffer_archive.gd new file mode 100644 index 0000000..41c7a1d --- /dev/null +++ b/addons/cyclops_level_builder/io/cyclops_io/buffer_archive.gd @@ -0,0 +1,82 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends ResourceInspector +class_name BufferArchive + +class BufferRegion extends Resource: + var builder:BufferArchive + #var index:int + var start_byte:int + var length:int + + func get_buffer()->PackedByteArray: + return builder.buffer.slice(start_byte, start_byte + length) + +var buffer:PackedByteArray +#var region_list:Array[BufferRegion] + +func store_buffer(buf:PackedByteArray)->BufferRegion: + var region:BufferRegion = BufferRegion.new() + + region.builder = self + #region.index = region_list.size() + region.start_byte = buffer.size() + region.length = buf.size() + + buffer.append_array(buf) +# buffer.resize(buffer.size() + byte_len) + + #region_list.append(region) + + return region + + +#func allocate_buffer(byte_len:int)->BufferRegion: + #var region:BufferRegion = BufferRegion.new() +# + #region.builder = self + #region.index = region_list.size() + #region.start_byte = buffer.size() + #region.length = byte_len + #buffer.resize(buffer.size() + byte_len) + # + #region_list.append(region) + # + #return region + +func to_dictionary()->Dictionary: + var result:Dictionary + + #result["regions"] = [] + #for region in region_list: + #result.region.append({ + ##"index": region.index, + #"start": region.start_byte, + #"length": region.length + #}) + + result["buffer"] = Marshalls.raw_to_base64(buffer.compress()) + + return result diff --git a/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_builder.gd b/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_builder.gd new file mode 100644 index 0000000..51e44ff --- /dev/null +++ b/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_builder.gd @@ -0,0 +1,199 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CyclopsFileBuilder +extends RefCounted + + +var plugin:CyclopsLevelBuilder +var buffer_archive:BufferArchive = BufferArchive.new() + +var document:Dictionary +var node_indexer:ItemIndexer = ItemIndexer.new() +var object_indexer:ItemIndexer = ItemIndexer.new() +var buffer_region_indexer:ItemIndexer = ItemIndexer.new() + +var buffer_region_map:Dictionary + + +func _init(plugin:CyclopsLevelBuilder): + self.plugin = plugin + +func should_include_branch(node:Node3D)->bool: + if node is CyclopsBlock: + return true + + for child in node.get_children(): + if child is Node3D && should_include_branch(child): + return true + + return false + +func build_file(): + + var root:Node = plugin.get_editor_interface().get_edited_scene_root() + + document = { + "header": { + "exporter": "Cyclops Level Builder " + plugin.get_plugin_version(), + "version": "1.0.0" + }, + "scenes": [], + "nodes": [], + "objects": [], + "buffer_regions": [], + "buffers": [] + } + + export_scene_recursive(root) + + #var build_scene:Dictionary + #build_scene["root"] = root.name + document.scenes.append({ + "id": 0, + "root": node_indexer.get_or_create_id(root) + }) + + for id in buffer_region_map.keys(): + var region:BufferArchive.BufferRegion = buffer_region_map[id] + document.buffer_regions.append({ + "id": id, + "start": region.start_byte, + "length": region.length, + "buffer_id": 0 + }) + + document.buffers.append({ + "id": 0, + "byte_length": buffer_archive.buffer.size(), + "data_buffer": Marshalls.raw_to_base64(buffer_archive.buffer.compress()) + }) + + +func export_scene_recursive(cur_node:Node3D): + #print(str(cur_node.get_path()) + "\n") + if !should_include_branch(cur_node): + return + + var build_node:Dictionary + build_node["id"] = node_indexer.get_or_create_id(cur_node) + build_node["name"] = cur_node.name + document.nodes.append(build_node) + + if !cur_node.visible: + build_node["visible"] = cur_node.visible + if !cur_node.position.is_equal_approx(Vector3.ZERO): + build_node["translate"] = [cur_node.position.x, cur_node.position.y, cur_node.position.z] + if !cur_node.transform.basis.is_equal_approx(Basis.IDENTITY): + build_node["basis"] = [ + cur_node.basis.x.x, cur_node.basis.x.y, cur_node.basis.x.z, + cur_node.basis.y.x, cur_node.basis.y.y, cur_node.basis.y.z, + cur_node.basis.z.x, cur_node.basis.z.y, cur_node.basis.z.z + ] + + + + if cur_node is CyclopsBlock: + var obj_id:int = object_indexer.get_or_create_id(cur_node) + build_node["object"] = obj_id + + var dict:Dictionary = cur_node.export_to_cyclops_file(self) + + document.objects.append( + { + "id": obj_id, + "type": "convex_block", + "body": dict + } + ) + #export_mesh_node(cur_node) + else: +# print("children of ", cur_node.name) + + var child_ids:Array[int] + var exp_children:Array[Node3D] + for local_child in cur_node.get_children(): + if local_child is Node3D && should_include_branch(local_child): + child_ids.append(node_indexer.get_or_create_id(local_child)) + exp_children.append(local_child) + + if !child_ids.is_empty(): + build_node["children"] = child_ids + + for local_child in exp_children: + export_scene_recursive(local_child) + +func export_mesh_node(cur_node:CyclopsBlock): + if !cur_node.mesh_vector_data: + return + + var build_mesh:Dictionary + document.objects.append(build_mesh) + + build_mesh["id"] = object_indexer.get_or_create_id(cur_node) + + build_mesh["collision_type"] = Collision.Type.keys()[cur_node.collision_type] + build_mesh["collision_layer"] = cur_node.collision_layer + build_mesh["collision_mask"] = cur_node.collision_mask + + var mat_res_paths:PackedStringArray + for mat in cur_node.materials: + if mat: + mat_res_paths.append(mat.resource_path) + else: + mat_res_paths.append("") + build_mesh["materials"] = mat_res_paths + + build_mesh["mesh"] = cur_node.mesh_vector_data.to_dictionary(self) + #build_mesh["mesh"] = cur_node.mesh_vector_data.to_dictionary(self) + + +func export_byte_array(byte_data:PackedByteArray)->int: + var result:Dictionary + + var region:BufferArchive.BufferRegion = buffer_archive.store_buffer(byte_data) + var buf_id:int = buffer_region_indexer.get_or_create_id(region) + buffer_region_map[buf_id] = region +# result["data_buffer"] = region.index + return buf_id + + +func export_vector(vec:DataVector)->Dictionary: + var result:Dictionary + + result["name"] = vec.name + result["data_type"] = DataVector.DataType.keys()[vec.data_type] + #if vec.stride != 1: + #result["stride"] = vec.stride + if !vec.category.is_empty(): + result["category"] = vec.category + + var region:BufferArchive.BufferRegion = buffer_archive.store_buffer(vec.get_buffer_byte_data()) + var buf_id:int = buffer_region_indexer.get_or_create_id(region) + buffer_region_map[buf_id] = region +# result["data_buffer"] = region.index + result["data_buffer"] = buf_id + + return result + diff --git a/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_loader.gd b/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_loader.gd new file mode 100644 index 0000000..f89ffc2 --- /dev/null +++ b/addons/cyclops_level_builder/io/cyclops_io/cyclops_file_loader.gd @@ -0,0 +1,209 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name CyclopsFileLoader +extends RefCounted + +class BufferRegion: + var start:int + var length:int + var buffer_id:int + +var buffer_archive:BufferArchive = BufferArchive.new() + +var buffer_map:Dictionary +var buffer_region_map:Dictionary +var object_map:Dictionary +var node_map:Dictionary +var scene_map:Dictionary + +var plugin:CyclopsLevelBuilder + +func load(root:Dictionary): + for buf_dict in root["buffers"]: + var buf_id:int = buf_dict["id"] + var buf_size:int = buf_dict["byte_length"] + var text:String = buf_dict["data_buffer"] + var zip_buf:PackedByteArray = Marshalls.base64_to_raw(text) + var buf:PackedByteArray = zip_buf.decompress(buf_size) + + var ba:BufferArchive = BufferArchive.new() + ba.buffer = buf + buffer_map[buf_id] = ba + + for reg_dict in root["buffer_regions"]: + var reg:BufferRegion = BufferRegion.new() + var id:int = reg_dict["id"] + reg.start = reg_dict["start"] + reg.length = reg_dict["length"] + reg.buffer_id = reg_dict["buffer_id"] + + buffer_region_map[id] = reg + + for obj_dict in root["objects"]: + var id:int = obj_dict["id"] + var type:String = obj_dict["type"] + var body:Dictionary = obj_dict["body"] + + var object_node + match type: + "convex_block": + object_node = load_convex_block(body) + + if object_node: + object_map[id] = object_node + + for node_dict in root["nodes"]: + var id:int = node_dict["id"] + var node:Node3D + if node_dict.has("object"): + var obj_id:int = node_dict["object"] + node = object_map[obj_id] + else: + node = Node3D.new() + + node_map[id] = node + + if node_dict.has("name"): + node.name = node_dict["name"] + + if node_dict.has("visible"): + node.visible = node_dict["visible"] + if node_dict.has("basis"): + var a:Array = node_dict["basis"] + var basis:Basis = Basis(Vector3(a[0], a[1], a[2]), Vector3(a[3], a[4], a[5]), Vector3(a[6], a[7], a[8])) + node.basis = basis + if node_dict.has("translate"): + var a:Array = node_dict["translate"] + node.position = Vector3(a[0], a[1], a[2]) + + for node_dict in root["nodes"]: + var id:int = node_dict["id"] + var node:Node3D = node_map[id] + + if node_dict.has("children"): + for child_idx in node_dict["children"]: + + var child_node:Node3D = node_map[int(child_idx)] + node.add_child(child_node) + + for scene_dict in root["scenes"]: + var id:int = scene_dict["id"] + var root_id:int = scene_dict["root"] + scene_map[id] = root_id + + +func load_convex_block(body_dict:Dictionary)->CyclopsBlock: + var block:CyclopsBlock = preload("res://addons/cyclops_level_builder/nodes/cyclops_block.gd").new() + #blocks_root.add_child(block) + #block.owner = builder.get_editor_interface().get_edited_scene_root() + #block.name = GeneralUtil.find_unique_name(blocks_root, block_name_prefix) + + block.collision_type = Collision.Type.get(body_dict["collision_type"]) + block.collision_layer = body_dict["collision_layer"] + block.collision_mask = body_dict["collision_mask"] + + for mat_res_path in body_dict["materials"]: + var res = ResourceLoader.load(mat_res_path) + block.materials.append(res) + + if body_dict.has("mesh"): + var mesh_dict:Dictionary = body_dict["mesh"] + var mesh:MeshVectorData = MeshVectorData.new() + mesh.num_vertices = mesh_dict["num_vertices"] + mesh.num_edges = mesh_dict["num_edges"] + mesh.num_faces = mesh_dict["num_faces"] + mesh.num_face_vertices = mesh_dict["num_face_vertices"] + mesh.active_vertex = mesh_dict["active_vertex"] + mesh.active_edge = mesh_dict["active_edge"] + mesh.active_face = mesh_dict["active_face"] + mesh.active_face_vertex = mesh_dict["active_face_vertex"] + + mesh.edge_vertex_indices = load_buffer(mesh_dict["edge_vertex_index_buffer"]).to_int32_array() + mesh.edge_face_indices = load_buffer(mesh_dict["edge_face_index_buffer"]).to_int32_array() + mesh.face_vertex_count = load_buffer(mesh_dict["face_vertex_count_buffer"]).to_int32_array() + mesh.face_vertex_indices = load_buffer(mesh_dict["face_vertex_index_buffer"]).to_int32_array() + + for vec_dict in mesh_dict["vectors"]["vertices"]: + var vec:DataVector = load_data_vector(vec_dict) + mesh.vertex_data[vec.name] = vec + + for vec_dict in mesh_dict["vectors"]["edges"]: + var vec:DataVector = load_data_vector(vec_dict) + mesh.edge_data[vec.name] = vec + + for vec_dict in mesh_dict["vectors"]["faces"]: + var vec:DataVector = load_data_vector(vec_dict) + mesh.face_data[vec.name] = vec + + for vec_dict in mesh_dict["vectors"]["face_vertices"]: + var vec:DataVector = load_data_vector(vec_dict) + mesh.face_vertex_data[vec.name] = vec + + block.mesh_vector_data = mesh + + return block + +#enum DataType { BOOL, INT, FLOAT, STRING, COLOR, VECTOR2, VECTOR3, VECTOR4, TRANSFORM_2D, TRANSFORM_3D } + +func load_data_vector(vec_dict)->DataVector: + match vec_dict["data_type"]: + "BOOL": + var buf:PackedByteArray = load_buffer(vec_dict["data_buffer"]) + return DataVectorByte.new(vec_dict["name"], buf, DataVector.DataType.BOOL) + "INT": + var buf:PackedInt32Array = load_buffer(vec_dict["data_buffer"]).to_int32_array() + return DataVectorInt.new(vec_dict["name"], buf, DataVector.DataType.INT) + "FLOAT": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.FLOAT) + "STRING": + var buf:PackedStringArray = bytes_to_var(load_buffer(vec_dict["data_buffer"])) + return DataVectorString.new(vec_dict["name"], buf, DataVector.DataType.STRING) + "COLOR": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.COLOR) + "TRANSFORM_2D": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.TRANSFORM_2D) + "TRANSFORM_3D": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.TRANSFORM_3D) + "VECTOR2": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.VECTOR2) + "VECTOR3": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.VECTOR3) + "VECTOR4": + var buf:PackedFloat32Array = load_buffer(vec_dict["data_buffer"]).to_float32_array() + return DataVectorFloat.new(vec_dict["name"], buf, DataVector.DataType.VECTOR4) + _: + return null + + +func load_buffer(buf_id:int)->PackedByteArray: + var buf_reg:BufferRegion = buffer_region_map[buf_id] + var buf_src:BufferArchive = buffer_map[buf_reg.buffer_id] + return buf_src.buffer.slice(buf_reg["start"], buf_reg["start"] + buf_reg["length"]) diff --git a/addons/cyclops_level_builder/io/cyclops_io/item_indexer.gd b/addons/cyclops_level_builder/io/cyclops_io/item_indexer.gd new file mode 100644 index 0000000..715ac0c --- /dev/null +++ b/addons/cyclops_level_builder/io/cyclops_io/item_indexer.gd @@ -0,0 +1,36 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name ItemIndexer +extends RefCounted + +var dict:Dictionary + +func get_or_create_id(node:Variant)->int: + if dict.has(node): + return dict[node] + + var id:int = dict.size() + dict[node] = id + return id diff --git a/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.gd b/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.gd new file mode 100644 index 0000000..de2fcd0 --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.gd @@ -0,0 +1,85 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Window +class_name ExporterCyclopsWizard + +var file_dialog:FileDialog +var save_path:String + +var plugin:CyclopsLevelBuilder + +# Called when the node enters the scene tree for the first time. +func _ready(): + file_dialog = FileDialog.new() + add_child(file_dialog) + file_dialog.size = Vector2(600, 400) + file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + file_dialog.set_access(FileDialog.ACCESS_RESOURCES) + file_dialog.title = "Save file..." + file_dialog.filters = PackedStringArray(["*.cyclops; Cyclops files"]) + file_dialog.current_file = save_path + file_dialog.file_selected.connect(on_save_file) + + %lineEdit_path.text = save_path + #_text_path = %lineEdit_path + #_text_path.text = save_path + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func on_save_file(path:String): + save_path = path + %lineEdit_path.text = path + +func _on_bn_browse_pressed(): + file_dialog.popup_centered() + + +func _on_bn_cancel_pressed(): + hide() + + +func _on_close_requested(): + hide() + + +func _on_bn_okay_pressed(): + var path:String = save_path + if !save_path.to_lower().ends_with(".cyclops"): + path = save_path + ".cyclops" + + var cyclops_file_builder:CyclopsFileBuilder = CyclopsFileBuilder.new(plugin) + + cyclops_file_builder.build_file() + + var text = JSON.stringify(cyclops_file_builder.document, " ", false) + + var file:FileAccess = FileAccess.open(path, FileAccess.WRITE) + file.store_string(text) + + hide() diff --git a/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.tscn b/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.tscn new file mode 100644 index 0000000..018342b --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=3 format=3 uid="uid://bxmmf4lvpqtvr"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/io/exporter/exporter_cyclops_wizard.gd" id="1_107gl"] + +[sub_resource type="Theme" id="Theme_5yuos"] +MarginContainer/constants/margin_bottom = 10 +MarginContainer/constants/margin_left = 10 +MarginContainer/constants/margin_right = 10 +MarginContainer/constants/margin_top = 10 + +[node name="Window" type="Window"] +position = Vector2i(0, 36) +size = Vector2i(600, 100) +script = ExtResource("1_107gl") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_5yuos") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Path +" + +[node name="lineEdit_path" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_browse" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Browse" +text = "..." + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="bn_okay" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Cancel" + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/bn_browse" to="." method="_on_bn_browse_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_okay" to="." method="_on_bn_okay_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_cancel" to="." method="_on_bn_cancel_pressed"] diff --git a/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.gd b/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.gd new file mode 100644 index 0000000..95ccca5 --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.gd @@ -0,0 +1,167 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Window +class_name ExporterGltfWizard + +#@onready var _text_path:LineEdit = $VBoxContainer/HBoxContainer/text_path +#var _text_path:LineEdit + +var file_dialog:FileDialog +var save_path:String + +var plugin:CyclopsLevelBuilder + + + +# Called when the node enters the scene tree for the first time. +func _ready(): + + file_dialog = FileDialog.new() + add_child(file_dialog) + file_dialog.size = Vector2(600, 400) + file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + file_dialog.set_access(FileDialog.ACCESS_FILESYSTEM) + file_dialog.title = "Export scene..." + file_dialog.filters = PackedStringArray(["*.gltf; glTF files"]) + file_dialog.current_file = save_path + file_dialog.file_selected.connect(on_save_file) + +# _text_path = $VBoxContainer/HBoxContainer/lineEdit_path + #var hh = get_node("VBoxContainer") + #var children = get_children() + + +# _text_path = get_node("VBoxContainer/HBoxContainer/lineEdit_path") + %lineEdit_path.text = save_path + #_text_path = %lineEdit_path + #_text_path.text = save_path + + +func on_save_file(path:String): + save_path = path + %lineEdit_path.text = path + +func _on_bn_browse_pressed(): + file_dialog.popup_centered() + +func branch_is_valid(node:Node)->bool: + if node is CyclopsBlock || (%check_markers.button_pressed && node is Marker3D): + return true + + for child in node.get_children(): + if child is Node3D and branch_is_valid(child): + return true + + return false + + +func clean_branch(node:Node3D)->Node3D: + if node is CyclopsBlock: + var block:CyclopsBlock = node + var new_mesh_node:MeshInstance3D = block.mesh_instance.duplicate() + new_mesh_node.name = block.mesh_instance.name + + var new_node:Node3D = Node3D.new() + new_node.name = node.name + new_node.transform = node.transform + new_node.add_child(new_mesh_node) + return new_node + + elif node is Marker3D: + var new_node:Marker3D = node.duplicate() + return new_node + + else: + var new_node:Node3D = Node3D.new() + new_node.transform = node.transform + new_node.name = node.name + for child in node.get_children(): + if branch_is_valid(child): + new_node.add_child(clean_branch(child)) + return new_node + + +func search_nodes_flat(node:Node, root:Node3D): +# print("searching %s" % node.name) + + if node is CyclopsBlock: + #print("exporting block %s" % node.name) + var block:CyclopsBlock = node + var new_mesh_node:MeshInstance3D = block.mesh_instance.duplicate() + new_mesh_node.name = block.name + + root.add_child(new_mesh_node) + new_mesh_node.global_transform = block.mesh_instance.global_transform + + elif node is Marker3D: + if %check_markers.button_pressed: + var new_node:Marker3D = Marker3D.new() + new_node.name = node.name + + root.add_child(new_node) + new_node.global_transform = node.global_transform + + + for child in node.get_children(): + search_nodes_flat(child, root) + + + +func clean_flat(node:Node3D)->Node3D: + #print("clean_flat") + var root:Node3D = Node3D.new() + root.name = "CyclopsScene" + + for child in node.get_children(): + #print("rpt chjild %s" % child.name) + search_nodes_flat(child, root) + + return root + + +func _on_bn_okay_pressed(): + + var path:String = save_path + if !save_path.to_lower().ends_with(".gltf") && !save_path.to_lower().ends_with(".glb"): + path = save_path + ".gltf" + + + var doc:GLTFDocument = GLTFDocument.new() + var state:GLTFState = GLTFState.new() + var root:Node = plugin.get_editor_interface().get_edited_scene_root() + var root_clean:Node3D = clean_flat(root) if %check_flatten.button_pressed else clean_branch(root) + + doc.append_from_scene(root_clean, state) + doc.write_to_filesystem(state, path) + + hide() + + +func _on_bn_cancel_pressed(): + hide() + + +func _on_close_requested(): + hide() diff --git a/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.tscn b/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.tscn new file mode 100644 index 0000000..7577b9d --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=3 format=3 uid="uid://ct2mftn2hge7k"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/io/exporter/exporter_gltf_wizard.gd" id="1_asb2l"] + +[sub_resource type="Theme" id="Theme_pvimd"] +MarginContainer/constants/margin_bottom = 10 +MarginContainer/constants/margin_left = 10 +MarginContainer/constants/margin_right = 10 +MarginContainer/constants/margin_top = 10 + +[node name="Window" type="Window"] +title = "Gltf Wizard" +position = Vector2i(0, 36) +size = Vector2i(400, 150) +script = ExtResource("1_asb2l") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_pvimd") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="check_flatten" type="CheckBox" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "If unchecked, the scene heierarchy will be refelected in the exported nodes. Otherwise all nodes will be children of the root." +button_pressed = true +text = "Flatten" + +[node name="check_markers" type="CheckBox" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Include Marker3Ds in export." +button_pressed = true +text = "Markers" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Path +" + +[node name="lineEdit_path" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_browse" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Browse" +text = "..." + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="bn_okay" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Cancel" + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/bn_browse" to="." method="_on_bn_browse_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_okay" to="." method="_on_bn_okay_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_cancel" to="." method="_on_bn_cancel_pressed"] diff --git a/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.gd b/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.gd new file mode 100644 index 0000000..45fa570 --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.gd @@ -0,0 +1,155 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Window +class_name ExporterGodotSceneWizard + +#var _text_path:LineEdit +var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") + +var file_dialog:FileDialog +var save_path:String + +var plugin:CyclopsLevelBuilder + +# Called when the node enters the scene tree for the first time. +func _ready(): + file_dialog = FileDialog.new() + add_child(file_dialog) + file_dialog.size = Vector2(600, 400) + file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE + file_dialog.set_access(FileDialog.ACCESS_RESOURCES) + file_dialog.title = "Export scene..." + file_dialog.filters = PackedStringArray(["*.tscn; tscn files"]) + file_dialog.current_file = save_path + file_dialog.file_selected.connect(on_save_file) + + %lineEdit_path.text = save_path + #_text_path = %lineEdit_path + #_text_path.text = save_path + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func on_save_file(path:String): + save_path = path + %lineEdit_path.text = path + +func _on_bn_browse_pressed(): + file_dialog.popup_centered() + + +func _on_bn_cancel_pressed(): + hide() + + +func _on_close_requested(): + hide() + + +func _on_bn_okay_pressed(): + var path:String = save_path + if !save_path.to_lower().ends_with(".tscn") && !save_path.to_lower().ends_with(".tscn"): + path = save_path + ".tscn" + + var root:Node = plugin.get_editor_interface().get_edited_scene_root() + #var dup_node:Node = copy_scene_recursive(root) + var dup_node:Node = root.duplicate() + await get_tree().process_frame + + replace_blocks_recursive(dup_node, dup_node) + #dup_node.name = "aaaaaaa" + + var dup_scene:PackedScene = PackedScene.new() + dup_scene.pack(dup_node) + ResourceSaver.save(dup_scene, path) + + hide() + +func replace_blocks_recursive(node:Node, root:Node): + + for child in node.get_children(): + #print("child.name ", child.name) + + if child is CyclopsBlock: + var child_block:CyclopsBlock = child + + var new_child:Node3D = Node3D.new() + child.add_sibling(new_child) + new_child.owner = root + new_child.transform = child_block.transform + new_child.set_display_folded(true) + + #Mesh + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(child_block.mesh_vector_data) + + var mesh:ArrayMesh = vol.create_mesh(child_block.materials, default_material) + + var mesh_instance:MeshInstance3D = MeshInstance3D.new() + new_child.add_child(mesh_instance) + mesh_instance.owner = root + mesh_instance.mesh = mesh + mesh_instance.name = "mesh_instance" + + #Collision + var collision_body:PhysicsBody3D + + match child_block.collision_type: + Collision.Type.STATIC: + collision_body = StaticBody3D.new() + Collision.Type.KINEMATIC: + collision_body = CharacterBody3D.new() + Collision.Type.RIGID: + collision_body = RigidBody3D.new() + + if collision_body: + collision_body.collision_layer = child_block.collision_layer + collision_body.collision_mask = child_block.collision_mask + new_child.add_child(collision_body) + collision_body.owner = root + collision_body.name = "collision_body" + + var collision_shape:CollisionShape3D = CollisionShape3D.new() + collision_body.add_child(collision_shape) + collision_shape.owner = root + + var shape:ConvexPolygonShape3D = ConvexPolygonShape3D.new() + shape.points = vol.get_points() + collision_shape.shape = shape + collision_shape.name = "collision_shape" + + var child_name:String = child.name + node.remove_child(child) + child.queue_free() + new_child.name = child_name + + else: + replace_blocks_recursive(child, root) + + + diff --git a/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.tscn b/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.tscn new file mode 100644 index 0000000..d1863e1 --- /dev/null +++ b/addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.tscn @@ -0,0 +1,61 @@ +[gd_scene load_steps=3 format=3 uid="uid://bqmvfbarjmc7c"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/io/exporter/exporter_godot_scene_wizard.gd" id="1_khuso"] + +[sub_resource type="Theme" id="Theme_3em6s"] +MarginContainer/constants/margin_bottom = 10 +MarginContainer/constants/margin_left = 10 +MarginContainer/constants/margin_right = 10 +MarginContainer/constants/margin_top = 10 + +[node name="Window" type="Window"] +title = "Export as Scene" +position = Vector2i(0, 36) +size = Vector2i(600, 100) +script = ExtResource("1_khuso") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_3em6s") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Path +" + +[node name="lineEdit_path" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_browse" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Browse" +text = "..." + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="bn_okay" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Cancel" + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/bn_browse" to="." method="_on_bn_browse_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_okay" to="." method="_on_bn_okay_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_cancel" to="." method="_on_bn_cancel_pressed"] diff --git a/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.gd b/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.gd new file mode 100644 index 0000000..47a669a --- /dev/null +++ b/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.gd @@ -0,0 +1,82 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Window +class_name ImporterCyclopsFileWizard + +#var _text_path:LineEdit +var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") + +var file_dialog:FileDialog +var file_path:String + +var plugin:CyclopsLevelBuilder + +# Called when the node enters the scene tree for the first time. +func _ready(): + file_dialog = FileDialog.new() + add_child(file_dialog) + file_dialog.size = Vector2(600, 400) + file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE + file_dialog.set_access(FileDialog.ACCESS_RESOURCES) + file_dialog.title = "Import scene..." + file_dialog.filters = PackedStringArray(["*.cyclops; Cyclops files"]) + file_dialog.current_file = file_path + file_dialog.file_selected.connect(on_open_file) + + %lineEdit_path.text = file_path + +func on_open_file(path:String): + file_path = path + %lineEdit_path.text = path + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_bn_browse_pressed(): + file_dialog.popup_centered() + + +func _on_close_requested(): + hide() + + +func _on_bn_cancel_pressed(): + hide() + +func _on_bn_okay_pressed(): + var editor_scene_root:Node = plugin.get_editor_interface().get_edited_scene_root() + + var cmd:CommandImportCyclopsFile = CommandImportCyclopsFile.new() + cmd.builder = plugin + cmd.file_path = file_path + cmd.target_parent = editor_scene_root.get_path() + + var undo:EditorUndoRedoManager = plugin.get_undo_redo() + cmd.add_to_undo_manager(undo) + + + hide() diff --git a/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.tscn b/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.tscn new file mode 100644 index 0000000..f49b29e --- /dev/null +++ b/addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.tscn @@ -0,0 +1,59 @@ +[gd_scene load_steps=3 format=3 uid="uid://bl2ohfqvxwjke"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/io/importer/importer_cyclops_file_wizard.gd" id="1_sehb5"] + +[sub_resource type="Theme" id="Theme_jvbwn"] +MarginContainer/constants/margin_bottom = 10 +MarginContainer/constants/margin_left = 10 +MarginContainer/constants/margin_right = 10 +MarginContainer/constants/margin_top = 10 + +[node name="ImporterCyclopsFileWizard" type="Window"] +size = Vector2i(600, 100) +script = ExtResource("1_sehb5") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_jvbwn") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Path +" + +[node name="lineEdit_path" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_browse" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Browse" +text = "..." + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="bn_okay" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Okay" + +[node name="bn_cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Cancel" + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/bn_browse" to="." method="_on_bn_browse_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_okay" to="." method="_on_bn_okay_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/bn_cancel" to="." method="_on_bn_cancel_pressed"] diff --git a/addons/cyclops_level_builder/materials/gizmo_axis_selected_material.tres b/addons/cyclops_level_builder/materials/gizmo_axis_selected_material.tres new file mode 100644 index 0000000..eb18fbe --- /dev/null +++ b/addons/cyclops_level_builder/materials/gizmo_axis_selected_material.tres @@ -0,0 +1,6 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://cfcmrftwleii6"] + +[resource] +no_depth_test = true +shading_mode = 0 +albedo_color = Color(1, 1, 0, 1) diff --git a/addons/cyclops_level_builder/materials/gizmo_axis_special_material.tres b/addons/cyclops_level_builder/materials/gizmo_axis_special_material.tres new file mode 100644 index 0000000..000ee8b --- /dev/null +++ b/addons/cyclops_level_builder/materials/gizmo_axis_special_material.tres @@ -0,0 +1,7 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://cqvh1j2n71fej"] + +[resource] +cull_mode = 2 +no_depth_test = true +shading_mode = 0 +fixed_size = true diff --git a/addons/cyclops_level_builder/materials/gizmo_axis_x_material.tres b/addons/cyclops_level_builder/materials/gizmo_axis_x_material.tres new file mode 100644 index 0000000..5508439 --- /dev/null +++ b/addons/cyclops_level_builder/materials/gizmo_axis_x_material.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://drodm0wf41vin"] + +[resource] +cull_mode = 2 +no_depth_test = true +shading_mode = 0 +albedo_color = Color(1, 0, 0, 1) +fixed_size = true diff --git a/addons/cyclops_level_builder/materials/gizmo_axis_y_material.tres b/addons/cyclops_level_builder/materials/gizmo_axis_y_material.tres new file mode 100644 index 0000000..7ea359d --- /dev/null +++ b/addons/cyclops_level_builder/materials/gizmo_axis_y_material.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://bv4k8o22vl6ub"] + +[resource] +cull_mode = 2 +no_depth_test = true +shading_mode = 0 +albedo_color = Color(0, 1, 0, 1) +fixed_size = true diff --git a/addons/cyclops_level_builder/materials/gizmo_axis_z_material.tres b/addons/cyclops_level_builder/materials/gizmo_axis_z_material.tres new file mode 100644 index 0000000..1322993 --- /dev/null +++ b/addons/cyclops_level_builder/materials/gizmo_axis_z_material.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://divsg4lq712rw"] + +[resource] +cull_mode = 2 +no_depth_test = true +shading_mode = 0 +albedo_color = Color(0, 0, 1, 1) +fixed_size = true diff --git a/addons/cyclops_level_builder/materials/grid.tres b/addons/cyclops_level_builder/materials/grid.tres new file mode 100644 index 0000000..ee80492 --- /dev/null +++ b/addons/cyclops_level_builder/materials/grid.tres @@ -0,0 +1,10 @@ +[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://rdhrhgrb78ls"] + +[ext_resource type="Texture2D" uid="uid://bnlqi20ay4vs1" path="res://addons/cyclops_level_builder/art/textures/grid_cell2.png" id="1_17wu6"] +[ext_resource type="Texture2D" uid="uid://b78mg60xhic6n" path="res://addons/cyclops_level_builder/art/textures/checkerboard.png" id="1_ldry2"] + +[resource] +vertex_color_use_as_albedo = true +albedo_texture = ExtResource("1_ldry2") +roughness_texture = ExtResource("1_17wu6") +texture_filter = 5 diff --git a/addons/cyclops_level_builder/materials/outline_material.tres b/addons/cyclops_level_builder/materials/outline_material.tres new file mode 100644 index 0000000..2640112 --- /dev/null +++ b/addons/cyclops_level_builder/materials/outline_material.tres @@ -0,0 +1,3 @@ +[gd_resource type="ShaderMaterial" format=3 uid="uid://kw5ymmorwkvp"] + +[resource] diff --git a/addons/cyclops_level_builder/materials/selection_rect_material.tres b/addons/cyclops_level_builder/materials/selection_rect_material.tres new file mode 100644 index 0000000..301ae09 --- /dev/null +++ b/addons/cyclops_level_builder/materials/selection_rect_material.tres @@ -0,0 +1,9 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://c4vils431vd0v"] + +[resource] +cull_mode = 2 +depth_draw_mode = 2 +no_depth_test = true +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(0.380392, 0.764706, 0.87451, 1) diff --git a/addons/cyclops_level_builder/materials/test_materials.tscn b/addons/cyclops_level_builder/materials/test_materials.tscn new file mode 100644 index 0000000..0efea49 --- /dev/null +++ b/addons/cyclops_level_builder/materials/test_materials.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=4 format=3 uid="uid://4ak07b64jmrq"] + +[sub_resource type="BoxMesh" id="BoxMesh_5fbgh"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0nsw5"] +no_depth_test = true +albedo_color = Color(1, 0.141176, 1, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_8rhcf"] +material = SubResource("StandardMaterial3D_0nsw5") + +[node name="Node3D" type="Node3D"] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_5fbgh") + +[node name="MeshInstance3D2" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.03827, 0, 1.15165) +mesh = SubResource("BoxMesh_8rhcf") diff --git a/addons/cyclops_level_builder/materials/tool_edit_active_fill_material.tres b/addons/cyclops_level_builder/materials/tool_edit_active_fill_material.tres new file mode 100644 index 0000000..aa77626 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_edit_active_fill_material.tres @@ -0,0 +1,9 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://dv5gwbhe5pg64"] + +[resource] +render_priority = 1 +transparency = 1 +albedo_color = Color(0, 0, 0, 0.0627451) +emission_enabled = true +emission = Color(1, 1, 1, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_edit_active_material.tres b/addons/cyclops_level_builder/materials/tool_edit_active_material.tres new file mode 100644 index 0000000..2472241 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_edit_active_material.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://dneusleqxicge"] + +[resource] +render_priority = 1 +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(1, 1, 1, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_edit_selected_fill_material.tres b/addons/cyclops_level_builder/materials/tool_edit_selected_fill_material.tres new file mode 100644 index 0000000..cc82309 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_edit_selected_fill_material.tres @@ -0,0 +1,9 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://b20sku4kdojbr"] + +[resource] +render_priority = 1 +transparency = 1 +albedo_color = Color(0, 0, 0, 0.0627451) +emission_enabled = true +emission = Color(1, 0.466667, 0.109804, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_edit_selected_material.tres b/addons/cyclops_level_builder/materials/tool_edit_selected_material.tres new file mode 100644 index 0000000..d634b72 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_edit_selected_material.tres @@ -0,0 +1,9 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://cmr7csndcasyp"] + +[resource] +render_priority = 1 +transparency = 4 +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(1, 0.466667, 0.109804, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_edit_unselected_material.tres b/addons/cyclops_level_builder/materials/tool_edit_unselected_material.tres new file mode 100644 index 0000000..ffaca44 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_edit_unselected_material.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://ff2cjjfaaqfb"] + +[resource] +render_priority = 1 +transparency = 4 +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_material.tres b/addons/cyclops_level_builder/materials/tool_material.tres new file mode 100644 index 0000000..48dfa0c --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_material.tres @@ -0,0 +1,9 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://bm85lvfgttrlj"] + +[resource] +render_priority = 1 +transparency = 4 +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(1, 1, 0, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_object_active_material.tres b/addons/cyclops_level_builder/materials/tool_object_active_material.tres new file mode 100644 index 0000000..6f666b9 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_object_active_material.tres @@ -0,0 +1,10 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://dcr6mnkfw7mvh"] + +[resource] +render_priority = 1 +transparency = 4 +no_depth_test = true +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(1, 0.717647, 0.529412, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/tool_object_selected_material.tres b/addons/cyclops_level_builder/materials/tool_object_selected_material.tres new file mode 100644 index 0000000..e08c765 --- /dev/null +++ b/addons/cyclops_level_builder/materials/tool_object_selected_material.tres @@ -0,0 +1,10 @@ +[gd_resource type="StandardMaterial3D" format=3 uid="uid://bscbhmr84pnpx"] + +[resource] +render_priority = 1 +transparency = 4 +no_depth_test = true +albedo_color = Color(0, 0, 0, 1) +emission_enabled = true +emission = Color(1, 0.494118, 0.160784, 1) +grow_amount = 0.1 diff --git a/addons/cyclops_level_builder/materials/vertex_active_material.tres b/addons/cyclops_level_builder/materials/vertex_active_material.tres new file mode 100644 index 0000000..6ed5da9 --- /dev/null +++ b/addons/cyclops_level_builder/materials/vertex_active_material.tres @@ -0,0 +1,12 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://rtk56g3h03nt"] + +[ext_resource type="Texture2D" uid="uid://dsvcm4kvcqlru" path="res://addons/cyclops_level_builder/art/textures/vertex.png" id="1_ondel"] + +[resource] +transparency = 2 +alpha_scissor_threshold = 0.5 +alpha_antialiasing_mode = 0 +shading_mode = 0 +albedo_texture = ExtResource("1_ondel") +use_point_size = true +point_size = 8.0 diff --git a/addons/cyclops_level_builder/materials/vertex_selected_material.tres b/addons/cyclops_level_builder/materials/vertex_selected_material.tres new file mode 100644 index 0000000..30e4982 --- /dev/null +++ b/addons/cyclops_level_builder/materials/vertex_selected_material.tres @@ -0,0 +1,13 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ba8rrvb78dmln"] + +[ext_resource type="Texture2D" uid="uid://dsvcm4kvcqlru" path="res://addons/cyclops_level_builder/art/textures/vertex.png" id="1_p0377"] + +[resource] +transparency = 2 +alpha_scissor_threshold = 0.5 +alpha_antialiasing_mode = 0 +shading_mode = 0 +albedo_color = Color(1, 0.560784, 0.341176, 1) +albedo_texture = ExtResource("1_p0377") +use_point_size = true +point_size = 8.0 diff --git a/addons/cyclops_level_builder/materials/vertex_tool_material.tres b/addons/cyclops_level_builder/materials/vertex_tool_material.tres new file mode 100644 index 0000000..39f9f30 --- /dev/null +++ b/addons/cyclops_level_builder/materials/vertex_tool_material.tres @@ -0,0 +1,13 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://slp88hyjpj6v"] + +[ext_resource type="Texture2D" uid="uid://dsvcm4kvcqlru" path="res://addons/cyclops_level_builder/art/textures/vertex.png" id="1_m6fsr"] + +[resource] +transparency = 2 +alpha_scissor_threshold = 0.5 +alpha_antialiasing_mode = 0 +shading_mode = 0 +albedo_color = Color(1, 1, 0, 1) +albedo_texture = ExtResource("1_m6fsr") +use_point_size = true +point_size = 8.0 diff --git a/addons/cyclops_level_builder/materials/vertex_unselected_material.tres b/addons/cyclops_level_builder/materials/vertex_unselected_material.tres new file mode 100644 index 0000000..068ed14 --- /dev/null +++ b/addons/cyclops_level_builder/materials/vertex_unselected_material.tres @@ -0,0 +1,13 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://dqwtka7ltyekm"] + +[ext_resource type="Texture2D" uid="uid://dsvcm4kvcqlru" path="res://addons/cyclops_level_builder/art/textures/vertex.png" id="1_a8v6v"] + +[resource] +transparency = 2 +alpha_scissor_threshold = 0.5 +alpha_antialiasing_mode = 0 +shading_mode = 0 +albedo_color = Color(0, 0, 0, 1) +albedo_texture = ExtResource("1_a8v6v") +use_point_size = true +point_size = 8.0 diff --git a/addons/cyclops_level_builder/math/clip_poly_result.gd b/addons/cyclops_level_builder/math/clip_poly_result.gd new file mode 100644 index 0000000..4e135a0 --- /dev/null +++ b/addons/cyclops_level_builder/math/clip_poly_result.gd @@ -0,0 +1,33 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name ClipPolyResult + +var polygons:Array[PackedVector3Array] +var cut_segments:Array[Segment3] + +func _init(polygons:Array[PackedVector3Array] = [], cut_segments:Array[Segment3] = []): + self.polygons = polygons + self.cut_segments = cut_segments diff --git a/addons/cyclops_level_builder/math/convex_volume.gd b/addons/cyclops_level_builder/math/convex_volume.gd new file mode 100644 index 0000000..0239c3a --- /dev/null +++ b/addons/cyclops_level_builder/math/convex_volume.gd @@ -0,0 +1,1447 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name ConvexVolume + + +class VertexInfo extends RefCounted: + var index:int + var mesh:ConvexVolume + var point:Vector3 + var normal:Vector3 + var edge_indices:Array[int] = [] + var selected:bool + + func _init(mesh:ConvexVolume, point:Vector3 = Vector3.ZERO): + self.mesh = mesh + self.point = point + + func _to_string(): + var s:String = "%s [" % [point] + for i in edge_indices: + s += "%s " % i + s += "]" + + return s + +class EdgeInfo extends RefCounted: + var index:int + var mesh:ConvexVolume + var start_index:int + var end_index:int + var face_indices:Array[int] = [] + var selected:bool + + func _init(mesh:ConvexVolume, start:int = 0, end:int = 0): + self.mesh = mesh + start_index = start + end_index = end + + func get_midpoint()->Vector3: + var p0:Vector3 = mesh.vertices[start_index].point + var p1:Vector3 = mesh.vertices[end_index].point + return (p0 + p1) / 2 + + func _to_string(): + var s:String = "%s %s [" % [start_index, end_index] + for i in face_indices: + s += "%s " % i + s += "]" + return s + + +class FaceInfo extends RefCounted: + var index:int + var mesh:ConvexVolume + #@deprecated + #var id:int + var normal:Vector3 #Face normal points in direction of interior + var material_id:int + var uv_transform:Transform2D + var color:Color + var visible:bool + var selected:bool + var vertex_indices:Array[int] + var face_vertex_indices:Array[int] + var triangulation_indices:Array[int] + var lightmap_uvs:PackedVector2Array + + func _init(mesh:ConvexVolume, normal:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = 0, visible:bool = true, color:Color = Color.WHITE, selected:bool = false): + self.mesh = mesh + #self.id = id + self.normal = normal + self.material_id = material_id + self.uv_transform = uv_transform + self.selected = selected + self.visible = visible + self.color = color + + func get_plane()->Plane: + return Plane(normal, mesh.vertices[vertex_indices[0]].point) + + func get_points()->PackedVector3Array: + var result:PackedVector3Array + for i in vertex_indices: + result.append(mesh.vertices[i].point) + return result + + func get_centroid()->Vector3: + var points:PackedVector3Array = get_points() + var center:Vector3 + for p in points: + center += p + center /= points.size() + return center + + ##Returns vector with magnitude equal to twice the area of the face and + ## pointing along the face normal + func get_area_vector_x2()->Vector3: + var points:PackedVector3Array = get_points() + return MathUtil.face_area_x2(points) + + func get_triangulation()->Array[int]: + if triangulation_indices.is_empty(): + var points:PackedVector3Array + for v_idx in vertex_indices: + points.append(mesh.vertices[v_idx].point) + +# print("start points %s" % points) + + var normal:Vector3 = MathUtil.face_area_x2(points).normalized() +# print("normal %s" % normal) + triangulation_indices = MathUtil.trianglate_face_vertex_indices(points, normal) +# print("triangulation %s" % str(triangulation_indices)) + + return triangulation_indices + + func get_trianges()->PackedVector3Array: + var indices:Array[int] = get_triangulation() + var result:PackedVector3Array + + for fv_idx in indices: + var v_idx:int = vertex_indices[fv_idx] + result.append(mesh.vertices[v_idx].point) + +# print("triangules %s" % result) + + return result + + func reverse(): + normal = -normal + vertex_indices.reverse() + triangulation_indices.clear() + + #Vertex on face closest to given point + func get_closest_vertex(point:Vector3)->int: + var best_dist:float = -1 + var best_idx:int = -1 + for v_idx in vertex_indices: + var v:VertexInfo = mesh.vertices[v_idx] + var dist:float = v.point.distance_to(point) + if best_idx == -1 || dist < best_dist: + best_idx = v_idx + best_dist = dist + + return best_idx + + + +class FaceVertexInfo extends RefCounted: + var index:int + var mesh:ConvexVolume + var face_index:int + var vertex_index:int + var vertex_local_index:int #Position of vertex within this face loop - eg, if this face has 5 verts, the local vert numbers are in order [0, 1, 2, 3, 4] + var color:Color = Color.WHITE + var normal:Vector3 + + +var vertices:Array[VertexInfo] = [] +var edges:Array[EdgeInfo] = [] +var faces:Array[FaceInfo] = [] +var face_vertices:Array[FaceVertexInfo] = [] +var face_vertex_coord_map:Dictionary + +var bounds:AABB + +var lightmap_uvs_dirty = true + +var active_vertex:int = -1 +var active_edge:int = -1 +var active_face:int = -1 +var active_face_vertex:int = -1 + +func _to_string()->String: + var result:String = "" + for v in vertices: + result += str(v.point) + ", " + return result + + +func init_block(block_bounds:AABB, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE): + var p000:Vector3 = block_bounds.position + var p111:Vector3 = block_bounds.end + var p001:Vector3 = Vector3(p000.x, p000.y, p111.z) + var p010:Vector3 = Vector3(p000.x, p111.y, p000.z) + var p011:Vector3 = Vector3(p000.x, p111.y, p111.z) + var p100:Vector3 = Vector3(p111.x, p000.y, p000.z) + var p101:Vector3 = Vector3(p111.x, p000.y, p111.z) + var p110:Vector3 = Vector3(p111.x, p111.y, p000.z) + + init_prism([p000, p001, p011, p010], p100 - p000, uv_transform, material_id, visible, color) + + +func init_prism(base_points:Array[Vector3], extrude_dir:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE): + vertices = [] + edges = [] + faces = [] + face_vertices = [] + face_vertex_coord_map.clear() + + var base_normal = -extrude_dir.normalized() + + var face_area_x2:Vector3 = MathUtil.face_area_x2(base_points) + if face_area_x2.dot(extrude_dir) > 0: + base_points.reverse() + + for p in base_points: + var v:VertexInfo = VertexInfo.new(self, p) + v.index = vertices.size() + vertices.append(v) + for p in base_points: + var v:VertexInfo = VertexInfo.new(self, p + extrude_dir) + v.index = vertices.size() + vertices.append(v) + + var f0:FaceInfo = FaceInfo.new(self, base_normal, uv_transform, material_id, visible, color) + f0.index = faces.size() + f0.vertex_indices = [] + f0.vertex_indices.append_array(range(base_points.size())) + faces.append(f0) + var f1:FaceInfo = FaceInfo.new(self, -base_normal, uv_transform, material_id, visible, color) + f1.index = faces.size() + f1.vertex_indices = [] + f1.vertex_indices.append_array(range(base_points.size(), base_points.size() * 2)) + f1.vertex_indices.reverse() + faces.append(f1) + + + for i in base_points.size(): + var p_idx0:int = i + var p_idx1:int = wrap(i + 1, 0, base_points.size()) + + var v0:VertexInfo = vertices[p_idx0] + var v1:VertexInfo = vertices[p_idx1] + + var normal = base_normal.cross(v1.point - v0.point).normalized() + var f:FaceInfo = FaceInfo.new(self, normal, uv_transform, material_id, visible, color) + f.index = faces.size() + f.vertex_indices = [p_idx1, p_idx0, p_idx0 + base_points.size(), p_idx1 + base_points.size()] + faces.append(f) + + build_edges() + build_face_vertices() + calc_vertex_normals() + + bounds = calc_bounds() + calc_lightmap_uvs() + +func init_from_convex_block_data(data:ConvexBlockData): + #print("init_from_convex_block_data") + #print(var_to_str(data)) + + vertices = [] + edges = [] + faces = [] + face_vertices = [] + face_vertex_coord_map.clear() + + if !data: + return + #data.validate_arrays() + + active_vertex = data.active_vertex + active_edge = data.active_edge + active_face = data.active_face + + for i in data.vertex_points.size(): + var v:VertexInfo = VertexInfo.new(self, data.vertex_points[i]) + v.index = vertices.size() + vertices.append(v) + v.selected = data.vertex_selected[i] + + var num_edges:int = data.edge_vertex_indices.size() / 2 + for i in num_edges: + var edge:EdgeInfo = EdgeInfo.new(self, data.edge_vertex_indices[i * 2], data.edge_vertex_indices[i * 2 + 1]) + edge.index = edges.size() + edges.append(edge) + edge.face_indices.append(data.edge_face_indices[i * 2]) + edge.face_indices.append(data.edge_face_indices[i * 2 + 1]) + edge.selected = data.edge_selected[i] + #edge.active = data.edge_active[i] + + #print("data.face_vertex_count ", data.face_vertex_count) + var face_vertex_count:int = 0 + for face_idx in data.face_vertex_count.size(): + var num_verts:int = data.face_vertex_count[face_idx] + var vert_indices:Array[int] + var vert_points:PackedVector3Array + for i in num_verts: + var vert_idx:int = data.face_vertex_indices[face_vertex_count] + vert_indices.append(vert_idx) + vert_points.append(vertices[vert_idx].point) + face_vertex_count += 1 + + var normal = MathUtil.face_area_x2(vert_points).normalized() + + var face_uv_transform:Transform2D = data.face_uv_transform[face_idx] + var face_mat_index:int = data.face_material_indices[face_idx] + var face_visible:int = data.face_visible[face_idx] + var face_color:Color = data.face_color[face_idx] + var f:FaceInfo = FaceInfo.new(self, normal, face_uv_transform, face_mat_index, face_visible, face_color) + f.index = faces.size() + f.selected = data.face_selected[face_idx] + #f.active = data.face_active[face_idx] + f.vertex_indices = vert_indices + + faces.append(f) + + #print("faces buit ", faces.size()) + + bounds = calc_bounds() + calc_lightmap_uvs() + + #Rebuild face verticies if input data is erronious + var all_zero:bool = true + for f_idx in data.face_vertex_face_index: + if f_idx != 0: + all_zero = false + break + + if data.face_vertex_face_index.size() == 0 || all_zero: + #print("<<0>>") + #Face vertices not initialized - generate new ones + build_face_vertices() + else: + #print("<<1>>") + for fv_idx in data.face_vertex_face_index.size(): + var f_idx:int = data.face_vertex_face_index[fv_idx] + var v_idx:int = data.face_vertex_vertex_index[fv_idx] + var fv:FaceVertexInfo = FaceVertexInfo.new() + face_vertices.append(fv) + #faces[f_idx].face_vertex_indices.append(fv_idx) + + fv.face_index = f_idx + fv.vertex_index = v_idx + var coord:Vector2i = Vector2i(f_idx, v_idx) + face_vertex_coord_map[coord] = fv + + var f:FaceInfo = faces[f_idx] + fv.normal = data.face_vertex_normal[fv_idx] if data.face_vertex_normal.size() > fv_idx else f.normal + fv.color = data.face_vertex_color[fv_idx] if data.face_vertex_color.size() > fv_idx else Color(1, 1, 1, 1) + #print("init_from_convex_block_data face_vertex_coord_map ", face_vertex_coord_map) + for f_idx in faces.size(): + var face:FaceInfo = faces[f_idx] + for v_idx in face.vertex_indices: + face.face_vertex_indices.append(face_vertex_coord_map[Vector2i(f_idx, v_idx)].index) + + + calc_vertex_normals() + + #print("init_from_convex_block_data %s" % format_faces_string()) + +func init_from_mesh_vector_data(mvd:MeshVectorData): + #print("init_from_mesh_vector_data") + var block_data:ConvexBlockData = ConvexBlockData.new() + block_data.init_from_mesh_vector_data(mvd) + init_from_convex_block_data(block_data) + + +#Calc convex hull bouding points +func init_from_points(points:PackedVector3Array, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE): + vertices = [] + edges = [] + faces = [] + face_vertices = [] + face_vertex_coord_map.clear() + + #print("init_from_points %s" % points) + var hull:QuickHull.Hull = QuickHull.quickhull(points) + #print("hull %s" % hull.format_points()) + + var hull_points:Array[Vector3] = hull.get_points() + + for p in hull_points: + var v:VertexInfo = VertexInfo.new(self, p) + v.index = vertices.size() + vertices.append(v) + + for facet in hull.facets: + var plane:Plane = facet.plane + var vert_indices:Array[int] = [] + + for p in facet.points: + var vert_idx:int = hull_points.find(p) + vert_indices.append(vert_idx) + + var f:FaceInfo = FaceInfo.new(self, plane.normal, uv_transform, material_id, visible, color) + f.index = faces.size() + f.vertex_indices = vert_indices + faces.append(f) + + + build_edges() + build_face_vertices() + calc_vertex_normals() + + bounds = calc_bounds() + calc_lightmap_uvs() + +func calc_vertex_normals(smooth:bool = false): + #print("calc_vertex_normals ", _to_string()) + #print("calc_vertex_normals face_vertex_coord_map ", face_vertex_coord_map) + + for v_idx in vertices.size(): + var v:VertexInfo = vertices[v_idx] + var weighted_normal:Vector3 + + for face in faces: + if face.vertex_indices.has(v_idx): + weighted_normal += MathUtil.face_area_x2(face.get_points()) + + v.normal = weighted_normal.normalized() + + #Calc face vertices + for f_idx in faces.size(): + var face:FaceInfo = faces[f_idx] + if face.vertex_indices.has(v_idx): + var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)] + fv.normal = v.normal if smooth else face.normal + +func get_vertices_in_sphere(center:Vector3, radius:float)->Array[VertexInfo]: + var result:Array[VertexInfo] + for v in vertices: + var dist2 = v.point.distance_squared_to(center) + if dist2 <= radius * radius: + result.append(v) + + return result + +func get_edge(vert_idx0:int, vert_idx1:int)->EdgeInfo: + for e in edges: + if e.start_index == vert_idx0 && e.end_index == vert_idx1: + return e + if e.start_index == vert_idx1 && e.end_index == vert_idx0: + return e + return null + +func get_face_vertex(face_idx:int, vertex_idx:int)->FaceVertexInfo: + var coord:Vector2i = Vector2i(face_idx, vertex_idx) + return face_vertex_coord_map[coord] + +func build_face_vertices(): + #print("build_face_vertices") + for f_idx in faces.size(): + var face:FaceInfo = faces[f_idx] + for v_local_idx in face.vertex_indices.size(): + var v_idx = face.vertex_indices[v_local_idx] + var vert:VertexInfo = vertices[v_idx] + + var fv:FaceVertexInfo = FaceVertexInfo.new() + var fv_idx:int = face_vertices.size() + face_vertices.append(fv) + var coord:Vector2i = Vector2i(f_idx, v_idx) + #print("Storing fv ", coord) + face_vertex_coord_map[coord] = fv + fv.index = fv_idx + fv.mesh = self + fv.face_index = f_idx + fv.vertex_index = v_idx + fv.vertex_local_index = v_local_idx + fv.color = face.color + + face.face_vertex_indices.append(fv_idx) + +func build_edges(): + + #Calculate edges + for face in faces: + var num_corners = face.vertex_indices.size() + for i0 in num_corners: + var i1:int = wrap(i0 + 1, 0, num_corners) + var v0_idx:int = face.vertex_indices[i0] + var v1_idx:int = face.vertex_indices[i1] + + var edge:EdgeInfo = get_edge(v0_idx, v1_idx) + if !edge: + var edge_idx = edges.size() + edge = EdgeInfo.new(self, v0_idx, v1_idx) + edge.index = edges.size() + edges.append(edge) + + var v0:VertexInfo = vertices[v0_idx] + v0.edge_indices.append(edge_idx) + + var v1:VertexInfo = vertices[v1_idx] + v1.edge_indices.append(edge_idx) + +# edge.face_indices.append(face.id) + edge.face_indices.append(face.index) + +func get_face_coincident_with_plane(plane:Plane)->FaceInfo: + for f in faces: + var p:Plane = f.get_plane() + if p.is_equal_approx(plane): + return f + return null + +func get_face_indices(selected_only:bool = false)->PackedInt32Array: + var result:PackedInt32Array + for f_idx in faces.size(): + var f:FaceInfo = faces[f_idx] + if !selected_only || f.selected: + result.append(f_idx) + return result + +func get_trimesh_indices()->PackedInt32Array: + var result:PackedInt32Array + + for f in faces: + for fv_idx in f.get_triangulation(): + var v_idx:int = f.vertex_indices[fv_idx] + result.append(v_idx) + + return result + +func get_face_most_similar_to_plane(plane:Plane)->FaceInfo: + var best_dot:float = -1 + var best_face:FaceInfo + + for f in faces: + var p:Plane = f.get_plane() + var dot = p.normal.dot(plane.normal) + if dot >= best_dot: + best_dot = dot + best_face = f + return best_face + +func get_vertex_at_position(point:Vector3)->VertexInfo: + for v in vertices: + if v.point.is_equal_approx(point): + return v + return null + +func get_edge_at_position(point:Vector3)->EdgeInfo: + for e in edges: + if e.get_midpoint().is_equal_approx(point): + return e + return null + +func get_face_at_position(point:Vector3)->FaceInfo: + for f in faces: + if f.get_centroid().is_equal_approx(point): + return f + return null + +func copy_vertex_attributes(ref_vol:ConvexVolume): + for v_idx in vertices.size(): + var v:VertexInfo = vertices[v_idx] + var ref_v:VertexInfo = ref_vol.get_vertex_at_position(v.point) + if ref_v: + v.selected = ref_v.selected + +func copy_face_attributes(ref_vol:ConvexVolume): + for f_idx in faces.size(): + var f:FaceInfo = faces[f_idx] + var ref_face:FaceInfo = ref_vol.get_face_most_similar_to_plane(f.get_plane()) + + f.material_id = ref_face.material_id + f.uv_transform = ref_face.uv_transform + f.visible = ref_face.visible + f.color = ref_face.color + f.selected = ref_face.selected + + #Copy face vertex values + for v_local_idx in f.vertex_indices.size(): + var v_idx:int = f.vertex_indices[v_local_idx] + var v:VertexInfo = vertices[v_idx] + var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)] + + var v_idx_ref:int = ref_face.get_closest_vertex(v.point) + + var fv_ref:FaceVertexInfo = ref_vol.face_vertex_coord_map[Vector2i(ref_face.index, v_idx_ref)] + + fv.normal = fv_ref.normal + fv.color = fv_ref.color + +func to_convex_block_data()->ConvexBlockData: + var result:ConvexBlockData = ConvexBlockData.new() + + result.active_vertex = active_vertex + result.active_edge = active_edge + result.active_face = active_face + result.active_face_vertex = active_face_vertex + + for v in vertices: + result.vertex_points.append(v.point) + result.vertex_selected.append(v.selected) + #result.vertex_active.append(v.active) + + for e in edges: + result.edge_vertex_indices.append_array([e.start_index, e.end_index]) + result.edge_face_indices.append_array([e.face_indices[0], e.face_indices[1]]) + result.edge_selected.append(e.selected) + #result.edge_active.append(e.active) + + for face in faces: + var num_verts:int = face.vertex_indices.size() + result.face_vertex_count.append(num_verts) + result.face_vertex_indices.append_array(face.vertex_indices) + #result.face_ids.append(face.id) + result.face_selected.append(face.selected) + #result.face_active.append(face.active) + result.face_material_indices.append(face.material_id) + result.face_uv_transform.append(face.uv_transform) + result.face_visible.append(face.visible) + result.face_color.append(face.color) + + for fv_idx in face_vertices.size(): + var fv:FaceVertexInfo = face_vertices[fv_idx] + #print("to_convex_block_data fv ", fv.face_index, " ", fv.vertex_index) + result.face_vertex_face_index.append(fv.face_index) + result.face_vertex_vertex_index.append(fv.vertex_index) + result.face_vertex_normal.append(fv.normal) + result.face_vertex_color.append(fv.color) + + return result + +func to_mesh_vector_data()->MeshVectorData: + var mvd:MeshVectorData = MeshVectorData.new() + var block_data:ConvexBlockData = to_convex_block_data() + mvd.create_from_convex_block(block_data) + return mvd + +func get_face(face_index:int)->FaceInfo: + return faces[face_index] + +func get_centroid()->Vector3: + var points:PackedVector3Array = get_points() + var sum:Vector3 + for p in points: + sum += p + return sum / points.size() + +# Creates a new volume that is equal to the portion of this volume on the top +# side of the passed plane. Does not modify the geometry of this volume. +func cut_with_plane(plane:Plane, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = 0)->ConvexVolume: +# + var planes:Array[Plane] + for f in faces: + #Top of planr should point toward interior + planes.append(MathUtil.flip_plane(f.get_plane())) + planes.append(plane) + + #print("planes %s" % GeneralUtil.format_planes_string(planes)) + + var hull_points:Array[Vector3] = MathUtil.get_convex_hull_points_from_planes(planes) + if hull_points.is_empty(): + return null + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(hull_points) + + new_vol.copy_face_attributes(self) + + for f in new_vol.faces: + var f_plane:Plane = MathUtil.flip_plane(f.get_plane()) + if f_plane.is_equal_approx(plane): + f.uv_transform = uv_transform + f.material_id = material_id + break + + return new_vol + +func is_empty(): + return bounds.size.is_zero_approx() + +# Returns a new ConvexVolume equal to this volume after the plane of the +# indicated face has been translated the given offset. Does not modify the +# geometry of this volume. +func translate_face_plane(face_index:int, offset:Vector3, lock_uvs:bool = false)->ConvexVolume: + var xform:Transform3D = Transform3D(Basis.IDENTITY, -offset) + + var source_face:FaceInfo + var transformed_plane:Plane + + var planes:Array[Plane] = [] + for f in faces: + if f.index == face_index: + transformed_plane = MathUtil.flip_plane(f.get_plane()) * xform + planes.append(transformed_plane) + source_face = f + else: + planes.append(MathUtil.flip_plane(f.get_plane())) + + #print("planes %s" % str(planes)) + var hull_points:Array[Vector3] = MathUtil.get_convex_hull_points_from_planes(planes) + if hull_points.is_empty(): + return null + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(hull_points) + new_vol.copy_face_attributes(self) + + return new_vol + +func translated(offset:Vector3, lock_uvs:bool = false)->ConvexVolume: + return transformed(Transform3D(Basis.IDENTITY, offset), lock_uvs) + +func translate(offset:Vector3, lock_uvs:bool = false): + transform(Transform3D(Basis.IDENTITY, offset), lock_uvs) + +func transformed(xform:Transform3D, lock_uvs:bool = false)->ConvexVolume: + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_convex_block_data(to_convex_block_data()) + new_vol.transform(xform) + return new_vol + + +func transform_uvs(xform:Transform3D): +# var xform:Transform3D = obj_xform.affine_inverse() + + for f in faces: + var axis:MathUtil.Axis = MathUtil.get_longest_axis(f.normal) + + match axis: + MathUtil.Axis.X: + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p + var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.z, u_p.y), \ + Vector2(v_p.z, v_p.y), \ + Vector2(orig_p.z, orig_p.y)) + + f.uv_transform = f.uv_transform * move_xform + + MathUtil.Axis.Y: + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p + var v_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.z), \ + Vector2(v_p.x, v_p.z), \ + Vector2(orig_p.x, orig_p.z)) + + f.uv_transform = f.uv_transform * move_xform + + MathUtil.Axis.Z: + #var xform_inv = xform.affine_inverse() + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p + var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.y), \ + Vector2(v_p.x, v_p.y), \ + Vector2(orig_p.x, orig_p.y)) + + f.uv_transform = f.uv_transform * move_xform + + +func transform(xform:Transform3D, lock_uvs:bool = false): + for v in vertices: + v.point = xform * v.point + + if xform.basis.determinant() < 0: + for f in faces: + f.reverse() + + if lock_uvs: + + for f in faces: + var axis:MathUtil.Axis = MathUtil.get_longest_axis(f.normal) + + match axis: + MathUtil.Axis.X: + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p + var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.z, u_p.y), \ + Vector2(v_p.z, v_p.y), \ + Vector2(orig_p.z, orig_p.y)) + + f.uv_transform = f.uv_transform * move_xform + + MathUtil.Axis.Y: + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p + var v_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.z), \ + Vector2(v_p.x, v_p.z), \ + Vector2(orig_p.x, orig_p.z)) + + f.uv_transform = f.uv_transform * move_xform + + MathUtil.Axis.Z: + #var xform_inv = xform.affine_inverse() + var orig_p:Vector3 = xform.origin + var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p + var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p + var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.y), \ + Vector2(v_p.x, v_p.y), \ + Vector2(orig_p.x, orig_p.y)) + + f.uv_transform = f.uv_transform * move_xform + + #calc_lightmap_uvs() + +#@deprecated +#func unused_face_id()->int: + #var idx = 0 + #for p in faces: + #idx = max(idx, p.id) + #return idx + 1 + +func contains_point(point:Vector3)->bool: + for f in faces: + var plane:Plane = f.get_plane() + if !plane.has_point(point) && !plane.is_point_over(point): + return false + return true + + +func get_points()->PackedVector3Array: + var points:PackedVector3Array + + for v in vertices: + points.append(v.point) + + return points + +func calc_bounds()->AABB: + if vertices.is_empty(): + return AABB() + + var result:AABB = AABB(vertices[0].point, Vector3.ZERO) + + for v_idx in range(1, vertices.size()): + result = result.expand(vertices[v_idx].point) + + return result + +func calc_bounds_xform(xform:Transform3D)->AABB: + if vertices.is_empty(): + return AABB() + + var result:AABB = AABB(xform * vertices[0].point, Vector3.ZERO) + + for v_idx in range(1, vertices.size()): + result = result.expand(xform * vertices[v_idx].point) + + return result + + +func tristrip_vertex_range(num_verts:int)->PackedInt32Array: + var result:PackedInt32Array + + result.append(0) + result.append(1) + for i in range(2, num_verts): + if (i & 1) == 0: + result.append(num_verts - (i >> 1)) + else: + result.append((i >> 1) + 1) + + return result + +func tristrip_vertex_range_reverse(num_verts:int)->PackedInt32Array: + var result:PackedInt32Array + + result.append(1) + result.append(0) + for i in range(2, num_verts): + if (i & 1) == 0: + result.append((i >> 1) + 1) + else: + result.append(num_verts - (i >> 1)) + + return result + +func calc_lightmap_uvs(): + var packer:FacePacker = FacePacker.new() + var max_dim:float = max(bounds.size.x, bounds.size.y, bounds.size.z) + var tree:FacePacker.FaceTree = packer.build_faces(self, max_dim * .1) + + var xform:Transform2D = Transform2D.IDENTITY + xform = xform.scaled(tree.bounds.size) + if is_zero_approx(xform.determinant()): + return + var xform_inv = xform.affine_inverse() + + for ft in tree.face_list: + var face:FaceInfo = faces[ft.face_index] + face.lightmap_uvs = xform_inv * ft.points + +func create_mesh(material_list:Array[Material], default_material:Material, override_with_default_material:bool = false)->ArrayMesh: + + var mesh:ArrayMesh = ArrayMesh.new() + mesh.blend_shape_mode = Mesh.BLEND_SHAPE_MODE_NORMALIZED + mesh.lightmap_size_hint = Vector2(1000, 1000) + + var shadow_mesh:ArrayMesh = ArrayMesh.new() + shadow_mesh.blend_shape_mode = Mesh.BLEND_SHAPE_MODE_NORMALIZED + + #print("create_mesh") + #print("faces.size() ", faces.size()) + + var face_dict:Dictionary = {} + for f_idx in faces.size(): +# print("check F_idx %s" % f_idx) + var face:FaceInfo = faces[f_idx] + if face_dict.has(face.material_id): + var arr = face_dict[face.material_id] + arr.append(f_idx) +# print("arr %s" % [arr]) + face_dict[face.material_id] = arr +# print("append %s to %s" % [f_idx, face.material_id]) + else: + face_dict[face.material_id] = [f_idx] +# print("starting %s to %s" % [f_idx, face.material_id]) + + var surface_idx:int = 0 + for mat_id in face_dict.keys(): +# print("surface mat grp %s" % mat_id) + + var points:PackedVector3Array + var normals:PackedVector3Array + var tangents:PackedFloat32Array + var colors:PackedColorArray + var uv1s:PackedVector2Array + var uv2s:PackedVector2Array + + var material = default_material + if !override_with_default_material: + if mat_id >= 0 && mat_id < material_list.size(): + material = material_list[mat_id] + + for f_idx in face_dict[mat_id]: +# print("f_idx %s" % f_idx) + + var face:FaceInfo = faces[f_idx] + if !face.visible: + continue + + var axis:MathUtil.Axis = MathUtil.get_longest_axis(face.normal) + + var fv_trianglation:Array[int] = face.get_triangulation() + + for v_local_idx in fv_trianglation: + + var v_idx:int = face.vertex_indices[v_local_idx] +# var fv_idx:int = face.face_vertex_indices[v_local_idx] +# var fv_idx:int = face_vertex_coord_map[Vector2i(f_idx, v_idx)].index + var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)] + #var fv:ConvexVolume.FaceVertexInfo = face + #vol.get_face_vertex(f_idx, v_idx) + + var p:Vector3 = vertices[v_idx].point + + var uv:Vector2 + if axis == MathUtil.Axis.X: + uv = Vector2(-p.z, -p.y) + elif axis == MathUtil.Axis.Y: + uv = Vector2(-p.x, -p.z) + elif axis == MathUtil.Axis.Z: + uv = Vector2(-p.x, -p.y) + + uv = face.uv_transform * uv + uv1s.append(uv) + uv2s.append(face.lightmap_uvs[v_local_idx]) + +# normals.append(face.normal) + normals.append(fv.normal) +# colors.append(face.color) +# colors.append(face_vertices[fv_idx].color) + colors.append(fv.color) + + points.append(p) + + #Calculate tangents + #http://foundationsofgameenginedev.com/FGED2-sample.pdf + for i in range(0, points.size(), 3): + var p0:Vector3 = points[i] + var p1:Vector3 = points[i + 1] + var p2:Vector3 = points[i + 2] + + var uv0:Vector2 = uv1s[i] + var uv1:Vector2 = uv1s[i + 1] + var uv2:Vector2 = uv1s[i + 2] + + var n:Vector3 = normals[i] + + var e1:Vector3 = p1 - p0 + var e2:Vector3 = p2 - p0 + + var duv1:Vector2 = uv1 - uv0 + var duv2:Vector2 = uv2 - uv0 + + var r:float = 1.0 / (duv1.x * duv2.y - duv2.x * duv1.y) + var t:Vector3 = (e1 * duv2.y - e2 * duv1.y) * r + var b:Vector3 = (e2 * duv1.x - e1 * duv2.x) * r + + t = t.normalized() + + for j in 3: + tangents.append(t.x) + tangents.append(t.y) + tangents.append(t.z) + tangents.append(-1.0 if t.cross(b).dot(n) > 0 else 1.0) + + var arrays:Array = create_indexed_vertex_array(points, normals, tangents, colors, uv1s, uv2s) + + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + mesh.surface_set_material(surface_idx, material) + + var shadow_arrays:Array = [] + shadow_arrays.resize(Mesh.ARRAY_MAX) + shadow_arrays[Mesh.ARRAY_VERTEX] = points + + shadow_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, shadow_arrays) + shadow_mesh.surface_set_material(surface_idx, material) + + surface_idx += 1 + + mesh.shadow_mesh = shadow_mesh +# var err = mesh.lightmap_unwrap(Transform3D.IDENTITY, 10) +# print("Lightmap unwrap Error: %s" % err) + return mesh + + + +func create_indexed_vertex_array(points:PackedVector3Array, normals:PackedVector3Array, tangents:PackedFloat32Array, colors:PackedColorArray, uv1s:PackedVector2Array, uv2s:PackedVector2Array)->Array: + var vert_idx_map:Dictionary + var indices:PackedInt32Array + var points_indexed:PackedVector3Array + var normals_indexed:PackedVector3Array + var tangents_indexed:PackedFloat32Array + var colors_indexed:PackedColorArray + var uv1s_indexed:PackedVector2Array + var uv2s_indexed:PackedVector2Array + + for v_idx in points.size(): + var vertex:PackedFloat32Array + vertex.append(points[v_idx].x) + vertex.append(points[v_idx].y) + vertex.append(points[v_idx].z) + + vertex.append(normals[v_idx].x) + vertex.append(normals[v_idx].y) + vertex.append(normals[v_idx].z) + + vertex.append(tangents[v_idx * 4]) + vertex.append(tangents[v_idx * 4 + 1]) + vertex.append(tangents[v_idx * 4 + 2]) + vertex.append(tangents[v_idx * 4 + 3]) + + vertex.append(colors[v_idx].r) + vertex.append(colors[v_idx].g) + vertex.append(colors[v_idx].b) + vertex.append(colors[v_idx].a) + + vertex.append(uv1s[v_idx].x) + vertex.append(uv1s[v_idx].y) + + vertex.append(uv2s[v_idx].x) + vertex.append(uv2s[v_idx].y) + + var new_index:int + if !vert_idx_map.has(vertex): + #print("alloc vtx ", vertex) + + new_index = vert_idx_map.size() + vert_idx_map[vertex] = new_index + points_indexed.append(points[v_idx]) + normals_indexed.append(normals[v_idx]) + tangents_indexed.append(tangents[v_idx * 4]) + tangents_indexed.append(tangents[v_idx * 4 + 1]) + tangents_indexed.append(tangents[v_idx * 4 + 2]) + tangents_indexed.append(tangents[v_idx * 4 + 3]) + colors_indexed.append(colors[v_idx]) + uv1s_indexed.append(uv1s[v_idx]) + uv2s_indexed.append(uv2s[v_idx]) + else: + new_index = vert_idx_map[vertex] + + #print("index ", new_index) + indices.append(new_index) + + #print("indices ", indices) + + var arrays:Array = [] + arrays.resize(Mesh.ARRAY_MAX) + arrays[Mesh.ARRAY_VERTEX] = points_indexed + arrays[Mesh.ARRAY_NORMAL] = normals_indexed + arrays[Mesh.ARRAY_TANGENT] = tangents_indexed + arrays[Mesh.ARRAY_TEX_UV] = uv1s_indexed + arrays[Mesh.ARRAY_TEX_UV2] = uv2s_indexed + arrays[Mesh.ARRAY_COLOR] = colors_indexed + arrays[Mesh.ARRAY_INDEX] = indices + + return arrays + + +func append_mesh_backfacing(mesh:ImmediateMesh, material:Material, offset:float = .2): +# if Engine.is_editor_hint(): +# return + + for face in faces: + + mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material) +# print("face %s" % face.index) + + mesh.surface_set_normal(face.normal) + +# for i in tristrip_vertex_range_reverse(face.vertex_indices.size()): + for i in tristrip_vertex_range_reverse(face.vertex_indices.size()): + var v_idx:int = face.vertex_indices[i] + var v:VertexInfo = vertices[v_idx] + var p:Vector3 = v.point + v.normal * offset + #var p:Vector3 = v.point + Vector3(.1, .1, .1) + + mesh.surface_add_vertex(p) + + mesh.surface_end() + +func append_mesh_outline(mesh:ImmediateMesh, viewport_camera:Camera3D, local_to_world:Transform3D, material:Material, thickness:float = 4): + var cam_orig:Vector3 = viewport_camera.global_transform.origin + + var segments:PackedVector2Array + + #print("--append_mesh_outline") + #var view_plane:Plane = Plane(-viewport_camera.global_basis.z, + #viewport_camera.global_position + #- viewport_camera.global_basis.z * viewport_camera.near * 1) + + var frustum:Array[Plane] = viewport_camera.get_frustum() + + for edge in edges: + var v0:VertexInfo = vertices[edge.start_index] + var v1:VertexInfo = vertices[edge.end_index] + var p0_world:Vector3 = local_to_world * v0.point + var p1_world:Vector3 = local_to_world * v1.point + + var frustum_culled:bool = false + for p in frustum: + var p_flip:Plane = MathUtil.flip_plane(p) + var result:PackedVector3Array = MathUtil.clip_segment_to_plane_3d(p_flip, p0_world, p1_world) + if result.is_empty(): + frustum_culled = true + break + p0_world = result[0] + p1_world = result[1] + + if frustum_culled: + continue + + var has_front:bool = false + var has_back:bool = false + + for f_idx in edge.face_indices: + var face = faces[f_idx] + + var plane = face.get_plane() + plane = local_to_world * plane + + if plane.is_point_over(cam_orig): + has_front = true + else: + has_back = true + + #print("front %s back %s" % [has_front, has_back]) + + if has_front && has_back: + #Draw edge + var p0_screen:Vector2 = viewport_camera.unproject_position(p0_world) + var p1_screen:Vector2 = viewport_camera.unproject_position(p1_world) + segments.append(p0_screen) + segments.append(p1_screen) + #print("seg %s %s" % [p0_screen, p1_screen]) + + #print("segments ", segments) + + var loops:Array[Loop2D] = MathUtil.get_loops_from_segments_2d(segments) + for loop in loops: + var out_dirs:PackedVector2Array + + #print("loop ", loop) + for v_idx in loop.points.size(): + var p0_screen:Vector2 = loop.points[wrap(v_idx - 1, 0, loop.points.size())] + var p1_screen:Vector2 = loop.points[v_idx] + var p2_screen:Vector2 = loop.points[wrap(v_idx + + 1, 0, loop.points.size())] + #var span:Vector2 = p2_screen - p1_screen + + var norm01:Vector2 = (p1_screen - p0_screen).normalized() + var norm12:Vector2 = (p2_screen - p1_screen).normalized() + + var out_dir1:Vector2 = (-norm01 + norm12).normalized() + var perp:Vector2 = out_dir1 - out_dir1.project(norm12) + #Check winding + if perp.x * norm12.y - perp.y * norm12.x < 0: + out_dir1 = -out_dir1 + + out_dirs.append(out_dir1) + + + mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material) + for v_idx in loop.points.size() + (1 if loop.closed else 0): + var p_screen:Vector2 = loop.points[wrap(v_idx, 0, loop.points.size())] + var p_out_dir:Vector2 = out_dirs[wrap(v_idx, 0, loop.points.size())] + + var z_pos:float = (viewport_camera.near + viewport_camera.far) / 2 + var p0:Vector3 = viewport_camera.project_position(p_screen, z_pos) + var p1:Vector3 = viewport_camera.project_position(p_screen + p_out_dir * thickness, z_pos) + + mesh.surface_add_vertex(p0) + mesh.surface_add_vertex(p1) + + mesh.surface_end() + + + +func create_mesh_wire(material:Material)->ImmediateMesh: +# if Engine.is_editor_hint(): +# return + var mesh:ImmediateMesh = ImmediateMesh.new() + + mesh.surface_begin(Mesh.PRIMITIVE_LINES, material) + + for e in edges: + var v0:VertexInfo = vertices[e.start_index] + var v1:VertexInfo = vertices[e.end_index] + + mesh.surface_add_vertex(v0.point) + mesh.surface_add_vertex(v1.point) + + mesh.surface_end() + + return mesh + + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + if bounds.intersects_ray(origin, dir) == null: + return null + + var best_result:IntersectResults + + for f_idx in faces.size(): + var face:FaceInfo = faces[f_idx] +# var tris:PackedVector3Array = MathUtil.trianglate_face(face.get_points(), face.normal) + var tris:PackedVector3Array = face.get_trianges() + for i in range(0, tris.size(), 3): + var p0:Vector3 = tris[i] + var p1:Vector3 = tris[i + 1] + var p2:Vector3 = tris[i + 2] + + #Godot uses clockwise winding + var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2) + + var p_hit:Vector3 = MathUtil.intersect_plane(origin, dir, p0, tri_area_x2) + if !p_hit.is_finite(): + continue + + if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0: + continue + if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0: + continue + if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0: + continue + + #Intersection + var dist_sq:float = (origin - p_hit).length_squared() + if !best_result || best_result.distance_squared > dist_sq: + + var result:IntersectResults = IntersectResults.new() + #result.face_id = face.id + result.face_index = f_idx + result.normal = face.normal + result.position = p_hit + result.distance_squared = dist_sq + + best_result = result + + return best_result + +func format_faces_string()->String: + var s:String = "" + for f in faces: + s = s + "[" + for v_idx in f.vertex_indices: + s += "%s, " % vertices[v_idx].point + s = s + "],\n" + return s + +func update_edge_and_face_selection_from_vertices(): + for e in edges: + e.selected = vertices[e.start_index].selected && vertices[e.end_index].selected + + for f in faces: + var all_sel:bool = true + for v_idx in f.vertex_indices: + if !vertices[v_idx].selected: + all_sel = false + break + f.selected = all_sel + + +func intersects_plane(plane:Plane)->bool: + + var is_over:bool = false + var is_under:bool = false + + for v in vertices: + var p:Vector3 = v.point + + if plane.has_point(p): + continue + + if plane.is_point_over(p): + is_over = true + else: + is_under = true + + if is_over && is_under: + return true + + return false + +func subtract(subtrahend:ConvexVolume)->Array[ConvexVolume]: + var result_list:Array[ConvexVolume] + + var split_vol:ConvexVolume = self + + for face in subtrahend.faces: + var p:Plane = face.get_plane() + + if !split_vol.intersects_plane(p): + continue + + var vol_over:ConvexVolume = split_vol.cut_with_plane(p) + var vol_under:ConvexVolume = split_vol.cut_with_plane(MathUtil.flip_plane(p)) + + result_list.append(vol_over) + split_vol = vol_under + +# result_list.append(split_vol) + + return result_list + + +func intersect(subtrahend:ConvexVolume)->ConvexVolume: + var result_list:Array[ConvexVolume] + + var split_vol:ConvexVolume = self + + for face in subtrahend.faces: + var p:Plane = face.get_plane() + + if !split_vol.intersects_plane(p): + continue + + var vol_over:ConvexVolume = split_vol.cut_with_plane(p) + var vol_under:ConvexVolume = split_vol.cut_with_plane(MathUtil.flip_plane(p)) + + result_list.append(vol_over) + split_vol = vol_under + + return split_vol + + +func is_over_or_on_plane(plane:Plane)->bool: + for v in vertices: + if !plane.is_point_over(v.point) && !plane.has_point(v.point): + return false + + return true + +func intersects_convex_volume(vol:ConvexVolume)->bool: + #Look for plane of separtion between two volumes + for f in vol.faces: + var p:Plane = f.get_plane() + if is_over_or_on_plane(p): + return false + + return true + + +func intersects_frustum(frustum:Array[Plane])->bool: + + for face in faces: + var points:PackedVector3Array = face.get_points() + if MathUtil.polygon_intersects_frustum(points, frustum): + return true + + return false + +func make_convex(): + var selected_points:PackedVector3Array + var new_points:PackedVector3Array + + for v in vertices: + new_points.append(v.point) + + var new_vol:ConvexVolume = ConvexVolume.new() + new_vol.init_from_points(new_points) + + new_vol.copy_vertex_attributes(self) + new_vol.copy_face_attributes(self) + + if active_vertex != -1: + var v:VertexInfo = vertices[active_vertex] + var new_v:VertexInfo = new_vol.get_vertex_at_position(v.point) + if new_v: + new_vol.active_vertex = new_v.index + + if active_edge != -1: + var e:EdgeInfo = edges[active_edge] + var mp:Vector3 = e.get_midpoint() + var new_e:EdgeInfo = new_vol.get_edge_at_position(mp) + if new_e: + new_vol.active_edge = new_e.index + + if active_face != -1: + var f:FaceInfo = faces[active_face] + var centroid:Vector3 = f.get_centroid() + var new_f:FaceInfo = new_vol.get_face_at_position(centroid) + if new_f: + new_vol.active_face = new_f.index + + + #for v_idx in new_vol.vertices.size(): + #var v:ConvexVolume.VertexInfo = new_vol.vertices[v_idx] +## print ("vol point %s " % v.point) + #if selected_points.has(v.point): +## print("set sel") + #v.selected = true +# + #block.mesh_vector_data = new_vol.to_mesh_vector_data() + + diff --git a/addons/cyclops_level_builder/math/face_packer.gd b/addons/cyclops_level_builder/math/face_packer.gd new file mode 100644 index 0000000..9a811ab --- /dev/null +++ b/addons/cyclops_level_builder/math/face_packer.gd @@ -0,0 +1,253 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name FacePacker + +class SpawnResult extends RefCounted: + var point:Vector2 + var flip:bool + + func _init(point:Vector2, flip:bool): + self.point = point + self.flip = flip + +class FaceTree extends RefCounted: +# var root:FaceTreeNode + var size:Vector2 + var spawn_points:PackedVector2Array = [Vector2.ZERO] + var face_list:Array[FaceTracker] + var bounds:Rect2 + + func _to_string()->String: + var res:String = "" + for face in face_list: + res += "%s,\n" % str(face) + return res + + func is_collision(rect:Rect2)->bool: + for face in face_list: + if face.bounds.intersects(rect): + return true + return false + + func max_vec_dim(v:Vector2): + return max(v.x, v.y) + + func get_best_spawn_point(face:FaceTracker)->SpawnResult: + var started:bool = false + var best_spawn_point:Vector2 = Vector2.INF + var best_bounds:Rect2 + var best_flip:bool + + for s_idx in spawn_points.size(): + var spawn_point:Vector2 = spawn_points[s_idx] + + var placed_bounds:Rect2 = face.bounds + placed_bounds.position += spawn_point + + if !is_collision(placed_bounds): + var new_bounds:Rect2 = bounds.merge(placed_bounds) + + if new_bounds.is_equal_approx(bounds): + return SpawnResult.new(spawn_point, false) + else: + if !started || max_vec_dim(best_bounds.size) > max_vec_dim(new_bounds.size): + best_bounds = new_bounds + best_flip = false + best_spawn_point = spawn_point + started = true + + var placed_bounds_flipped:Rect2 = face.bounds + placed_bounds_flipped.size = Vector2(placed_bounds_flipped.size.y, placed_bounds_flipped.size.x) + placed_bounds_flipped.position += spawn_point + + if !is_collision(placed_bounds_flipped): + var new_bounds_flipped:Rect2 = bounds.merge(placed_bounds_flipped) + + if new_bounds_flipped.is_equal_approx(bounds): + return SpawnResult.new(spawn_point, true) + else: + if !started || max_vec_dim(best_bounds.size) > max_vec_dim(new_bounds_flipped.size): + best_bounds = new_bounds_flipped + best_flip = true + best_spawn_point = spawn_point + started = true + + return SpawnResult.new(best_spawn_point, best_flip) + + func add_face(face:FaceTracker): + var spawn:SpawnResult = get_best_spawn_point(face) + + var idx = spawn_points.find(spawn.point) + spawn_points.remove_at(idx) + + if spawn.flip: + face.reflect_diagonal() + + face.translate(spawn.point) + face_list.append(face) + bounds = bounds.merge(face.bounds) + + var sp_0:Vector2 = face.bounds.position + Vector2(face.bounds.size.x, 0) + var sp_1:Vector2 = face.bounds.position + Vector2(0, face.bounds.size.y) + if !spawn_points.has(sp_0): + spawn_points.append(sp_0) + if !spawn_points.has(sp_1): + spawn_points.append(sp_1) + + + +class FaceTracker extends RefCounted: + var points:PackedVector2Array + var indices:PackedInt32Array + var bounds:Rect2 + var face_index:int + + func _to_string()->String: + var res:String = "[" + for p in points: + res += "%s, " % str(p) + res += "]" + return res + + func max_dim()->float: + return max(bounds.size.x, bounds.size.y) + + func reflect_diagonal(): + for p_idx in points.size(): + var p:Vector2 = points[p_idx] + points[p_idx] = Vector2(p.y, p.x) + bounds.size = Vector2(bounds.size.y, bounds.size.x) + + func translate(offset:Vector2): + for p_idx in points.size(): + points[p_idx] += offset + bounds.position += offset + + func fit_initial_rect(): + bounds = Rect2(points[0], Vector2.ZERO) + for i in range(1, points.size()): + bounds = bounds.expand(points[i]) + + #Move so corner of bounds is at (0, 0) + for i in points.size(): + points[i] -= bounds.position + bounds.position = Vector2.ZERO + + func get_best_base_index()->int: + var best_index:int = -1 + var best_height:float = INF + + for i0 in points.size(): + var i1:int = wrap(i0 + 1, 0, points.size()) + + var base_dir:Vector2 = points[i1] - points[i0] + var base_origin:Vector2 = points[i0] + var base_dir_perp:Vector2 = Vector2(-base_dir.y, base_dir.x) + + var max_height:float = 0 + + for j in range(2, points.size()): + var p_idx:int = wrap(j + i0, 0, points.size()) + var p:Vector2 = points[p_idx] + var offset:Vector2 = p - base_origin + var offset_proj:Vector2 = offset.project(base_dir_perp) + + max_height = max(max_height, offset_proj.length_squared()) + + if max_height < best_height: + best_height = max_height + best_index = i0 + + return best_index + + func rotate_to_best_fit(): + var i0:int = get_best_base_index() + var i1:int = wrap(i0 + 1, 0, points.size()) + + var base_dir:Vector2 = (points[i1] - points[i0]).normalized() + var base_dir_perp:Vector2 = Vector2(-base_dir.y, base_dir.x) + + var xform:Transform2D = Transform2D(base_dir, base_dir_perp, Vector2.ZERO) + var xform_inv:Transform2D = xform.affine_inverse() + + for p_idx in points.size(): + var p:Vector2 = xform_inv * points[p_idx] + points[p_idx] = p + + +func pack_faces(faces:Array[FaceTracker])->FaceTree: + faces.sort_custom(func (a:FaceTracker, b:FaceTracker): return a.max_dim() > b.max_dim()) + + var tree:FaceTree = FaceTree.new() + for f in faces: + tree.add_face(f) + + #print(tree) + return tree + +func build_faces(vol:ConvexVolume, margin:float)->FaceTree: + var faces:Array[FaceTracker] + + for f_idx in vol.faces.size(): + var face:ConvexVolume.FaceInfo = vol.faces[f_idx] + var axis:MathUtil.Axis = MathUtil.get_longest_axis(face.normal) + + var cross_vec:Vector3 + if axis == MathUtil.Axis.Y: + cross_vec = Vector3.FORWARD + else: + cross_vec = Vector3.UP + + var u_axis:Vector3 = face.normal.cross(cross_vec) + var v_axis:Vector3 = u_axis.cross(face.normal) + var basis:Basis = Basis(u_axis, face.normal, v_axis) + + var xform:Transform3D = Transform3D(basis, face.get_centroid()) + var xz_xform:Transform3D = xform.affine_inverse() + + var tracker:FaceTracker = FaceTracker.new() + tracker.face_index = f_idx + faces.append(tracker) + + for v_idx in face.vertex_indices: + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + var proj:Vector3 = xz_xform * v.point + tracker.points.append(Vector2(proj.x, proj.z)) + tracker.indices.append(v_idx) + + #print("face init points %s" % tracker.points) + + tracker.rotate_to_best_fit() + #print("after rot %s" % tracker.points) + tracker.fit_initial_rect() + #print("after fit %s" % tracker.points) + for p_idx in tracker.points.size(): + tracker.points[p_idx] += Vector2(margin, margin) + tracker.bounds.size += Vector2(margin, margin) * 2 + + return pack_faces(faces) + + diff --git a/addons/cyclops_level_builder/math/general_mesh.gd b/addons/cyclops_level_builder/math/general_mesh.gd new file mode 100644 index 0000000..85d8ef1 --- /dev/null +++ b/addons/cyclops_level_builder/math/general_mesh.gd @@ -0,0 +1,446 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +#@deprecated +@tool +extends RefCounted +class_name GeneralMesh + + +class VertexInfo extends RefCounted: + var index:int + var point:Vector3 + var edge_indices:Array[int] = [] + var selected:bool + + func _init(_index:int, _point:Vector3 = Vector3.ZERO): + index = _index + point = _point + + func _to_string(): + var s:String = "%s %s [" % [index, point] + for i in edge_indices: + s += "%s " % i + s += "]" + + return s + +class EdgeInfo extends RefCounted: + var index:int + var start_index:int + var end_index:int + var face_indices:Array[int] = [] + var selected:bool + + func _init(_index:int, _start:int = 0, _end:int = 0): + index = _index + start_index = _start + end_index = _end + + func _to_string(): + var s:String = "%s %s %s [" % [index, start_index, end_index] + for i in face_indices: + s += "%s " % i + s += "]" + return s + +class FaceInfo extends RefCounted: + var index:int + var normal:Vector3 +# var vertex_indices:Array[int] + var face_corner_indices:Array[int] + var material_index:int + var selected:bool + + func _init(_index:int, _face_corner_indices:Array[int] = [], _mat_index:int = 0): + index = _index + face_corner_indices = _face_corner_indices + material_index = _mat_index + + func _to_string(): + var s:String = "%s %s %s [" % [index, normal, material_index] + for i in face_corner_indices: + s += "%s " % i + s += "]" + return s + +class FaceCornerInfo extends RefCounted: + var index:int + var uv:Vector2 + var vertex_index:int + var face_index:int + var selected:bool + + func _init(_index:int, _vertex_index:int, _face_index:int): + vertex_index = _vertex_index + face_index = _face_index + + func _to_string(): + var s:String = "%s %s %s %s" % [index, uv, vertex_index, face_index] + return s + + + +var vertices:Array[VertexInfo] = [] +var edges:Array[EdgeInfo] = [] +var faces:Array[FaceInfo] = [] +var face_corners:Array[FaceCornerInfo] = [] +var bounds:AABB + +#var points:PackedVector3Array + +func _init(): + pass + +func get_face_indices()->PackedInt32Array: + var result:PackedInt32Array + for f in faces: + result.append(f.index) + return result + +func clear_lists(): + vertices = [] + edges = [] + faces = [] + face_corners = [] + bounds = AABB() + +func init_block(block_bounds:AABB): + var p000:Vector3 = block_bounds.position + var p111:Vector3 = block_bounds.end + var p001:Vector3 = Vector3(p000.x, p000.y, p111.z) + var p010:Vector3 = Vector3(p000.x, p111.y, p000.z) + var p011:Vector3 = Vector3(p000.x, p111.y, p111.z) + var p100:Vector3 = Vector3(p111.x, p000.y, p000.z) + var p101:Vector3 = Vector3(p111.x, p000.y, p111.z) + var p110:Vector3 = Vector3(p111.x, p111.y, p000.z) + + init_prism([p000, p001, p011, p010], p100 - p000) + + +func init_prism(base_points:Array[Vector3], extrude_dir:Vector3): + + var verts:PackedVector3Array + for p in base_points: + verts.append(p) + for p in base_points: + verts.append(p + extrude_dir) + + var index_list:PackedInt32Array + var face_len_list:PackedInt32Array + + var num_points:int = base_points.size() + for i0 in num_points: + var i1:int = wrap(i0 + 1, 0, num_points) + + index_list.append(i0) + index_list.append(i1) + index_list.append(i1 + num_points) + index_list.append(i0 + num_points) + face_len_list.append(4) + + for i0 in num_points: +# index_list.append(i0) + index_list.append(num_points - i0 - 1) + face_len_list.append(num_points) + + for i0 in num_points: + index_list.append(i0 + num_points) +# index_list.append(num_points * 2 - i0 - 1) + face_len_list.append(num_points) + + init_from_face_lists(verts, index_list, face_len_list) + + +func init_from_face_lists(verts:PackedVector3Array, index_list:PackedInt32Array, face_len_list:PackedInt32Array): + clear_lists() + + for i in verts.size(): + var v:VertexInfo = VertexInfo.new(i, verts[i]) + vertices.append(v) + + if i == 0: + bounds = AABB(verts[0], Vector3.ZERO) + else: + bounds = bounds.expand(verts[i]) + + var vertex_index_offset:int = 0 + for face_index in face_len_list.size(): + var num_face_verts = face_len_list[face_index] +# if num_face_verts < 3: +# continue + + var face_corners_local:Array[int] = [] + for i in num_face_verts: + var face_corner_index:int = face_corners.size() + var face_corner:FaceCornerInfo = FaceCornerInfo.new(face_corner_index, index_list[vertex_index_offset], face_index) + face_corners.append(face_corner) + face_corners_local.append(face_corner_index) + vertex_index_offset += 1 + + var face:FaceInfo = FaceInfo.new(face_index, face_corners_local) + faces.append(face) + + #Calc normal + var fc0:FaceCornerInfo = face_corners[face_corners_local[0]] +# var vidx0 = fc0.vertex_index + var p0:Vector3 = vertices[fc0.vertex_index].point +# + var weighted_normal:Vector3 + for i in range(1, num_face_verts - 1): + var fc1:FaceCornerInfo = face_corners[face_corners_local[i]] + var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]] +# var vidx1 = fc1.vertex_index +# var vidx2 = fc2.vertex_index + var p1:Vector3 = vertices[fc1.vertex_index].point + var p2:Vector3 = vertices[fc2.vertex_index].point + + var v1:Vector3 = p1 - p0 + var v2:Vector3 = p2 - p0 + weighted_normal += v2.cross(v1) + + face.normal = weighted_normal.normalized() + + #Calculate edges + for face in faces: + var num_corners = face.face_corner_indices.size() + for i0 in num_corners: + var i1:int = wrap(i0 + 1, 0, num_corners) + var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]] + var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]] + + var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index) + if !edge: + var edge_idx = edges.size() + edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index) + edges.append(edge) + + var v0:VertexInfo = vertices[fc0.vertex_index] + v0.edge_indices.append(edge_idx) + + var v1:VertexInfo = vertices[fc1.vertex_index] + v1.edge_indices.append(edge_idx) + + edge.face_indices.append(face.index) + + +func get_edge(vert_idx0:int, vert_idx1:int)->EdgeInfo: + for e in edges: + if e.start_index == vert_idx0 && e.end_index == vert_idx1: + return e + if e.start_index == vert_idx1 && e.end_index == vert_idx0: + return e + return null + + +func init_block_data(block:BlockData): + clear_lists() + + for i in block.points.size(): + var v:VertexInfo = VertexInfo.new(i, block.points[i]) + vertices.append(v) + + if i == 0: + bounds = AABB(v.point, Vector3.ZERO) + else: + bounds = bounds.expand(v.point) + + var corner_index_offset:int = 0 + for face_index in block.face_vertex_count.size(): + var num_face_verts = block.face_vertex_count[face_index] + + var face_corners_local:Array[int] = [] + for i in num_face_verts: + var vertex_index = block.face_vertex_indices[corner_index_offset] + + var face_corner:FaceCornerInfo = FaceCornerInfo.new(corner_index_offset, vertex_index, face_index) + face_corner.uv = block.uvs[corner_index_offset] + face_corners.append(face_corner) + face_corners_local.append(corner_index_offset) + corner_index_offset += 1 + + var face:FaceInfo = FaceInfo.new(face_index, face_corners_local) + face.material_index = block.face_material_indices[face_index] + faces.append(face) + + #Calc normal + var fc0:FaceCornerInfo = face_corners[face_corners_local[0]] + var p0:Vector3 = vertices[fc0.vertex_index].point +# + var weighted_normal:Vector3 + for i in range(1, num_face_verts - 1): + var fc1:FaceCornerInfo = face_corners[face_corners_local[i]] + var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]] + var p1:Vector3 = vertices[fc1.vertex_index].point + var p2:Vector3 = vertices[fc2.vertex_index].point + + var v1:Vector3 = p1 - p0 + var v2:Vector3 = p2 - p0 + weighted_normal += v2.cross(v1) + + face.normal = weighted_normal.normalized() + + #Calculate edges + for face in faces: + var num_corners = face.face_corner_indices.size() + for i0 in num_corners: + var i1:int = wrap(i0 + 1, 0, num_corners) + var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]] + var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]] + + var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index) + if !edge: + var edge_idx = edges.size() + edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index) + edges.append(edge) + + var v0:VertexInfo = vertices[fc0.vertex_index] + v0.edge_indices.append(edge_idx) + + var v1:VertexInfo = vertices[fc1.vertex_index] + v1.edge_indices.append(edge_idx) + + edge.face_indices.append(face.index) + + +func to_block_data()->BlockData: + var block:BlockData = preload("res://addons/cyclops_level_builder/resources/block_data.gd").new() +# var block:BlockData = BlockData.new() + + for v in vertices: + block.points.append(v.point) + + for f in faces: + block.face_vertex_count.append(f.face_corner_indices.size()) + block.face_material_indices.append(f.material_index) + + for fc_idx in f.face_corner_indices: + var fc:FaceCornerInfo = face_corners[fc_idx] + block.face_vertex_indices.append(fc.vertex_index) + block.uvs.append(fc.uv) + + return block + +func append_mesh(mesh:ImmediateMesh, material:Material, color:Color = Color.WHITE): + + for face in faces: + mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material) +# print("face %s" % face.index) + + mesh.surface_set_normal(face.normal) + + var num_corners:int = face.face_corner_indices.size() + for i in num_corners: + var idx = (i + 1) / 2 if i & 1 else wrap(num_corners - (i / 2), 0, num_corners) + var fc:FaceCornerInfo = face_corners[face.face_corner_indices[idx]] + + mesh.surface_set_color(color) + mesh.surface_set_uv(fc.uv) + mesh.surface_add_vertex(vertices[fc.vertex_index].point) +# print ("%s %s %s" % [idx, fc.vertex_index, control_mesh.vertices[fc.vertex_index].point]) + + mesh.surface_end() + +func triplanar_unwrap(scale:float = 1): + for fc in face_corners: + var v:VertexInfo = vertices[fc.vertex_index] + var f:FaceInfo = faces[fc.face_index] + + if abs(f.normal.x) > abs(f.normal.y) && abs(f.normal.x) > abs(f.normal.z): + fc.uv = Vector2(v.point.y, v.point.z) * scale + elif abs(f.normal.y) > abs(f.normal.z): + fc.uv = Vector2(v.point.x, v.point.z) * scale + else: + fc.uv = Vector2(v.point.x, v.point.y) * scale + + +func get_face_points(face:FaceInfo)->PackedVector3Array: + var points:PackedVector3Array + for fc_idx in face.face_corner_indices: + var fc:FaceCornerInfo = face_corners[fc_idx] + points.append(vertices[fc.vertex_index].point) + return points + +func triangulate_face(face:FaceInfo)->PackedVector3Array: + var points:PackedVector3Array = get_face_points(face) + return MathUtil.trianglate_face(points, face.normal) + + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + if bounds.intersects_ray(origin, dir) == null: + return null + + var best_result:IntersectResults + + for f in faces: + var tris:PackedVector3Array = triangulate_face(f) + for i in range(0, tris.size(), 3): + var p0:Vector3 = tris[i] + var p1:Vector3 = tris[i + 1] + var p2:Vector3 = tris[i + 2] + + #Godot uses clockwise winding + var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2) + + var p_hit:Vector3 = MathUtil.intersect_plane(origin, dir, p0, tri_area_x2) + if !p_hit.is_finite(): + continue + + if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0: + continue + if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0: + continue + if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0: + continue + + #Intersection + var dist_sq:float = (origin - p_hit).length_squared() + if !best_result || best_result.distance_squared > dist_sq: + + var result:IntersectResults = IntersectResults.new() + result.face_index = f.index + result.normal = f.normal + result.position = p_hit + result.distance_squared = dist_sq + + best_result = result + + return best_result + +func translate(offset:Vector3): + for v in vertices: + v.point += offset + +func dump(): + print ("Verts") + for v in vertices: + print(v.to_string()) + print ("Edges") + for e in edges: + print(e.to_string()) + print ("Faces") + for f in faces: + print(f.to_string()) + print ("Face Corners") + for f in face_corners: + print(f.to_string()) diff --git a/addons/cyclops_level_builder/math/geometry_mesh.gd b/addons/cyclops_level_builder/math/geometry_mesh.gd new file mode 100644 index 0000000..7d96ffe --- /dev/null +++ b/addons/cyclops_level_builder/math/geometry_mesh.gd @@ -0,0 +1,63 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +#extends RefCounted +class_name GeometryMesh + +var coords:PackedVector3Array +var normals:PackedVector3Array +var uvs:PackedVector2Array + +func transform(xform:Transform3D)->GeometryMesh: + var result:GeometryMesh = GeometryMesh.new() + + var basis:Basis = xform.basis + basis = basis.inverse() + basis = basis.transposed() + + for i in coords.size(): + result.coords.append(xform * coords[i]) + result.uvs.append(uvs[i]) + result.normals.append(basis * normals[i]) + + return result + +func append_to_immediate_mesh(mesh:ImmediateMesh, material:Material, xform:Transform3D = Transform3D.IDENTITY): + mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES, material) + + var basis:Basis = xform.basis + basis = basis.inverse() + basis = basis.transposed() + + for i in coords.size(): + var normal:Vector3 = basis * normals[i] + var coord:Vector3 = xform * coords[i] + var uv:Vector2 = uvs[i] + + mesh.surface_set_normal(normal) + mesh.surface_set_uv(uv) + mesh.surface_add_vertex(coord) + + mesh.surface_end() + diff --git a/addons/cyclops_level_builder/math/loop_2d.gd b/addons/cyclops_level_builder/math/loop_2d.gd new file mode 100644 index 0000000..179f909 --- /dev/null +++ b/addons/cyclops_level_builder/math/loop_2d.gd @@ -0,0 +1,36 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name Loop2D + +var points:PackedVector2Array +var closed:bool + +func reverse(): + points.reverse() + +func _to_string(): + return "Loop2D(%s, %s)" % [closed, str(points)] + diff --git a/addons/cyclops_level_builder/math/math_geometry.gd b/addons/cyclops_level_builder/math/math_geometry.gd new file mode 100644 index 0000000..e8536ec --- /dev/null +++ b/addons/cyclops_level_builder/math/math_geometry.gd @@ -0,0 +1,182 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +#extends RefCounted +class_name MathGeometry + +#static func circle_points(radius:float = 1, segs:int = 16, u_axis:Vector3 = Vector3.RIGHT, v_axis:Vector3 = Vector3.BACK)->PackedVector3Array: + #var result:PackedVector3Array + # + #for i in (segs + 1): + #result.append(u_axis * cos(i / float(segs)) + v_axis * sin(i / float(segs))) +# + #return result + # +#static func circle(radius:float = 1, segs:int = 16, u_axis:Vector3 = Vector3.RIGHT, v_axis:Vector3 = Vector3.BACK)->GeometryMesh: + #var mesh:GeometryMesh = GeometryMesh.new() + # + #for i in (segs + 1): + #mesh.coords.append(u_axis * cos(i / float(segs)) + v_axis * sin(i / float(segs))) + #mesh.uvs.append(Vector2(i / float(segs), 0)) + #mesh.normals.append(mesh.coords[-1].normalized()) + # + #return mesh + +static func unit_cylinder(segs:int = 16, radius0:float = 1, radius1:float = 1, top_height:float = 1, bottom_height:float = -1, bottom_cap:bool = false, top_cap:bool = false)->GeometryMesh: + var mesh:GeometryMesh = GeometryMesh.new() + + var vc0:Vector3 = Vector3(0, 0, -1) + var vc1:Vector3 = Vector3(0, 0, 1) + var uvc:Vector2 = Vector2(.5, .5) + + for s in range(segs): + + var sin0:float = sin(deg_to_rad(360 * s / segs)) + var cos0:float = cos(deg_to_rad(360 * s / segs)) + var sin1:float = sin(deg_to_rad(360 * (s + 1) / segs)) + var cos1:float = cos(deg_to_rad(360 * (s + 1) / segs)) + + var v00:Vector3 = Vector3(sin0 * radius0, cos0 * radius0, bottom_height) + var v10:Vector3 = Vector3(sin1 * radius0, cos1 * radius0, bottom_height) + var v01:Vector3 = Vector3(sin0 * radius1, cos0 * radius1, top_height) + var v11:Vector3 = Vector3(sin1 * radius1, cos1 * radius1, top_height) + + var tan0:Vector3 = Vector3(cos0, sin0, 0) + var n00:Vector3 = (v01 - v00).cross(tan0) + n00 = n00.normalized() + var n01:Vector3 = n00 + var tan1:Vector3 = Vector3(cos1, sin1, 0) + var n10:Vector3 = (v11 - v10).cross(tan1) + n10 = n10.normalized() + var n11 = n10 + + var uv00:Vector2 = Vector2(s / segs, 0) + var uv10:Vector2 = Vector2((s + 1) / segs, 0) + var uv01:Vector2 = Vector2(s / segs, 1) + var uv11:Vector2 = Vector2((s + 1) / segs, 1) + + if radius0 != 0: + mesh.coords.append(v00) + mesh.coords.append(v10) + mesh.coords.append(v11) + + mesh.normals.append(n00) + mesh.normals.append(n10) + mesh.normals.append(n11) + + mesh.uvs.append(uv00) + mesh.uvs.append(uv10) + mesh.uvs.append(uv11) + + if radius1 != 0: + mesh.coords.append(v00) + mesh.coords.append(v11) + mesh.coords.append(v01) + + mesh.normals.append(n00) + mesh.normals.append(n11) + mesh.normals.append(n01) + + mesh.uvs.append(uv00) + mesh.uvs.append(uv11) + mesh.uvs.append(uv01) + + if top_cap and radius1 != 0: + mesh.coords.append(v01) + mesh.coords.append(v11) + mesh.coords.append(vc1) + + mesh.normals.append(Vector3(0, 0, 1)) + mesh.normals.append(Vector3(0, 0, 1)) + mesh.normals.append(Vector3(0, 0, 1)) + + mesh.uvs.append(Vector2(sin0, cos0)) + mesh.uvs.append(Vector2(sin1, cos1)) + mesh.uvs.append(uvc) + + if bottom_cap and radius0 != 0: + mesh.coords.append(v00) + mesh.coords.append(v10) + mesh.coords.append(vc0) + + mesh.normals.append(-Vector3(0, 0, 1)) + mesh.normals.append(-Vector3(0, 0, 1)) + mesh.normals.append(-Vector3(0, 0, 1)) + + mesh.uvs.append(Vector2(sin0, cos0)) + mesh.uvs.append(Vector2(sin1, cos1)) + mesh.uvs.append(uvc) + + + return mesh + +static func unit_sphere(segs_lat:int = 6, segs_long:int = 8)->GeometryMesh: + var mesh:GeometryMesh = GeometryMesh.new() + + for la in range(segs_lat): + + var z0:float = cos(deg_to_rad(180 * la / segs_lat)) + var z1:float = cos(deg_to_rad(180 * (la + 1) / segs_lat)) + var r0:float = sin(deg_to_rad(180 * la / segs_lat)) + var r1:float = sin(deg_to_rad(180 * (la + 1) / segs_lat)) + + for lo in range(segs_long): + var cx0:float = sin(deg_to_rad(360 * lo / segs_long)) + var cx1:float = sin(deg_to_rad(360 * (lo + 1) / segs_long)) + var cy0:float = cos(deg_to_rad(360 * lo / segs_long)) + var cy1:float = cos(deg_to_rad(360 * (lo + 1) / segs_long)) + + var v00:Vector3 = Vector3(cx0 * r0, cy0 * r0, z0) + var v10:Vector3 = Vector3(cx1 * r0, cy1 * r0, z0) + var v01:Vector3 = Vector3(cx0 * r1, cy0 * r1, z1) + var v11:Vector3 = Vector3(cx1 * r1, cy1 * r1, z1) + + if la != 0: + mesh.coords.append(v00) + mesh.coords.append(v11) + mesh.coords.append(v10) + + mesh.normals.append(v00) + mesh.normals.append(v10) + mesh.normals.append(v10) + + mesh.uvs.append(Vector2(lo / segs_long, la / segs_lat)) + mesh.uvs.append(Vector2((lo + 1) / segs_long, la / segs_lat)) + mesh.uvs.append(Vector2((lo + 1) / segs_long, (la + 1) / segs_lat)) + + if la != segs_lat - 1: + mesh.coords.append(v00) + mesh.coords.append(v01) + mesh.coords.append(v11) + + mesh.normals.append(v00) + mesh.normals.append(v01) + mesh.normals.append(v11) + + mesh.uvs.append(Vector2(lo / segs_long, la / segs_lat)) + mesh.uvs.append(Vector2((lo + 1) / segs_long, (la + 1) / segs_lat)) + mesh.uvs.append(Vector2(lo / segs_long, (la + 1) / segs_lat)) + + return mesh + diff --git a/addons/cyclops_level_builder/math/math_util.gd b/addons/cyclops_level_builder/math/math_util.gd new file mode 100644 index 0000000..545df22 --- /dev/null +++ b/addons/cyclops_level_builder/math/math_util.gd @@ -0,0 +1,934 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name MathUtil + +enum Axis { X, Y, Z } + +static func square(value:float)->float: + return value * value + +static func snap_to_grid(pos:Vector3, cell_size:float)->Vector3: +# return floor(pos / cell_size) * cell_size + return floor((pos + Vector3(cell_size, cell_size, cell_size) / 2) / cell_size) * cell_size + + +#Returns intersection of line with point. +# plane_perp_dir points in direction of plane's normal and does not need to be normalized +static func intersect_plane(ray_origin:Vector3, ray_dir:Vector3, plane_origin:Vector3, plane_perp_dir:Vector3)->Vector3: + var s:float = (plane_origin - ray_origin).dot(plane_perp_dir) / ray_dir.dot(plane_perp_dir) + return ray_origin + ray_dir * s + +static func intersects_triangle(ray_origin:Vector3, ray_dir:Vector3, p0:Vector3, p1:Vector3, p2:Vector3)->bool: + #Godot uses clockwise winding + var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2) + + var p_hit:Vector3 = MathUtil.intersect_plane(ray_origin, ray_dir, p0, tri_area_x2) + if !p_hit.is_finite(): + return false + + if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0: + return false + if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0: + return false + if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0: + return false + + return true + +class IntersectTriangleResult: + var position:Vector3 + var normal:Vector3 + +static func intersect_triangle(ray_origin:Vector3, ray_dir:Vector3, p0:Vector3, p1:Vector3, p2:Vector3)->IntersectTriangleResult: + #Godot uses clockwise winding + var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2) + + var p_hit:Vector3 = MathUtil.intersect_plane(ray_origin, ray_dir, p0, tri_area_x2) + if !p_hit.is_finite(): + return null + + if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0: + return null + if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0: + return null + if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0: + return null + + var result:IntersectTriangleResult = IntersectTriangleResult.new() + result.position = p_hit + result.normal = tri_area_x2.normalized() + return result + +#Returns the closest point on the line to the ray +static func closest_point_on_line(ray_origin:Vector3, ray_dir:Vector3, line_origin:Vector3, line_dir:Vector3)->Vector3: + var a:Vector3 = ray_dir.cross(line_dir) + var w_perp:Vector3 = ray_dir.cross(a) + return intersect_plane(line_origin, line_dir, ray_origin, w_perp) + +static func closest_point_on_plane(point:Vector3, plane_origin:Vector3, plane_dir:Vector3)->Vector3: + return point - (point - plane_origin).project(plane_dir) + +static func closest_point_on_segment(ray_origin:Vector3, ray_dir:Vector3, seg_start:Vector3, seg_end:Vector3)->Vector3: + var seg_span:Vector3 = seg_end - seg_start + var p:Vector3 = closest_point_on_line(ray_origin, ray_dir, seg_start, seg_span) + var offset:Vector3 = p - seg_start + if offset.dot(seg_span) < 0: + return seg_start + if offset.length_squared() > seg_span.length_squared(): + return seg_end + return p + +#Shortest distance from point to given ray. Returns NAN if point is behind origin of ray. +static func distance_to_ray(ray_origin:Vector3, ray_dir:Vector3, point:Vector3): + var offset = point - ray_origin + var parallel:Vector3 = offset.project(ray_dir) + if parallel.dot(ray_dir) < 0: + return NAN + + var perp:Vector3 = offset - parallel + return perp.length() + + +static func trianglate_face(points:PackedVector3Array, normal:Vector3)->PackedVector3Array: + var result:PackedVector3Array + + while (points.size() >= 3): + var num_points:int = points.size() + for i in range(0, num_points): + var p0:Vector3 = points[i] + var p1:Vector3 = points[wrap(i + 1, 0, num_points)] + var p2:Vector3 = points[wrap(i + 2, 0, num_points)] + + #Godot uses clockwise winding + var tri_norm_dir:Vector3 = (p2 - p0).cross(p1 - p0) + if tri_norm_dir.dot(normal) > 0: + result.append(p0) + result.append(p1) + result.append(p2) + + points.remove_at(i + 1) + break + + return result + +static func trianglate_face_indices(points:PackedVector3Array, indices:Array[int], normal:Vector3)->Array[int]: + var result:Array[int] = [] + +# print("trianglate_face_indices %s" % points) + + while (points.size() >= 3): + var num_points:int = points.size() + var added_point:bool = false + + for i in range(0, num_points): + var idx0:int = i + var idx1:int = wrap(i + 1, 0, num_points) + var idx2:int = wrap(i + 2, 0, num_points) + var p0:Vector3 = points[idx0] + var p1:Vector3 = points[idx1] + var p2:Vector3 = points[idx2] + + #Godot uses clockwise winding + var tri_norm_dir:Vector3 = (p2 - p0).cross(p1 - p0) + if tri_norm_dir.dot(normal) > 0: + result.append(indices[idx0]) + result.append(indices[idx1]) + result.append(indices[idx2]) + +# print("adding indices %s %s %s" % [indices[idx0], indices[idx1], indices[idx2]]) + + points.remove_at(idx1) + indices.remove_at(idx1) + added_point = true + break + + assert(added_point, "failed to add point in triangulation") +# print("tri_done %s" % str(result)) + + return result + +static func trianglate_face_vertex_indices(points:PackedVector3Array, normal:Vector3)->Array[int]: + var result:Array[int] = [] + var fv_indices:Array = range(0, points.size()) +# print("trianglate_face_indices %s" % points) + + while (points.size() >= 3): + var num_points:int = points.size() + var added_point:bool = false + + for i in range(0, num_points): + var idx0:int = i + var idx1:int = wrap(i + 1, 0, num_points) + var idx2:int = wrap(i + 2, 0, num_points) + var p0:Vector3 = points[idx0] + var p1:Vector3 = points[idx1] + var p2:Vector3 = points[idx2] + + #Godot uses clockwise winding + var tri_norm_dir:Vector3 = (p2 - p0).cross(p1 - p0) + if tri_norm_dir.dot(normal) > 0: + result.append(fv_indices[idx0]) + result.append(fv_indices[idx1]) + result.append(fv_indices[idx2]) + +# print("adding indices %s %s %s" % [indices[idx0], indices[idx1], indices[idx2]]) + + points.remove_at(idx1) + fv_indices.remove_at(idx1) + added_point = true + break + + assert(added_point, "failed to add point in triangulation") +# print("tri_done %s" % str(result)) + + return result + +static func flip_plane(plane:Plane)->Plane: + return Plane(-plane.normal, plane.get_center()) + +#Returns a vector pointing along the normal in the clockwise winding direction with a length equal to twice the area of the triangle +static func triangle_area_x2(p0:Vector3, p1:Vector3, p2:Vector3)->Vector3: + return (p2 - p0).cross(p1 - p0) + +#Returns a vector pointing along the normal in the clockwise winding direction with a lengh equal to twice the area of the face +static func face_area_x2(points:PackedVector3Array)->Vector3: + if points.size() <= 1: + return Vector3.ZERO + + var result:Vector3 + var p0:Vector3 = points[0] + + for i in range(1, points.size() - 1): + var p1:Vector3 = points[i] + var p2:Vector3 = points[i + 1] + + result += (p2 - p0).cross(p1 - p0) + + return result + +static func face_area_x2_2d(points:PackedVector2Array)->float: + if points.size() <= 1: + return 0 + + var result:float + var p0:Vector2 = points[0] + + for i in range(1, points.size() - 1): + var p1:Vector2 = points[i] + var p2:Vector2 = points[i + 1] + + result += triange_area_2x_2d(p1 - p0, p2 - p0) + + return result + +static func fit_plane(points:PackedVector3Array)->Plane: + var normal:Vector3 = face_area_x2(points).normalized() + return Plane(normal, points[0]) + +static func snap_to_best_axis_normal(vector:Vector3)->Vector3: + if abs(vector.x) > abs(vector.y) and abs(vector.x) > abs(vector.z): + return Vector3(1, 0, 0) if vector.x > 0 else Vector3(-1, 0, 0) + elif abs(vector.y) > abs(vector.z): + return Vector3(0, 1, 0) if vector.y > 0 else Vector3(0, -1, 0) + else: + return Vector3(0, 0, 1) if vector.z > 0 else Vector3(0, 0, -1) + +static func get_longest_axis(vector:Vector3)->Axis: + if abs(vector.x) > abs(vector.y) and abs(vector.x) > abs(vector.z): + return Axis.X + elif abs(vector.y) > abs(vector.z): + return Axis.Y + else: + return Axis.Z + +static func calc_bounds(points:PackedVector3Array)->AABB: + if points.is_empty(): + return AABB(Vector3.ZERO, Vector3.ZERO) + + var result:AABB = AABB(points[0], Vector3.ZERO) + for i in range(1, points.size()): + result = result.expand(points[i]) + return result + +#Returns value equal to twise the area between the two vectors. Clockwise windings have negative area +static func triange_area_2x_2d(a:Vector2, b:Vector2)->float: + return a.x * b.y - a.y * b.x + +#Finds the bouding polygons of this set of points with a clockwise winding +static func bounding_polygon_2d(base_points:PackedVector2Array)->PackedVector2Array: + if base_points.size() <= 2: + return base_points + + + #Start with leftmost vertex, topmost if more than one + var p_init:Vector2 = base_points[0] + for p in base_points: + if p.x < p_init.x or (p.x == p_init.x and p.y > p_init.y): + p_init = p + + + var p_cur:Vector2 = p_init + var last_segment_dir = Vector2(0, 1) + + var polygon:PackedVector2Array + + while true: + var best_point:Vector2 + var best_dir:Vector2 + var best_angle:float = 0 + + for p in base_points: + if p.is_equal_approx(p_cur): + continue + + var point_dir:Vector2 = (p - p_cur).normalized() + var angle:float = acos(-last_segment_dir.dot(point_dir)) + + if angle > best_angle or (angle == best_angle and p_cur.distance_squared_to(p) > p_cur.distance_squared_to(best_point)): + best_point = p + best_dir = point_dir + best_angle = angle + + p_cur = best_point + last_segment_dir = best_dir + polygon.append(best_point) + + if best_point.is_equal_approx(p_init): + break + + return polygon + +#static func bounding_polygon(base_points:PackedVector3Array, plane:Plane)->PackedVector3Array: +static func bounding_polygon_3d(base_points:PackedVector3Array, normal:Vector3)->PackedVector3Array: + if base_points.size() <= 2: + return base_points + + var quat:Quaternion = Quaternion(normal, Vector3.FORWARD) + +# var xform:Transform3D = Transform3D(Basis(quat), -base_points[0]) + var xform:Transform3D = Transform3D(Basis(quat)) + xform = xform.translated_local(-base_points[0]) + var xform_inv = xform.inverse() + + #print("xform %s" % xform) + + var points_local:PackedVector2Array + + for p in base_points: + var p_local = xform * p + points_local.append(Vector2(p_local.x, p_local.y)) + + var points_bounds:PackedVector2Array = bounding_polygon_2d(points_local) + + var result:PackedVector3Array + for p in points_bounds: + var p_result = xform_inv * Vector3(p.x, p.y, 0) + result.append(p_result) + + return result + +static func points_are_colinear(points:PackedVector3Array)->bool: + if points.size() <= 2: + return true + + var p0:Vector3 = points[0] + var p1:Vector3 = p0 + var index:int = 0 + for i in range(1, points.size()): + if !points[i].is_equal_approx(p0): + p1 = points[i] + index = i + break + + if index == 0: + return true + + var v10:Vector3 = p1 - p0 + for i in range(index + 1, points.size()): + if !triangle_area_x2(p0, p1, points[i]).is_zero_approx(): + return false + + return true + + +static func furthest_point_from_line(line_origin:Vector3, line_dir:Vector3, points:PackedVector3Array)->Vector3: + var best_point:Vector3 + var best_dist:float = 0 + + for p in points: + var offset:Vector3 = p - line_origin + var along:Vector3 = offset.project(line_dir) + var perp:Vector3 = offset - along + var dist:float = perp.length_squared() + if dist > best_dist: + best_dist = dist + best_point = p + + return best_point + +static func furthest_point_from_plane(plane:Plane, points:PackedVector3Array)->Vector3: + var best_point:Vector3 + var best_distance:float = 0 + + for p in points: + var dist = abs(plane.distance_to(p)) + if dist > best_distance: + best_point = p + best_distance = dist + + return best_point + +static func planar_volume_contains_point(planes:Array[Plane], point:Vector3)->bool: +# print("candidate %s" % point) + + for p in planes: + var is_over:bool = p.is_point_over(point) + var is_on:bool = p.has_point(point) + if !is_over && !is_on: +# print("reject by %s" % p) + return false +# print("passed %s" % point) + return true + +static func get_convex_hull_points_from_planes(planes:Array[Plane])->Array[Vector3]: + #Check for overlapping planes + for i0 in range(0, planes.size()): + for i1 in range(i0 + 1, planes.size()): + var p0:Plane = planes[i0] + var p1:Plane = flip_plane(planes[i1]) + if p0.is_equal_approx(p1): + return [] + + var points:Array[Vector3] + + for i0 in range(0, planes.size()): + for i1 in range(i0 + 1, planes.size()): + for i2 in range(i1 + 1, planes.size()): + var result = planes[i0].intersect_3(planes[i1], planes[i2]) + + if result == null: + continue + #print("candidate %s" % result) + if !planar_volume_contains_point(planes, result): + continue + if points.any(func(p):return p.is_equal_approx(result)): + continue + #print("adding %s" % result) + points.append(result) + + return points + +static func dist_to_segment_squared_2d(point:Vector2, seg_start:Vector2, seg_end:Vector2)->float: + if seg_start.is_equal_approx(seg_end): + return point.distance_squared_to(seg_start) + + var dist_sq_p0:float = point.distance_squared_to(seg_start) + var dist_sq_p1:float = point.distance_squared_to(seg_end) + var seg_span:Vector2 = seg_end - seg_start + + var offset:Vector2 = point - seg_start + var offset_proj:Vector2 = offset.project(seg_span) + var perp_dist_sq:float = (offset - offset_proj).length_squared() + + if seg_span.dot(offset) < 0: + return dist_sq_p0 + elif offset_proj.length_squared() > seg_span.length_squared(): + return dist_sq_p1 + return perp_dist_sq + +class Segment2d extends RefCounted: + var p0:Vector2 + var p1:Vector2 + + func _init(p0:Vector2, p1:Vector2): + self.p0 = p0 + self.p1 = p1 + + func reverse()->Segment2d: + return Segment2d.new(p1, p0) + + func _to_string(): + return "[%s %s]" % [p0, p1] + +static func extract_loop_2d(seg_stack:Array[Segment2d])->Loop2D: + var segs_sorted:Array[Segment2d] = [] + var seg_tail = seg_stack.pop_back() + segs_sorted.append(seg_tail) + var seg_head = seg_tail + + while !seg_stack.is_empty(): + var found_seg:bool = false + for s_idx in seg_stack.size(): + var cur_seg:Segment2d = seg_stack[s_idx] + + if cur_seg.p0.is_equal_approx(seg_tail.p1): + #print("matching %s with %s" % [seg_tail, cur_seg]) + segs_sorted.append(cur_seg) + seg_stack.remove_at(s_idx) + seg_tail = cur_seg + found_seg = true + break + elif cur_seg.p1.is_equal_approx(seg_tail.p1): + #print("matching %s with %s" % [seg_tail, cur_seg]) + cur_seg = cur_seg.reverse() + segs_sorted.append(cur_seg) + seg_stack.remove_at(s_idx) + seg_tail = cur_seg + found_seg = true + break + elif cur_seg.p1.is_equal_approx(seg_head.p0): + #print("matching %s with %s" % [seg_head, cur_seg]) + segs_sorted.insert(0, cur_seg) + seg_stack.remove_at(s_idx) + seg_head = cur_seg + found_seg = true + break + elif cur_seg.p0.is_equal_approx(seg_head.p0): + #print("matching %s with %s" % [seg_head, cur_seg]) + cur_seg = cur_seg.reverse() + segs_sorted.insert(0, cur_seg) + seg_stack.remove_at(s_idx) + seg_head = cur_seg + found_seg = true + break + + if !found_seg: +# push_warning("loop not continuous") + break + + #print("segs_sorted %s" % str(segs_sorted)) + + var result:Loop2D = Loop2D.new() + result.closed = true + for s in segs_sorted: + result.points.append(s.p0) + + if seg_head.p0 != seg_tail.p1: + result.points.append(seg_tail.p1) + result.closed = false + + if face_area_x2_2d(result.points) < 0: + result.reverse() + + #print("loop %s" % str(result)) + + return result + +static func get_loops_from_segments_2d(segments:PackedVector2Array)->Array[Loop2D]: + #print("segments %s" % segments) + var loops:Array[Loop2D] = [] + + var seg_stack:Array[Segment2d] = [] + for i in range(0, segments.size(), 2): + seg_stack.append(Segment2d.new(segments[i], segments[i + 1])) + +# print("segs %s" % str(seg_stack)) + + while !seg_stack.is_empty(): + var loop:Loop2D = extract_loop_2d(seg_stack) + loops.append(loop) + + #print("result %s" % str(loops)) + return loops + +static func create_transform(translation:Vector3, rotation_axis:Vector3, rotation_angle:float, scale:Vector3, pivot:Vector3)->Transform3D: + var xform:Transform3D = Transform3D.IDENTITY + + xform = xform.translated_local(pivot + translation) + xform = xform.rotated_local(rotation_axis, rotation_angle) + xform = xform.scaled_local(scale) + xform = xform.translated_local(-pivot) + + return xform + +static func create_circle_points(center:Vector3, normal:Vector3, radius:float, num_segments:int)->PackedVector3Array: + var result:PackedVector3Array + + var axis:Axis = get_longest_axis(normal) + var perp_normal:Vector3 + match axis: + Axis.X: + perp_normal = normal.cross(Vector3.UP) + Axis.Y: + perp_normal = normal.cross(Vector3.FORWARD) + Axis.Z: + perp_normal = normal.cross(Vector3.UP) + + var angle_incrment = (PI * 2 / num_segments) + for i in num_segments: + var offset:Vector3 = perp_normal.rotated(normal, i * angle_incrment) + result.append(offset * radius + center) + + return result + +static func get_axis_aligned_tangent_and_binormal(normal:Vector3)->Array[Vector3]: + var axis:MathUtil.Axis = MathUtil.get_longest_axis(normal) + #calc tangent and binormal + var u_normal:Vector3 + var v_normal:Vector3 + match axis: + MathUtil.Axis.Y: + u_normal = normal.cross(Vector3.FORWARD) + v_normal = u_normal.cross(normal) + return [u_normal, v_normal] + MathUtil.Axis.X: + u_normal = normal.cross(Vector3.UP) + v_normal = u_normal.cross(normal) + return [u_normal, v_normal] + MathUtil.Axis.Z: + u_normal = normal.cross(Vector3.UP) + v_normal = u_normal.cross(normal) + return [u_normal, v_normal] + + return [] + +#Returns the planes of a frustum for the rectangular region on the camera's near +# plane with all planes pointing toward the interior of the frustum +static func calc_frustum_camera_rect(cam:Camera3D, p0:Vector2, p1:Vector2)->Array[Plane]: + + var x0 = min(p0.x, p1.x) + var x1 = max(p0.x, p1.x) + var y0 = min(p0.y, p1.y) + var y1 = max(p0.y, p1.y) + + var p00:Vector2 = Vector2(x0, y0) + var p01:Vector2 = Vector2(x0, y1) + var p10:Vector2 = Vector2(x1, y0) + var p11:Vector2 = Vector2(x1, y1) + +# print("cam rect %s" % str([p00, p11])) + + #Cam project_position does not work if we set distance to far plane, so back off a bit + var far_scalar:float = .95 + + var p000:Vector3 = cam.project_position(p00, cam.near) + var p100:Vector3 = cam.project_position(p10, cam.near) + var p010:Vector3 = cam.project_position(p01, cam.near) + var p110:Vector3 = cam.project_position(p11, cam.near) + var p001:Vector3 = cam.project_position(p00, cam.far * far_scalar) + var p101:Vector3 = cam.project_position(p10, cam.far * far_scalar) + var p011:Vector3 = cam.project_position(p01, cam.far * far_scalar) + var p111:Vector3 = cam.project_position(p11, cam.far * far_scalar) + +# print("points %s" % str([p000, p100, p010, p110, p001, p101, p011, p111, ])) + + var plane_left:Plane = Plane(p001, p011, p010) + var plane_right:Plane = Plane(p101, p110, p111) + var plane_top:Plane = Plane(p011, p111, p110) + var plane_bottom:Plane = Plane(p001, p100, p101) + var plane_near:Plane = Plane(p000, p110, p100) + var plane_far:Plane = Plane(p001, p111, p011) + + return [plane_left, plane_right, plane_top, plane_bottom, plane_near, plane_far] + +static func clip_polygon(points:PackedVector3Array, plane:Plane)->PackedVector3Array: + var result:PackedVector3Array + + #Cut at planr intersection + var points_on_or_over:PackedVector3Array + + for p_idx0 in points.size(): + var p_idx1:int = wrap(p_idx0 + 1, 0, points.size()) + + var p0:Vector3 = points[p_idx0] + var p1:Vector3 = points[p_idx1] + + var on0:bool = plane.has_point(p0) + var over0:bool = plane.is_point_over(p0) + var under0:bool = !on0 && !over0 + var on1:bool = plane.has_point(p1) + var over1:bool = plane.is_point_over(p1) + var under1:bool = !on1 && !over1 + + if on0 || over0: + points_on_or_over.append(p0) + + if (under0 && over1) || (over0 && under1): + points_on_or_over.append(plane.intersects_segment(p0, p1)) + + return points_on_or_over + + +#Snaps point to a point appearing in the list if distance to it is <= radius. Otherwise appends +# point to point list +static func snap_point_to_point_list_or_append(point:Vector3, list:PackedVector3Array, radius:float = .005): + for p in list: + if p.distance_squared_to(point) < radius * radius: + return p + list.append(point) + return point + + +static func create_loop_from_directed_segments(segs:Array[Segment3], snap_radius:float = .005)->PackedVector3Array: + var snap_list:PackedVector3Array + for seg in segs: + seg.p0 = snap_point_to_point_list_or_append(seg.p0, snap_list, snap_radius) + seg.p1 = snap_point_to_point_list_or_append(seg.p1, snap_list, snap_radius) + + + var seg_stack:Array[Segment3] + var sorted_segs:Array[Segment3] + + for s in segs: + if !is_zero_approx(s.length_squared()): + seg_stack.append(s) + + + sorted_segs.append(seg_stack.pop_back()) + while !seg_stack.is_empty(): + var found_seg:bool = false + var min_dist:float = 10000 + for i in seg_stack.size(): + var s:Segment3 = seg_stack[i] + +# if s.p0.is_equal_approx(sorted_segs.back().p1): + var dist:float = s.p0.distance_to(sorted_segs.back().p1) + min_dist = min(min_dist, dist) + + if dist < .005: +# if s.p0.is_equal_approx(sorted_segs.back().p1): + sorted_segs.append(s) + seg_stack.remove_at(i) + found_seg = true + break +# if s.p1.is_equal_approx(sorted_segs.back().p1): +# sorted_segs.append(s.reversed()) +# seg_stack.remove_at(i) +# found_seg = true +# break + + if !found_seg: + print("Error: could not form loop") + return [] + + var result:PackedVector3Array + for s in sorted_segs: + result.append(s.p0) + + return result + +static func clip_polygon_separate(points:PackedVector3Array, plane:Plane)->ClipPolyResult: + + #Clip points to plane. + var clipped_points:PackedVector3Array = clip_polygon(points, plane) + + #Every point should now be on or above the plane + var is_over:Array[bool] + var all_over:bool = true + var none_over:bool = true + for p in clipped_points: + var is_on:bool = plane.has_point(p) + if is_on: + all_over = false + else: + none_over = false + + is_over.append(!is_on) + + if all_over: + return ClipPolyResult.new([clipped_points]) + + if none_over: + return ClipPolyResult.new() + + var start_idx:int = -1 + for p_idx0 in clipped_points.size(): + var p_idx1:int = wrap(p_idx0 + 1, 0, clipped_points.size()) + + var over0:bool = is_over[p_idx0] + var over1:bool = is_over[p_idx1] + + if !over0 && over1: + start_idx = p_idx0 + break + + #If you think of the clipped_points as a string where every point on the plane is + # represented by the character 'n' and every point over the plane is the character + # 'v', then every sub polygon will be a string that can be represented by the + # regular expression "(nv+n)" + var results:Array[PackedVector3Array]= [] + var cut_segments:Array[Segment3] + + var writing_shape:bool = true + var sub_poly:PackedVector3Array + for i in clipped_points.size(): + var p_idx0:int = wrap(i + start_idx, 0, clipped_points.size()) + var p_idx1:int = wrap(i + start_idx + 1, 0, clipped_points.size()) + + if is_over[p_idx1]: + sub_poly.append(clipped_points[p_idx0]) + + elif is_over[p_idx0]: + sub_poly.append(clipped_points[p_idx0]) + sub_poly.append(clipped_points[p_idx1]) + + cut_segments.append(Segment3.new(sub_poly[sub_poly.size() - 1], sub_poly[0])) + results.append(sub_poly.duplicate()) + sub_poly.clear() + + return ClipPolyResult.new(results, cut_segments) + + + +static func polygon_intersects_frustum(points:PackedVector3Array, frustum:Array[Plane])->bool: + var points_i:PackedVector3Array = points + + for plane in frustum: + points_i = clip_polygon(points_i, plane) + if points_i.is_empty(): + return false + + return true + + +static func frustum_contians_point(planes:Array[Plane], point:Vector3)->bool: + for plane in planes: + if !plane.is_point_over(point) && !plane.has_point(point): + return false + return true + +static func frustum_intersects_sphere(planes:Array[Plane], center:Vector3, radius:float)->bool: + for plane in planes: + var dist:float = plane.distance_to(center) + + if dist < -radius: + return false + + return true + + +func plane_intesects_point_cloud(points:PackedVector3Array, plane:Plane)->bool: + + var is_over:bool = false + var is_under:bool = false + for p in points: + if plane.has_point(p): + continue + + if plane.is_point_over(p): + is_over = true + else: + is_under = true + if is_over && is_under: + return true + + return false + +#Returns vector with [R, Q] where R is the orthogonal basis +# and Q is a triangular matrix such that basis = R * Q +static func gram_schmidt_decomposition(basis:Basis)->Array[Basis]: + #https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process + var v0:Vector3 = basis.x + var v1:Vector3 = basis.y + var v2:Vector3 = basis.z + + var u0:Vector3 = v0 + var u1:Vector3 = v1 - v1.project(u0) + var u2:Vector3 = v2 - v2.project(u0) - v2.project(u1) + + var R:Basis = Basis(u0.normalized(), u1.normalized(), u2.normalized()) + var R_inv:Basis = R.inverse() + var Q:Basis = R_inv * basis + + return [R, Q] + +#Decomposes matrix into translate, rotate, scale and shear vectors where +# M = T * R * Sh * S +# where: +# T - translate matrix +# R - rotate matrix +# Sh - shear matrix +# S - scale matrix +# +# Shear matrix for vector (x, y, z) is +# [1 x y] +# [0 1 z] +# [0 0 1] +static func decompose_matrix_3d(m:Transform3D, order:EulerOrder = EULER_ORDER_YXZ)->Dictionary: + + if is_zero_approx(m.basis.determinant()): + return {"valid": false} + + var basis:Basis = m.basis + var gram_schmidt = gram_schmidt_decomposition(basis) + var rot_mtx = gram_schmidt[0] + var euler:Vector3 = rot_mtx.get_euler(order) + + var scale_shear = gram_schmidt[1] + var scale:Vector3 = Vector3(scale_shear.x.x, scale_shear.y.y, scale_shear.z.z) + var scale_mat:Basis = Basis.from_scale(scale) + var shear:Basis = scale_shear * scale_mat.inverse() + + #print(shear) + + return { + "valid": true, + "translate": m.origin, + "rotate": euler, + "scale": scale, + "shear": Vector3(shear.y.x, shear.z.x, shear.z.y) + } + +static func compose_matrix_3d(translate:Vector3, rotate:Vector3 = Vector3.ZERO, order:EulerOrder = EULER_ORDER_YXZ, shear:Vector3 = Vector3.ZERO, scale:Vector3 = Vector3.ONE)->Transform3D: + var scale_mat:Basis = Basis.from_scale(scale) + var shear_mat:Basis = Basis( + Vector3(1, 0, 0), + Vector3(shear.x, 1, 0), + Vector3(shear.y, shear.z, 1)) + var rot_mat:Basis = Basis.from_euler(rotate, order) + var basis:Basis = rot_mat * shear_mat * scale_mat + + return Transform3D(basis, translate) + +static func clip_segment_to_plane_3d(p:Plane, v0:Vector3, v1:Vector3)->PackedVector3Array: + var clip_v0:bool = !p.is_point_over(v0) + var clip_v1:bool = !p.is_point_over(v1) + if clip_v0 && clip_v1: + return [] + + if clip_v0: + v0 = p.intersects_segment(v0, v1) + elif clip_v1: + v1 = p.intersects_segment(v0, v1) + + return [v0, v1] + + +static func blend_over_with_alpha(src:Color, dest:Color): + #https://en.wikipedia.org/wiki/Alpha_compositing + var a0:float = src.a + dest.a * (1 - src.a) + var r0:float = (src.r * src.a + dest.r * dest.a * (1 - src.a)) / a0 + var g0:float = (src.g * src.a + dest.g * dest.a * (1 - src.a)) / a0 + var b0:float = (src.b * src.a + dest.b * dest.a * (1 - src.a)) / a0 + return Color(r0, g0, b0, a0) + +static func blend_colors_with_alpha(src:Color, dest:Color, weight:float)->Color: + var col:Color = blend_over_with_alpha(src, dest) + col.a *= weight + return blend_over_with_alpha(col, dest) + +static func blend_colors_ignore_alpha(src:Color, dest:Color, weight:float)->Color: + return weight * src + (1 - weight) * dest + diff --git a/addons/cyclops_level_builder/math/pen_stroke.gd b/addons/cyclops_level_builder/math/pen_stroke.gd new file mode 100644 index 0000000..1bdf658 --- /dev/null +++ b/addons/cyclops_level_builder/math/pen_stroke.gd @@ -0,0 +1,94 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name PenStroke +extends Resource + +class StrokePoint extends Resource: + @export var position:Vector3 + @export var pressure:float + + #func _init(position:Vector3 = Vector3.ZERO, pressure:float = 1): + #print("ppp ", position) + #self.position = position + #self.pressure = pressure + + func _to_string()->String: + return "%s %f" % [str(position), pressure] + + func lerp(p:StrokePoint, weight:float): + var r:StrokePoint = StrokePoint.new() + r.position = lerp(position, p.position, weight) + r.pressure = lerp(pressure, p.pressure, weight) + return r + +var stroke_points:Array[StrokePoint] + +func clear(): + stroke_points.clear() + +func is_empty()->bool: + return stroke_points.is_empty() + +func append_stroke_point(position:Vector3, pressure:float = 1): + var p:StrokePoint = StrokePoint.new() + p.position = position + p.pressure = pressure + stroke_points.append(p) + +func resample_points(resample_dist:float)->PenStroke: + if stroke_points.is_empty(): + return null + + var result:PenStroke = PenStroke.new() + + #var p_start:StrokePoint = stroke_points[0] + #var p_start1:StrokePoint = p_start.duplicate(true) + #print("p_start ", p_start) + #print("p_start1 ", p_start1) + #print("stroke_points[0] ", stroke_points[0].position) + result.stroke_points.append(stroke_points[0].duplicate()) + #print("--stroke_points[0] ", stroke_points[0].position) + + var seg_dist_covered:float = 0 + var last_pos_plotted:float = 0 + + for src_p_idx in stroke_points.size() - 1: + var p0:StrokePoint = stroke_points[src_p_idx] + var p1:StrokePoint = stroke_points[src_p_idx + 1] + var seg_len:float = p0.position.distance_to(p1.position) + + while last_pos_plotted + resample_dist <= seg_dist_covered + seg_len: + var pn:StrokePoint = p0.lerp(p1, \ + (last_pos_plotted + resample_dist - seg_dist_covered) / seg_len) + result.stroke_points.append(pn) + last_pos_plotted += resample_dist + + seg_dist_covered += seg_len + + #print("stroke points res ", str(result.stroke_points)) + + return result + + diff --git a/addons/cyclops_level_builder/math/quick_hull.gd b/addons/cyclops_level_builder/math/quick_hull.gd new file mode 100644 index 0000000..fc08316 --- /dev/null +++ b/addons/cyclops_level_builder/math/quick_hull.gd @@ -0,0 +1,360 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name QuickHull + +class DirectedEdge extends RefCounted: + var p0:Vector3 + var p1:Vector3 + + func _init(p0:Vector3, p1:Vector3): + self.p0 = p0 + self.p1 = p1 + + func _to_string()->String: + return "%s %s" % [p0, p1] + + func reverse()->DirectedEdge: + return DirectedEdge.new(p1, p0) + + func equals(e:DirectedEdge)->bool: + return p0 == e.p0 && p1 == e.p1 + +class Facet extends RefCounted: + var plane:Plane + var points:PackedVector3Array #Clockwise winding faces out + var over_points:PackedVector3Array + + func _to_string(): + var result:String = "plane %s\ncentroid %s\npoints %s\nover %s" % [plane, (points[0] + points[1] + points[2])/3, points, over_points] + + return result + + func has_edge(e:DirectedEdge)->bool: + return (points[0] == e.p0 && points[1] == e.p1) || \ + (points[1] == e.p0 && points[2] == e.p1) || \ + (points[2] == e.p0 && points[0] == e.p1) + + func get_edges()->Array[DirectedEdge]: + var result:Array[DirectedEdge] = [] + + result.append(DirectedEdge.new(points[0], points[1])) + result.append(DirectedEdge.new(points[1], points[2])) + result.append(DirectedEdge.new(points[2], points[0])) + return result + + func init_from_points(p0:Vector3, p1:Vector3, p2:Vector3): + #Facet normal points to outside + plane = Plane(p0, p1, p2) + points = [p0, p1, p2] + + #Create a facet with vertices at p0, p1, p2 and winding such that under_ref + # is on the under side of the plane + func init_from_points_under(p0:Vector3, p1:Vector3, p2:Vector3, under_ref:Vector3): + #Facet normal points to outside + plane = Plane(p0, p1, p2) + if plane.is_point_over(under_ref): + plane = Plane(p0, p2, p1) + points = [p0, p2, p1] + else: + points = [p0, p1, p2] + + func get_furthest_point()->Vector3: + var best_point:Vector3 + var best_distance:float = 0 + + for p in over_points: + var dist = abs(plane.distance_to(p)) + if dist > best_distance: + best_point = p + best_distance = dist + + return best_point + +class Hull extends RefCounted: + var facets:Array[Facet] = [] + + func get_non_empty_facet()->Facet: + for f in facets: + if !f.over_points.is_empty(): + return f + return null + + func get_facet_with_edge(e:DirectedEdge)->Facet: + for f in facets: + if f.has_edge(e): + return f + return null + + func _to_string(): + var result:String = "" + for f in facets: + result += "%s\n" % f + return result + + + func get_points()->Array[Vector3]: + var result:Array[Vector3] + + for f in facets: + for p in f.points: + if !result.any(func(pl):return pl.is_equal_approx(p)): + result.append(p) + + return result + + func format_points()->String: + var result:String = "" + for f in facets: + result += "%s,\n" % f.points + return result + + +static func form_loop(edges:Array[DirectedEdge])->PackedVector3Array: + var sorted:Array[DirectedEdge] = [] + + var cur_edge:DirectedEdge = edges.pop_back() + sorted.append(cur_edge) + + while !edges.is_empty(): + var found_edge:bool = false + for i in edges.size(): + var e:DirectedEdge = edges[i] + if e.p0.is_equal_approx(cur_edge.p1): + edges.remove_at(i) + cur_edge = e + sorted.append(e) + found_edge = true + break + + if !found_edge: + assert(found_edge, "Unable to complete loop") + pass +# if !found_edge: +# assert(false, "Unable to complete loop") +# return PackedVector3Array() + + var result:PackedVector3Array + for e in sorted: + result.append(e.p0) + return result + +static func merge_coplanar_facets(hull:Hull)->Hull: +# print("hull %s " % hull) + #print("hull %s " % hull.format_points()) + + var new_hull:Hull = Hull.new() + var already_seen:Array[Facet] = [] + + for facet_idx in hull.facets.size(): + var facet:Facet = hull.facets[facet_idx] + if already_seen.has(facet): + continue + already_seen.append(facet) + + #print("merging facet %s" % facet) + + var neighbor_set:Array[Facet] = [] + neighbor_set.append(facet) + var boundary:Array[DirectedEdge] = [] + + while !neighbor_set.is_empty(): + var cur_facet:Facet = neighbor_set.pop_back() + var edges:Array[DirectedEdge] = cur_facet.get_edges() + + for e in edges: + var neighbor:Facet = hull.get_facet_with_edge(e.reverse()) + if neighbor.plane.is_equal_approx(facet.plane): + if !already_seen.has(neighbor): + already_seen.append(neighbor) + neighbor_set.append(neighbor) + else: + boundary.append(e) + + + var points:PackedVector3Array = form_loop(boundary) + + var nf:Facet = Facet.new() + nf.plane = facet.plane + nf.points = points + new_hull.facets.append(nf) + + return new_hull + + +static func create_initial_simplex(points:PackedVector3Array)->Hull: + if points.size() < 4: + return null + + #For first two points, pick furthest apart along one of the axes + var max_x:Vector3 = points[0] + var min_x:Vector3 = points[0] + var max_y:Vector3 = points[0] + var min_y:Vector3 = points[0] + var max_z:Vector3 = points[0] + var min_z:Vector3 = points[0] + + for idx in range(1, points.size()): + var p:Vector3 = points[idx] + if p.x > max_x.x: + max_x = p + if p.x < min_x.x: + min_x = p + if p.y > max_y.y: + max_y = p + if p.y < min_y.y: + min_y = p + if p.z > max_z.z: + max_z = p + if p.z < min_z.z: + min_z = p + + var p0:Vector3 + var p1:Vector3 + var dx:float = max_x.distance_squared_to(min_x) + var dy:float = max_y.distance_squared_to(min_y) + var dz:float = max_z.distance_squared_to(min_z) + + if dx > dy and dx > dz: + p0 = max_x + p1 = min_x + elif dy > dz: + p0 = max_y + p1 = min_y + else: + p0 = max_z + p1 = min_z + + #Find furthest point from line for second point + var p2:Vector3 = MathUtil.furthest_point_from_line(p0, p1 - p0, points) + var p3:Vector3 = MathUtil.furthest_point_from_plane(Plane(p0, p1, p2), points) + + #Make simplex + var hull:Hull = Hull.new() + + var f0:Facet = Facet.new() + f0.init_from_points_under(p1, p2, p3, p0) + var f1:Facet = Facet.new() + f1.init_from_points_under(p2, p3, p0, p1) + var f2:Facet = Facet.new() + f2.init_from_points_under(p3, p0, p1, p2) + var f3:Facet = Facet.new() + f3.init_from_points_under(p0, p1, p2, p3) + + hull.facets.append(f0) + hull.facets.append(f1) + hull.facets.append(f2) + hull.facets.append(f3) + + for p in points: + for f in hull.facets: + if f.plane.is_point_over(p) && !f.plane.has_point(p): + f.over_points.append(p) + + return hull + + +static func quickhull(points:PackedVector3Array)->Hull: + if points.size() < 4: + return null + + var hull:Hull = create_initial_simplex(points) + if !hull: + return null + + #print("initial points %s" % points) + #print("initial simplex %s" % hull.format_points()) + + while true: + var facet:Facet = hull.get_non_empty_facet() + if facet == null: + break + + #print("-facet %s" % facet) + + var p_over:Vector3 = facet.get_furthest_point() + #print("over point %s" % p_over) + + #print("hull %s" % hull.format_points()) + + var visibile_faces:Array[Facet] = [facet] + var edges:Array[DirectedEdge] = facet.get_edges() + var visited_edges:Array[DirectedEdge] = [] + var boundary_edges:Array[DirectedEdge] = [] + +# for e in edges: +# print("init edge search set %s" % e) + + + #Find set of edges that form the boundary of faces visible to point + # being added. We're basically flood filling from central facet until + # we hit faces pointing away from reference point. + while !edges.is_empty(): + var edge:DirectedEdge = edges.pop_back() + visited_edges.append(edge) + var edge_inv:DirectedEdge = edge.reverse() + + var neighbor_facet:Facet = hull.get_facet_with_edge(edge_inv) + if neighbor_facet.plane.is_point_over(p_over): + visibile_faces.append(neighbor_facet) + visited_edges.append(edge_inv) + var neighbor_edges:Array[DirectedEdge] = neighbor_facet.get_edges() + for e in neighbor_edges: + if !visited_edges.any(func(edge): return edge.equals(e)): + #print("adding edge to search set %s" % e) + edges.append(e) + else: + boundary_edges.append(edge) + #print("adding edge to boundary set %s" % edge) + + var remaining_over_points:PackedVector3Array + for f in visibile_faces: + for pf in f.over_points: + if pf == p_over: + continue + if !remaining_over_points.has(pf): + remaining_over_points.append(pf) + #print("over point for test %s" % pf) + + hull.facets.remove_at(hull.facets.find(f)) + + for e in boundary_edges: + var f:Facet = Facet.new() + f.init_from_points(e.p0, e.p1, p_over) + hull.facets.append(f) + + #print("adding facet %s" % f) + + for p in remaining_over_points: + if f.plane.is_point_over(p) && !f.plane.has_point(p): + f.over_points.append(p) + + #print("hull %s" % hull.format_points()) + + hull = merge_coplanar_facets(hull) + return hull + + + diff --git a/addons/cyclops_level_builder/math/segent_3.gd b/addons/cyclops_level_builder/math/segent_3.gd new file mode 100644 index 0000000..1b217a8 --- /dev/null +++ b/addons/cyclops_level_builder/math/segent_3.gd @@ -0,0 +1,43 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name Segment3 + +var p0:Vector3 +var p1:Vector3 + +func _init(p0:Vector3 = Vector3.ZERO, p1:Vector3 = Vector3.ZERO): + self.p0 = p0 + self.p1 = p1 + +func reversed()->Segment3: + return Segment3.new(p1, p0) + +func length_squared()->float: + return p0.distance_squared_to(p1) + + +func _to_string(): + return "[%s, %s]" % [str(p0), str(p1)] diff --git a/addons/cyclops_level_builder/menu/action_popup_menu.gd b/addons/cyclops_level_builder/menu/action_popup_menu.gd new file mode 100644 index 0000000..4ac8a73 --- /dev/null +++ b/addons/cyclops_level_builder/menu/action_popup_menu.gd @@ -0,0 +1,46 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PopupMenu +class_name ActionPopupMenu + +var action_map:Dictionary = {} + +func _ready(): + id_pressed.connect(on_id_pressed) + +func add_action_item(action:CyclopsAction): +# var id:int = action_map.size() + var id:int = action_map.size() + 1000 + add_item(action.name, id, action.accellerator) + action_map[id] = action + +#func add_separator(label:String, id:int = -1): +# pass + +func on_id_pressed(id:int): + var action:CyclopsAction = action_map[id] + action._execute() + + diff --git a/addons/cyclops_level_builder/menu/editor_toolbar.gd b/addons/cyclops_level_builder/menu/editor_toolbar.gd new file mode 100644 index 0000000..c9b6574 --- /dev/null +++ b/addons/cyclops_level_builder/menu/editor_toolbar.gd @@ -0,0 +1,231 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name EditorToolbar + +var editor_plugin:CyclopsLevelBuilder: + get: + return editor_plugin + set(value): + editor_plugin = value + editor_plugin.active_node_changed.connect(on_active_node_changed) + + build_ui() + + +var tool_button_group = ButtonGroup.new() +var override_shortcuts: Dictionary = {} #Dictionary[InputEvent, String] +var currently_in_3d := false + +func on_active_node_changed(): + update_grid() + + +# Called when the node enters the scene tree for the first time. +func _ready(): + + %Menu.clear() + %Menu.add_action_item(ActionToolDuplicate.new(editor_plugin)) + %Menu.add_action_item(ActionMergeSelectedBlocks.new(editor_plugin)) + %Menu.add_action_item(ActionSubtractBlock.new(editor_plugin)) + %Menu.add_action_item(ActionIntersectBlock.new(editor_plugin)) + %Menu.add_action_item(ActionDeleteSelectedBlocks.new(editor_plugin)) + %Menu.add_action_item(ActionSnapToGrid.new(editor_plugin)) + %Menu.add_action_item(ActionMergeVerticesCenter.new(editor_plugin)) + %Menu.add_separator() + %Menu.add_action_item(ActionConvertToMesh.new(editor_plugin)) + %Menu.add_action_item(ActionExportAsGltf.new(editor_plugin)) + %Menu.add_action_item(ActionExportAsGodotScene.new(editor_plugin)) + %Menu.add_action_item(ActionExportAsCyclops.new(editor_plugin)) + %Menu.add_separator() + %Menu.add_action_item(ActionRotateX90Ccw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateX90Cw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateX180.new(editor_plugin)) + %Menu.add_action_item(ActionMirrorSelectionX2.new(editor_plugin)) + %Menu.add_separator() + %Menu.add_action_item(ActionRotateY90Ccw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateY90Cw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateY180.new(editor_plugin)) + %Menu.add_action_item(ActionMirrorSelectionY2.new(editor_plugin)) + %Menu.add_separator() + %Menu.add_action_item(ActionRotateZ90Ccw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateZ90Cw.new(editor_plugin)) + %Menu.add_action_item(ActionRotateZ180.new(editor_plugin)) + %Menu.add_action_item(ActionMirrorSelectionZ.new(editor_plugin)) + + var global_scene = get_node("/root/CyclopsAutoload") + + global_scene.xray_mode_changed.connect(on_xray_mode_changed) + %bn_xray.button_pressed = global_scene.xray_mode + + update_grid() + + + + +var prev_button_pressed: Button = null +func _press_button_line(button: Button) -> void: + if prev_button_pressed != null: + var line := prev_button_pressed.get_node_or_null('line') + if line != null: + prev_button_pressed.remove_child(line) + line.queue_free() + prev_button_pressed = null + + var new_line := ColorRect.new() + new_line.anchor_left = 0.05 + new_line.anchor_top = 0.9 + new_line.anchor_right = 0.95 + new_line.anchor_bottom = 0.94 + button.add_child(new_line) + new_line.name = 'line' + prev_button_pressed = button + + +func build_ui(): + #Tools + for child in %ToolButtonContainer.get_children(): + %ToolButtonContainer.remove_child(child) + + %snap_options.clear() + + if !editor_plugin: + return + + editor_plugin.main_screen_changed.connect(_on_main_screen_changed) + set_process_input(true) + + + var config:CyclopsConfig = editor_plugin.config + for tag: ToolTag in config.tool_tags: +# print("adding tag %s" % tag.name) + var bn:Button = Button.new() + if tag.icon: + bn.icon = tag.icon + else: + bn.text = tag.name + + bn.name = tag.name + + if !tag.input_events.is_empty(): #InputEvent + if tag.input_events_override: #bool + for v: InputEvent in tag.input_events: + override_shortcuts[v] = tag.name #for _input function + else: + bn.shortcut = Shortcut.new() + for v: InputEvent in tag.input_events: + bn.shortcut.events.append(v) + + bn.tooltip_text = tag.tooltip + bn.pressed.connect(func(): + _press_button_line(bn) + tag._activate(editor_plugin) + ) +# print("adding bn %s" % tag.name) + + %ToolButtonContainer.add_child(bn) + + %display_mode.select(editor_plugin.display_mode) + + #Snapping + for tag in config.snapping_tags: + if tag.icon: + %snap_options.add_icon_item(tag.icon, tag.name) + else: + %snap_options.add_item(tag.name) + +func update_grid(): + if !editor_plugin: + return + + #var size:int = editor_plugin.get_global_scene().grid_size + #$HBoxContainer/grid_size.select(size + 4) + + $HBoxContainer/display_mode.select(editor_plugin.display_mode) + + + +func _on_main_screen_changed(screen_name: String): + currently_in_3d = (screen_name == '3D') + +func _input(event: InputEvent) -> void: + if !currently_in_3d: return + + for v: InputEvent in override_shortcuts: + if event.is_match(v, true) and event.is_pressed() and not event.is_echo(): + var button := %ToolButtonContainer.get_node_or_null(override_shortcuts[v] as String) as Button + if button: + button.pressed.emit() #simulate press + break + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +#func _on_grid_size_item_selected(index): + #editor_plugin.get_global_scene().grid_size = index - 4 + + + +func _on_check_lock_uvs_toggled(button_pressed): + editor_plugin.lock_uvs = button_pressed + + + +func _on_display_mode_item_selected(index:int): + editor_plugin.display_mode = index + + +func on_xray_mode_changed(value:bool): + %bn_xray.button_pressed = value + +func _on_bn_xray_toggled(button_pressed:bool): + if !editor_plugin: + return + + var global_scene:CyclopsGlobalScene = editor_plugin.get_global_scene() + global_scene.xray_mode = button_pressed + +# +#func _on_bn_snap_settings_pressed(): +## var rect:Rect2 = %bn_snap_settings.get_rect() + # + #var rect:Rect2 = %bn_snap_settings.get_global_rect() + #var new_rect:Rect2 = Rect2(rect.position.x, rect.position.y + rect.size.y, 200, 100) + #%snap_settings_popup.popup_on_parent(new_rect) + ##print("snap popup2 ", rect) + + +func _on_snap_options_item_selected(index:int): + var tag:SnappingTag = editor_plugin.config.snapping_tags[index] + tag._activate(editor_plugin) + + + +func _on_bn_snap_toggled(toggled_on): + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_ENABLED, toggled_on) + pass # Replace with function body. diff --git a/addons/cyclops_level_builder/menu/editor_toolbar.tscn b/addons/cyclops_level_builder/menu/editor_toolbar.tscn new file mode 100644 index 0000000..c680725 --- /dev/null +++ b/addons/cyclops_level_builder/menu/editor_toolbar.tscn @@ -0,0 +1,129 @@ +[gd_scene load_steps=7 format=3 uid="uid://c3cl77r65dexu"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/menu/editor_toolbar.gd" id="1_o71fd"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/menu/action_popup_menu.gd" id="2_ni0c8"] +[ext_resource type="Texture2D" uid="uid://bs54uhn80ykrr" path="res://addons/cyclops_level_builder/art/icons/xray_normal.svg" id="3_ldp0l"] +[ext_resource type="Texture2D" uid="uid://dloyvoq8piwx0" path="res://addons/cyclops_level_builder/art/icons/snap.svg" id="4_begwr"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_o7kxq"] + +[sub_resource type="Theme" id="Theme_0hxey"] +PanelContainer/styles/panel = SubResource("StyleBoxEmpty_o7kxq") + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 739.0 +offset_bottom = 31.0 +size_flags_horizontal = 3 +theme = SubResource("Theme_0hxey") +script = ExtResource("1_o71fd") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="ToolButtonContainer" type="HBoxContainer" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="MenuBar" type="MenuBar" parent="HBoxContainer"] +layout_mode = 2 +prefer_global_menu = false + +[node name="Menu" type="PopupMenu" parent="HBoxContainer/MenuBar"] +unique_name_in_owner = true +item_count = 27 +item_0/text = "Duplicate Selected Blocks" +item_0/id = 1000 +item_1/text = "Merge Selected Blocks" +item_1/id = 1001 +item_2/text = "Subtract Block" +item_2/id = 1002 +item_3/text = "Intersect Blocks" +item_3/id = 1003 +item_4/text = "Delete Selected Blocks" +item_4/id = 1004 +item_5/text = "Snap to grid" +item_5/id = 1005 +item_6/text = "Merge Vertices Center" +item_6/id = 1006 +item_7/text = "" +item_7/id = -1 +item_7/separator = true +item_8/text = "Convert To Godot Mesh" +item_8/id = 1007 +item_9/text = "Export As Gltf..." +item_9/id = 1008 +item_10/text = "Export As Godot Scene..." +item_10/id = 1009 +item_11/text = "Export As Cyclops File..." +item_11/id = 1010 +item_12/text = "" +item_12/id = -1 +item_12/separator = true +item_13/text = "Rotate 90 Ccw X" +item_13/id = 1011 +item_14/text = "Rotate 90 Cw X" +item_14/id = 1012 +item_15/text = "Rotate 180 X" +item_15/id = 1013 +item_16/text = "Mirror Selection X" +item_16/id = 1014 +item_17/text = "" +item_17/id = -1 +item_17/separator = true +item_18/text = "Rotate 90 Ccw Y" +item_18/id = 1015 +item_19/text = "Rotate 90 Cw Y" +item_19/id = 1016 +item_20/text = "Rotate 180 Y" +item_20/id = 1017 +item_21/text = "Mirror Selection Y" +item_21/id = 1018 +item_22/text = "" +item_22/id = -1 +item_22/separator = true +item_23/text = "Rotate 90 Ccw Z" +item_23/id = 1019 +item_24/text = "Rotate 90 Cw Z" +item_24/id = 1020 +item_25/text = "Rotate 180 Z" +item_25/id = 1021 +item_26/text = "Mirror Selection Z" +item_26/id = 1022 +script = ExtResource("2_ni0c8") + +[node name="bn_snap" type="Button" parent="HBoxContainer"] +layout_mode = 2 +tooltip_text = "Snapping on/off" +toggle_mode = true +button_pressed = true +icon = ExtResource("4_begwr") + +[node name="snap_options" type="OptionButton" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Snapping system" + +[node name="display_mode" type="OptionButton" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "How the mesh is shown in the viewport." +item_count = 3 +selected = 1 +popup/item_0/text = "Wireframe" +popup/item_0/id = 0 +popup/item_1/text = "Mesh" +popup/item_1/id = 1 +popup/item_2/text = "Materials" +popup/item_2/id = 2 + +[node name="bn_xray" type="Button" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Xray" +toggle_mode = true +icon = ExtResource("3_ldp0l") + +[connection signal="toggled" from="HBoxContainer/bn_snap" to="." method="_on_bn_snap_toggled"] +[connection signal="item_selected" from="HBoxContainer/snap_options" to="." method="_on_snap_options_item_selected"] +[connection signal="item_selected" from="HBoxContainer/display_mode" to="." method="_on_display_mode_item_selected"] +[connection signal="toggled" from="HBoxContainer/bn_xray" to="." method="_on_bn_xray_toggled"] diff --git a/addons/cyclops_level_builder/menu/main_toolbar.gd b/addons/cyclops_level_builder/menu/main_toolbar.gd new file mode 100644 index 0000000..41a520f --- /dev/null +++ b/addons/cyclops_level_builder/menu/main_toolbar.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name MainToolbar + +var editor_plugin:CyclopsLevelBuilder + +# Called when the node enters the scene tree for the first time. +func _ready(): + %Cyclops.clear() + %Cyclops.add_action_item(ActionImportMeshInstance.new(editor_plugin)) + %Cyclops.add_action_item(ActionImportCyclopsFile.new(editor_plugin)) + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/menu/main_toolbar.tscn b/addons/cyclops_level_builder/menu/main_toolbar.tscn new file mode 100644 index 0000000..d969248 --- /dev/null +++ b/addons/cyclops_level_builder/menu/main_toolbar.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://bm17ky7j4lwqc"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/menu/main_toolbar.gd" id="1_7i5xc"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/menu/action_popup_menu.gd" id="2_8w58b"] + +[node name="MainToolbar" type="PanelContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_7i5xc") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="MenuBar" type="MenuBar" parent="HBoxContainer"] +layout_mode = 2 + +[node name="Cyclops" type="PopupMenu" parent="HBoxContainer/MenuBar"] +unique_name_in_owner = true +script = ExtResource("2_8w58b") diff --git a/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.gd b/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.gd new file mode 100644 index 0000000..493ad8e --- /dev/null +++ b/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.gd @@ -0,0 +1,84 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name UpgradeCyclopsBlocksToolbar + +var editor_plugin:CyclopsLevelBuilder + +var activated:bool = false + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_bn_upgrade_pressed(): + var ed_iface:EditorInterface = editor_plugin.get_editor_interface() + var nodes:Array = ed_iface.get_selection().get_selected_nodes() + + if nodes.is_empty(): + return + + if !(nodes[0] is CyclopsBlocks): + return + + + var root:CyclopsBlocks = nodes[0] + var parent:Node = root.get_parent() + var index:int = root.get_index() + + var new_root:Node3D = Node3D.new() + root.add_sibling(new_root) + new_root.name = root.name + "_upgraded" + new_root.owner = ed_iface.get_edited_scene_root() + + root.visible = false + + #var grid_step_size:float = pow(2, editor_plugin.get_global_scene().grid_size) + + for child in root.get_children(): + if child is CyclopsConvexBlock: + var old_block:CyclopsConvexBlock = child + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(old_block.mesh_vector_data) + var centroid:Vector3 = vol.get_centroid() + #centroid = MathUtil.snap_to_grid(centroid, grid_step_size) + vol.translate(-centroid) + + var new_block:CyclopsBlock = CyclopsBlock.new() + new_root.add_child(new_block) + new_block.owner = ed_iface.get_edited_scene_root() + + new_block.name = old_block.name + new_block.materials = old_block.materials + new_block.mesh_vector_data = vol.to_mesh_vector_data() + new_block.global_transform = Transform3D.IDENTITY.translated(centroid) + diff --git a/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.tscn b/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.tscn new file mode 100644 index 0000000..2f4075d --- /dev/null +++ b/addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://u52a8gflbktl"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/menu/upgrade_cyclops_blocks_toolbar.gd" id="1_pbwhi"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_pbwhi") + +[node name="bn_upgrade" type="Button" parent="."] +layout_mode = 2 +text = "Upgrade CyclopsBlocks" + +[connection signal="pressed" from="bn_upgrade" to="." method="_on_bn_upgrade_pressed"] diff --git a/addons/cyclops_level_builder/nodes/cyclops_block.gd b/addons/cyclops_level_builder/nodes/cyclops_block.gd new file mode 100644 index 0000000..a5ff16f --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_block.gd @@ -0,0 +1,293 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name CyclopsBlock + +signal mesh_changed + +var mesh_instance:MeshInstance3D +var mesh_wire:MeshInstance3D +var collision_body:PhysicsBody3D +var collision_shape:CollisionShape3D +var active:bool + +var dirty:bool = true + +var control_mesh:ConvexVolume + +@export var block_data:ConvexBlockData: + get: + return block_data + + set(value): + if block_data != value: + block_data = value + control_mesh = ConvexVolume.new() + control_mesh.init_from_convex_block_data(block_data) + + #Convert to mesh vector data + var mvd:MeshVectorData = MeshVectorData.new() + mvd.create_from_convex_block(block_data) + mesh_vector_data = mvd + + #dirty = true + #mesh_changed.emit() + +@export var mesh_vector_data:MeshVectorData: + set(value): + if mesh_vector_data != value: + mesh_vector_data = value + + control_mesh = ConvexVolume.new() + control_mesh.init_from_mesh_vector_data(mesh_vector_data) + + dirty = true + mesh_changed.emit() + +@export var materials:Array[Material] + +var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") +var display_mode:DisplayMode.Type = DisplayMode.Type.MATERIAL + +@export var collision_type:Collision.Type = Collision.Type.STATIC: + get: + return collision_type + set(value): + collision_type = value + update_physics_body() + +@export_flags_3d_physics var collision_layer:int = 1: + get: + return collision_layer + set(value): + collision_layer = value + if collision_body: + collision_body.collision_layer = collision_layer + +@export_flags_3d_physics var collision_mask:int = 1: + get: + return collision_mask + set(value): + collision_mask = value + if collision_body: + collision_body.collision_mask = collision_mask + +# Called when the node enters the scene tree for the first time. +func _ready(): + mesh_instance = MeshInstance3D.new() + add_child(mesh_instance) + mesh_instance.gi_mode = GeometryInstance3D.GI_MODE_STATIC + + #print("block owner path %s" % owner.get_path()) + + if Engine.is_editor_hint(): + mesh_wire = MeshInstance3D.new() + add_child(mesh_wire) + + collision_shape = CollisionShape3D.new() + + #occluder = OccluderInstance3D.new() + #add_child(occluder) + + build_from_block() + update_physics_body() + +func update_physics_body(): + + if collision_body: + collision_body.remove_child(collision_shape) + collision_body.queue_free() + collision_body = null + + match collision_type: + Collision.Type.STATIC: + collision_body = StaticBody3D.new() + Collision.Type.KINEMATIC: + collision_body = CharacterBody3D.new() + Collision.Type.RIGID: + collision_body = RigidBody3D.new() + + if collision_body: + collision_body.collision_layer = collision_layer + collision_body.collision_mask = collision_mask + add_child(collision_body) + + collision_body.add_child(collision_shape) + + +func build_from_block(): + #print("build_from_block") + + dirty = false + + mesh_instance.mesh = null + collision_shape.shape = null + + if Engine.is_editor_hint(): +# var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + var global_scene = get_node("/root/CyclopsAutoload") + display_mode = global_scene.builder.display_mode + +# print("block_data %s" % block_data) +# print("vert points %s" % block_data.vertex_points) + #if !block_data: + #return + if !mesh_vector_data: + return + +# print("got block data") + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(mesh_vector_data) + #vol.init_from_convex_block_data(block_data) + + #print("volume %s" % vol) + + var mesh:ArrayMesh + + if Engine.is_editor_hint(): + var global_scene = get_node("/root/CyclopsAutoload") + mesh_wire.mesh = vol.create_mesh_wire(global_scene.outline_material) + #print ("added wireframe") + + #print("rebuilding mesh") + if display_mode == DisplayMode.Type.MATERIAL: + mesh = vol.create_mesh(materials, default_material) + if display_mode == DisplayMode.Type.MESH: + mesh = vol.create_mesh(materials, default_material, true) + #print ("added faces") + else: + mesh = vol.create_mesh(materials, default_material) + + mesh_instance.mesh = mesh + + var shape:ConvexPolygonShape3D = ConvexPolygonShape3D.new() + shape.points = vol.get_points() + collision_shape.shape = shape + + #if !Engine.is_editor_hint(): + ##Disabling this in the editor for now since this is causing slowdown + #var occluder_object:ArrayOccluder3D = ArrayOccluder3D.new() + #occluder_object.vertices = vol.get_points() + #occluder_object.indices = vol.get_trimesh_indices() + #occluder.occluder = occluder_object + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + + build_from_block() + + if Engine.is_editor_hint(): +# var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + var global_scene = get_node("/root/CyclopsAutoload") + + if display_mode != global_scene.builder.display_mode: + dirty = true + return + +func draw_unit_labels(viewport_camera:Camera3D, local_to_world:Transform3D): + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + var font:Font = global_scene.units_font + var font_size:float = global_scene.units_font_size + var descent:float = font.get_descent(font_size) + var text_offset:Vector2 = Vector2(0, -global_scene.vertex_radius - descent) + + if control_mesh: + for e_idx in control_mesh.edges.size(): + var e:ConvexVolume.EdgeInfo = control_mesh.edges[e_idx] + var focus:Vector3 = local_to_world * e.get_midpoint() + if !viewport_camera.is_position_behind(focus): + var focus_2d:Vector2 = viewport_camera.unproject_position(focus) + + var v0:ConvexVolume.VertexInfo = control_mesh.vertices[e.start_index] + var v1:ConvexVolume.VertexInfo = control_mesh.vertices[e.end_index] + var distance:Vector3 = v1.point - v0.point + global_scene.draw_text("%.3f" % distance.length(), focus_2d, font, font_size) + + + +func append_mesh_outline(mesh:ImmediateMesh, viewport_camera:Camera3D, local_to_world:Transform3D, mat:Material): + #var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + if control_mesh: + control_mesh.append_mesh_outline(mesh, viewport_camera, local_to_world, mat) + +func append_mesh_wire(mesh:ImmediateMesh): + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + var mat:Material = global_scene.outline_material + control_mesh.append_mesh_wire(mesh, mat) + + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + if !mesh_vector_data: + return null + + var xform:Transform3D = global_transform.affine_inverse() + var origin_local:Vector3 = xform * origin + var dir_local:Vector3 = xform.basis * dir + + var result:IntersectResults = control_mesh.intersect_ray_closest(origin_local, dir_local) + if result: + result.object = self + + return result + + +func select_face(face_idx:int, select_type:Selection.Type = Selection.Type.REPLACE): + if select_type == Selection.Type.REPLACE: + for f in control_mesh.faces: + f.selected = f.index == face_idx + elif select_type == Selection.Type.ADD: + control_mesh.faces[face_idx].selected = true + elif select_type == Selection.Type.SUBTRACT: + control_mesh.faces[face_idx].selected = true + elif select_type == Selection.Type.TOGGLE: + control_mesh.faces[face_idx].selected = !control_mesh.faces[face_idx].selected + + mesh_changed.emit() + +func export_to_cyclops_file(file_builder:CyclopsFileBuilder)->Dictionary: + var result:Dictionary + + result["collision_type"] = Collision.Type.keys()[collision_type] + result["collision_layer"] = collision_layer + result["collision_mask"] = collision_mask + + var mat_res_paths:PackedStringArray + for mat in materials: + if mat: + mat_res_paths.append(mat.resource_path) + else: + mat_res_paths.append("") + result["materials"] = mat_res_paths + + if mesh_vector_data: + result["mesh"] = mesh_vector_data.to_dictionary(file_builder) + #build_mesh["mesh"] = cur_node.mesh_vector_data.to_dictionary(self) + return result + diff --git a/addons/cyclops_level_builder/nodes/cyclops_blocks.gd b/addons/cyclops_level_builder/nodes/cyclops_blocks.gd new file mode 100644 index 0000000..d57beee --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_blocks.gd @@ -0,0 +1,155 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name CyclopsBlocks + +signal blocks_changed + +@export var occluder_vertex_offset:float = 0: + get: + return occluder_vertex_offset + set(value): + occluder_vertex_offset = value + dirty = true + +var dirty:bool = true + +var block_bodies:Node3D + +# Called when the node enters the scene tree for the first time. +func _ready(): + + child_entered_tree.connect(on_child_entered_tree) + child_exiting_tree.connect(on_child_exiting_tree) + + block_bodies = Node3D.new() + block_bodies.name = "block_bodies" + add_child(block_bodies) + + for node in get_children(): + if node is CyclopsConvexBlock: + var block:CyclopsConvexBlock = node + block.mesh_changed.connect(on_child_mesh_changed) + + + +func on_child_mesh_changed(): + dirty = true + blocks_changed.emit() + + +func on_child_entered_tree(node:Node): + if node is CyclopsConvexBlock: + var block:CyclopsConvexBlock = node + block.mesh_changed.connect(on_child_mesh_changed) + +# print("on_child_entered_tree %s" % node.name) + dirty = true + +func on_child_exiting_tree(node:Node): + if node is CyclopsConvexBlock: + var block:CyclopsConvexBlock = node + block.mesh_changed.disconnect(on_child_mesh_changed) + +# print("on_child_exited_tree %s" % node.name) + + dirty = true + +func has_selected_blocks()->bool: + for child in get_children(): + if child is CyclopsConvexBlock and child.selected: + return true + return false + + +func rebuild_mesh(): + for child in block_bodies.get_children(): + child.queue_free() + + for child in get_children(): + if child is CyclopsConvexBlock: + var block:CyclopsConvexBlock = child + +# var block_body:CyclopsConvexBlockBody = preload("res://addons/cyclops_level_builder/nodes/cyclops_convex_block_body.gd").instantiate() + var block_body:CyclopsConvexBlockBody = CyclopsConvexBlockBody.new() + block_body.materials = block.materials + block_body.block_data = block.block_data + block_bodies.add_child(block_body) + + + dirty = false + +func get_active_block()->CyclopsConvexBlock: + for child in get_children(): + if child is CyclopsConvexBlock: + var block:CyclopsConvexBlock = child + if block.active: + return block + return null + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + rebuild_mesh() + +func _input(event): + if Engine.is_editor_hint(): + pass + + #print(event.as_text()) + pass + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + return intersect_ray_closest_filtered(origin, dir, func(block:CyclopsConvexBlock): return true) + +func intersect_ray_closest_selected_only(origin:Vector3, dir:Vector3)->IntersectResults: + return intersect_ray_closest_filtered(origin, dir, func(block:CyclopsConvexBlock): return block.selected) + +func intersect_ray_closest_filtered(origin:Vector3, dir:Vector3, filter:Callable)->IntersectResults: + var best_result:IntersectResults + + for child in get_children(): + if child is CyclopsConvexBlock: + var result:IntersectResults = child.intersect_ray_closest(origin, dir) + if result: + if !filter.call(result.object): + continue + + if !best_result or result.distance_squared < best_result.distance_squared: + best_result = result + + return best_result + +func intersect_frustum_all(frustum:Array[Plane])->Array[CyclopsConvexBlock]: + var result:Array[CyclopsConvexBlock] = [] + + for child in get_children(): + if child is CyclopsConvexBlock: + var block:CyclopsConvexBlock = child + var vol:ConvexVolume = block.control_mesh + if vol.intersects_frustum(frustum): + result.append(block) + + return result diff --git a/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png new file mode 100644 index 0000000..89a3bc2 Binary files /dev/null and b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png differ diff --git a/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png.import b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png.import new file mode 100644 index 0000000..d1622bc --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdmaqfi82aq41" +path="res://.godot/imported/cyclops_blocks_icon.png-24c4441d9ebe4c1c02b0cf9fcbf537f3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/nodes/cyclops_blocks_icon.png" +dest_files=["res://.godot/imported/cyclops_blocks_icon.png-24c4441d9ebe4c1c02b0cf9fcbf537f3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg new file mode 100644 index 0000000..9d6e240 --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg.import b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg.import new file mode 100644 index 0000000..1c1ce7d --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chuwp6awho53q" +path="res://.godot/imported/cyclops_blocks_icon.svg-5e216ef2489ff1d30c472c6b8748fdf1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/cyclops_level_builder/nodes/cyclops_blocks_icon.svg" +dest_files=["res://.godot/imported/cyclops_blocks_icon.svg-5e216ef2489ff1d30c472c6b8748fdf1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/cyclops_level_builder/nodes/cyclops_convex_block.gd b/addons/cyclops_level_builder/nodes/cyclops_convex_block.gd new file mode 100644 index 0000000..7c71e0f --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_convex_block.gd @@ -0,0 +1,124 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node +class_name CyclopsConvexBlock + +signal mesh_changed + +@export var materials:Array[Material] + +var control_mesh:ConvexVolume + +var selected:bool = false: + get: + return selected + set(value): + if value == selected: + return + selected = value + mesh_changed.emit() + +var active:bool: + get: + return active + set(value): + if value == active: + return + active = value + mesh_changed.emit() + + +var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") + +@export var block_data:ConvexBlockData: + get: + return block_data + set(value): + if block_data != value: + block_data = value + control_mesh = ConvexVolume.new() + control_mesh.init_from_convex_block_data(block_data) + + mesh_changed.emit() + + +func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults: + if !block_data: + return null + + var result:IntersectResults = control_mesh.intersect_ray_closest(origin, dir) + if result: +# result.object = self + result.object = null + + return result + + +func select_face(face_idx:int, select_type:Selection.Type = Selection.Type.REPLACE): + if select_type == Selection.Type.REPLACE: + for f in control_mesh.faces: + f.selected = f.index == face_idx + elif select_type == Selection.Type.ADD: + control_mesh.faces[face_idx].selected = true + elif select_type == Selection.Type.SUBTRACT: + control_mesh.faces[face_idx].selected = true + elif select_type == Selection.Type.TOGGLE: + control_mesh.faces[face_idx].selected = !control_mesh.faces[face_idx].selected + + mesh_changed.emit() + +func append_mesh(mesh:ImmediateMesh): +# print("adding block mesh %s" % name) + #var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + control_mesh.append_mesh(mesh, materials, default_material) + +func append_mesh_wire(mesh:ImmediateMesh): + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + var mat:Material = global_scene.outline_material + control_mesh.append_mesh_wire(mesh, mat) + +func append_mesh_backfacing(mesh:ImmediateMesh): + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + var mat:Material = global_scene.tool_object_selected_material + control_mesh.append_mesh_backfacing(mesh, mat) + +func append_mesh_outline(mesh:ImmediateMesh, viewport_camera:Camera3D, local_to_world:Transform3D): + var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + + var mat:Material = global_scene.tool_object_active_material if active else global_scene.tool_object_selected_material + control_mesh.append_mesh_outline(mesh, viewport_camera, local_to_world, mat) + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + diff --git a/addons/cyclops_level_builder/nodes/cyclops_convex_block_body.gd b/addons/cyclops_level_builder/nodes/cyclops_convex_block_body.gd new file mode 100644 index 0000000..f660d2a --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_convex_block_body.gd @@ -0,0 +1,142 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name CyclopsConvexBlockBody + +var mesh_instance:MeshInstance3D +var mesh_wire:MeshInstance3D +var collision_body:StaticBody3D +var collision_shape:CollisionShape3D +var occluder:OccluderInstance3D + +var dirty:bool = true + +@export var block_data:ConvexBlockData: + get: + return block_data + set(value): + block_data = value + dirty = true + + +@export var materials:Array[Material] + +var default_material:Material = preload("res://addons/cyclops_level_builder/materials/grid.tres") +var display_mode:DisplayMode.Type = DisplayMode.Type.MATERIAL + +# Called when the node enters the scene tree for the first time. +func _ready(): + mesh_instance = MeshInstance3D.new() + add_child(mesh_instance) + mesh_instance.gi_mode = GeometryInstance3D.GI_MODE_STATIC + + if Engine.is_editor_hint(): + mesh_wire = MeshInstance3D.new() + add_child(mesh_wire) + + collision_body = StaticBody3D.new() + add_child(collision_body) + collision_shape = CollisionShape3D.new() + collision_body.add_child(collision_shape) + + occluder = OccluderInstance3D.new() + add_child(occluder) + + build_from_block() + + +func build_from_block(): + + dirty = false + + mesh_instance.mesh = null + collision_shape.shape = null + + if Engine.is_editor_hint(): +# var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + var global_scene = get_node("/root/CyclopsAutoload") + display_mode = global_scene.builder.display_mode + +# print("block_data %s" % block_data) +# print("vert points %s" % block_data.vertex_points) + if !block_data: + return + +# print("got block data") + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_convex_block_data(block_data) + + #print("volume %s" % vol) + +# var mesh:ImmediateMesh = ImmediateMesh.new() + var mesh:ArrayMesh + + if Engine.is_editor_hint(): +# var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + var global_scene = get_node("/root/CyclopsAutoload") + mesh_wire.mesh = vol.create_mesh_wire(global_scene.outline_material) + + if display_mode == DisplayMode.Type.MATERIAL: + mesh = vol.create_mesh(materials, default_material) + elif display_mode == DisplayMode.Type.MESH: + mesh = vol.create_mesh(materials, default_material, true) + #print ("added faces") + else: + mesh = vol.create_mesh(materials, default_material) +# vol.append_mesh(mesh, materials, default_material) + + mesh_instance.mesh = mesh + +# print("===============") +# GeneralUtil.dump_properties(mesh_instance) +# print("---------------") +# GeneralUtil.dump_properties(mesh_instance.mesh) + + var shape:ConvexPolygonShape3D = ConvexPolygonShape3D.new() + shape.points = vol.get_points() + collision_shape.shape = shape + + if !Engine.is_editor_hint(): + #Disabling this in the editor for now since this is causing slowdown + var occluder_object:ArrayOccluder3D = ArrayOccluder3D.new() + occluder_object.vertices = vol.get_points() + occluder_object.indices = vol.get_trimesh_indices() + occluder.occluder = occluder_object + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + if dirty: + build_from_block() + + + if Engine.is_editor_hint(): +# var global_scene:CyclopsGlobalScene = get_node("/root/CyclopsAutoload") + var global_scene = get_node("/root/CyclopsAutoload") + + if display_mode != global_scene.builder.display_mode: + dirty = true + return + diff --git a/addons/cyclops_level_builder/nodes/cyclops_scene.gd b/addons/cyclops_level_builder/nodes/cyclops_scene.gd new file mode 100644 index 0000000..969b255 --- /dev/null +++ b/addons/cyclops_level_builder/nodes/cyclops_scene.gd @@ -0,0 +1,35 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Node3D +class_name CyclopsScene + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/nodes/intersect_results.gd b/addons/cyclops_level_builder/nodes/intersect_results.gd new file mode 100644 index 0000000..9d9a7dd --- /dev/null +++ b/addons/cyclops_level_builder/nodes/intersect_results.gd @@ -0,0 +1,44 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name IntersectResults + +var object:CyclopsBlock +#var face_id:int +var face_index:int +var position:Vector3 #local space of block +var normal:Vector3 +var distance_squared:float + +func get_world_position()->Vector3: + return object.global_transform * position + +func get_world_normal()->Vector3: + var basis:Basis = object.global_transform.basis + var basis_normals:Basis = basis.inverse().transposed() + return basis_normals * normal + +func _to_string(): + return "object:%s face_idx:%s pos:%s norm:%s dist_sq:%s" % [object, face_index, position, normal, distance_squared] diff --git a/addons/cyclops_level_builder/plugin.cfg b/addons/cyclops_level_builder/plugin.cfg new file mode 100644 index 0000000..fb1e042 --- /dev/null +++ b/addons/cyclops_level_builder/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Cyclops Level Builder" +description="Tools for rapidly blocking in levels." +author="Mark McKay" +version="1.0.4" +script="cyclops_level_builder.gd" diff --git a/addons/cyclops_level_builder/resources/block_data.gd b/addons/cyclops_level_builder/resources/block_data.gd new file mode 100644 index 0000000..eb4a10d --- /dev/null +++ b/addons/cyclops_level_builder/resources/block_data.gd @@ -0,0 +1,32 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name BlockData + +@export var points:PackedVector3Array #Per vertex +@export var uvs:PackedVector2Array #Per face corner uv info +@export var face_vertex_indices:PackedInt32Array #Vertex index per face +@export var face_vertex_count:PackedInt32Array #Number of verts in each face +@export var face_material_indices:PackedInt32Array #Material index for each face diff --git a/addons/cyclops_level_builder/resources/convex_block_data.gd b/addons/cyclops_level_builder/resources/convex_block_data.gd new file mode 100644 index 0000000..524b5b6 --- /dev/null +++ b/addons/cyclops_level_builder/resources/convex_block_data.gd @@ -0,0 +1,137 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ConvexBlockData + +@export var selected:bool = false +@export var active:bool = false +@export var collision:bool = true +@export_flags_3d_physics var physics_layer:int +@export_flags_3d_physics var physics_mask:int + +@export var vertex_points:PackedVector3Array #Per vertex +@export var vertex_selected:PackedByteArray #Per vertex + +@export var edge_selected:PackedByteArray + +@export var face_material_indices:PackedInt32Array #Material index for each face +@export var face_uv_transform:Array[Transform2D] +@export var face_visible:PackedByteArray +@export var face_color:PackedColorArray +@export var face_selected:PackedByteArray #Per face + +@export var face_vertex_face_index:PackedInt32Array #Face index of this face-vertex +@export var face_vertex_vertex_index:PackedInt32Array #Vertex index of this face-vertex +@export var face_vertex_normal:PackedVector3Array #Per face-vertex +@export var face_vertex_color:PackedColorArray #Per face-vertex + + +@export var edge_vertex_indices:PackedInt32Array +@export var edge_face_indices:PackedInt32Array + +@export var face_vertex_count:PackedInt32Array #Number of verts in each face +@export var face_vertex_indices:PackedInt32Array #Vertex indices encountered as you iterate over mesh one face at a time and each vertex per face + +@export var active_vertex:int +@export var active_edge:int +@export var active_face:int +@export var active_face_vertex:int + + +#Validate arrays to make sure they're the right size +#@deprecated +func validate_arrays(): + #print("deprecated validate_arrays") + var num_faces:int = face_vertex_count.size() + + if face_visible.size() < num_faces: + var arr:PackedByteArray + arr.resize(num_faces - face_visible.size()) + arr.fill(true) + face_visible.append_array(arr) + + + if face_color.size() < num_faces: + var arr:PackedColorArray + arr.resize(num_faces - face_color.size()) + arr.fill(Color.WHITE) + face_color.append_array(arr) + +func init_from_mesh_vector_data(mvd:MeshVectorData): + + active_vertex = mvd.active_vertex + active_edge = mvd.active_edge + active_face = mvd.active_face + active_face_vertex = mvd.active_face_vertex + + #selected = mvd.selected + #active = mvd.active + #collision = mvd.collision + #physics_layer = mvd.physics_layer + #physics_mask = mvd.physics_mask + + var v_pos:DataVectorFloat = mvd.get_vertex_data(MeshVectorData.V_POSITION) + vertex_points = v_pos.to_vec3_array() + + var v_sel:DataVectorByte = mvd.get_vertex_data(MeshVectorData.V_SELECTED) + vertex_selected = v_sel.data + + var e_sel:DataVectorByte = mvd.get_edge_data(MeshVectorData.E_SELECTED) + edge_selected = e_sel.data + + var f_mat:DataVectorInt = mvd.get_face_data(MeshVectorData.F_MATERIAL_INDEX) + face_material_indices = f_mat.data + +# print("+build convex_block_data") + var f_uv_xform:DataVectorFloat = mvd.get_face_data(MeshVectorData.F_UV_XFORM) + face_uv_transform = f_uv_xform.to_transform2d_array() +# print("-build convex_block_data") + + var f_vis:DataVectorByte = mvd.get_face_data(MeshVectorData.F_VISIBLE) + face_visible = f_vis.data + + var f_col:DataVectorFloat = mvd.get_face_data(MeshVectorData.F_COLOR) + face_color = f_col.to_color_array() + + var f_sel:DataVectorByte = mvd.get_face_data(MeshVectorData.F_SELECTED) + face_selected = f_sel.data + + var fv_fidx:DataVectorInt = mvd.get_face_vertex_data(MeshVectorData.FV_FACE_INDEX) + face_vertex_face_index = fv_fidx.data + + var fv_vidx:DataVectorInt = mvd.get_face_vertex_data(MeshVectorData.FV_VERTEX_INDEX) + face_vertex_vertex_index = fv_vidx.data + + var fv_norm:DataVectorFloat = mvd.get_face_vertex_data(MeshVectorData.FV_NORMAL) + face_vertex_normal = fv_norm.to_vec3_array() + + var fv_col:DataVectorFloat = mvd.get_face_vertex_data(MeshVectorData.FV_COLOR) + face_vertex_color = fv_col.to_color_array() + + edge_vertex_indices = mvd.edge_vertex_indices + edge_face_indices = mvd.edge_face_indices + face_vertex_count = mvd.face_vertex_count + face_vertex_indices = mvd.face_vertex_indices + diff --git a/addons/cyclops_level_builder/resources/cyclops_config.gd b/addons/cyclops_level_builder/resources/cyclops_config.gd new file mode 100644 index 0000000..5a97368 --- /dev/null +++ b/addons/cyclops_level_builder/resources/cyclops_config.gd @@ -0,0 +1,30 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name CyclopsConfig + +@export var tool_tags:Array[ToolTag] +@export var snapping_tags:Array[SnappingTag] + diff --git a/addons/cyclops_level_builder/resources/data_vector.gd b/addons/cyclops_level_builder/resources/data_vector.gd new file mode 100644 index 0000000..dbf84a6 --- /dev/null +++ b/addons/cyclops_level_builder/resources/data_vector.gd @@ -0,0 +1,84 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name DataVector + +enum DataFormatType { BYTE, INT32, FLOAT32, STRING } +enum DataType { BOOL, INT, FLOAT, STRING, COLOR, VECTOR2, VECTOR3, VECTOR4, TRANSFORM_2D, TRANSFORM_3D } + +@export var name:StringName +@export var category:String #uv, color, weights, etc. +@export var data_type:DataType +@export var stride:int = 1 + +func get_data_format_type()->DataFormatType: + return DataFormatType.BYTE + +func size()->int: + return 0 + +func num_components()->int: + return size() / stride + +func get_buffer_byte_data()->PackedByteArray: + return [] + +#func to_dictionary(buffer_ar:BufferArchive)->Dictionary: + #var result:Dictionary + # + #result["name"] = name + #result["data_type"] = DataType.values()[data_type] + #if stride != 1: + #result["stride"] = stride + #if !category.is_empty(): + #result["category"] = category + # + #return result + +static func data_type_num_components(type:DataType)->int: + match type: + DataType.BOOL: + return 1 + DataType.INT: + return 1 + DataType.FLOAT: + return 1 + DataType.STRING: + return 1 + DataType.COLOR: + return 4 + DataType.VECTOR2: + return 2 + DataType.VECTOR3: + return 3 + DataType.VECTOR4: + return 4 + DataType.TRANSFORM_2D: + return 6 + DataType.TRANSFORM_3D: + return 12 + _: + push_error("Invalid data type") + return 1 diff --git a/addons/cyclops_level_builder/resources/data_vector_byte.gd b/addons/cyclops_level_builder/resources/data_vector_byte.gd new file mode 100644 index 0000000..19b0157 --- /dev/null +++ b/addons/cyclops_level_builder/resources/data_vector_byte.gd @@ -0,0 +1,62 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends DataVector +class_name DataVectorByte + +@export var data:PackedByteArray + +func _init(name:StringName = "", data:PackedByteArray = [], data_type:DataType = DataType.BOOL): + self.name = name + self.data = data + self.data_type = data_type + self.stride = data_type_num_components(data_type) + + +func get_buffer_byte_data()->PackedByteArray: + return data + +#func to_dictionary(buffer_ar:BufferArchive)->Dictionary: + #var result:Dictionary = super(buffer_ar) + #var region:BufferArchive.BufferRegion = buffer_ar.store_buffer(data) + # +## result["data"] = Marshalls.raw_to_base64(data.compress()) + #result["data_buffer"] = region.index + # + #return result + +func get_data_format_type()->DataFormatType: + return DataFormatType.BYTE + +func size()->int: + return data.size() + +func resize(size:int): + data.resize(size * stride) + +func get_value(index:int)->int: + return data[index] + +func set_value(value:int, index:int): + data[index] = value diff --git a/addons/cyclops_level_builder/resources/data_vector_float.gd b/addons/cyclops_level_builder/resources/data_vector_float.gd new file mode 100644 index 0000000..5eea45f --- /dev/null +++ b/addons/cyclops_level_builder/resources/data_vector_float.gd @@ -0,0 +1,140 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends DataVector +class_name DataVectorFloat + +@export var data:PackedFloat32Array + +func _init(name:StringName = "", data:PackedFloat32Array = [], data_type:DataType = DataType.FLOAT): + self.name = name + self.data = data + self.data_type = data_type + self.stride = data_type_num_components(data_type) + +func get_data_format_type()->DataFormatType: + return DataFormatType.FLOAT32 + +func size()->int: + return data.size() + +func resize(size:int): + data.resize(size * stride) + +func get_value(index:int)->float: + return data[index] + +func to_vec2_array()->PackedVector2Array: + var result:PackedVector2Array + for i in num_components(): + result.append(get_value_vec2(i)) + return result + +func to_vec3_array()->PackedVector3Array: + var result:PackedVector3Array + for i in num_components(): + result.append(get_value_vec3(i)) + return result + +func to_vec4_array()->Array[Vector4]: + var result:Array[Vector4] + for i in num_components(): + result.append(get_value_vec4(i)) + return result + +func to_color_array()->PackedColorArray: + var result:PackedColorArray + for i in num_components(): + result.append(get_value_color(i)) + return result + +func to_transform2d_array()->Array[Transform2D]: + #print("to_transform2d_array num_components() ", num_components()) + var result:Array[Transform2D] + for i in num_components(): + result.append(get_value_transform2d(i)) + return result + +func get_value_vec2(index:int)->Vector2: + return Vector2(data[index * stride], data[index * stride + 1]) + +func get_value_vec3(index:int)->Vector3: + return Vector3(data[index * stride], data[index * stride + 1], data[index * stride + 2]) + +func get_value_vec4(index:int)->Vector4: + return Vector4(data[index * stride], data[index * stride + 1], data[index * stride + 2], data[index * stride + 3]) + +func get_value_color(index:int)->Color: + return Color(data[index * stride], data[index * stride + 1], data[index * stride + 2], data[index * stride + 3]) + +func get_value_transform2d(index:int)->Transform2D: + return Transform2D( + Vector2(data[index * stride], data[index * stride + 1]), + Vector2(data[index * stride + 2], data[index * stride + 3]), + Vector2(data[index * stride + 4], data[index * stride + 5]) + ) + +func get_value_transform3d(index:int)->Transform3D: + return Transform3D( + Vector3(data[index * stride], data[index * stride + 1], data[index * stride + 2]), + Vector3(data[index * stride + 3], data[index * stride + 4], data[index * stride + 5]), + Vector3(data[index * stride + 6], data[index * stride + 7], data[index * stride + 8]), + Vector3(data[index * stride + 9], data[index * stride + 10], data[index * stride + 11]) + ) + + +func set_value(value:int, index:int): + data[index] = value + +func set_value_vec2(value:Vector2, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + +func set_value_vec3(value:Vector3, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + data[index * stride + 2] = value.z + +func set_value_vec4(value:Vector4, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + data[index * stride + 2] = value.z + data[index * stride + 3] = value.w + +func set_value_color(value:Color, index:int): + data[index * stride] = value.r + data[index * stride + 1] = value.g + data[index * stride + 2] = value.b + data[index * stride + 3] = value.a + +func get_buffer_byte_data()->PackedByteArray: + return data.to_byte_array() + +#func to_dictionary(buffer_ar:BufferArchive)->Dictionary: + #var result:Dictionary = super(buffer_ar) + #var region:BufferArchive.BufferRegion = buffer_ar.store_buffer(data.to_byte_array()) + # + #result["data_buffer"] = region.index + # + #return result diff --git a/addons/cyclops_level_builder/resources/data_vector_int.gd b/addons/cyclops_level_builder/resources/data_vector_int.gd new file mode 100644 index 0000000..a990a82 --- /dev/null +++ b/addons/cyclops_level_builder/resources/data_vector_int.gd @@ -0,0 +1,85 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends DataVector +class_name DataVectorInt + +@export var data:PackedInt32Array + +func _init(name:StringName = "", data:PackedInt32Array = [], data_type:DataType = DataType.INT): + self.name = name + self.data = data + self.data_type = data_type + self.stride = data_type_num_components(data_type) + +func get_data_format_type()->DataFormatType: + return DataFormatType.INT32 + +func size()->int: + return data.size() + +func resize(size:int): + data.resize(size * stride) + +func get_value(index:int)->int: + return data[index] + +func get_value_ivec2(index:int)->Vector2i: + return Vector2i(data[index * stride], data[index * stride + 1]) + +func get_value_ivec3(index:int)->Vector3i: + return Vector3i(data[index * stride], data[index * stride + 1], data[index * stride + 2]) + +func get_value_ivec4(index:int)->Vector4i: + return Vector4i(data[index * stride], data[index * stride + 1], data[index * stride + 2], data[index * stride + 3]) + + +func set_value(value:int, index:int): + data[index] = value + +func set_value_ivec2(value:Vector2i, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + +func set_value_ivec3(value:Vector3i, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + data[index * stride + 2] = value.z + +func set_value_ivec4(value:Vector4i, index:int): + data[index * stride] = value.x + data[index * stride + 1] = value.y + data[index * stride + 2] = value.z + data[index * stride + 3] = value.w + +func get_buffer_byte_data()->PackedByteArray: + return data.to_byte_array() + +#func to_dictionary(buffer_ar:BufferArchive)->Dictionary: + #var result:Dictionary = super(buffer_ar) + #var region:BufferArchive.BufferRegion = buffer_ar.store_buffer(data.to_byte_array()) + # + #result["data_buffer"] = region.index + # + #return result diff --git a/addons/cyclops_level_builder/resources/data_vector_string.gd b/addons/cyclops_level_builder/resources/data_vector_string.gd new file mode 100644 index 0000000..fcff4a4 --- /dev/null +++ b/addons/cyclops_level_builder/resources/data_vector_string.gd @@ -0,0 +1,61 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends DataVector +class_name DataVectorString + +@export var data:PackedStringArray + +func _init(name:StringName = "", data:PackedStringArray = [], data_type:DataType = DataType.STRING): + self.name = name + self.data = data + self.data_type = data_type + self.stride = data_type_num_components(data_type) + +func get_data_format_type()->DataFormatType: + return DataFormatType.STRING + +func size()->int: + return data.size() + +func resize(size:int): + data.resize(size * stride) + +func get_value(index:int)->String: + return data[index] + +func set_value(value:String, index:int): + data[index] = value + +func get_buffer_byte_data()->PackedByteArray: + return var_to_bytes(data) +# return data.to_byte_array() + +#func to_dictionary(buffer_ar:BufferArchive)->Dictionary: + #var result:Dictionary = super(buffer_ar) + #var region:BufferArchive.BufferRegion = buffer_ar.store_buffer(data.to_byte_array()) + # + #result["data_buffer"] = region.index + # + #return result diff --git a/addons/cyclops_level_builder/resources/mesh_vector_data.gd b/addons/cyclops_level_builder/resources/mesh_vector_data.gd new file mode 100644 index 0000000..a501e5a --- /dev/null +++ b/addons/cyclops_level_builder/resources/mesh_vector_data.gd @@ -0,0 +1,369 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name MeshVectorData + + +#@export var selected:bool = false +#@export var active:bool = false +#@export var collision:bool = true +#@export_flags_3d_physics var physics_layer:int +#@export_flags_3d_physics var physics_mask:int + +@export var num_vertices:int +@export var num_edges:int +@export var num_faces:int +@export var num_face_vertices:int + +@export var active_vertex:int +@export var active_edge:int +@export var active_face:int +@export var active_face_vertex:int + + +@export var edge_vertex_indices:PackedInt32Array +@export var edge_face_indices:PackedInt32Array + +@export var face_vertex_count:PackedInt32Array #Number of verts in each face +@export var face_vertex_indices:PackedInt32Array #Vertex index per face + +@export var vertex_data:Dictionary +@export var edge_data:Dictionary +@export var face_data:Dictionary +@export var face_vertex_data:Dictionary + +const V_POSITION: StringName = "position" +const V_SELECTED: StringName = "selected" +const V_COLOR: StringName = "color" + +const E_SELECTED: StringName = "selected" + +const F_MATERIAL_INDEX: StringName = "material_index" +const F_UV_XFORM: StringName = "uv_transform" +const F_VISIBLE: StringName = "visible" +const F_COLOR: StringName = "color" +const F_SELECTED: StringName = "selected" + +const FV_VERTEX_INDEX: StringName = "vertex_index" +const FV_FACE_INDEX: StringName = "face_index" +const FV_VERTEX_LOCAL_INDEX: StringName = "vertex_local_index" +const FV_SELECTED: StringName = "selected" +const FV_COLOR: StringName = "color" +const FV_NORMAL: StringName = "normal" +const FV_UV1: StringName = "uv1" +const FV_UV2: StringName = "uv2" + + +func create_from_convex_block(block_data:ConvexBlockData): + + #selected = block_data.selected + #active = block_data.active + #collision = block_data.collision + #physics_layer = block_data.physics_layer + #physics_mask = block_data.physics_mask + + active_vertex = block_data.active_vertex + active_edge = block_data.active_edge + active_face = block_data.active_face + active_face_vertex = block_data.active_face_vertex + + num_vertices = block_data.vertex_points.size() + num_edges = block_data.edge_vertex_indices.size() / 2 + num_faces = block_data.face_vertex_count.size() + + set_vertex_data(DataVectorFloat.new(V_POSITION, + block_data.vertex_points.to_byte_array().to_float32_array(), + DataVector.DataType.VECTOR3)) + + set_vertex_data(DataVectorByte.new(V_SELECTED, + block_data.vertex_selected, + DataVector.DataType.BOOL)) + + set_edge_data(DataVectorByte.new(E_SELECTED, + block_data.edge_selected, + DataVector.DataType.BOOL)) + + set_face_data(DataVectorInt.new(F_MATERIAL_INDEX, + block_data.face_material_indices, + DataVector.DataType.INT)) + + set_face_data(DataVectorByte.new(F_VISIBLE, + block_data.face_visible, + DataVector.DataType.BOOL)) + + set_face_data(DataVectorFloat.new(F_COLOR, + block_data.face_color.to_byte_array().to_float32_array(), + DataVector.DataType.COLOR)) + + var f_uv_xform:PackedFloat32Array + for t in block_data.face_uv_transform: + f_uv_xform.append_array([t.x.x, t.x.y, t.y.x, t.y.y, t.origin.x, t.origin.y]) + set_face_data(DataVectorFloat.new(F_UV_XFORM, + f_uv_xform, + DataVector.DataType.TRANSFORM_2D)) + + + set_face_data(DataVectorByte.new(F_SELECTED, + block_data.face_selected, + DataVector.DataType.BOOL)) + + set_face_data(DataVectorFloat.new(F_COLOR, + block_data.face_color.to_byte_array().to_float32_array(), + DataVector.DataType.COLOR)) + + + #Create face-vertex data + edge_vertex_indices = block_data.edge_vertex_indices + edge_face_indices = block_data.edge_face_indices + face_vertex_count = block_data.face_vertex_count + face_vertex_indices = block_data.face_vertex_indices + + num_face_vertices = 0 + for n in block_data.face_vertex_count: + num_face_vertices += n + + var fv_array_offset:int = 0 + var next_fv_idx:int = 0 + var face_indices:PackedInt32Array + var vert_indices:PackedInt32Array + + for f_idx in block_data.face_vertex_count.size(): + var num_verts_in_face:int = block_data.face_vertex_count[f_idx] + for fv_local_idx in num_verts_in_face: + var v_idx:int = block_data.face_vertex_indices[fv_array_offset + fv_local_idx] + + face_indices.append(f_idx) + vert_indices.append(v_idx) + + fv_array_offset += num_verts_in_face + + + set_face_vertex_data(DataVectorInt.new(FV_FACE_INDEX, + face_indices, + DataVector.DataType.INT)) + + set_face_vertex_data(DataVectorInt.new(FV_VERTEX_INDEX, + vert_indices, + DataVector.DataType.INT)) + + #set_face_vertex_data(DataVectorInt.new(FV_VERTEX_LOCAL_INDEX, + #fv_local_indices, + #DataVector.DataType.INT)) + + if block_data.face_vertex_color.is_empty(): + #Construct face vertex colors from old face colors system + var col_fv_data:PackedColorArray + for fv_idx in num_face_vertices: + var f_idx:int = face_indices[fv_idx] + var v_idx:int = vert_indices[fv_idx] + col_fv_data.append(block_data.face_color[f_idx]) + + + set_face_vertex_data(DataVectorFloat.new(FV_COLOR, + col_fv_data.to_byte_array().to_float32_array(), + DataVector.DataType.COLOR)) + else: + #Copy face vertex colors + set_face_vertex_data(DataVectorFloat.new(FV_COLOR, + block_data.face_vertex_color.to_byte_array().to_float32_array(), + DataVector.DataType.COLOR)) + + set_face_vertex_data(DataVectorFloat.new(FV_NORMAL, + block_data.face_vertex_normal.to_byte_array().to_float32_array(), + DataVector.DataType.VECTOR3)) + + +func get_vertex_data(vector_name:String)->DataVector: + return vertex_data[vector_name] + +func get_edge_data(vector_name:String)->DataVector: + return edge_data[vector_name] + +func get_face_data(vector_name:String)->DataVector: + return face_data[vector_name] + +func get_face_vertex_data(vector_name:String)->DataVector: + return face_vertex_data[vector_name] + +func set_vertex_data(data_vector:DataVector): + vertex_data[data_vector.name] = data_vector + +func set_edge_data(data_vector:DataVector): + edge_data[data_vector.name] = data_vector + +func set_face_data(data_vector:DataVector): + face_data[data_vector.name] = data_vector + +func set_face_vertex_data(data_vector:DataVector): + face_vertex_data[data_vector.name] = data_vector + +func validate()->bool: + return true + + +func create_vector_xml_node(name:String, type:String, value:String)->XMLElement: + var evi_ele:XMLElement = XMLElement.new("vector") + evi_ele.set_attribute("name", name) + evi_ele.set_attribute("type", type) + evi_ele.set_attribute("value", value) + return evi_ele + +func section_to_xml(type:String, vertex_data:Dictionary)->XMLElement: + var sec_vertex_ele:XMLElement = XMLElement.new("section") + sec_vertex_ele.set_attribute("type", type) + + for vec_name in vertex_data.keys(): + var v:DataVector = vertex_data[vec_name] + match v.data_type: + DataVector.DataType.BOOL: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "bool", var_to_str(v.data))) + DataVector.DataType.INT: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "int", var_to_str(v.data))) + DataVector.DataType.FLOAT: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "float", var_to_str(v.data))) + DataVector.DataType.STRING: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "string", var_to_str(v.data))) + DataVector.DataType.COLOR: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "color", var_to_str(v.data))) + DataVector.DataType.VECTOR2: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "vector2", var_to_str(v.data))) + DataVector.DataType.VECTOR3: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "vector3", var_to_str(v.data))) + DataVector.DataType.VECTOR4: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "vector4", var_to_str(v.data))) + DataVector.DataType.TRANSFORM_2D: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "transform2D", var_to_str(v.data))) + DataVector.DataType.TRANSFORM_3D: + sec_vertex_ele.add_child(create_vector_xml_node(v.name, "transform3D", var_to_str(v.data))) + + return sec_vertex_ele + +func to_xml()->XMLElement: + var rec_ele:XMLElement = XMLElement.new("record") + rec_ele.set_attribute("type", "mesh") + + #rec_ele.set_attribute("selected", str(selected)) + #rec_ele.set_attribute("active", str(active)) + #rec_ele.set_attribute("collision", str(collision)) + #rec_ele.set_attribute("physics_layer", str(physics_layer)) + #rec_ele.set_attribute("physics_mask", str(physics_mask)) + + rec_ele.set_attribute("num_vertices", str(num_vertices)) + rec_ele.set_attribute("num_edges", str(num_edges)) + rec_ele.set_attribute("num_faces", str(num_faces)) + rec_ele.set_attribute("num_face_vertices", str(num_face_vertices)) + + + rec_ele.add_child(create_vector_xml_node("edge_vertex_indices", "int", var_to_str(edge_vertex_indices))) + rec_ele.add_child(create_vector_xml_node("edge_face_indices", "int", var_to_str(edge_face_indices))) + rec_ele.add_child(create_vector_xml_node("face_vertex_count", "int", var_to_str(face_vertex_count))) + rec_ele.add_child(create_vector_xml_node("face_vertex_indices", "int", var_to_str(face_vertex_indices))) + + rec_ele.set_attribute("active_vertex", str(active_vertex)) + rec_ele.set_attribute("active_edge", str(active_edge)) + rec_ele.set_attribute("active_face", str(active_face)) + rec_ele.set_attribute("active_face_vertex", str(active_face_vertex)) + + var sec_vertex_ele:XMLElement = XMLElement.new("data") + sec_vertex_ele.set_attribute("type", "vertex") + rec_ele.add_child(sec_vertex_ele) + + rec_ele.add_child(section_to_xml("vertex", vertex_data)) + rec_ele.add_child(section_to_xml("edge", edge_data)) + rec_ele.add_child(section_to_xml("face", face_data)) + rec_ele.add_child(section_to_xml("faceVertex", face_vertex_data)) + + + return rec_ele + +func to_dictionary(file_builder:CyclopsFileBuilder)->Dictionary: + var result:Dictionary + + result["num_vertices"] = num_vertices + result["num_edges"] = num_edges + result["num_faces"] = num_faces + result["num_face_vertices"] = num_face_vertices + + result["active_vertex"] = active_vertex + result["active_edge"] = active_edge + result["active_face"] = active_face + result["active_face_vertex"] = active_face_vertex + +# vectors["face_vertices"].append(file_builder.export_vector(data_vec)) + result["edge_vertex_index_buffer"] = file_builder.export_byte_array(edge_vertex_indices.to_byte_array()) + result["edge_face_index_buffer"] = file_builder.export_byte_array(edge_face_indices.to_byte_array()) + result["face_vertex_count_buffer"] = file_builder.export_byte_array(face_vertex_count.to_byte_array()) + result["face_vertex_index_buffer"] = file_builder.export_byte_array(face_vertex_indices.to_byte_array()) + #result["edge_vertex_indices"] = edge_vertex_indices + #result["edge_face_indices"] = edge_face_indices + # + #result["face_vertex_count"] = face_vertex_count + #result["face_vertex_indices"] = face_vertex_indices + + var vectors:Dictionary = { + "vertices": [], + "edges": [], + "faces": [], + "face_vertices": [] + } + result["vectors"] = vectors + + for key in vertex_data.keys(): + var data_vec:DataVector = vertex_data[key] +# vectors["vertices"].append(data_vec.to_dictionary(buf_ar)) + vectors["vertices"].append(file_builder.export_vector(data_vec)) + + for key in edge_data.keys(): + var data_vec:DataVector = edge_data[key] +# vectors["edges"].append(data_vec.to_dictionary(buf_ar)) + vectors["edges"].append(file_builder.export_vector(data_vec)) + + for key in face_data.keys(): + var data_vec:DataVector = face_data[key] +# vectors["faces"].append(data_vec.to_dictionary(buf_ar)) + vectors["faces"].append(file_builder.export_vector(data_vec)) + + for key in face_vertex_data.keys(): + var data_vec:DataVector = face_vertex_data[key] +# vectors["face_vertices"].append(data_vec.to_dictionary(buf_ar)) + vectors["face_vertices"].append(file_builder.export_vector(data_vec)) + + return result + +#func export_vector(vec:DataVector, file_builder:CyclopsFileBuilder)->Dictionary: + #var result:Dictionary + # + #result["name"] = vec.name + #result["data_type"] = DataVector.DataType.values()[vec.data_type] + #if vec.stride != 1: + #result["stride"] = vec.stride + #if !vec.category.is_empty(): + #result["category"] = vec.category + # + #var region:BufferArchive.BufferRegion = file_builder.buf_ar.store_buffer(vec.get_buffer_byte_data()) + #result["data_buffer"] = region.index + # + #return result + diff --git a/addons/cyclops_level_builder/resources/tool_tag.gd b/addons/cyclops_level_builder/resources/tool_tag.gd new file mode 100644 index 0000000..5d1799d --- /dev/null +++ b/addons/cyclops_level_builder/resources/tool_tag.gd @@ -0,0 +1,50 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolTag + +@export var id:String +@export var name:String +@export var input_events: Array[InputEvent] = [] +@export var input_events_override := false +@export_multiline var tooltip:String +@export var icon:Texture2D +@export var tool_script:Script + +var tool:CyclopsTool + +func _activate(plugin:CyclopsLevelBuilder): + if !tool_script: + return + + if !tool: + tool = tool_script.new() + + +# print("Activating %s" % tool_script.resource_path) +# print("tool id %s" % tool._get_tool_id()) + +# print("Activating %s" % name) + plugin.switch_to_tool(tool) diff --git a/addons/cyclops_level_builder/shaders/outline_shader.tres b/addons/cyclops_level_builder/shaders/outline_shader.tres new file mode 100644 index 0000000..5b55986 --- /dev/null +++ b/addons/cyclops_level_builder/shaders/outline_shader.tres @@ -0,0 +1,66 @@ +[gd_resource type="VisualShader" load_steps=5 format=3 uid="uid://cc5tovf48xmg1"] + +[sub_resource type="VisualShaderNodeColorConstant" id="VisualShaderNodeColorConstant_mqcg1"] +constant = Color(0, 0, 0, 1) + +[sub_resource type="VisualShaderNodeColorParameter" id="VisualShaderNodeColorParameter_kvn47"] +parameter_name = "ColorParameter" +default_value_enabled = true +default_value = Color(0, 0, 0, 1) + +[sub_resource type="VisualShaderNodeLinearSceneDepth" id="VisualShaderNodeLinearSceneDepth_d3ao7"] + +[sub_resource type="VisualShaderNodeFloatOp" id="VisualShaderNodeFloatOp_pyrux"] +default_input_values = [0, 0.0, 1, 1.0] + +[resource] +code = "shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx; + +uniform vec4 ColorParameter : source_color = vec4(0.000000, 0.000000, 0.000000, 1.000000); +uniform sampler2D depth_tex_frg_4 : hint_depth_texture; + + + +void fragment() { +// ColorConstant:2 + vec4 n_out2p0 = vec4(0.000000, 0.000000, 0.000000, 1.000000); + + +// ColorParameter:3 + vec4 n_out3p0 = ColorParameter; + + + float n_out4p0; +// LinearSceneDepth:4 + { + float __log_depth = textureLod(depth_tex_frg_4, SCREEN_UV, 0.0).x; + vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(vec3(SCREEN_UV, __log_depth) * 2.0 - 1.0, 1.0); + __depth_view.xyz /= __depth_view.w; + n_out4p0 = -__depth_view.z; + } + + +// FloatOp:5 + float n_in5p1 = 1.00000; + float n_out5p0 = n_out4p0 + n_in5p1; + + +// Output:0 + ALBEDO = vec3(n_out2p0.xyz); + EMISSION = vec3(n_out3p0.xyz); + NORMAL_MAP_DEPTH = n_out5p0; + + +} +" +nodes/fragment/0/position = Vector2(480, 140) +nodes/fragment/2/node = SubResource("VisualShaderNodeColorConstant_mqcg1") +nodes/fragment/2/position = Vector2(227.319, 156.469) +nodes/fragment/3/node = SubResource("VisualShaderNodeColorParameter_kvn47") +nodes/fragment/3/position = Vector2(80, 280) +nodes/fragment/4/node = SubResource("VisualShaderNodeLinearSceneDepth_d3ao7") +nodes/fragment/4/position = Vector2(0, 560) +nodes/fragment/5/node = SubResource("VisualShaderNodeFloatOp_pyrux") +nodes/fragment/5/position = Vector2(260, 520) +nodes/fragment/connections = PackedInt32Array(2, 0, 0, 0, 3, 0, 0, 5, 4, 0, 5, 0, 5, 0, 0, 10) diff --git a/addons/cyclops_level_builder/shaders/tool_outline_shader.tres b/addons/cyclops_level_builder/shaders/tool_outline_shader.tres new file mode 100644 index 0000000..763d03a --- /dev/null +++ b/addons/cyclops_level_builder/shaders/tool_outline_shader.tres @@ -0,0 +1,40 @@ +[gd_resource type="VisualShader" load_steps=3 format=3 uid="uid://c33k8fbmgw46b"] + +[sub_resource type="VisualShaderNodeColorConstant" id="VisualShaderNodeColorConstant_mqcg1"] +constant = Color(0, 0, 0, 1) + +[sub_resource type="VisualShaderNodeColorParameter" id="VisualShaderNodeColorParameter_kvn47"] +parameter_name = "ColorParameter" +default_value_enabled = true +default_value = Color(1, 1, 0, 1) + +[resource] +code = "shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_lambert, specular_schlick_ggx; + +uniform vec4 ColorParameter : source_color = vec4(1.000000, 1.000000, 0.000000, 1.000000); + + + +void fragment() { +// ColorConstant:2 + vec4 n_out2p0 = vec4(0.000000, 0.000000, 0.000000, 1.000000); + + +// ColorParameter:3 + vec4 n_out3p0 = ColorParameter; + + +// Output:0 + ALBEDO = vec3(n_out2p0.xyz); + EMISSION = vec3(n_out3p0.xyz); + + +} +" +nodes/fragment/0/position = Vector2(480, 140) +nodes/fragment/2/node = SubResource("VisualShaderNodeColorConstant_mqcg1") +nodes/fragment/2/position = Vector2(227.319, 156.469) +nodes/fragment/3/node = SubResource("VisualShaderNodeColorParameter_kvn47") +nodes/fragment/3/position = Vector2(80, 280) +nodes/fragment/connections = PackedInt32Array(2, 0, 0, 0, 3, 0, 0, 5) diff --git a/addons/cyclops_level_builder/shaders/vertex_shader.tres b/addons/cyclops_level_builder/shaders/vertex_shader.tres new file mode 100644 index 0000000..5ee3ea9 --- /dev/null +++ b/addons/cyclops_level_builder/shaders/vertex_shader.tres @@ -0,0 +1,60 @@ +[gd_resource type="Shader" format=3 uid="uid://dewb6ohh2wxv0"] + +[resource] +code = "// NOTE: Shader automatically converted from Godot Engine 4.0.2.stable's StandardMaterial3D. + +shader_type spatial; +render_mode blend_mix,depth_draw_never,cull_disabled,diffuse_burley,specular_schlick_ggx,ambient_light_disabled,alpha_to_coverage; +uniform float radius = 4; + +uniform sampler2D texture_emission : source_color, hint_default_black,filter_linear_mipmap,repeat_enable; +uniform vec4 emission : source_color; +uniform float emission_energy; + +uniform float alpha_scissor_threshold; +uniform float alpha_antialiasing_edge; + +void vertex() { + //UV=UV*uv1_scale.xy+uv1_offset.xy; + + vec4 model_pos_world = MODEL_MATRIX[3]; +// vec4 cam_origin_world = INV_VIEW_MATRIX[3]; + vec4 model_origin_cam_space = VIEW_MATRIX * model_pos_world; + vec4 model_origin_proj_space = PROJECTION_MATRIX * VIEW_MATRIX * model_pos_world; + model_origin_proj_space /= model_origin_proj_space.w; + vec4 offset_proj_space = model_origin_proj_space + vec4(0.0, radius / VIEWPORT_SIZE.y, 0.0, 0.0); + vec4 offset_cam_space = INV_PROJECTION_MATRIX * offset_proj_space; + offset_cam_space /= offset_cam_space.w; + + vec4 radius_offset_cam_space = offset_cam_space - model_origin_cam_space; + +// float scale = length(model_origin_cam_space.xyz) * radius * 5.0 / VIEWPORT_SIZE.y; + float scale = length(radius_offset_cam_space.xy) * 10.0; + + mat4 SCALE = mat4(vec4(scale, 0.0, 0.0, 0.0), + vec4(0.0, scale, 0.0, 0.0), + vec4(0.0, 0.0, scale, 0.0), + vec4(0.0, 0.0, 0.0, 1.0)); + + MODELVIEW_MATRIX = VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0], INV_VIEW_MATRIX[1], INV_VIEW_MATRIX[2], MODEL_MATRIX[3]) * SCALE; + + MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX); +} + + + +void fragment() { + vec2 base_uv = UV; + vec4 emission_tex = texture(texture_emission, base_uv); + emission_tex *= COLOR * emission; + + ALBEDO = vec3(0.0, 0.0, 0.0); + EMISSION = emission_tex.xyz; + + ALPHA = emission_tex.a; + ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold; + ALPHA_ANTIALIASING_EDGE = alpha_antialiasing_edge; + ALPHA_TEXTURE_COORDINATE = UV; +} + +" diff --git a/addons/cyclops_level_builder/snapping/cyclops_snapping_system.gd b/addons/cyclops_level_builder/snapping/cyclops_snapping_system.gd new file mode 100644 index 0000000..c811541 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/cyclops_snapping_system.gd @@ -0,0 +1,67 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool + +extends Resource +class_name CyclopsSnappingSystem + +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +var plugin:CyclopsLevelBuilder + +func _activate(plugin:CyclopsLevelBuilder): + self.plugin = plugin + +func _deactivate(): + pass + +func _snap_point(point:Vector3, query:SnappingQuery)->Vector3: + return point + +func _snap_angle(angle:float, query:SnappingQuery)->float: + return angle + +func _get_properties_editor()->Control: + return null + +func constrain_point(point:Vector3, target_point:Vector3, move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE)->Vector3: + match move_constraint: + MoveConstraint.Type.NONE: + return target_point + MoveConstraint.Type.AXIS_X: + return Vector3(target_point.x, point.y, point.z) + MoveConstraint.Type.AXIS_Y: + return Vector3(point.x, target_point.y, point.z) + MoveConstraint.Type.AXIS_Z: + return Vector3(point.x, point.y, target_point.z) + MoveConstraint.Type.PLANE_XY: + return Vector3(target_point.x, target_point.y, point.z) + MoveConstraint.Type.PLANE_XZ: + return Vector3(target_point.x, point.y, target_point.z) + MoveConstraint.Type.PLANE_YZ: + return Vector3(point.x, target_point.y, target_point.z) + _: + return point + + diff --git a/addons/cyclops_level_builder/snapping/move_constraint.gd b/addons/cyclops_level_builder/snapping/move_constraint.gd new file mode 100644 index 0000000..e2a4417 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/move_constraint.gd @@ -0,0 +1,28 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name MoveConstraint + +enum Type { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } + diff --git a/addons/cyclops_level_builder/snapping/snap_to_grid_util.gd b/addons/cyclops_level_builder/snapping/snap_to_grid_util.gd new file mode 100644 index 0000000..3c0e1f5 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snap_to_grid_util.gd @@ -0,0 +1,92 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name SnapToGridUtil + +#const feet_per_meter:float = 3.28084 + +@export var unit_size:float = 1 + +@export var use_subdivisions:bool = false +@export var grid_subdivisions:int = 10 + +@export var power_of_two_scale:int = 0 #Scaling 2^n + +#local transform matrix for grid +@export var grid_transform:Transform3D = Transform3D.IDENTITY: + get: + return grid_transform + set(value): + grid_transform = value + grid_transform_inv = grid_transform.affine_inverse() + +var grid_transform_inv:Transform3D = Transform3D.IDENTITY + +func load_from_cache(cache:Dictionary): + unit_size = cache.get("unit_size", 1) + use_subdivisions = cache.get("use_subdivisions", false) + grid_subdivisions = cache.get("grid_subdivisions", 10) + power_of_two_scale = cache.get("power_of_two_scale", 0) + #print("load grid_transform before") + grid_transform = SerialUtil.load_cache_transform_3d(cache.get("grid_transform", ""), Transform3D.IDENTITY) + #print("load grid_transform after ") + + if is_zero_approx(grid_transform.basis.determinant()): + #print("replace") + grid_transform = Transform3D.IDENTITY + #grid_transform = cache.get("grid_transform", Transform3D.IDENTITY) + +func save_to_cache(): + #print("save SnapToGridUtil") + return { + "unit_size": unit_size, + "use_subdivisions": use_subdivisions, + "grid_subdivisions": grid_subdivisions, + "power_of_two_scale": power_of_two_scale, + "grid_transform": SerialUtil.save_cache_transform_3d(grid_transform), + } + +#Point is in world space +func snap_point(point:Vector3)->Vector3: + + var p_local:Vector3 = grid_transform_inv * point + + #print("unit_size %s pow 2 %s" % [unit_size, pow(2, power_of_two_scale)]) + var scale:Vector3 = Vector3.ONE * unit_size * pow(2, power_of_two_scale) + if use_subdivisions: + scale /= float(grid_subdivisions) + + p_local = floor(p_local / scale + Vector3(.5, .5, .5)) * scale + + var target_point:Vector3 = grid_transform * p_local + + #print("point %s target_point %s scale %s" % [point, target_point, scale]) + return target_point + +func _to_string(): + return "unit_size %s use_subdiv %s subdiv %s pot %s xform %s" \ + % [unit_size, use_subdivisions, grid_subdivisions, power_of_two_scale, grid_transform] + + diff --git a/addons/cyclops_level_builder/snapping/snapping_manager.gd b/addons/cyclops_level_builder/snapping/snapping_manager.gd new file mode 100644 index 0000000..d35723e --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_manager.gd @@ -0,0 +1,45 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name SnappingManager + +var snap_enabled:bool +var snap_tool:CyclopsSnappingSystem + +func snap_point(point:Vector3, query:SnappingQuery)->Vector3: + if !snap_enabled || !snap_tool: + return point + + return snap_tool._snap_point(point, query) + +func snap_angle(angle:float, query:SnappingQuery)->float: + if !snap_enabled || !snap_tool: + return angle + + return snap_tool._snap_angle(angle, query) + + + + diff --git a/addons/cyclops_level_builder/snapping/snapping_query.gd b/addons/cyclops_level_builder/snapping/snapping_query.gd new file mode 100644 index 0000000..f6e1422 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_query.gd @@ -0,0 +1,35 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name SnappingQuery + + +var viewport_camera:Camera3D +var exclude_blocks:Array[NodePath] + + +func _init(viewport_camera:Camera3D = null, exclude_blocks:Array[NodePath] = []): + self.viewport_camera = viewport_camera + self.exclude_blocks = exclude_blocks diff --git a/addons/cyclops_level_builder/snapping/snapping_system_grid.gd b/addons/cyclops_level_builder/snapping/snapping_system_grid.gd new file mode 100644 index 0000000..1593f1a --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_grid.gd @@ -0,0 +1,68 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool + +extends CyclopsSnappingSystem +class_name SnappingSystemGrid + +const SNAPPING_TOOL_ID:String = "grid" + +var snap_to_grid_util:SnapToGridUtil = SnapToGridUtil.new() + +func _activate(plugin:CyclopsLevelBuilder): + super._activate(plugin) + + snap_to_grid_util = plugin.get_global_scene().calc_snap_to_grid_util() + + var cache:Dictionary = plugin.get_snapping_cache(SNAPPING_TOOL_ID) + snap_to_grid_util.load_from_cache(cache) + +func _deactivate(): + super._deactivate() + + flush_cache() + +func flush_cache(): + var cache:Dictionary = snap_to_grid_util.save_to_cache() + plugin.set_snapping_cache(SNAPPING_TOOL_ID, cache) + +#Point is in world space +func _snap_point(point:Vector3, query:SnappingQuery)->Vector3: + + var target_point = snap_to_grid_util.snap_point(point) + return target_point + +func _snap_angle(angle:float, query:SnappingQuery)->float: + var snap_angle:float = plugin.get_global_scene().settings.get_property(CyclopsGlobalScene.SNAPPING_GRID_ANGLE) + return floor(angle / snap_angle) * snap_angle + + +func _get_properties_editor()->Control: + var ed:SnappingSystemGridPropertiesEditor = preload("res://addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.tscn").instantiate() + ed.tool = self + + return ed + + + diff --git a/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.gd b/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.gd new file mode 100644 index 0000000..21e8d16 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.gd @@ -0,0 +1,204 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name SnappingSystemGridPropertiesEditor + +const meters_per_yard:float = 0.9144 +const meters_per_feet:float = 0.3048 + +var tool:SnappingSystemGrid: + get: + return tool + set(value): + #print("setting SnappingSystemGridPropertiesEditor props") + if value == tool: + return + tool = value + update_ui_from_props() + +func update_ui_from_props(): + #print("setting SnappingSystemGridPropertiesEditor props") + + if !tool: + return + + var properties:SnapToGridUtil = tool.snap_to_grid_util + %spin_power_of_two.value = properties.power_of_two_scale + %ed_unit_size.value = properties.unit_size + %check_use_subdiv.button_pressed = properties.use_subdivisions + %spin_subdiv.value = properties.grid_subdivisions + + var parts:Dictionary = MathUtil.decompose_matrix_3d(properties.grid_transform) + + %xform_translate.value = parts.translate + %xform_rotate.value = parts.rotate + %xform_shear.value = parts.shear + %xform_scale.value = parts.scale + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_spin_power_of_two_value_changed(value:float): + if !tool: + return + + tool.snap_to_grid_util.power_of_two_scale = value + tool.flush_cache() + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_POWER_OF_TWO_SCALE, int(value)) + CyclopsAutoload.save_settings() + +func _on_ed_unit_size_value_changed(value:float): + if !tool: + return + + tool.snap_to_grid_util.unit_size = value + tool.flush_cache() + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_UNIT_SIZE, value) + CyclopsAutoload.save_settings() + +func _on_check_use_subdiv_toggled(toggled_on:bool): + if !tool: + return + + tool.snap_to_grid_util.use_subdivisions = toggled_on + tool.flush_cache() + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_USE_SUBDIVISIONS, toggled_on) + CyclopsAutoload.save_settings() + +func _on_spin_subdiv_value_changed(value): + if !tool: + return + + tool.snap_to_grid_util.grid_subdivisions = value + tool.flush_cache() + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_SUBDIVISIONS, int(value)) + CyclopsAutoload.save_settings() + +func _on_xform_translate_value_changed(value): + if !tool: + return + + set_grid_transform_from_ui() + +func _on_xform_rotate_value_changed(value): + if !tool: + return + + set_grid_transform_from_ui() + +func _on_xform_scale_value_changed(value): + if !tool: + return + + set_grid_transform_from_ui() + +func _on_xform_shear_value_changed(value): + if !tool: + return + + set_grid_transform_from_ui() + +func set_grid_transform_from_ui(): + var xform:Transform3D = MathUtil.compose_matrix_3d(%xform_translate.value, + %xform_rotate.value, + EULER_ORDER_YXZ, + %xform_shear.value, + %xform_scale.value) + tool.snap_to_grid_util.grid_transform = xform + tool.flush_cache() + + CyclopsAutoload.save_settings() + +func _on_popup_presets_index_pressed(index): + #print("Preset ", index) + var unit_size:float + var subdiv:int + match index: + 0: + unit_size = 1 + subdiv = 10 + 1: + unit_size = meters_per_yard + subdiv = 3 + 2: + unit_size = meters_per_feet + subdiv = 12 + _: + return + + %ed_unit_size.value = unit_size + %spin_subdiv.value = subdiv + + tool.snap_to_grid_util.unit_size = unit_size + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_UNIT_SIZE, unit_size) + + tool.snap_to_grid_util.grid_subdivisions = subdiv + tool.flush_cache() + + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_SUBDIVISIONS, int(subdiv)) + + CyclopsAutoload.save_settings() + + +func _on_bn_presets_pressed(): + var rect:Rect2 = %bn_presets.get_global_rect() + %popup_presets.popup_on_parent(Rect2i(rect.position.x, rect.position.y + rect.size.y, 0, 0)) + + +func _on_bn_presets_transform_pressed(): + var rect:Rect2 = %bn_presets_transform.get_global_rect() + %popup_transform_presets.popup_on_parent(Rect2i(rect.position.x, rect.position.y + rect.size.y, 0, 0)) + + +func _on_popup_transform_presets_index_pressed(index): + var xform:Transform3D + match index: + 0: + xform = Transform3D.IDENTITY + 1: + var x:Vector3 = Vector3(1, 0, 0) + var y:Vector3 = Vector3(0, 1, 0) + var angle:float = deg_to_rad(60) + + var z:Vector3 = Vector3(cos(angle), 0, sin(angle)) + xform = Transform3D(Basis(x, y, z), Vector3.ZERO) + _: + return + + + tool.snap_to_grid_util.grid_transform = xform + tool.flush_cache() + + CyclopsAutoload.settings.set_property(CyclopsGlobalScene.SNAPPING_GRID_TRANSFORM, xform) + + CyclopsAutoload.save_settings() + update_ui_from_props() diff --git a/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.tscn b/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.tscn new file mode 100644 index 0000000..afb71a1 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.tscn @@ -0,0 +1,153 @@ +[gd_scene load_steps=4 format=3 uid="uid://c165arqp73p1k"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_system_grid_properties_editor.gd" id="1_jva1e"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_3bhn6"] +[ext_resource type="PackedScene" uid="uid://cphtpklx81l3w" path="res://addons/cyclops_level_builder/controls/vector3_edit.tscn" id="2_beo4d"] + +[node name="snapping_system_grid_properties" type="PanelContainer"] +offset_right = 400.0 +offset_bottom = 337.0 +script = ExtResource("1_jva1e") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Power of 2 Scale" + +[node name="spin_power_of_two" type="SpinBox" parent="VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +min_value = -16.0 +max_value = 16.0 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer3"] +layout_mode = 2 +text = "Unit size:" + +[node name="ed_unit_size" parent="VBoxContainer/HBoxContainer3" instance=ExtResource("2_3bhn6")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="bn_presets" type="Button" parent="VBoxContainer/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 +text = "Presets" + +[node name="check_use_subdiv" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Use Subdivisions" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Subdivisions" + +[node name="spin_subdiv" type="SpinBox" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +min_value = 1.0 +max_value = 16.0 +value = 10.0 +rounded = true +allow_greater = true + +[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer2"] +layout_mode = 2 +text = "Transform:" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer2"] +layout_mode = 2 + +[node name="bn_presets_transform" type="Button" parent="VBoxContainer/VBoxContainer2/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Transform Presets" + +[node name="GridContainer" type="GridContainer" parent="VBoxContainer/VBoxContainer2"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Translate" + +[node name="xform_translate" parent="VBoxContainer/VBoxContainer2/GridContainer" instance=ExtResource("2_beo4d")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="VBoxContainer/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Rotate" + +[node name="xform_rotate" parent="VBoxContainer/VBoxContainer2/GridContainer" instance=ExtResource("2_beo4d")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label3" type="Label" parent="VBoxContainer/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Scale" + +[node name="xform_scale" parent="VBoxContainer/VBoxContainer2/GridContainer" instance=ExtResource("2_beo4d")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label4" type="Label" parent="VBoxContainer/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Shear" + +[node name="xform_shear" parent="VBoxContainer/VBoxContainer2/GridContainer" instance=ExtResource("2_beo4d")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="popup_presets" type="PopupMenu" parent="."] +unique_name_in_owner = true +item_count = 3 +item_0/text = "Meters" +item_0/id = 0 +item_1/text = "Yards" +item_1/id = 1 +item_2/text = "Feet" +item_2/id = 2 + +[node name="popup_transform_presets" type="PopupMenu" parent="."] +unique_name_in_owner = true +item_count = 2 +item_0/text = "Cube Grid" +item_0/id = 0 +item_1/text = "Equilateral Triangles XZ" +item_1/id = 1 + +[connection signal="value_changed" from="VBoxContainer/HBoxContainer2/spin_power_of_two" to="." method="_on_spin_power_of_two_value_changed"] +[connection signal="value_changed" from="VBoxContainer/HBoxContainer3/ed_unit_size" to="." method="_on_ed_unit_size_value_changed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer3/bn_presets" to="." method="_on_bn_presets_pressed"] +[connection signal="toggled" from="VBoxContainer/check_use_subdiv" to="." method="_on_check_use_subdiv_toggled"] +[connection signal="value_changed" from="VBoxContainer/HBoxContainer/spin_subdiv" to="." method="_on_spin_subdiv_value_changed"] +[connection signal="pressed" from="VBoxContainer/VBoxContainer2/HBoxContainer/bn_presets_transform" to="." method="_on_bn_presets_transform_pressed"] +[connection signal="value_changed" from="VBoxContainer/VBoxContainer2/GridContainer/xform_translate" to="." method="_on_xform_translate_value_changed"] +[connection signal="value_changed" from="VBoxContainer/VBoxContainer2/GridContainer/xform_rotate" to="." method="_on_xform_rotate_value_changed"] +[connection signal="value_changed" from="VBoxContainer/VBoxContainer2/GridContainer/xform_scale" to="." method="_on_xform_scale_value_changed"] +[connection signal="value_changed" from="VBoxContainer/VBoxContainer2/GridContainer/xform_shear" to="." method="_on_xform_shear_value_changed"] +[connection signal="index_pressed" from="popup_presets" to="." method="_on_popup_presets_index_pressed"] +[connection signal="index_pressed" from="popup_transform_presets" to="." method="_on_popup_transform_presets_index_pressed"] diff --git a/addons/cyclops_level_builder/snapping/snapping_system_vertex.gd b/addons/cyclops_level_builder/snapping/snapping_system_vertex.gd new file mode 100644 index 0000000..a73590f --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_vertex.gd @@ -0,0 +1,117 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsSnappingSystem +class_name SnappingSystemVertex + +@export var max_radius:float = .2 + +const SNAPPING_TOOL_ID:String = "vertex" + +#var snap_to_grid_util:SnapToGridUtil = SnapToGridUtil.new() + +var settings:SnappingSystemVertexSettings = SnappingSystemVertexSettings.new() + +func _activate(plugin:CyclopsLevelBuilder): + super._activate(plugin) + + var cache:Dictionary = plugin.get_snapping_cache(SNAPPING_TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + super._deactivate() + + flush_cache() + +func flush_cache(): + var cache:Dictionary = settings.save_to_cache() + plugin.set_snapping_cache(SNAPPING_TOOL_ID, cache) + + +#Point is in world space +func _snap_point(point:Vector3, query:SnappingQuery)->Vector3: + + var screen_point:Vector2 = query.viewport_camera.unproject_position(point) + + var blocks:Array[CyclopsBlock] = plugin.get_blocks() + + var best_vertex:Vector3 = Vector3.INF + var best_dist:float = INF + + #print("Exclude blocks ", query.exclude_blocks) + for block in blocks: + if query.exclude_blocks.has(block.get_path()): + continue + + #print("check block ", block.name) + var ctrl_mesh:ConvexVolume = block.control_mesh + var bounds_local:AABB = ctrl_mesh.bounds + + var obj_center:Vector3 = block.global_transform * bounds_local.get_center() + var obj_corner:Vector3 = block.global_transform * bounds_local.position + var radius:float = obj_corner.distance_to(obj_center) + var obj_offset:Vector3 = obj_center + query.viewport_camera.global_basis.x * radius + + var screen_obj_center:Vector2 = query.viewport_camera.unproject_position(obj_center) + var screen_obj_offset:Vector2 = query.viewport_camera.unproject_position(obj_offset) + + #print("screen_point ", screen_point) + #print("screen_obj_center ", screen_obj_center) + #print("screen_obj_offset ", screen_obj_offset) + #print("screen_point.distance_to(screen_obj_center) ", screen_point.distance_to(screen_obj_center)) + #print("screen_obj_center.distance_to(screen_obj_offset) ", screen_obj_center.distance_to(screen_obj_offset)) + if screen_point.distance_to(screen_obj_center) > \ + screen_obj_center.distance_to(screen_obj_offset) + settings.snap_radius: + #Skip if bounding box text fails + continue + + + #print("snap block ", block.name) + for v_idx in ctrl_mesh.vertices.size(): + var v:ConvexVolume.VertexInfo = ctrl_mesh.vertices[v_idx] + var v_point_world:Vector3 = block.global_transform * v.point + var v_point_screen:Vector2 = query.viewport_camera.unproject_position(v_point_world) + + var dist:float = v_point_screen.distance_to(screen_point) + #print("dist ", dist, " settings.snap_radius ", settings.snap_radius) + if dist > settings.snap_radius: + continue + + #print("try vertex ", v_point_world) + if dist < best_dist: +# if dist < best_dist: + best_vertex = v_point_world + best_dist = dist + + + return best_vertex if is_finite(best_dist) else point + + +func _get_properties_editor()->Control: + var ed:SnappingSystemVertexPropertiesEditor = preload("res://addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.tscn").instantiate() + ed.snap_tool = self + + return ed + + diff --git a/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.gd b/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.gd new file mode 100644 index 0000000..8160636 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.gd @@ -0,0 +1,62 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name SnappingSystemVertexPropertiesEditor + +var snap_tool:SnappingSystemVertex: + get: + return snap_tool + set(value): + #print("setting SnappingSystemGridPropertiesEditor props") + if value == snap_tool: + return + snap_tool = value + update_ui_from_props() + +#var settings:SnappingSystemVertexSettings: + #get: + #return settings + #set(value): + ##print("setting SnappingSystemGridPropertiesEditor props") + #if value == settings: + #return + #settings = value + #update_ui_from_props() + +func update_ui_from_props(): + if !snap_tool: + return + + var settings = snap_tool.settings + + %snap_radius.value = settings.snap_radius + +func _on_snap_radius_value_changed(value): + if !snap_tool: + return + + snap_tool.settings.snap_radius = value + snap_tool.flush_cache() + diff --git a/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.tscn b/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.tscn new file mode 100644 index 0000000..c52e3e5 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=3 format=3 uid="uid://cbucsqmj5g1i1"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/snapping/snapping_system_vertex_properties_editor.gd" id="1_tibga"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_hl6or"] + +[node name="SnappingSystemVertexPropertiesEditor" type="PanelContainer"] +offset_right = 319.0 +offset_bottom = 210.0 +script = ExtResource("1_tibga") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Snap Radius +" + +[node name="snap_radius" parent="GridContainer" instance=ExtResource("2_hl6or")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[connection signal="value_changed" from="GridContainer/snap_radius" to="." method="_on_snap_radius_value_changed"] diff --git a/addons/cyclops_level_builder/snapping/snapping_system_vertex_settings.gd b/addons/cyclops_level_builder/snapping/snapping_system_vertex_settings.gd new file mode 100644 index 0000000..4380b97 --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_system_vertex_settings.gd @@ -0,0 +1,37 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name SnappingSystemVertexSettings + +#Snapping redous in viewport pixels +@export var snap_radius:float = 6 + +func load_from_cache(cache:Dictionary): + snap_radius = cache.get("snap_radius", 6.0) + +func save_to_cache()->Dictionary: + return { + "snap_radius": snap_radius + } diff --git a/addons/cyclops_level_builder/snapping/snapping_tag.gd b/addons/cyclops_level_builder/snapping/snapping_tag.gd new file mode 100644 index 0000000..190f14d --- /dev/null +++ b/addons/cyclops_level_builder/snapping/snapping_tag.gd @@ -0,0 +1,45 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name SnappingTag + +@export var name:String +@export var icon:Texture2D +@export_multiline var tooltip:String +@export var snapping_script:Script + + +var snapping_system:CyclopsSnappingSystem + +func _activate(plugin:CyclopsLevelBuilder): + if !snapping_script: + return + + if !snapping_system: + snapping_system = snapping_script.new() + + plugin.switch_to_snapping_system(snapping_system) + + diff --git a/addons/cyclops_level_builder/tools/block_alignment.gd b/addons/cyclops_level_builder/tools/block_alignment.gd new file mode 100644 index 0000000..d067997 --- /dev/null +++ b/addons/cyclops_level_builder/tools/block_alignment.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name BlockAlignment + +enum Type { ALIGN_TO_SURFACE, XY_PLANE, XZ_PLANE, YZ_PLANE } + +static func get_plane_normal(type:Type)->Vector3: + match type: + BlockAlignment.Type.XY_PLANE: + return Vector3(0, 0, 1) + BlockAlignment.Type.XZ_PLANE: + return Vector3(0, 1, 0) + BlockAlignment.Type.YZ_PLANE: + return Vector3(1, 0, 0) + _: + return Vector3(0, 1, 0) + diff --git a/addons/cyclops_level_builder/tools/cyclops_tool.gd b/addons/cyclops_level_builder/tools/cyclops_tool.gd new file mode 100644 index 0000000..d9da058 --- /dev/null +++ b/addons/cyclops_level_builder/tools/cyclops_tool.gd @@ -0,0 +1,189 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name CyclopsTool + +var builder:CyclopsLevelBuilder + +#func _init(_editorPlugin:EditorPlugin): +# editorPlugin = _editorPlugin + +func _activate(builder:CyclopsLevelBuilder): + self.builder = builder + +func _deactivate(): + pass + +func _get_tool_id()->String: + return "" + +func _draw_tool(viewport_camera:Camera3D): + pass + +func _get_tool_properties_editor()->Control: + return null + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_X: + if e.is_pressed(): + #print("cyc tool X") + var action:ActionDeleteSelectedBlocks = ActionDeleteSelectedBlocks.new(builder) + action._execute() + + return true + + if e.keycode == KEY_D: + if e.is_pressed(): + if e.shift_pressed && !Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + builder.switch_to_tool(ToolDuplicate.new()) + + return true + + if event is InputEventMouseButton: + var e:InputEventMouseButton = event + + if e.button_index == MOUSE_BUTTON_MIDDLE: + if e.alt_pressed: + if e.is_pressed(): + if builder.get_active_block(): + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + +# var start_pos:Vector3 = origin + builder.block_create_distance * dir +# var w2l = builder.active_node.global_transform.inverse() +# var origin_local:Vector3 = w2l * origin +# var dir_local:Vector3 = w2l.basis * dir + + var result:IntersectResults = builder.active_node.intersect_ray_closest(origin, dir) + if result: + var ed_iface:EditorInterface = builder.get_editor_interface() + var base_control:Control = ed_iface.get_base_control() + + #viewport_camera + var new_cam_origin:Vector3 = result.position + \ + viewport_camera.global_transform.basis.z * builder.block_create_distance + viewport_camera.global_transform.origin = new_cam_origin + return true + + return false + + + +func to_local(point:Vector3, world_to_local:Transform3D, grid_step_size:float)->Vector3: + var p_local:Vector3 = world_to_local * point + + return MathUtil.snap_to_grid(p_local, grid_step_size) + + +func calc_empty_space_draw_plane_origin(viewport_camera:Camera3D, draw_plane_point:Vector3 = Vector3.ZERO, draw_plane_normal:Vector3 = Vector3.UP): + var active_block:CyclopsBlock = builder.get_active_block() + var block_xfrom:Transform3D = active_block.global_transform + if active_block: + var vol:ConvexVolume = active_block.control_mesh + var bounds:AABB = vol.calc_bounds_xform(block_xfrom) + var plane:Plane = Plane(draw_plane_normal, bounds.get_center()) + + var p0:Vector3 = bounds.position + var p1:Vector3 = bounds.position + bounds.size + if plane.is_point_over(viewport_camera.global_transform.origin): + if plane.is_point_over(p0): + draw_plane_point = p1 + else: + draw_plane_point = p0 + else: + if plane.is_point_over(p0): + draw_plane_point = p0 + else: + draw_plane_point = p1 + + return draw_plane_point + +func calc_hit_point_empty_space(origin:Vector3, dir:Vector3, viewport_camera:Camera3D = null, base_plane_origin:Vector3 = Vector3.ZERO, drag_floor_normal:Vector3 = Vector3.UP): + #print("Miss") + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + + var angle_y_axis:float = acos(dir.dot(Vector3.UP)) + if angle_y_axis > PI / 2 - drag_angle_limit && angle_y_axis < PI / 2 + drag_angle_limit: + #Nearly parallel with ground plane + if abs(dir.z) > abs(dir.x): + drag_floor_normal = Vector3.FORWARD + else: + drag_floor_normal = Vector3.LEFT + + #print("base_plane_normal ", base_plane_normal) + + var hit_base:Vector3 = MathUtil.intersect_plane(origin, dir, base_plane_origin, drag_floor_normal) + #print("hit_base 1 ", hit_base) + + if (hit_base - origin).dot(dir) < 0: + #Hit point is behind camera + var plane_offset:Vector3 = origin.project(drag_floor_normal) + base_plane_origin += plane_offset * 2 + hit_base = MathUtil.intersect_plane(origin, dir, base_plane_origin, drag_floor_normal) + + #print("base_plane_origin ", base_plane_origin) + #print("hit_base ", hit_base) + + var block_drag_p0:Vector3 = builder.get_snapping_manager().snap_point(hit_base, SnappingQuery.new(viewport_camera)) + + return [block_drag_p0, drag_floor_normal] + +func calc_active_block_orthogonal_height(plane_origin:Vector3, drag_floor_normal:Vector3)->float: + var active_block:CyclopsBlock = builder.get_active_block() + var block_bounds:AABB = active_block.control_mesh.calc_bounds_xform(active_block.global_transform) + var plane:Plane = Plane(drag_floor_normal, block_bounds.get_center()) + var p0_over:bool = plane.is_point_over(plane_origin) + + var height:float = abs(block_bounds.size.dot(drag_floor_normal)) + if p0_over: + height = -height + + return height + +func select_block_under_cursor(viewport_camera:Camera3D, mouse_pos:Vector2): + var origin:Vector3 = viewport_camera.project_ray_origin(mouse_pos) + var dir:Vector3 = viewport_camera.project_ray_normal(mouse_pos) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result: + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.block_paths.append(result.object.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + _deactivate() + _activate(builder) + diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_base.gd b/addons/cyclops_level_builder/tools/gizmos/gizmo_base.gd new file mode 100644 index 0000000..0b7d28f --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_base.gd @@ -0,0 +1,92 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + + +@tool +extends Node3D +class_name GizmoBase + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func intersect_part(ray_origin:Vector3, ray_dir:Vector3, viewport_camera:Camera3D, mesh_inst:MeshInstance3D)->MathUtil.IntersectTriangleResult: + var proj:Projection = viewport_camera.get_camera_projection() + + #Calc modelview matrix + var view_inv_matrix:Transform3D = viewport_camera.global_transform.affine_inverse() + var mv:Projection = Projection(view_inv_matrix * mesh_inst.global_transform) + #Static size adjustment + if proj[3][3] != 0: + var h:float = abs(1 / (2 * proj[1][1])) + var sc = h * 2 + mv[0] *= sc + mv[1] *= sc + mv[2] *= sc + else: + var sc:float = -mv[3].z + mv[0] *= sc + mv[1] *= sc + mv[2] *= sc + + var model_mtx:Projection = Projection(viewport_camera.global_transform) * mv + + var mesh:Mesh = mesh_inst.mesh + var tris:PackedVector3Array = mesh.get_faces() + for i in range(0, tris.size(), 3): + var p0:Vector3 = tris[i] + var p1:Vector3 = tris[i + 1] + var p2:Vector3 = tris[i + 2] + + var p0_t:Vector3 = proj_mul_point(model_mtx, p0) + var p1_t:Vector3 = proj_mul_point(model_mtx, p1) + var p2_t:Vector3 = proj_mul_point(model_mtx, p2) + + #print("tri world %s %s %s" % [p0_t, p1_t, p2_t]) + var res = MathUtil.intersect_triangle(ray_origin, ray_dir, p0_t, p1_t, p2_t) + + if res: + return res + + return null + + +func proj_mul_point(m:Projection, p:Vector3)->Vector3: + var p4:Vector4 = Vector4(p.x, p.y, p.z, 1) + var p4_t = m * p4 + p4_t /= p4_t.w + return Vector3(p4_t.x, p4_t.y, p4_t.z) + + +func proj_mul_vec(m:Projection, p:Vector3)->Vector3: + var p4:Vector4 = Vector4(p.x, p.y, p.z, 0) + var p4_t = m * p4 + p4_t /= p4_t.w + return Vector3(p4_t.x, p4_t.y, p4_t.z) diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.gd b/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.gd new file mode 100644 index 0000000..4ee039c --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.gd @@ -0,0 +1,72 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + + +@tool +extends GizmoBase +class_name GizmoRotate + +enum Part { NONE, PLANE_XY, PLANE_XZ, PLANE_YZ, VIEWPORT, TRACKBALL } + + +class IntersectResult: + var part:Part + var pos_world:Vector3 + + +# Called when the node enters the scene tree for the first time. +func _ready(): + $gizmo_rotate/rot_axis_viewport.visible = false + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + get_viewport() + pass + + +func intersect(ray_origin:Vector3, ray_dir:Vector3, viewport_camera:Camera3D)->IntersectResult: + var result:IntersectResult = IntersectResult.new() + result.part = Part.NONE + + for child in $gizmo_rotate.get_children(): + var part_res:MathUtil.IntersectTriangleResult = intersect_part(ray_origin, ray_dir, viewport_camera, child) + + if part_res: + result.pos_world = part_res.position + match child.name: + "rot_axis_x": + result.part = Part.PLANE_YZ + "rot_axis_y": + result.part = Part.PLANE_XZ + "rot_axis_z": + result.part = Part.PLANE_XY + + return result +# print("hit " + child.name) +# return + + return null + + diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.tscn b/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.tscn new file mode 100644 index 0000000..e56cbad --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=7 format=3 uid="uid://cyi4s4loi2i15"] + +[ext_resource type="PackedScene" uid="uid://p26cj0m5amq0" path="res://addons/cyclops_level_builder/art/gizmos/gizmo_rotate.glb" id="1_knqem"] +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.gd" id="1_vhpbh"] +[ext_resource type="Material" uid="uid://bv4k8o22vl6ub" path="res://addons/cyclops_level_builder/materials/gizmo_axis_y_material.tres" id="2_538x0"] +[ext_resource type="Material" uid="uid://drodm0wf41vin" path="res://addons/cyclops_level_builder/materials/gizmo_axis_x_material.tres" id="3_7q6t6"] +[ext_resource type="Material" uid="uid://divsg4lq712rw" path="res://addons/cyclops_level_builder/materials/gizmo_axis_z_material.tres" id="4_eibo1"] +[ext_resource type="Material" uid="uid://cqvh1j2n71fej" path="res://addons/cyclops_level_builder/materials/gizmo_axis_special_material.tres" id="5_ib271"] + +[node name="GizmoRotate" type="Node3D"] +script = ExtResource("1_vhpbh") + +[node name="gizmo_rotate" parent="." instance=ExtResource("1_knqem")] +transform = Transform3D(0.2, 0, 0, 0, 0.2, 0, 0, 0, 0.2, 0, 0, 0) + +[node name="rot_axis_y" parent="gizmo_rotate" index="0"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("2_538x0") + +[node name="rot_axis_x" parent="gizmo_rotate" index="1"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("3_7q6t6") + +[node name="rot_axis_z" parent="gizmo_rotate" index="2"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("4_eibo1") + +[node name="rot_axis_viewport" parent="gizmo_rotate" index="3"] +visible = false +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("5_ib271") + +[editable path="gizmo_rotate"] diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_test.gd b/addons/cyclops_level_builder/tools/gizmos/gizmo_test.gd new file mode 100644 index 0000000..5685a28 --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_test.gd @@ -0,0 +1,23 @@ +extends Node3D + +func _input(event): + if event is InputEventMouseButton: + var e:InputEventMouseButton = event + + if e.is_pressed(): + + var cam:Camera3D = %Camera3D + var ray_norm:Vector3 = cam.project_ray_normal(e.position) + var ray_orig:Vector3 = cam.project_ray_origin(e.position) + %gizmo_translate.intersect(ray_orig, ray_norm, cam) + + pass + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_test.tscn b/addons/cyclops_level_builder/tools/gizmos/gizmo_test.tscn new file mode 100644 index 0000000..b98711a --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_test.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=4 format=3 uid="uid://bykffnaq3h1ar"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/gizmos/gizmo_test.gd" id="1_0e45b"] +[ext_resource type="PackedScene" uid="uid://2pi622xycrd6" path="res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn" id="2_2n1ok"] + +[sub_resource type="SphereMesh" id="SphereMesh_off5f"] +radius = 0.02 +height = 0.04 + +[node name="gizmo_test" type="Node3D"] +script = ExtResource("1_0e45b") + +[node name="gizmo_translate" parent="." instance=ExtResource("2_2n1ok")] +unique_name_in_owner = true + +[node name="Camera3D" type="Camera3D" parent="."] +unique_name_in_owner = true +transform = Transform3D(0.930759, 0.202432, -0.30448, -0.0272565, 0.868848, 0.494329, 0.364615, -0.451802, 0.814206, -0.279477, 1.02398, 1.38375) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.126, 0, 0.266) +mesh = SubResource("SphereMesh_off5f") + +[node name="MeshInstance3D2" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.266, 0, 0.126) +mesh = SubResource("SphereMesh_off5f") + +[node name="MeshInstance3D3" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.266, 0, 0.266) +mesh = SubResource("SphereMesh_off5f") + +[node name="MeshInstance3D4" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.126, 0, 0.126) +mesh = SubResource("SphereMesh_off5f") + +[editable path="gizmo_translate"] +[editable path="gizmo_translate/gizmo_translate"] diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.gd b/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.gd new file mode 100644 index 0000000..9c3f248 --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.gd @@ -0,0 +1,73 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + + +@tool +extends GizmoBase +class_name GizmoTranslate + +enum Part { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ } + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + +class IntersectResult: + var part:Part + var pos_world:Vector3 + +func intersect(ray_origin:Vector3, ray_dir:Vector3, viewport_camera:Camera3D)->IntersectResult: + var result:IntersectResult = IntersectResult.new() + result.part = Part.NONE +# if intersect_part(ray_origin, ray_dir, viewport_camera, $gizmo_translate/axis_y): + for child in $gizmo_translate.get_children(): + var part_res:MathUtil.IntersectTriangleResult = intersect_part(ray_origin, ray_dir, viewport_camera, child) + + if part_res: + result.pos_world = part_res.position + match child.name: + "axis_x": + result.part = Part.AXIS_X + "axis_y": + result.part = Part.AXIS_Y + "axis_z": + result.part = Part.AXIS_Z + "plane_xy": + result.part = Part.PLANE_XY + "plane_xz": + result.part = Part.PLANE_XZ + "plane_yz": + result.part = Part.PLANE_YZ + + return result +# print("hit " + child.name) +# return + + return null + + diff --git a/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn b/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn new file mode 100644 index 0000000..ebed020 --- /dev/null +++ b/addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn @@ -0,0 +1,45 @@ +[gd_scene load_steps=6 format=3 uid="uid://2pi622xycrd6"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.gd" id="1_fyqe0"] +[ext_resource type="PackedScene" uid="uid://ujq3kes2sdfu" path="res://addons/cyclops_level_builder/art/gizmos/gizmo_translate.glb" id="1_ljs46"] +[ext_resource type="Material" uid="uid://bv4k8o22vl6ub" path="res://addons/cyclops_level_builder/materials/gizmo_axis_y_material.tres" id="3_tsii4"] +[ext_resource type="Material" uid="uid://divsg4lq712rw" path="res://addons/cyclops_level_builder/materials/gizmo_axis_z_material.tres" id="4_0qd8v"] +[ext_resource type="Material" uid="uid://drodm0wf41vin" path="res://addons/cyclops_level_builder/materials/gizmo_axis_x_material.tres" id="5_xvd3e"] + +[node name="gizmo_translate" type="Node3D"] +script = ExtResource("1_fyqe0") + +[node name="gizmo_translate" parent="." instance=ExtResource("1_ljs46")] +transform = Transform3D(0.2, 0, 0, 0, 0.2, 0, 0, 0, 0.2, 0, 0, 0) + +[node name="axis_y" parent="gizmo_translate" index="0"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("3_tsii4") + +[node name="axis_z" parent="gizmo_translate" index="1"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("4_0qd8v") + +[node name="axis_x" parent="gizmo_translate" index="2"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("5_xvd3e") + +[node name="plane_xz" parent="gizmo_translate" index="3"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("3_tsii4") + +[node name="plane_yz" parent="gizmo_translate" index="4"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("5_xvd3e") + +[node name="plane_xy" parent="gizmo_translate" index="5"] +lod_bias = 128.0 +ignore_occlusion_culling = true +surface_material_override/0 = ExtResource("4_0qd8v") + +[editable path="gizmo_translate"] diff --git a/addons/cyclops_level_builder/tools/tool_block.gd b/addons/cyclops_level_builder/tools/tool_block.gd new file mode 100644 index 0000000..342ddc2 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_block.gd @@ -0,0 +1,360 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolBlock + +const TOOL_ID:String = "block" + +enum ToolState { NONE, READY, BLOCK_BASE, BLOCK_HEIGHT, MOVE_FACE } +var tool_state:ToolState = ToolState.NONE + +#enum BlockAlign { ALIGN_TO_SURFACE, XY_PLANE, XZ_PLANE, YZ_PLANE } + +#var drag_angle_limit:float = deg_to_rad(5) + +var viewport_camera_start:Camera3D +var event_start:InputEventMouseButton + +var block_drag_cur:Vector3 +var block_drag_p0:Vector3 +var block_drag_p1:Vector3 +var block_drag_p2:Vector3 + +var drag_floor_normal:Vector3 + +var settings:ToolBlockSettings = ToolBlockSettings.new() + +#Keep a copy of move command here while we are building it +var cmd_move_face:CommandMoveFacePlanar +var move_face_origin:Vector3 #Kep track of the origin when moving a face + +var base_points:PackedVector3Array + +var mouse_hover_pos:Vector2 + +func _get_tool_id()->String: + return TOOL_ID + +func _get_tool_properties_editor()->Control: + var ed:ToolBlockSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_block_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func start_block_drag(viewport_camera:Camera3D, event:InputEvent): + var blocks_root:Node = builder.get_block_add_parent() + var e:InputEventMouseButton = event + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + #print("origin %s dir %s" % [origin, dir]) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + #print("result %s" % result) + + if result && settings.block_alignment == BlockAlignment.Type.ALIGN_TO_SURFACE: + #print("Hit! %s" % result) + drag_floor_normal = MathUtil.snap_to_best_axis_normal(result.get_world_normal()) + + var start_pos:Vector3 = result.get_world_position() + + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + #block_drag_p0 = MathUtil.snap_to_grid(start_pos, grid_step_size) + + block_drag_p0 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + + + if e.ctrl_pressed: + tool_state = ToolState.MOVE_FACE + + cmd_move_face = CommandMoveFacePlanar.new() + cmd_move_face.builder = builder + cmd_move_face.blocks_root_path = builder.get_block_add_parent().get_path() + cmd_move_face.block_path = result.object.get_path() + cmd_move_face.face_index = result.face_index + cmd_move_face.lock_uvs = builder.lock_uvs + cmd_move_face.move_dir_normal = result.object.control_mesh.faces[result.face_id].normal + + move_face_origin = result.object.global_transform * result.position + #print("moving face move_face_origin %s" % move_face_origin) + + else: + tool_state = ToolState.BLOCK_BASE + + + else: + #print("Miss") + var draw_plane_point:Vector3 = Vector3.ZERO + var draw_plane_normal:Vector3 = BlockAlignment.get_plane_normal(settings.block_alignment) + + if settings.match_selected_block: + draw_plane_point = calc_empty_space_draw_plane_origin(viewport_camera, draw_plane_point, draw_plane_normal) + + var hit_result = calc_hit_point_empty_space(origin, dir, viewport_camera, draw_plane_point, draw_plane_normal) + block_drag_p0 = hit_result[0] + drag_floor_normal = hit_result[1] + + tool_state = ToolState.BLOCK_BASE + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if tool_state == ToolState.BLOCK_BASE: + global_scene.draw_loop(base_points, true, global_scene.tool_material) + global_scene.draw_points(base_points, global_scene.vertex_tool_material) + + if tool_state == ToolState.BLOCK_HEIGHT: + global_scene.draw_cube(block_drag_p0, block_drag_p1, block_drag_cur, global_scene.tool_material, global_scene.vertex_tool_material) + + +func create_block(): + block_drag_p2 = block_drag_cur +# print("Adding block %s %s %s" % [block_drag_p0, block_drag_p1, block_drag_p2]) + + var bounds:AABB = AABB(block_drag_p0, Vector3.ZERO) + bounds = bounds.expand(block_drag_p1) + bounds = bounds.expand(block_drag_p2) + + if bounds.has_volume(): + var blocks_root:Node = builder.get_block_add_parent() + + var command:CommandAddBlock = CommandAddBlock.new() + + command.builder = builder + command.blocks_root_path = blocks_root.get_path() + command.block_name = GeneralUtil.find_unique_name(blocks_root, "Block_") + command.bounds = bounds +# command.origin = block_drag_p0 + command.uv_transform = builder.tool_uv_transform + command.material_path = builder.tool_material_path + command.collision_type = settings.collision_type + command.collision_layers = settings.collision_layer + command.collision_mask = settings.collision_mask + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + command.add_to_undo_manager(undo) + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + #print("tool_block gui_input %s" % event) + + var blocks_root:Node = builder.get_block_add_parent() + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.NONE + return true + + if e.keycode == KEY_Q && e.alt_pressed: + if e.is_pressed(): + select_block_under_cursor(viewport_camera, mouse_hover_pos) + #var origin:Vector3 = viewport_camera.project_ray_origin(mouse_hover_pos) + #var dir:Vector3 = viewport_camera.project_ray_normal(mouse_hover_pos) + # + #var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + #if result: + #var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + #cmd.builder = builder + #cmd.block_paths.append(result.object.get_path()) + # + #if cmd.will_change_anything(): + #var undo:EditorUndoRedoManager = builder.get_undo_redo() + #cmd.add_to_undo_manager(undo) + # + #_deactivate() + #_activate(builder) + + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + if tool_state == ToolState.NONE: + event_start = event + viewport_camera_start = viewport_camera + + tool_state = ToolState.READY + + else: + if tool_state == ToolState.READY: + + tool_state = ToolState.NONE + + elif tool_state == ToolState.BLOCK_BASE: + block_drag_p1 = block_drag_cur + + var camera_dir:Vector3 = viewport_camera.project_ray_normal(e.position) + var angle_with_base:float = acos(drag_floor_normal.dot(camera_dir)) + + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + var height = settings.default_block_height + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(block_drag_p0, drag_floor_normal) + + block_drag_cur = block_drag_p1 + drag_floor_normal * height + + create_block() + + tool_state = ToolState.NONE + else: + + tool_state = ToolState.BLOCK_HEIGHT + + elif tool_state == ToolState.BLOCK_HEIGHT: + create_block() + + tool_state = ToolState.NONE + + + elif tool_state == ToolState.MOVE_FACE: + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_move_face.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + return true + + #elif e.button_index == MOUSE_BUTTON_RIGHT: + #if tool_state == ToolState.BLOCK_BASE || tool_state == ToolState.BLOCK_HEIGHT: + #if e.is_pressed(): + #tool_state = ToolState.NONE + #return true + + + elif event is InputEventMouseMotion: + + var e:InputEventMouseMotion = event + + mouse_hover_pos = e.position + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var start_pos:Vector3 = origin + builder.block_create_distance * dir + + #print("tool_state %s" % tool_state) + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return super._gui_input(viewport_camera, event) + + if tool_state == ToolState.NONE: + if e.ctrl_pressed: + #block_drag_cur = MathUtil.intersect_plane(origin_local, dir_local, block_drag_p0_local, drag_floor_normal) + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + #print("picked result %s" % result) + if result: + var block:CyclopsBlock = result.object + var convex_mesh:ConvexVolume = block.control_mesh + base_points = convex_mesh.get_face(result.face_index).get_points() + return true + + return false + + elif tool_state == ToolState.READY: + var offset:Vector2 = e.position - event_start.position + if offset.length_squared() > MathUtil.square(builder.drag_start_radius): + start_block_drag(viewport_camera_start, event_start) + + return true + + elif tool_state == ToolState.BLOCK_BASE: + + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, drag_floor_normal) + + #print("block_drag_cur %s" % block_drag_cur) + + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + #print("block_drag_cur snapped %s" % block_drag_cur) + + #Draw tool + var p01:Vector3 + var p10:Vector3 + if abs(drag_floor_normal.x) > abs(drag_floor_normal.y) and abs(drag_floor_normal.x) > abs(drag_floor_normal.z): + p01 = Vector3(block_drag_p0.x, block_drag_p0.y, block_drag_cur.z) + p10 = Vector3(block_drag_p0.x, block_drag_cur.y, block_drag_p0.z) + elif abs(drag_floor_normal.y) > abs(drag_floor_normal.z): + p01 = Vector3(block_drag_p0.x, block_drag_p0.y, block_drag_cur.z) + p10 = Vector3(block_drag_cur.x, block_drag_p0.y, block_drag_p0.z) + else: + p01 = Vector3(block_drag_p0.x, block_drag_cur.y, block_drag_p0.z) + p10 = Vector3(block_drag_cur.x, block_drag_p0.y, block_drag_p0.z) + + base_points = [block_drag_p0, p01, block_drag_cur, p10] + + return true + + elif tool_state == ToolState.BLOCK_HEIGHT: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, block_drag_p1, drag_floor_normal) + + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + return true + + elif tool_state == ToolState.MOVE_FACE: + var drag_to:Vector3 = MathUtil.closest_point_on_line(origin, dir, move_face_origin, cmd_move_face.move_dir_normal) + #print("move_face_origin %s norm %s" % [move_face_origin, cmd_move_face.move_dir_normal]) + + drag_to = builder.get_snapping_manager().snap_point(drag_to, SnappingQuery.new(viewport_camera)) + + #print("move_face drag_to %s" % [drag_to]) + cmd_move_face.move_amount = (drag_to - move_face_origin).dot(cmd_move_face.move_dir_normal) + #print("move by %s" % [drag_to - move_face_origin]) + + cmd_move_face.do_it_intermediate() + + return true + + return super._gui_input(viewport_camera, event) + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + #print("loaded cache ", str(cache)) + settings.load_from_cache(cache) + +func _deactivate(): + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + diff --git a/addons/cyclops_level_builder/tools/tool_block_settings.gd b/addons/cyclops_level_builder/tools/tool_block_settings.gd new file mode 100644 index 0000000..57d468b --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_block_settings.gd @@ -0,0 +1,54 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolBlockSettings + +@export var block_alignment:BlockAlignment.Type = BlockAlignment.Type.ALIGN_TO_SURFACE +@export var match_selected_block:bool = true +@export var default_block_elevation:float = 0 +@export var default_block_height:float = 1 +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export_flags_3d_physics var collision_layer:int = 1 +@export_flags_3d_physics var collision_mask:int = 1 + +func load_from_cache(cache:Dictionary): + block_alignment = cache.get("block_alignment", BlockAlignment.Type.ALIGN_TO_SURFACE) + match_selected_block = cache.get("match_selected_block", true) + default_block_elevation = cache.get("default_block_elevation", 0) + default_block_height = cache.get("default_block_height", 1) + collision_type = cache.get("collision_type", Collision.Type.STATIC) + collision_layer = cache.get("collision_layer", 1) + collision_mask = cache.get("collision_mask", 1) + +func save_to_cache(): + return { + "block_alignment": block_alignment, + "match_selected_block": match_selected_block, + "default_block_elevation": default_block_elevation, + "default_block_height": default_block_height, + "collision_type": collision_type, + "collision_layer": collision_layer, + "collision_mask": collision_mask, + } diff --git a/addons/cyclops_level_builder/tools/tool_block_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_block_settings_editor.gd new file mode 100644 index 0000000..55e0c21 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_block_settings_editor.gd @@ -0,0 +1,92 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolBlockSettingsEditor + +var settings:ToolBlockSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + +func _ready(): + %collision_type.clear() + for text in Collision.Type.keys(): + %collision_type.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + if !settings: + %check_match_selected_block.disabled = true + %default_block_elevation.disabled = true + %default_block_height.disabled = true + return + + %check_match_selected_block.disabled = false + %check_match_selected_block.button_pressed = settings.match_selected_block + %default_block_elevation.disabled = false + %default_block_elevation.value = settings.default_block_elevation + %default_block_height.disabled = false + %default_block_height.value = settings.default_block_height + + %alignment_type.selected = settings.block_alignment + + %collision_type.selected = settings.collision_type + %collision_layers.value = settings.collision_layer + %collision_mask.value = settings.collision_mask + + +func _on_default_block_height_value_changed(value:float): + settings.default_block_height = value + + +func _on_default_block_elevation_value_changed(value:float): + settings.default_block_elevation = value + + +func _on_check_match_selected_block_toggled(value:bool): + settings.match_selected_block = value + + +func _on_collision_layers_value_changed(value): + settings.collision_layer = value + + +func _on_collision_mask_value_changed(value): + settings.collision_mask = value + +func _on_collision_type_item_selected(index): + settings.collision_type = index + + +func _on_alignment_type_item_selected(index): + settings.block_alignment = index diff --git a/addons/cyclops_level_builder/tools/tool_block_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_block_settings_editor.tscn new file mode 100644 index 0000000..081b730 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_block_settings_editor.tscn @@ -0,0 +1,132 @@ +[gd_scene load_steps=3 format=3 uid="uid://baccfnd0b5yqv"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_block_settings_editor.gd" id="1_nvmrk"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_1r7f8"] + +[node name="ToolBlockSettings" type="PanelContainer"] +offset_right = 413.0 +offset_bottom = 232.0 +script = ExtResource("1_nvmrk") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Type" + +[node name="collision_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "NONE" +popup/item_0/id = 0 +popup/item_1/text = "STATIC" +popup/item_1/id = 1 +popup/item_2/text = "KINEMATIC" +popup/item_2/id = 2 +popup/item_3/text = "RIGID" +popup/item_3/id = 3 + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Layers" + +[node name="collision_layers" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label3" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Mask" + +[node name="collision_mask" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label5" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Alignment" + +[node name="alignment_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "Align to surface" +popup/item_0/id = 0 +popup/item_1/text = "XY Plane" +popup/item_1/id = 1 +popup/item_2/text = "XZ Plane" +popup/item_2/id = 2 +popup/item_3/text = "YZ Plane" +popup/item_3/id = 3 + +[node name="Label4" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Match Active Block" + +[node name="check_match_selected_block" type="CheckBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "When drawing in empty space, copy elevation and height properties from currently selected block." +disabled = true +text = "On" + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Orthogonal Viewport:" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Elevation" + +[node name="default_block_elevation" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_1r7f8")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Height" + +[node name="default_block_height" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_1r7f8")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/collision_type" to="." method="_on_collision_type_item_selected"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_layers" to="." method="_on_collision_layers_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_mask" to="." method="_on_collision_mask_value_changed"] +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/alignment_type" to="." method="_on_alignment_type_item_selected"] +[connection signal="toggled" from="PanelContainer/VBoxContainer/GridContainer/check_match_selected_block" to="." method="_on_check_match_selected_block_toggled"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_elevation" to="." method="_on_default_block_elevation_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_height" to="." method="_on_default_block_height_value_changed"] diff --git a/addons/cyclops_level_builder/tools/tool_clip.gd b/addons/cyclops_level_builder/tools/tool_clip.gd new file mode 100644 index 0000000..a10a80a --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_clip.gd @@ -0,0 +1,170 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolClip + +const TOOL_ID:String = "clip" + +enum ToolState { READY, PICK_POINTS, PICK_SIDE } +var tool_state:ToolState = ToolState.READY + +var clip_points:PackedVector3Array +var clip_normals:PackedVector3Array +var clip_block:CyclopsBlock + +func _get_tool_id()->String: + return TOOL_ID + +func has_clip_point(point:Vector3)->bool: + for p in clip_points: + if p.is_equal_approx(point): + return true + return false + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if !clip_points.is_empty(): + global_scene.draw_points(clip_points, global_scene.vertex_tool_material) + + if clip_points.size() >= 2: + global_scene.draw_loop(clip_points, false, global_scene.tool_material) + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + var blocks_root:Node = builder.get_block_add_parent() + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_BACKSPACE: + if e.is_pressed(): + if !clip_points.is_empty(): + var count:int = clip_points.size() + clip_points.remove_at(count - 1) + clip_normals.remove_at(count - 1) + if clip_points.is_empty(): + clip_block = null + + return true + + elif e.keycode == KEY_ESCAPE: + clip_points.clear() + clip_normals.clear() + clip_block = null +# _draw_tool(viewport_camera) + return true + + elif e.keycode == KEY_ENTER: + #Cut at plane + var cut_plane:Plane + + #for p in clip_points: + #print("clip ", p) + + if clip_points.size() == 3: + cut_plane = Plane(clip_points[0], clip_points[1], clip_points[2]) + elif clip_points.size() == 2: + var dir:Vector3 = clip_points[1] - clip_points[0] + var face_dir:Vector3 = clip_normals[0].cross(dir) + cut_plane = Plane(face_dir.normalized(), clip_points[0]) + else: + #Cannot cut with fewer than 2 points + return true + + var cmd:CommandClipBlock = CommandClipBlock.new() + cmd.builder = builder + cmd.blocks_root_path = blocks_root.get_path() + cmd.block_path = clip_block.get_path() + cmd.block_sibling_name = GeneralUtil.find_unique_name(blocks_root, "Block_") + cmd.cut_plane = cut_plane + cmd.material_path = builder.tool_material_path + cmd.uv_transform = builder.tool_uv_transform + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + #Clean up + clip_points.clear() + clip_normals.clear() + clip_block = null + +# _draw_tool(viewport_camera) + + return true + + if event is InputEventMouseButton: + + var e:InputEventMouseButton = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + #var p:Vector3 = to_local(result.position, blocks_root.global_transform.inverse(), grid_step_size) +# var p:Vector3 = MathUtil.snap_to_grid(result.get_world_position(), grid_step_size) + var p_hit:Vector3 = result.get_world_position() + var p_norm:Vector3 = result.get_world_normal() + var p:Vector3 = builder.get_snapping_manager().snap_point(p_hit, SnappingQuery.new(viewport_camera)) + p = MathUtil.closest_point_on_plane(p, p_hit, p_norm) + + if !has_clip_point(p): + if clip_points.is_empty(): + clip_block = result.object + + if clip_points.size() < 3: + clip_points.append(p) + clip_normals.append(p_norm) + else: + clip_points[2] = p + clip_normals[2] = p_norm + +# _draw_tool(viewport_camera) + + return true + + return false + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() diff --git a/addons/cyclops_level_builder/tools/tool_cylinder.gd b/addons/cyclops_level_builder/tools/tool_cylinder.gd new file mode 100644 index 0000000..129c31a --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_cylinder.gd @@ -0,0 +1,297 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolCylinder + +const TOOL_ID:String = "cylinder" + +enum ToolState { READY, FIRST_RING, SECOND_RING, DRAG_HEIGHT } +var tool_state:ToolState = ToolState.READY + +#@export var segments:int = 16 +#@export var tube:bool = false +var settings:ToolCylinderSettings = ToolCylinderSettings.new() + +var floor_normal:Vector3 +var base_center:Vector3 +var block_drag_cur:Vector3 +var drag_offset:Vector3 +var first_ring_radius:float +var second_ring_radius:float + + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + + +func _get_tool_properties_editor()->Control: +# var res_insp:ResourceInspector = preload("res://addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.tscn").instantiate() + var ed:ToolCylinderSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if tool_state == ToolState.FIRST_RING: + var bounding_points:PackedVector3Array = MathUtil.create_circle_points(base_center, floor_normal, first_ring_radius, settings.segments) + global_scene.draw_loop(bounding_points, true, global_scene.tool_material) + global_scene.draw_points(bounding_points, global_scene.vertex_tool_material) + + elif tool_state == ToolState.SECOND_RING: + for radius in [first_ring_radius, second_ring_radius]: + var bounding_points:PackedVector3Array = MathUtil.create_circle_points(base_center, floor_normal, radius, settings.segments) + global_scene.draw_loop(bounding_points, true, global_scene.tool_material) + global_scene.draw_points(bounding_points, global_scene.vertex_tool_material) + + elif tool_state == ToolState.DRAG_HEIGHT: + var bounding_points:PackedVector3Array = MathUtil.create_circle_points(base_center, floor_normal, first_ring_radius, settings.segments) + global_scene.draw_prism(bounding_points, drag_offset, global_scene.tool_material, global_scene.vertex_tool_material) + + if settings.tube: + bounding_points = MathUtil.create_circle_points(base_center, floor_normal, second_ring_radius, settings.segments) + global_scene.draw_prism(bounding_points, drag_offset, global_scene.tool_material, global_scene.vertex_tool_material) + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + var blocks_root:Node = builder.get_block_add_parent() + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.READY + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + + if tool_state == ToolState.READY: + tool_state = ToolState.FIRST_RING + + first_ring_radius = 0 + second_ring_radius = 0 + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result && settings.block_alignment == BlockAlignment.Type.ALIGN_TO_SURFACE: + #print("init base point block") +# floor_normal = result.normal + floor_normal = result.get_world_normal() + +# var p:Vector3 = to_local(result.position, blocks_root.global_transform.inverse(), grid_step_size) + var p:Vector3 = builder.get_snapping_manager().snap_point(result.get_world_position(), SnappingQuery.new(viewport_camera)) + base_center = p + + return true + + else: + #print("init base point empty space") + var draw_plane_point:Vector3 = Vector3.ZERO + var draw_plane_normal:Vector3 = BlockAlignment.get_plane_normal(settings.block_alignment) + if settings.match_selected_block: + draw_plane_point = calc_empty_space_draw_plane_origin(viewport_camera, draw_plane_point, draw_plane_normal) + + var hit_result = calc_hit_point_empty_space(origin, dir, viewport_camera, draw_plane_point, draw_plane_normal) + var start_pos:Vector3 = hit_result[0] + floor_normal = hit_result[1] + + var p:Vector3 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + base_center = p + + return true + else: + if tool_state == ToolState.FIRST_RING: + if settings.tube: + tool_state = ToolState.SECOND_RING + else: + var camera_dir:Vector3 = viewport_camera.project_ray_normal(e.position) + var angle_with_base:float = acos(floor_normal.dot(camera_dir)) + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + #block_drag_cur = base_center + floor_normal + var height = settings.default_block_height + + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(base_center, floor_normal) + + block_drag_cur = base_center + floor_normal * height + drag_offset = block_drag_cur - base_center + + create_block() + + tool_state = ToolState.READY + else: + tool_state = ToolState.DRAG_HEIGHT + return true + + elif tool_state == ToolState.SECOND_RING: + var camera_dir:Vector3 = viewport_camera.project_ray_normal(e.position) + var angle_with_base:float = acos(floor_normal.dot(camera_dir)) + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + #block_drag_cur = base_center + floor_normal + var height = settings.default_block_height + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(base_center, floor_normal) + + block_drag_cur = base_center + floor_normal * height + drag_offset = block_drag_cur - base_center + + create_block() + + tool_state = ToolState.READY + else: + + tool_state = ToolState.DRAG_HEIGHT + return true + + elif tool_state == ToolState.DRAG_HEIGHT: + + create_block() + + tool_state = ToolState.READY + return true + + #elif e.button_index == MOUSE_BUTTON_RIGHT: + #if tool_state == ToolState.FIRST_RING || tool_state == ToolState.SECOND_RING || tool_state == ToolState.DRAG_HEIGHT: + #if e.is_pressed(): + #tool_state = ToolState.READY + #return true + + elif e.button_index == MOUSE_BUTTON_WHEEL_UP: + if tool_state == ToolState.FIRST_RING || tool_state == ToolState.SECOND_RING || tool_state == ToolState.DRAG_HEIGHT: + if e.pressed: + settings.segments += 1 + return true + + elif e.button_index == MOUSE_BUTTON_WHEEL_DOWN: + if tool_state == ToolState.FIRST_RING || tool_state == ToolState.SECOND_RING || tool_state == ToolState.DRAG_HEIGHT: + if e.pressed: + settings.segments = max(settings.segments - 1, 3) + return true + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var start_pos:Vector3 = origin + builder.block_create_distance * dir +# var w2l = blocks_root.global_transform.inverse() +# var origin_local:Vector3 = w2l * origin +# var dir_local:Vector3 = w2l.basis * dir + + if tool_state == ToolState.FIRST_RING: + var p_isect:Vector3 = MathUtil.intersect_plane(origin, dir, base_center, floor_normal) + #var p_snapped = to_local(p_isect, blocks_root.global_transform.inverse(), grid_step_size) + #var p_snapped = MathUtil.snap_to_grid(p_isect, grid_step_size) + var p_snapped:Vector3 = builder.get_snapping_manager().snap_point(p_isect, SnappingQuery.new(viewport_camera)) + first_ring_radius = (p_snapped - base_center).length() + + return true + + elif tool_state == ToolState.SECOND_RING: + var p_isect:Vector3 = MathUtil.intersect_plane(origin, dir, base_center, floor_normal) + #var p_snapped = to_local(p_isect, blocks_root.global_transform.inverse(), grid_step_size) +# var p_snapped = MathUtil.snap_to_grid(p_isect, grid_step_size) + var p_snapped:Vector3 = builder.get_snapping_manager().snap_point(p_isect, SnappingQuery.new(viewport_camera)) + second_ring_radius = (p_snapped - base_center).length() + + return true + + elif tool_state == ToolState.DRAG_HEIGHT: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, base_center, floor_normal) + + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + drag_offset = block_drag_cur - base_center +# var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + +# global_scene.clear_tool_mesh() +# global_scene.draw_prism(bounding_points, drag_offset, global_scene.tool_material) + + return true + + return super._gui_input(viewport_camera, event) + +func create_block(): + var blocks_root:Node = builder.get_block_add_parent() + + var cmd:CommandAddCylinder = CommandAddCylinder.new() + cmd.builder = builder + cmd.block_name_prefix = "Block_" + cmd.blocks_root_path = blocks_root.get_path() + cmd.tube = settings.tube + cmd.origin = base_center + cmd.axis_normal = floor_normal + var height:float = drag_offset.length() if drag_offset.dot(floor_normal) > 0 else - drag_offset.length() + cmd.height = height + cmd.collision_type = settings.collision_type + cmd.collision_layers = settings.collision_layer + cmd.collision_mask = settings.collision_mask + + if settings.tube: + cmd.radius_inner = min(first_ring_radius, second_ring_radius) + cmd.radius_outer = max(first_ring_radius, second_ring_radius) + else: + cmd.radius_inner = first_ring_radius + cmd.radius_outer = first_ring_radius + cmd.segments = settings.segments + cmd.uv_transform = builder.tool_uv_transform + cmd.material_path = builder.tool_material_path + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/tools/tool_cylinder_settings.gd b/addons/cyclops_level_builder/tools/tool_cylinder_settings.gd new file mode 100644 index 0000000..93fd993 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_cylinder_settings.gd @@ -0,0 +1,62 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolCylinderSettings + +@export var block_alignment:BlockAlignment.Type = BlockAlignment.Type.ALIGN_TO_SURFACE +@export var match_selected_block:bool = true +@export var default_block_elevation:float = 0 +@export var default_block_height:float = 1 +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export_flags_3d_physics var collision_layer:int = 1 +@export_flags_3d_physics var collision_mask:int = 1 + +@export var segments:int = 16 +@export var tube:bool = false + +func load_from_cache(cache:Dictionary): + block_alignment = cache.get("block_alignment", BlockAlignment.Type.ALIGN_TO_SURFACE) + match_selected_block = cache.get("match_selected_block", true) + default_block_elevation = cache.get("default_block_elevation", 0) + default_block_height = cache.get("default_block_height", 1) + collision_type = cache.get("collision_type", Collision.Type.STATIC) + collision_layer = cache.get("collision_layer", 1) + collision_mask = cache.get("collision_mask", 1) + + segments = cache.get("segments", 16) + tube = cache.get("tube", false) + +func save_to_cache(): + return { + "block_alignment": block_alignment, + "match_selected_block": match_selected_block, + "default_block_elevation": default_block_elevation, + "default_block_height": default_block_height, + "collision_type": collision_type, + "collision_layer": collision_layer, + "collision_mask": collision_mask, + "segments": segments, + "tube": tube, + } diff --git a/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.gd new file mode 100644 index 0000000..de7fe39 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.gd @@ -0,0 +1,104 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolCylinderSettingsEditor + + +var settings:ToolCylinderSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + +func _ready(): + %collision_type.clear() + for text in Collision.Type.keys(): + %collision_type.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + + +func update(): + if !settings: + %check_tube.disabled = true + %check_match_selected_block.disabled = true + %default_block_elevation.disabled = true + %default_block_height.disabled = true + return + + %spin_segments.value = settings.segments + %check_tube.disabled = false + %check_tube.button_pressed = settings.match_selected_block + %check_match_selected_block.disabled = false + %check_match_selected_block.button_pressed = settings.match_selected_block + %default_block_elevation.disabled = false + %default_block_elevation.value = settings.default_block_elevation + %default_block_height.disabled = false + %default_block_height.value = settings.default_block_height + + %alignment_type.selected = settings.block_alignment + + %collision_type.selected = settings.collision_type + %collision_layers.value = settings.collision_layer + %collision_mask.value = settings.collision_mask + +func _on_check_match_selected_block_toggled(value): + settings.match_selected_block = value + + +func _on_default_block_elevation_value_changed(value): + settings.default_block_elevation = value + + +func _on_default_block_height_value_changed(value): + settings.default_block_height = value + + +func _on_check_tube_toggled(value): + settings.tube = value + + +func _on_spin_segments_value_changed(value): + settings.segments = value + +func _on_collision_layers_value_changed(value): + settings.collision_layer = value + + +func _on_collision_mask_value_changed(value): + settings.collision_mask = value + +func _on_collision_type_item_selected(index): + settings.collision_type = index + + +func _on_alignment_type_item_selected(index): + settings.block_alignment = index diff --git a/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.tscn new file mode 100644 index 0000000..044d69d --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.tscn @@ -0,0 +1,157 @@ +[gd_scene load_steps=3 format=3 uid="uid://dx804lkvek177"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_cylinder_settings_editor.gd" id="1_oyugv"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_wm7rv"] + +[node name="ToolCylinderSettings" type="PanelContainer"] +offset_right = 413.0 +offset_bottom = 232.0 +script = ExtResource("1_oyugv") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label4" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Type" + +[node name="collision_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "NONE" +popup/item_0/id = 0 +popup/item_1/text = "STATIC" +popup/item_1/id = 1 +popup/item_2/text = "KINEMATIC" +popup/item_2/id = 2 +popup/item_3/text = "RIGID" +popup/item_3/id = 3 + +[node name="Label5" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Layers" + +[node name="collision_layers" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label6" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Mask" + +[node name="collision_mask" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Segments" + +[node name="spin_segments" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +min_value = 3.0 +max_value = 20.0 +value = 3.0 +rounded = true +allow_greater = true + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Tube" + +[node name="check_tube" type="CheckBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = "On" + +[node name="Label7" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Alignment" + +[node name="alignment_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "Align to surface" +popup/item_0/id = 0 +popup/item_1/text = "XY Plane" +popup/item_1/id = 1 +popup/item_2/text = "XZ Plane" +popup/item_2/id = 2 +popup/item_3/text = "YZ Plane" +popup/item_3/id = 3 + +[node name="Label3" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Match selected block" + +[node name="check_match_selected_block" type="CheckBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "When drawing in empty space, copy elevation and height properties from currently selected block." +disabled = true +text = "On" + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Orthogonal Viewport:" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Elevation" + +[node name="default_block_elevation" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_wm7rv")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Height" + +[node name="default_block_height" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_wm7rv")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/collision_type" to="." method="_on_collision_type_item_selected"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_layers" to="." method="_on_collision_layers_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_mask" to="." method="_on_collision_mask_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/spin_segments" to="." method="_on_spin_segments_value_changed"] +[connection signal="toggled" from="PanelContainer/VBoxContainer/GridContainer/check_tube" to="." method="_on_check_tube_toggled"] +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/alignment_type" to="." method="_on_alignment_type_item_selected"] +[connection signal="toggled" from="PanelContainer/VBoxContainer/GridContainer/check_match_selected_block" to="." method="_on_check_match_selected_block_toggled"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_elevation" to="." method="_on_default_block_elevation_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_height" to="." method="_on_default_block_height_value_changed"] diff --git a/addons/cyclops_level_builder/tools/tool_duplicate.gd b/addons/cyclops_level_builder/tools/tool_duplicate.gd new file mode 100644 index 0000000..6873ef7 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_duplicate.gd @@ -0,0 +1,124 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolDuplicate + +const TOOL_ID:String = "duplicate" + +var drag_start_point:Vector3 +var cmd_duplicate:CommandDuplicateBlocks + +enum ToolState { READY, DRAGGING, DONE } +var tool_state:ToolState = ToolState.READY + +func _get_tool_id()->String: + return TOOL_ID + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if !e.is_pressed(): + if tool_state == ToolState.DRAGGING: + #print("committing duplicate") + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + if cmd_duplicate.will_change_anything(): + cmd_duplicate.add_to_undo_manager(undo) + + tool_state = ToolState.DONE + builder.switch_to_tool(ToolBlock.new()) + + return true + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return super._gui_input(viewport_camera, event) + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + if tool_state == ToolState.DRAGGING: + var drag_to:Vector3 + if e.alt_pressed: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_start_point, Vector3.UP) + else: + drag_to = MathUtil.intersect_plane(origin, dir, drag_start_point, Vector3.UP) + + var offset:Vector3 = drag_to - drag_start_point + offset = builder.get_snapping_manager().snap_point(offset, SnappingQuery.new(viewport_camera)) + #print("drag offset %s" % offset) + + #print("duplicate drag by %s" % offset) + + cmd_duplicate.move_offset = offset + cmd_duplicate.do_it() + + return true + + + return false + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + #Invoke command immediately + cmd_duplicate = CommandDuplicateBlocks.new() + cmd_duplicate.builder = builder + var blocks_root:Node = builder.get_block_add_parent() + cmd_duplicate.blocks_root_path = blocks_root.get_path() + var centroid:Vector3 + var count:int = 0 + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd_duplicate.blocks_to_duplicate.append(block.get_path()) + centroid += block.global_transform * block.control_mesh.bounds.get_center() + count += 1 + + cmd_duplicate.lock_uvs = builder.lock_uvs + + centroid /= count + drag_start_point = centroid + tool_state = ToolState.DRAGGING + + cmd_duplicate.do_it() + + + diff --git a/addons/cyclops_level_builder/tools/tool_edit_base.gd b/addons/cyclops_level_builder/tools/tool_edit_base.gd new file mode 100644 index 0000000..1600b43 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_base.gd @@ -0,0 +1,100 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolEditBase + +var mouse_hover_pos:Vector2 + +var drag_select_start_pos:Vector2 +var drag_select_to_pos:Vector2 + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_Q && e.alt_pressed: + select_block_under_cursor(viewport_camera, mouse_hover_pos) + + return true + + + elif event is InputEventMouseButton: + var e:InputEventMouseButton = event + mouse_hover_pos = e.position + return false + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + mouse_hover_pos = e.position + return false + + return false + +func pick_material(global_scene:CyclopsGlobalScene, selected:bool = false, active = false)->Material: + if active: + return global_scene.tool_edit_active_material + if selected: + return global_scene.tool_edit_selected_material + return global_scene.tool_edit_unselected_material + +func pick_vertex_material(global_scene:CyclopsGlobalScene, selected:bool = false, active = false)->Material: + if active: + return global_scene.vertex_active_material + if selected: + return global_scene.vertex_selected_material + return global_scene.vertex_unselected_material + + +func calc_gizmo_basis(average_normal:Vector3, active_block:Node3D, viewport_camera:Camera3D, orientation:TransformSpace.Type)->Basis: + var result:Basis + + match orientation: + TransformSpace.Type.GLOBAL: + result = Basis.IDENTITY + TransformSpace.Type.LOCAL: + result = active_block.global_basis + + #var xform:Transform3D = active_block.global_transform + #gizmo_translate.global_transform = xform + #gizmo_translate.global_position = origin + TransformSpace.Type.NORMAL: + var up:Vector3 = Vector3.UP + var x:Vector3 = up.cross(average_normal).normalized() + var y:Vector3 = average_normal.cross(x) + #gizmo_translate.global_basis = Basis(x, y, average_normal) + #gizmo_translate.global_position = origin + result = Basis(x, y, average_normal) + TransformSpace.Type.VIEW: + #gizmo_translate.global_basis = viewport_camera.global_basis + #gizmo_translate.global_position = origin + + result = viewport_camera.global_basis + TransformSpace.Type.PARENT: + result = active_block.get_parent_node_3d().global_basis + #var xform:Transform3D = active_block.get_parent_node_3d().global_transform + #gizmo_translate.global_transform = xform + + return result diff --git a/addons/cyclops_level_builder/tools/tool_edit_edge.gd b/addons/cyclops_level_builder/tools/tool_edit_edge.gd new file mode 100644 index 0000000..67e332c --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_edge.gd @@ -0,0 +1,646 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends ToolEditBase +class_name ToolEditEdge + +const TOOL_ID:String = "edit_edge" + +var handles:Array[HandleEdge] = [] + +enum ToolState { NONE, READY, DRAGGING, MOVE_HANDLES_CLICK, DRAG_SELECTION } +var tool_state:ToolState = ToolState.NONE + +#var drag_handle:HandleEdge +var drag_mouse_start_pos:Vector2 +var drag_handle_start_pos:Vector3 + +#enum MoveConstraint { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +var gizmo_translate:Node3D + +var cmd_move_edge:CommandMoveEdges + + +class PickHandleResult extends RefCounted: + var handle:HandleEdge + var position:Vector3 + + +var settings:ToolEditEdgeSettings = ToolEditEdgeSettings.new() + +var average_normal:Vector3 = Vector3.UP + +func _get_tool_id()->String: + return TOOL_ID + +func _get_tool_properties_editor()->Control: + var ed:ToolEditEdgeSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func draw_gizmo(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + if !gizmo_translate: + gizmo_translate = preload("res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn").instantiate() + + var origin:Vector3 + var count:int = 0 + for h in handles: + var block:CyclopsBlock = builder.get_node(h.block_path) + var l2w:Transform3D = block.global_transform + + if h.edge_index >= block.control_mesh.edges.size(): + continue + var e:ConvexVolume.EdgeInfo = block.control_mesh.edges[h.edge_index] + if e.selected: +# print("adding midpoint ", e.get_midpoint()) + origin += l2w * e.get_midpoint() + count += 1 + + if count == 0: + global_scene.set_custom_gizmo(null) + else: + origin /= count + global_scene.set_custom_gizmo(gizmo_translate) +# gizmo_translate.global_transform.origin = origin + var active_block:Node3D = builder.get_active_block() + + gizmo_translate.global_basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + gizmo_translate.global_position = origin + + #match settings.transform_space: + #TransformSpace.Type.GLOBAL: + #var xform:Transform3D = Transform3D.IDENTITY + #xform.origin = origin + #gizmo_translate.global_transform = xform + #TransformSpace.Type.LOCAL: + #var xform:Transform3D = active_block.global_transform + #gizmo_translate.global_transform = xform + #gizmo_translate.global_position = origin + #TransformSpace.Type.NORMAL: + #var up:Vector3 = Vector3.UP + #var x:Vector3 = up.cross(average_normal).normalized() + #var y:Vector3 = average_normal.cross(x) + #gizmo_translate.global_basis = Basis(x, y, average_normal) + #gizmo_translate.global_position = origin + #TransformSpace.Type.VIEW: + #gizmo_translate.global_basis = viewport_camera.global_basis + #gizmo_translate.global_position = origin + #TransformSpace.Type.PARENT: + #var xform:Transform3D = active_block.get_parent_node_3d().global_transform + #gizmo_translate.global_transform = xform +# + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + if tool_state == ToolState.DRAG_SELECTION: + global_scene.draw_screen_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos, global_scene.selection_rect_material) + + for h in handles: + var block:CyclopsBlock = builder.get_node(h.block_path) + if h.edge_index >= block.control_mesh.edges.size(): + #TODO: Sometimes we are retaining handles that do not corepond to the correct edges after an undo operation. + continue + var e:ConvexVolume.EdgeInfo = block.control_mesh.edges[h.edge_index] + var p0:Vector3 = block.global_transform * block.control_mesh.vertices[e.start_index].point + var p1:Vector3 = block.global_transform * block.control_mesh.vertices[e.end_index].point + + var active:bool = block.control_mesh.active_edge == h.edge_index + global_scene.draw_vertex((p0 + p1) / 2, pick_vertex_material(global_scene, e.selected, active)) + global_scene.draw_line(p0, p1, pick_material(global_scene, e.selected, active)) + + draw_gizmo(viewport_camera) + +func setup_tool(): + handles = [] + + average_normal = Vector3.ZERO + #print("setup_tool") + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + var l2w:Transform3D = block.global_transform + + var l2w_normal:Basis = l2w.basis.transposed().inverse() + + for e_idx in block.control_mesh.edges.size(): + var ctl_mesh:ConvexVolume = block.control_mesh + var e:ConvexVolume.EdgeInfo = ctl_mesh.edges[e_idx] + + var handle:HandleEdge = HandleEdge.new() + handle.edge_index = e_idx + handle.block_path = block.get_path() + handles.append(handle) + + if e.selected: + var edge_normal:Vector3 = Vector3.ZERO + for f_idx in e.face_indices: + #print("f_idx ", f_idx) + var f:ConvexVolume.FaceInfo = ctl_mesh.faces[f_idx] + #print("f.normal ", f.normal) + edge_normal += f.get_area_vector_x2().normalized() + + average_normal += l2w_normal * edge_normal.normalized() + #print("average_normal ", average_normal) + + average_normal = average_normal.normalized() + #print("setup_tool handles.size() ", handles.size()) + +func pick_closest_handle(viewport_camera:Camera3D, position:Vector2, radius:float)->PickHandleResult: + var best_dist:float = INF + var best_handle:HandleEdge = null + var best_pick_position:Vector3 + + var pick_origin:Vector3 = viewport_camera.project_ray_origin(position) + var pick_dir:Vector3 = viewport_camera.project_ray_normal(position) + + #print("pick_closest_handle") + for h in handles: + #print("<<0>>") + #print("h ", h) + var block:CyclopsBlock = builder.get_node(h.block_path) + #print("<<0.1>>") + var ctl_mesh:ConvexVolume = block.control_mesh + #print("<<0.2>>") + if ctl_mesh.edges.size() <= h.edge_index: + continue + var edge:ConvexVolume.EdgeInfo = ctl_mesh.edges[h.edge_index] + #print("<<1>>") + + var p0 = ctl_mesh.vertices[edge.start_index].point + var p1 = ctl_mesh.vertices[edge.end_index].point + var p0_world:Vector3 = block.global_transform * p0 + var p1_world:Vector3 = block.global_transform * p1 + + var p0_screen:Vector2 = viewport_camera.unproject_position(p0_world) + var p1_screen:Vector2 = viewport_camera.unproject_position(p1_world) + + var dist_to_seg_2d_sq = MathUtil.dist_to_segment_squared_2d(position, p0_screen, p1_screen) + + #print("<<2>>") + if dist_to_seg_2d_sq > radius * radius: + #print("<<2.5>>") + #Failed handle radius test + continue + #print("<<3>>") + + var point_on_seg:Vector3 = MathUtil.closest_point_on_segment(pick_origin, pick_dir, p0_world, p1_world) + #print("dist_to_seg_2d_sq ", dist_to_seg_2d_sq) + + var offset:Vector3 = point_on_seg - pick_origin + var parallel:Vector3 = offset.project(pick_dir) + var dist = parallel.dot(pick_dir) + #print("offset ", offset) + #print("parallel ", parallel) + #print("dist ", dist) + #print("<<4>>") + if dist <= 0: + #Behind camera + continue + + #print("<<5>>") + #print("best_dist ", best_dist) + #print("h pos %s ray orig %s ray dir %s offset %s para %s dist %s" % [str(h.position), pick_origin, pick_dir, offset, parallel, dist]) + if dist >= best_dist: + continue + + #print("<<6>>") + best_pick_position = point_on_seg + best_dist = dist + best_handle = h + #print("best_handle ", best_handle) + #print("<<7>>") + + #print("bar") + if !best_handle: + return null + + #print("foo") + var result:PickHandleResult = PickHandleResult.new() + result.handle = best_handle + result.position = best_pick_position + #print("result ", result) + return result + +func active_node_changed(): + setup_tool() + +func active_node_updated(): + setup_tool() + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.EDIT + builder.edit_mode = CyclopsLevelBuilder.EditMode.EDGE + builder.active_node_changed.connect(active_node_changed) + + setup_tool() + + +func _deactivate(): + super._deactivate() + builder.active_node_changed.disconnect(active_node_changed) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.set_custom_gizmo(null) + + +func start_drag(viewport_camera:Camera3D, event:InputEvent): + var e:InputEventMouseMotion = event + move_constraint = MoveConstraint.Type.NONE + + if gizmo_translate: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var part_res:GizmoTranslate.IntersectResult = gizmo_translate.intersect(origin, dir, viewport_camera) + if part_res: + #print("Gizmo hit ", part_res.part) + match part_res.part: + GizmoTranslate.Part.AXIS_X: + move_constraint = MoveConstraint.Type.AXIS_X + GizmoTranslate.Part.AXIS_Y: + move_constraint = MoveConstraint.Type.AXIS_Y + GizmoTranslate.Part.AXIS_Z: + move_constraint = MoveConstraint.Type.AXIS_Z + GizmoTranslate.Part.PLANE_XY: + move_constraint = MoveConstraint.Type.PLANE_XY + GizmoTranslate.Part.PLANE_XZ: + move_constraint = MoveConstraint.Type.PLANE_XZ + GizmoTranslate.Part.PLANE_YZ: + move_constraint = MoveConstraint.Type.PLANE_YZ + + drag_handle_start_pos = part_res.pos_world +# drag_handle_start_pos = gizmo_translate.global_position + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + #drag_handle_start_pos = MathUtil.snap_to_grid(start_pos, grid_step_size) + #drag_handle_start_pos = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + + # print("res obj %s" % result.object.get_path()) + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.DRAGGING + #print("Move block") + + cmd_move_edge = CommandMoveEdges.new() + cmd_move_edge.builder = builder + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for e_idx in vol.edges.size(): + var edge:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if edge.selected: + cmd_move_edge.add_edge(block.get_path(), e_idx) + + return + + + if e.alt_pressed: + move_constraint = MoveConstraint.Type.AXIS_Y + else: + move_constraint = MoveConstraint.Type.PLANE_XZ + + var res:PickHandleResult = pick_closest_handle(viewport_camera, drag_mouse_start_pos, builder.handle_screen_radius) + + if res: + var handle:HandleEdge = res.handle +# drag_handle = handle +# drag_handle_start_pos = handle.p_ref + drag_handle_start_pos = res.position + tool_state = ToolState.DRAGGING + #print("drag habdle start pos ", drag_handle_start_pos) + + cmd_move_edge = CommandMoveEdges.new() + cmd_move_edge.builder = builder + + var handle_block:CyclopsBlock = builder.get_node(handle.block_path) + if handle_block.control_mesh.edges[handle.edge_index].selected: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for e_idx in vol.edges.size(): + var edge:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if edge.selected: + cmd_move_edge.add_edge(block.get_path(), e_idx) + else: + cmd_move_edge.add_edge(handle.block_path, handle.edge_index) + + return + + #Drag selectio rectangle + tool_state = ToolState.DRAG_SELECTION + drag_select_start_pos = e.position + drag_select_to_pos = e.position + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + var gui_result = super._gui_input(viewport_camera, event) + if gui_result: + return true + +# var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + if cmd_move_edge: + cmd_move_edge.undo_it() + cmd_move_edge = null + tool_state = ToolState.NONE + + setup_tool() + + return true + + elif e.keycode == KEY_A: + + if e.is_pressed(): + var cmd:CommandSelectEdges = CommandSelectEdges.new() + cmd.builder = builder + + if e.alt_pressed: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd.add_edges(block.get_path(), []) + + else: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + for e_idx in block.control_mesh.edges.size(): + cmd.add_edge(block.get_path(), e_idx) + + cmd.selection_type = Selection.Type.REPLACE + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + elif e.keycode == KEY_G: + + if e.is_pressed() && tool_state == ToolState.NONE: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.MOVE_HANDLES_CLICK + move_constraint = MoveConstraint.Type.PLANE_VIEWPORT + + drag_handle_start_pos = Vector3.INF + + cmd_move_edge = CommandMoveEdges.new() + cmd_move_edge.builder = builder + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for e_idx in vol.edges.size(): + var edge:ConvexVolume.EdgeInfo = vol.edges[e_idx] + if edge.selected: + cmd_move_edge.add_edge(block.get_path(), e_idx) + + return true + + elif e.keycode == KEY_X: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_YZ + else: + move_constraint = MoveConstraint.Type.AXIS_X + return true + + elif e.keycode == KEY_Y: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XZ + else: + move_constraint = MoveConstraint.Type.AXIS_Y + return true + + elif e.keycode == KEY_Z: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XY + else: + move_constraint = MoveConstraint.Type.AXIS_Z + return true + + if event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + if tool_state == ToolState.NONE: + drag_mouse_start_pos = e.position + tool_state = ToolState.READY + + return true + else: +# print("bn up: state %s" % tool_state) + if tool_state == ToolState.READY: + #print("cmd select") + var cmd:CommandSelectEdges = CommandSelectEdges.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] + for block in sel_blocks: + cmd.add_edges(block.get_path(), []) + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + #print("handles.size() ", handles.size()) + var res:PickHandleResult = pick_closest_handle(viewport_camera, e.position, builder.handle_screen_radius) + if res: + var handle:HandleEdge = res.handle + + #print("handle %s" % handle) + + cmd.add_edge(handle.block_path, handle.edge_index) + #print("selectibg %s" % handle.vertex_index) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + setup_tool() + + elif tool_state == ToolState.DRAGGING: + #Finish drag + #print("cmd finish drag") + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd_move_edge.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_move_edge = null + + elif tool_state == ToolState.MOVE_HANDLES_CLICK: + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_move_edge.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_move_edge = null + + elif tool_state == ToolState.DRAG_SELECTION: + + var frustum:Array[Plane] = MathUtil.calc_frustum_camera_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos) + + var cmd:CommandSelectEdges = CommandSelectEdges.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + + for e_idx in block.control_mesh.edges.size(): + var edge:ConvexVolume.EdgeInfo = block.control_mesh.edges[e_idx] + var point_w:Vector3 = block.global_transform * edge.get_midpoint() + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) +# var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + + #Obstruction check + if !global_scene.xray_mode && builder.display_mode != DisplayMode.Type.WIRE: + var result:IntersectResults = builder.intersect_ray_closest(origin, point_w - origin) + if result: + var res_point_w:Vector3 = result.get_world_position() + if !res_point_w.is_equal_approx(point_w): + continue + + if MathUtil.frustum_contians_point(frustum, point_w): + cmd.add_edge(block.get_path(), e_idx) + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + #setup_tool() + + return true + + elif e.button_index == MOUSE_BUTTON_RIGHT: + if e.is_pressed(): + #Right click cancel + if cmd_move_edge: + cmd_move_edge.undo_it() + cmd_move_edge = null + tool_state = ToolState.NONE + + setup_tool() + return true + + return false + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + if tool_state == ToolState.READY: + if e.position.distance_squared_to(drag_mouse_start_pos) > MathUtil.square(builder.drag_start_radius): + start_drag(viewport_camera, event) + + return true + + elif tool_state == ToolState.DRAGGING || tool_state == ToolState.MOVE_HANDLES_CLICK: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + if !drag_handle_start_pos.is_finite(): + #If start point set to infinite, replace with point along view ray + drag_handle_start_pos = origin + dir * 20 + + var active_block:Node3D = builder.get_active_block() + var xform_basis:Basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + + #match settings.transform_space: + #TransformSpace.Type.GLOBAL: + #xform_basis = Basis.IDENTITY + #TransformSpace.Type.LOCAL: + #var active_block:Node3D = builder.get_active_block() + #xform_basis = active_block.basis + #TransformSpace.Type.NORMAL: + #var up:Vector3 = Vector3.UP + #var x:Vector3 = up.cross(average_normal).normalized() + #var y:Vector3 = average_normal.cross(x) + #xform_basis = Basis(x, y, average_normal) + #TransformSpace.Type.VIEW: + #xform_basis = viewport_camera.global_basis + #TransformSpace.Type.PARENT: + #var active_block:Node3D = builder.get_active_block().get_parent_node_3d() + #xform_basis = active_block.basis + + var drag_to:Vector3 + match move_constraint: + MoveConstraint.Type.AXIS_X: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.AXIS_Y: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.AXIS_Z: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XY: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.PLANE_YZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.PLANE_VIEWPORT: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, viewport_camera.global_transform.basis.z) + + var offset:Vector3 = drag_to - drag_handle_start_pos + offset = builder.get_snapping_manager().snap_point(offset, SnappingQuery.new(viewport_camera)) + #drag_to = drag_handle_start_pos + offset + + cmd_move_edge.move_offset = offset + cmd_move_edge.do_it() + + setup_tool() +# draw_tool() + return true + + elif tool_state == ToolState.DRAG_SELECTION: + drag_select_to_pos = e.position + return true + + return false diff --git a/addons/cyclops_level_builder/tools/tool_edit_edge_settings.gd b/addons/cyclops_level_builder/tools/tool_edit_edge_settings.gd new file mode 100644 index 0000000..0158968 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_edge_settings.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolEditEdgeSettings + +@export var transform_space:TransformSpace.Type = TransformSpace.Type.GLOBAL +@export var triplanar_lock_uvs:bool = true + +func load_from_cache(cache:Dictionary): + transform_space = cache.get("transform_space", TransformSpace.Type.GLOBAL) + triplanar_lock_uvs = cache.get("triplanar_lock_uvs", true) + +func save_to_cache(): + return { + "transform_space": transform_space, + "triplanar_lock_uvs": triplanar_lock_uvs, + } diff --git a/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.gd new file mode 100644 index 0000000..9ee0bb9 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.gd @@ -0,0 +1,60 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolEditEdgeSettingsEditor + +var settings:ToolEditEdgeSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + + +func _ready(): + %transform_space.clear() + for text in TransformSpace.Type.keys(): + %transform_space.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + %transform_space.selected = settings.transform_space + %check_correct_uvs.button_pressed = settings.triplanar_lock_uvs + + pass + + +func _on_transform_space_item_selected(index): + settings.transform_space = index + + +func _on_check_correct_uvs_toggled(toggled_on): + settings.triplanar_lock_uvs = toggled_on diff --git a/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.tscn new file mode 100644 index 0000000..0522250 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=2 format=3 uid="uid://d3hfpe2pe0ml2"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_edge_settings_editor.gd" id="1_a1oyt"] + +[node name="ToolMoveSettingsEditor" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -929.0 +offset_bottom = -415.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_a1oyt") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Transform space" + +[node name="transform_space" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 5 +selected = 0 +popup/item_0/text = "GLOBAL" +popup/item_0/id = 0 +popup/item_1/text = "LOCAL" +popup/item_1/id = 1 +popup/item_2/text = "NORMAL" +popup/item_2/id = 2 +popup/item_3/text = "VIEW" +popup/item_3/id = 3 +popup/item_4/text = "PARENT" +popup/item_4/id = 4 + +[node name="Label2" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Triplanar lock UVs" + +[node name="check_correct_uvs" type="CheckBox" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "On" + +[connection signal="item_selected" from="GridContainer/transform_space" to="." method="_on_transform_space_item_selected"] +[connection signal="toggled" from="GridContainer/check_correct_uvs" to="." method="_on_check_correct_uvs_toggled"] diff --git a/addons/cyclops_level_builder/tools/tool_edit_face.gd b/addons/cyclops_level_builder/tools/tool_edit_face.gd new file mode 100644 index 0000000..cab14b5 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_face.gd @@ -0,0 +1,642 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends ToolEditBase +class_name ToolEditFace + +const TOOL_ID:String = "edit_face" + +var handles:Array[HandleFace] = [] + +enum ToolState { NONE, READY, DRAGGING, MOVE_HANDLES_CLICK, DRAG_SELECTION } +var tool_state:ToolState = ToolState.NONE + +#var drag_handle:HandleFace +var drag_mouse_start_pos:Vector2 +var drag_handle_start_pos:Vector3 + +#enum MoveConstraint { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +var gizmo_translate:Node3D + +var cmd_move_face:CommandMoveFaces + + +class PickHandleResult extends RefCounted: + var handle:HandleFace + var position:Vector3 + +var settings:ToolEditFaceSettings = ToolEditFaceSettings.new() + +var average_normal:Vector3 = Vector3.UP + +func _get_tool_id()->String: + return TOOL_ID + +func _get_tool_properties_editor()->Control: + var ed:ToolEditFaceSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func draw_gizmo(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + if !gizmo_translate: + gizmo_translate = preload("res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn").instantiate() + + var origin:Vector3 + var count:int = 0 + for h in handles: + var block:CyclopsBlock = builder.get_node(h.block_path) + if !block: + continue + var l2w:Transform3D = block.global_transform + + var f:ConvexVolume.FaceInfo = block.control_mesh.faces[h.face_index] + if f.selected: +# print("adding midpoint ", e.get_midpoint()) + origin += l2w * f.get_centroid() + count += 1 + + if count == 0: + global_scene.set_custom_gizmo(null) + else: + origin /= count + global_scene.set_custom_gizmo(gizmo_translate) +# gizmo_translate.global_transform.origin = origin + var active_block:Node3D = builder.get_active_block() + + gizmo_translate.global_basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + gizmo_translate.global_position = origin + #match settings.transform_space: + #TransformSpace.Type.GLOBAL: + #var xform:Transform3D = Transform3D.IDENTITY + #xform.origin = origin + #gizmo_translate.global_transform = xform + #TransformSpace.Type.LOCAL: + #var xform:Transform3D = active_block.global_transform + #gizmo_translate.global_transform = xform + #gizmo_translate.global_position = origin + #TransformSpace.Type.NORMAL: + #var up:Vector3 = Vector3.UP + #var x:Vector3 = up.cross(average_normal).normalized() + #var y:Vector3 = average_normal.cross(x) + #gizmo_translate.global_basis = Basis(x, y, average_normal) + #gizmo_translate.global_position = origin + #TransformSpace.Type.VIEW: + #gizmo_translate.global_basis = viewport_camera.global_basis + #gizmo_translate.global_position = origin + #TransformSpace.Type.PARENT: + #var xform:Transform3D = active_block.get_parent_node_3d().global_transform + #gizmo_translate.global_transform = xform + + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + if tool_state == ToolState.DRAG_SELECTION: + global_scene.draw_screen_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos, global_scene.selection_rect_material) + + #var blocks_root:CyclopsBlocks = builder.active_node + for h in handles: +# print("draw face %s" % h) + if Engine.is_editor_hint() && !builder.has_node(h.block_path): + continue + + var block:CyclopsBlock = builder.get_node(h.block_path) + var f:ConvexVolume.FaceInfo = block.control_mesh.faces[h.face_index] + + var active:bool = block.control_mesh.active_face == h.face_index + global_scene.draw_vertex(h.p_center, pick_vertex_material(global_scene, f.selected, active)) + + var l2w:Transform3D = block.global_transform + #var w2l:Transform3D = block.global_transform.affine_inverse() + + if f.selected: + var edge_loop:PackedVector3Array = f.get_points() + for p_idx in edge_loop.size(): + edge_loop[p_idx] += f.normal * builder.tool_overlay_extrude + global_scene.draw_loop(l2w * edge_loop, true, pick_material(global_scene, f.selected, active)) + + var tris:PackedVector3Array = f.get_trianges() + for p_idx in tris.size(): + tris[p_idx] += f.normal * builder.tool_overlay_extrude + +# print("draw face %s %s %s" % [h.face_index, f.selected, f.active]) + var mat:Material = global_scene.tool_edit_active_fill_material if active else global_scene.tool_edit_selected_fill_material + global_scene.draw_triangles(l2w * tris, mat) + + draw_gizmo(viewport_camera) + +func setup_tool(): + handles = [] + #print("setup_tool") + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + average_normal = Vector3.ZERO + + for block in sel_blocks: + var l2w:Transform3D = block.global_transform + + var l2w_normal:Basis = l2w.basis.transposed().inverse() + + for f_idx in block.control_mesh.faces.size(): + + var ctl_mesh:ConvexVolume = block.control_mesh + var face:ConvexVolume.FaceInfo = ctl_mesh.faces[f_idx] + + var handle:HandleFace = HandleFace.new() + + var p_start:Vector3 = l2w * face.get_centroid() + #print("p_start %s" % p_start) + + handle.p_center = p_start + + handle.face_index = f_idx + handle.block_path = block.get_path() + handles.append(handle) + + if face.selected: + average_normal += l2w_normal * face.get_area_vector_x2() + + average_normal = average_normal.normalized() + +func pick_closest_handle(viewport_camera:Camera3D, position:Vector2, radius:float)->PickHandleResult: + + var pick_origin:Vector3 = viewport_camera.project_ray_origin(position) + var pick_dir:Vector3 = viewport_camera.project_ray_normal(position) + + if builder.display_mode == DisplayMode.Type.MATERIAL || builder.display_mode == DisplayMode.Type.MESH: + var result:IntersectResults = builder.intersect_ray_closest_selected_only(pick_origin, pick_dir) + if result: + for h in handles: + if h.block_path == result.object.get_path() && h.face_index == result.face_index: + var ret:PickHandleResult = PickHandleResult.new() + ret.handle = h + ret.position = result.get_world_position() + return ret + + elif builder.display_mode == DisplayMode.Type.WIRE: + var best_dist:float = INF + var best_handle:HandleFace = null + var best_position:Vector3 + + + for h in handles: +# var h_world_pos:Vector3 = blocks_root.global_transform * h.p_ref + var h_world_pos:Vector3 = h.p_center + var h_screen_pos:Vector2 = viewport_camera.unproject_position(h_world_pos) + if position.distance_squared_to(h_screen_pos) > radius * radius: + #Failed handle radius test + continue + + var offset:Vector3 = h_world_pos - pick_origin + var parallel:Vector3 = offset.project(pick_dir) + var dist = parallel.dot(pick_dir) + if dist <= 0: + #Behind camera + continue + + #print("h pos %s ray orig %s ray dir %s offset %s para %s dist %s perp %s" % [h.position, ray_origin, ray_dir, offset, parallel, dist, perp]) + if dist >= best_dist: + continue + + best_dist = dist + best_handle = h + best_position = h_world_pos + + var result:PickHandleResult = PickHandleResult.new() + result.handle = best_handle + result.position = best_position + return result + + return null + + + +func active_node_changed(): + setup_tool() + + +func active_node_updated(): + setup_tool() + #draw_tool() + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.EDIT + builder.edit_mode = CyclopsLevelBuilder.EditMode.FACE + builder.active_node_changed.connect(active_node_changed) + + setup_tool() + + +func _deactivate(): + super._deactivate() + builder.active_node_changed.disconnect(active_node_changed) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.set_custom_gizmo(null) + + +func start_drag(viewport_camera:Camera3D, event:InputEvent): + var e:InputEventMouseMotion = event + move_constraint = MoveConstraint.Type.NONE + + if gizmo_translate: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var part_res:GizmoTranslate.IntersectResult = gizmo_translate.intersect(origin, dir, viewport_camera) + if part_res: + #print("Gizmo hit ", part_res.part) + match part_res.part: + GizmoTranslate.Part.AXIS_X: + move_constraint = MoveConstraint.Type.AXIS_X + GizmoTranslate.Part.AXIS_Y: + move_constraint = MoveConstraint.Type.AXIS_Y + GizmoTranslate.Part.AXIS_Z: + move_constraint = MoveConstraint.Type.AXIS_Z + GizmoTranslate.Part.PLANE_XY: + move_constraint = MoveConstraint.Type.PLANE_XY + GizmoTranslate.Part.PLANE_XZ: + move_constraint = MoveConstraint.Type.PLANE_XZ + GizmoTranslate.Part.PLANE_YZ: + move_constraint = MoveConstraint.Type.PLANE_YZ + + var start_pos:Vector3 = part_res.pos_world + + drag_handle_start_pos = start_pos + + # print("res obj %s" % result.object.get_path()) + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.DRAGGING + #print("Move block") + + cmd_move_face = CommandMoveFaces.new() + cmd_move_face.builder = builder + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var face:ConvexVolume.FaceInfo = vol.faces[f_idx] + if face.selected: + cmd_move_face.add_face(block.get_path(), f_idx) + + return + + + if e.alt_pressed: + move_constraint = MoveConstraint.Type.AXIS_Y + else: + move_constraint = MoveConstraint.Type.PLANE_XZ + + + var res:PickHandleResult = pick_closest_handle(viewport_camera, drag_mouse_start_pos, builder.handle_screen_radius) + + if res && res.handle: + #print("pick handle %s" % res.handle) + + var handle:HandleFace = res.handle + #drag_handle = handle + drag_handle_start_pos = res.position + #print("drag_handle_start_pos %s" % drag_handle_start_pos) + tool_state = ToolState.DRAGGING + + cmd_move_face = CommandMoveFaces.new() + cmd_move_face.builder = builder + + var handle_block:CyclopsBlock = builder.get_node(handle.block_path) + if handle_block.control_mesh.faces[handle.face_index].selected: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var face:ConvexVolume.FaceInfo = vol.faces[f_idx] + if face.selected: + cmd_move_face.add_face(block.get_path(), f_idx) + + else: + cmd_move_face.add_face(handle.block_path, handle.face_index) + + return + + + #Drag selectio rectangle + tool_state = ToolState.DRAG_SELECTION + drag_select_start_pos = e.position + drag_select_to_pos = e.position + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + var gui_result = super._gui_input(viewport_camera, event) + if gui_result: + return true + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + if cmd_move_face: + cmd_move_face.undo_it() + cmd_move_face = null + tool_state = ToolState.NONE + + setup_tool() + + return true + + elif e.keycode == KEY_A: + + if e.is_pressed(): + var cmd:CommandSelectFaces = CommandSelectFaces.new() + cmd.builder = builder + + if e.alt_pressed: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd.add_faces(block.get_path(), []) + + else: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + for f_idx in block.control_mesh.faces.size(): + cmd.add_face(block.get_path(), f_idx) + + cmd.selection_type = Selection.Type.REPLACE + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + elif e.keycode == KEY_G: + + if e.is_pressed() && tool_state == ToolState.NONE: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.MOVE_HANDLES_CLICK + move_constraint = MoveConstraint.Type.PLANE_VIEWPORT + + drag_handle_start_pos = Vector3.INF + + cmd_move_face = CommandMoveFaces.new() + cmd_move_face.builder = builder + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for f_idx in vol.faces.size(): + var face:ConvexVolume.FaceInfo = vol.faces[f_idx] + if face.selected: + cmd_move_face.add_face(block.get_path(), f_idx) + + return true + + elif e.keycode == KEY_X: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_YZ + else: + move_constraint = MoveConstraint.Type.AXIS_X + return true + + elif e.keycode == KEY_Y: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XZ + else: + move_constraint = MoveConstraint.Type.AXIS_Y + return true + + elif e.keycode == KEY_Z: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XY + else: + move_constraint = MoveConstraint.Type.AXIS_Z + return true + + if event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + if tool_state == ToolState.NONE: + drag_mouse_start_pos = e.position + tool_state = ToolState.READY + + return true + else: +# print("bn up: state %s" % tool_state) + if tool_state == ToolState.READY: + #print("cmd select") + + var cmd:CommandSelectFaces = CommandSelectFaces.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd.add_faces(block.get_path(), []) + + var res:PickHandleResult = pick_closest_handle(viewport_camera, e.position, builder.handle_screen_radius) + if res: + var handle:HandleFace = res.handle + #print("pick handle %s" % handle) + + cmd.add_face(handle.block_path, handle.face_index) + #print("selecting %s" % handle.face_index) + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + + tool_state = ToolState.NONE + setup_tool() + + elif tool_state == ToolState.DRAGGING: + #Finish drag + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd_move_face.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_move_face = null + setup_tool() + + + elif tool_state == ToolState.MOVE_HANDLES_CLICK: + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_move_face.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_move_face = null + + elif tool_state == ToolState.DRAG_SELECTION: + + var frustum:Array[Plane] = MathUtil.calc_frustum_camera_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos) + + var cmd:CommandSelectFaces = CommandSelectFaces.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + #print("block ", block.name) + + for f_idx in block.control_mesh.faces.size(): + var face:ConvexVolume.FaceInfo = block.control_mesh.faces[f_idx] + var point_w:Vector3 = block.global_transform * face.get_centroid() + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + + #print("precheck") + #Obstruction check + if !global_scene.xray_mode && builder.display_mode != DisplayMode.Type.WIRE: + var result:IntersectResults = builder.intersect_ray_closest(origin, point_w - origin) + if result: + var res_point_w:Vector3 = result.get_world_position() + if !res_point_w.is_equal_approx(point_w): + continue + + #print("frustum check ", point_w) + if MathUtil.frustum_contians_point(frustum, point_w): + #print("frustim hit ", point_w) + cmd.add_face(block.get_path(), f_idx) + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + return true + + elif e.button_index == MOUSE_BUTTON_RIGHT: + if e.is_pressed(): + #Right click cancel + if cmd_move_face: + cmd_move_face.undo_it() + cmd_move_face = null + tool_state = ToolState.NONE + + setup_tool() + return true + + return false + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + if tool_state == ToolState.READY: + if e.position.distance_squared_to(drag_mouse_start_pos) > MathUtil.square(builder.drag_start_radius): + start_drag(viewport_camera, event) + return true + + + elif tool_state == ToolState.DRAGGING || tool_state == ToolState.MOVE_HANDLES_CLICK: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + + if !drag_handle_start_pos.is_finite(): + #If start point set to infinite, replace with point along view ray + drag_handle_start_pos = origin + dir * 20 + + var active_block:Node3D = builder.get_active_block() + var xform_basis:Basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + + #match settings.transform_space: + #TransformSpace.Type.GLOBAL: + #xform_basis = Basis.IDENTITY + #TransformSpace.Type.LOCAL: + #var active_block:Node3D = builder.get_active_block() + #xform_basis = active_block.basis + #TransformSpace.Type.NORMAL: + #var up:Vector3 = Vector3.UP + #var x:Vector3 = up.cross(average_normal).normalized() + #var y:Vector3 = average_normal.cross(x) + #xform_basis = Basis(x, y, average_normal) + #TransformSpace.Type.VIEW: + #xform_basis = viewport_camera.global_basis + #TransformSpace.Type.PARENT: + #var active_block:Node3D = builder.get_active_block().get_parent_node_3d() + #xform_basis = active_block.basis + + var drag_to:Vector3 + match move_constraint: + MoveConstraint.Type.AXIS_X: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.AXIS_Y: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.AXIS_Z: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XY: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.PLANE_YZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.PLANE_VIEWPORT: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, viewport_camera.global_transform.basis.z) + + + var offset = drag_to - drag_handle_start_pos +# offset = MathUtil.snap_to_grid(offset, grid_step_size) + offset = builder.get_snapping_manager().snap_point(offset, SnappingQuery.new(viewport_camera)) + + #print("offset %s" % offset) + + cmd_move_face.move_offset = offset + cmd_move_face.do_it() + + setup_tool() + return true + + elif tool_state == ToolState.DRAG_SELECTION: + drag_select_to_pos = e.position + return true + + return false + diff --git a/addons/cyclops_level_builder/tools/tool_edit_face_settings.gd b/addons/cyclops_level_builder/tools/tool_edit_face_settings.gd new file mode 100644 index 0000000..6535e82 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_face_settings.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolEditFaceSettings + +@export var transform_space:TransformSpace.Type = TransformSpace.Type.GLOBAL +@export var triplanar_lock_uvs:bool = true + +func load_from_cache(cache:Dictionary): + transform_space = cache.get("transform_space", TransformSpace.Type.GLOBAL) + triplanar_lock_uvs = cache.get("triplanar_lock_uvs", true) + +func save_to_cache(): + return { + "transform_space": transform_space, + "triplanar_lock_uvs": triplanar_lock_uvs, + } diff --git a/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.gd new file mode 100644 index 0000000..8b20375 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.gd @@ -0,0 +1,60 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolEditFaceSettingsEditor + +var settings:ToolEditFaceSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + + +func _ready(): + %transform_space.clear() + for text in TransformSpace.Type.keys(): + %transform_space.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + %transform_space.selected = settings.transform_space + %check_correct_uvs.button_pressed = settings.triplanar_lock_uvs + + pass + + +func _on_transform_space_item_selected(index): + settings.transform_space = index + + +func _on_check_correct_uvs_toggled(toggled_on): + settings.triplanar_lock_uvs = toggled_on diff --git a/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.tscn new file mode 100644 index 0000000..940d9c5 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=2 format=3 uid="uid://bxy3qukjatj4l"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_face_settings_editor.gd" id="1_i7hy2"] + +[node name="ToolMoveSettingsEditor" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -929.0 +offset_bottom = -415.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_i7hy2") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Transform space" + +[node name="transform_space" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 5 +selected = 0 +popup/item_0/text = "GLOBAL" +popup/item_0/id = 0 +popup/item_1/text = "LOCAL" +popup/item_1/id = 1 +popup/item_2/text = "NORMAL" +popup/item_2/id = 2 +popup/item_3/text = "VIEW" +popup/item_3/id = 3 +popup/item_4/text = "PARENT" +popup/item_4/id = 4 + +[node name="Label2" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Triplanar lock UVs" + +[node name="check_correct_uvs" type="CheckBox" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "On" + +[connection signal="item_selected" from="GridContainer/transform_space" to="." method="_on_transform_space_item_selected"] +[connection signal="toggled" from="GridContainer/check_correct_uvs" to="." method="_on_check_correct_uvs_toggled"] diff --git a/addons/cyclops_level_builder/tools/tool_edit_vertex.gd b/addons/cyclops_level_builder/tools/tool_edit_vertex.gd new file mode 100644 index 0000000..e3f5e09 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_vertex.gd @@ -0,0 +1,632 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends ToolEditBase +class_name ToolEditVertex + +const TOOL_ID:String = "edit_vertex" + +var handles:Array[HandleVertex] = [] + +enum ToolState { NONE, READY, DRAGGING, DRAGGING_ADD, MOVE_HANDLES_CLICK, DRAG_SELECTION } +var tool_state:ToolState = ToolState.NONE + +#enum MoveConstraint { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +#var mouse_hover_pos:Vector2 + +#var drag_handle:HandleVertex +var drag_mouse_start_pos:Vector2 +var drag_handle_start_pos:Vector3 +var drag_home_block:NodePath +var added_point_pos:Vector3 + +var cmd_move_vertex:CommandMoveVertices +var cmd_add_vertex:CommandAddVertices + +var gizmo_translate:Node3D + +var watched_blocks:Array[CyclopsBlock] + +var settings:ToolEditVertexSettings = ToolEditVertexSettings.new() + +var average_normal:Vector3 = Vector3.UP + +func _get_tool_id()->String: + return TOOL_ID + +func _get_tool_properties_editor()->Control: + var ed:ToolEditVertexSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func draw_gizmo(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + if !gizmo_translate: + gizmo_translate = preload("res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn").instantiate() + + var origin:Vector3 + var count:int = 0 + for h in handles: + var block:CyclopsBlock = builder.get_node(h.block_path) + var v:ConvexVolume.VertexInfo = block.control_mesh.vertices[h.vertex_index] + if v.selected: + origin += h.position + count += 1 + + if count == 0: + global_scene.set_custom_gizmo(null) + else: + origin /= count + #print("gizmo origin ", origin) + global_scene.set_custom_gizmo(gizmo_translate) + var active_block:Node3D = builder.get_active_block() + + gizmo_translate.global_basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + gizmo_translate.global_position = origin + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + if tool_state == ToolState.DRAG_SELECTION: + global_scene.draw_screen_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos, global_scene.selection_rect_material) + + for h in handles: + var block:CyclopsBlock = builder.get_node(h.block_path) + var v:ConvexVolume.VertexInfo = block.control_mesh.vertices[h.vertex_index] + + var active:bool = block.control_mesh.active_vertex == h.vertex_index + #print("draw vert idx:%s sel:%s active:%s" % [h.vertex_index, v.selected, active]) + global_scene.draw_vertex(h.position, pick_vertex_material(global_scene, v.selected, active)) + + draw_gizmo(viewport_camera) + +func on_block_changed(): + setup_tool() + +func setup_tool(): + handles = [] + #print("setup_tool") + + for block in watched_blocks: + block.mesh_changed.disconnect(on_block_changed) + watched_blocks.clear() + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + average_normal = Vector3.ZERO + + for block in sel_blocks: +# print("block sel %s" % block.block_data.vertex_selected) + var l2w:Transform3D = block.global_transform + block.mesh_changed.connect(on_block_changed) + watched_blocks.append(block) + + var l2w_normal:Basis = l2w.basis.transposed().inverse() + + for v_idx in block.control_mesh.vertices.size(): + var v:ConvexVolume.VertexInfo = block.control_mesh.vertices[v_idx] + var handle:HandleVertex = HandleVertex.new() + handle.position = l2w * v.point + handle.initial_position = handle.position + handle.vertex_index = v_idx + handle.block_path = block.get_path() + handles.append(handle) + + if v.selected: + #print("v.normal ", l2w_normal * v.normal) + average_normal += l2w_normal * v.normal + + #print("adding handle %s" % handle) + average_normal = average_normal.normalized() + + +func pick_closest_handle(viewport_camera:Camera3D, position:Vector2, radius:float)->HandleVertex: +# print("pick radius ", radius) + var best_dist:float = INF + var best_handle:HandleVertex = null + + var origin:Vector3 = viewport_camera.project_ray_origin(position) + var dir:Vector3 = viewport_camera.project_ray_normal(position) + + for h in handles: +# var h_world_pos:Vector3 = blocks_root.global_transform * h.position + var h_world_pos:Vector3 = h.position + var h_screen_pos:Vector2 = viewport_camera.unproject_position(h_world_pos) + if position.distance_squared_to(h_screen_pos) > radius * radius: + #Failed handle radius test + continue + + var offset:Vector3 = h_world_pos - origin + var parallel:Vector3 = offset.project(dir) + var dist = parallel.dot(dir) + if dist <= 0: + #Behind camera + continue + + #print("h pos %s ray orig %s ray dir %s offset %s para %s dist %s perp %s" % [h.position, ray_origin, ray_dir, offset, parallel, dist, perp]) + if dist >= best_dist: + continue + + best_dist = dist + best_handle = h + + return best_handle + +func active_node_changed(): + setup_tool() + + +func active_node_updated(): + setup_tool() + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.EDIT + builder.edit_mode = CyclopsLevelBuilder.EditMode.VERTEX + builder.active_node_changed.connect(active_node_changed) + + setup_tool() + + +func _deactivate(): + super._deactivate() + builder.active_node_changed.disconnect(active_node_changed) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.set_custom_gizmo(null) + +func start_drag(viewport_camera:Camera3D, event:InputEvent): + var e:InputEventMouseMotion = event + move_constraint = MoveConstraint.Type.NONE + + if gizmo_translate: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var part_res:GizmoTranslate.IntersectResult = gizmo_translate.intersect(origin, dir, viewport_camera) + if part_res: +# print("Gizmo hit ", part_res.part) + match part_res.part: + GizmoTranslate.Part.AXIS_X: + move_constraint = MoveConstraint.Type.AXIS_X + GizmoTranslate.Part.AXIS_Y: + move_constraint = MoveConstraint.Type.AXIS_Y + GizmoTranslate.Part.AXIS_Z: + move_constraint = MoveConstraint.Type.AXIS_Z + GizmoTranslate.Part.PLANE_XY: + move_constraint = MoveConstraint.Type.PLANE_XY + GizmoTranslate.Part.PLANE_XZ: + move_constraint = MoveConstraint.Type.PLANE_XZ + GizmoTranslate.Part.PLANE_YZ: + move_constraint = MoveConstraint.Type.PLANE_YZ + + drag_handle_start_pos = gizmo_translate.global_position + #print("drag_handle_start_pos ", drag_handle_start_pos) +# var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + # print("res obj %s" % result.object.get_path()) + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.DRAGGING + #print("Move block") + + cmd_move_vertex = CommandMoveVertices.new() + cmd_move_vertex.builder = builder + + cmd_move_vertex.triplanar_lock_uvs = settings.triplanar_lock_uvs + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if v.selected: + cmd_move_vertex.add_vertex(block.get_path(), v_idx) + if vol.active_vertex == v_idx: + #drag_handle_start_pos = block.global_transform * v.point + drag_home_block = block.get_path() + + return + + if e.alt_pressed: + move_constraint = MoveConstraint.Type.AXIS_Y + else: + move_constraint = MoveConstraint.Type.PLANE_XZ + + var handle:HandleVertex = pick_closest_handle(viewport_camera, drag_mouse_start_pos, builder.handle_screen_radius) + + if handle: + #drag_handle = handle + drag_handle_start_pos = handle.position + drag_home_block = handle.block_path + tool_state = ToolState.DRAGGING + + cmd_move_vertex = CommandMoveVertices.new() + cmd_move_vertex.builder = builder + + var handle_block:CyclopsBlock = builder.get_node(handle.block_path) + if handle_block.control_mesh.vertices[handle.vertex_index].selected: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if v.selected: + cmd_move_vertex.add_vertex(block.get_path(), v_idx) + else: + cmd_move_vertex.add_vertex(handle.block_path, handle.vertex_index) + + return true + + else: + if e.ctrl_pressed: + #Add vertex under cursor + var pick_origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var pick_dir:Vector3 = viewport_camera.project_ray_normal(e.position) + var result:IntersectResults = builder.intersect_ray_closest_selected_only(pick_origin, pick_dir) + if result: + #print("start drag add") + drag_handle_start_pos = result.get_world_position() + added_point_pos = result.get_world_position() + tool_state = ToolState.DRAGGING_ADD + + cmd_add_vertex = CommandAddVertices.new() + cmd_add_vertex.builder = builder + + cmd_add_vertex.block_path = result.object.get_path() + cmd_add_vertex.points_to_add = [added_point_pos] + #print("init point %s" % added_point_pos) + + return true + + #Drag selection rectangle + tool_state = ToolState.DRAG_SELECTION + drag_select_start_pos = e.position + drag_select_to_pos = e.position + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + var gui_result = super._gui_input(viewport_camera, event) + if gui_result: + return true + +# var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + if cmd_move_vertex: + cmd_move_vertex.undo_it() + cmd_move_vertex = null + tool_state = ToolState.NONE + + setup_tool() + + if cmd_add_vertex: + cmd_add_vertex.undo_it() + cmd_add_vertex = null + tool_state = ToolState.NONE + + setup_tool() + + return true + + elif e.keycode == KEY_A: + + if e.is_pressed(): + var cmd:CommandSelectVertices = CommandSelectVertices.new() + cmd.builder = builder + + if e.alt_pressed: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd.add_vertices(block.get_path(), []) + + else: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + for v_idx in block.control_mesh.vertices.size(): + cmd.add_vertex(block.get_path(), v_idx) + + cmd.selection_type = Selection.Type.REPLACE + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + elif e.keycode == KEY_G: + + if e.is_pressed() && tool_state == ToolState.NONE: + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.MOVE_HANDLES_CLICK + move_constraint = MoveConstraint.Type.PLANE_VIEWPORT + + drag_handle_start_pos = Vector3.INF + + cmd_move_vertex = CommandMoveVertices.new() + cmd_move_vertex.builder = builder + + for block in sel_blocks: + var vol:ConvexVolume = block.control_mesh + for v_idx in vol.vertices.size(): + var v:ConvexVolume.VertexInfo = vol.vertices[v_idx] + if v.selected: + cmd_move_vertex.add_vertex(block.get_path(), v_idx) + + return true + + elif e.keycode == KEY_X: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_YZ + else: + move_constraint = MoveConstraint.Type.AXIS_X + return true + + elif e.keycode == KEY_Y: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XZ + else: + move_constraint = MoveConstraint.Type.AXIS_Y + return true + + elif e.keycode == KEY_Z: + if tool_state == ToolState.MOVE_HANDLES_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XY + else: + move_constraint = MoveConstraint.Type.AXIS_Z + return true + + + + if event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + if tool_state == ToolState.NONE: + drag_mouse_start_pos = e.position + tool_state = ToolState.READY + #print("Start READY") + + return true + else: + if tool_state == ToolState.READY: + #print("cmd select") + var handle:HandleVertex = pick_closest_handle(viewport_camera, drag_mouse_start_pos, builder.handle_screen_radius) + + var cmd:CommandSelectVertices = CommandSelectVertices.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + cmd.add_vertices(block.get_path(), []) + + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if handle: + cmd.add_vertex(handle.block_path, handle.vertex_index) + #print("selectibg %s" % handle.vertex_index) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + + tool_state = ToolState.NONE + cmd_move_vertex = null + + elif tool_state == ToolState.DRAGGING: + #Finish drag + + #print("cmd finish drag") + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd_move_vertex.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_move_vertex = null + + elif tool_state == ToolState.DRAGGING_ADD: + #Finish drag + #print("cmd finish drag add") + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd_add_vertex.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_add_vertex = null + + elif tool_state == ToolState.MOVE_HANDLES_CLICK: + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_move_vertex.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + cmd_add_vertex = null + + + elif tool_state == ToolState.DRAG_SELECTION: + + var frustum:Array[Plane] = MathUtil.calc_frustum_camera_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos) + + var cmd:CommandSelectVertices = CommandSelectVertices.new() + cmd.builder = builder + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + for block in sel_blocks: + + for v_idx in block.control_mesh.vertices.size(): + var v:ConvexVolume.VertexInfo = block.control_mesh.vertices[v_idx] + var point_w:Vector3 = block.global_transform * v.point + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) +# var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + + #Obstruction check + if !global_scene.xray_mode && builder.display_mode != DisplayMode.Type.WIRE: + var result:IntersectResults = builder.intersect_ray_closest(origin, point_w - origin) + if result: + var res_point_w:Vector3 = result.get_world_position() + if !res_point_w.is_equal_approx(point_w): + continue + + if MathUtil.frustum_contians_point(frustum, point_w): + cmd.add_vertex(block.get_path(), v_idx) + + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + return true + + elif e.button_index == MOUSE_BUTTON_RIGHT: + if e.is_pressed(): + #Right click cancel + if cmd_move_vertex: + cmd_move_vertex.undo_it() + cmd_move_vertex = null + tool_state = ToolState.NONE + + setup_tool() + return true + + if cmd_add_vertex: + cmd_add_vertex.undo_it() + cmd_add_vertex = null + tool_state = ToolState.NONE + + setup_tool() + return true + + return false + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + #mouse_hover_pos = e.position + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + if tool_state == ToolState.READY: + if e.position.distance_squared_to(drag_mouse_start_pos) > MathUtil.square(builder.drag_start_radius): + start_drag(viewport_camera, event) + + return true + + elif tool_state == ToolState.DRAGGING || tool_state == ToolState.MOVE_HANDLES_CLICK: + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + if !drag_handle_start_pos.is_finite(): + #If start point set to infinite, replace with point along view ray + drag_handle_start_pos = origin + dir * 20 + + var active_block:Node3D = builder.get_active_block() +# var gizmo_global_xform:Transform3D = calc_gizmo_transform(origin, average_normal, active_block, viewport_camera, settings.transform_space) + var xform_basis:Basis = calc_gizmo_basis(average_normal, active_block, viewport_camera, settings.transform_space) + + #print("drag_handle_start_pos ", drag_handle_start_pos) + #print("basis ", xform_basis) + var drag_to:Vector3 + match move_constraint: + MoveConstraint.Type.AXIS_X: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.AXIS_Y: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.AXIS_Z: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XY: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.z) + MoveConstraint.Type.PLANE_XZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.y) + MoveConstraint.Type.PLANE_YZ: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, xform_basis.x) + MoveConstraint.Type.PLANE_VIEWPORT: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, viewport_camera.global_transform.basis.z) + + + #print("send snap bock-2- ", drag_home_block) + drag_to = builder.get_snapping_manager().snap_point(drag_to, SnappingQuery.new(viewport_camera, [drag_home_block])) + #print("drag_to snapped ", drag_to) + + cmd_move_vertex.move_offset = drag_to - drag_handle_start_pos + #print("cmd_move_vertex.move_offset ", cmd_move_vertex.move_offset) + cmd_move_vertex.do_it() + + setup_tool() + return true + + elif tool_state == ToolState.DRAGGING_ADD: + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var drag_to:Vector3 + if e.alt_pressed: + drag_to = MathUtil.closest_point_on_line(origin, dir, drag_handle_start_pos, Vector3.UP) + else: + drag_to = MathUtil.intersect_plane(origin, dir, drag_handle_start_pos, Vector3.UP) + + #drag_to = MathUtil.snap_to_grid(drag_to, grid_step_size) + #print("send snap bock ", drag_home_block) + drag_to = builder.get_snapping_manager().snap_point(drag_to, SnappingQuery.new(viewport_camera, [drag_home_block])) + + added_point_pos = drag_to + #print("drag point to %s" % drag_to) + + cmd_add_vertex.points_to_add = [drag_to] + cmd_add_vertex.do_it() + + setup_tool() + + elif tool_state == ToolState.DRAG_SELECTION: + drag_select_to_pos = e.position + return true + + return false + diff --git a/addons/cyclops_level_builder/tools/tool_edit_vertex_settings.gd b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings.gd new file mode 100644 index 0000000..c751523 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolEditVertexSettings + +@export var transform_space:TransformSpace.Type = TransformSpace.Type.GLOBAL +@export var triplanar_lock_uvs:bool = true + +func load_from_cache(cache:Dictionary): + transform_space = cache.get("transform_space", TransformSpace.Type.GLOBAL) + triplanar_lock_uvs = cache.get("triplanar_lock_uvs", true) + +func save_to_cache(): + return { + "transform_space": transform_space, + "triplanar_lock_uvs": triplanar_lock_uvs, + } diff --git a/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.gd new file mode 100644 index 0000000..fab7200 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.gd @@ -0,0 +1,60 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolEditVertexSettingsEditor + +var settings:ToolEditVertexSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + + +func _ready(): + %transform_space.clear() + for text in TransformSpace.Type.keys(): + %transform_space.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + %transform_space.selected = settings.transform_space + %check_correct_uvs.button_pressed = settings.triplanar_lock_uvs + + pass + + +func _on_transform_space_item_selected(index): + settings.transform_space = index + + +func _on_check_correct_uvs_toggled(toggled_on): + settings.triplanar_lock_uvs = toggled_on diff --git a/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.tscn new file mode 100644 index 0000000..7811bb0 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=2 format=3 uid="uid://s8gaqniiv1on"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_edit_vertex_settings_editor.gd" id="1_6qkra"] + +[node name="ToolMoveSettingsEditor" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -929.0 +offset_bottom = -415.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_6qkra") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Transform space" + +[node name="transform_space" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 5 +selected = 0 +popup/item_0/text = "GLOBAL" +popup/item_0/id = 0 +popup/item_1/text = "LOCAL" +popup/item_1/id = 1 +popup/item_2/text = "NORMAL" +popup/item_2/id = 2 +popup/item_3/text = "VIEW" +popup/item_3/id = 3 +popup/item_4/text = "PARENT" +popup/item_4/id = 4 + +[node name="Label2" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Triplanar lock UVs" + +[node name="check_correct_uvs" type="CheckBox" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "On" + +[connection signal="item_selected" from="GridContainer/transform_space" to="." method="_on_transform_space_item_selected"] +[connection signal="toggled" from="GridContainer/check_correct_uvs" to="." method="_on_check_correct_uvs_toggled"] diff --git a/addons/cyclops_level_builder/tools/tool_material_brush.gd b/addons/cyclops_level_builder/tools/tool_material_brush.gd new file mode 100644 index 0000000..71a0bd7 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_material_brush.gd @@ -0,0 +1,209 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolMaterialBrush + +enum ToolState { READY, PAINTING } +var tool_state:ToolState = ToolState.READY + +const TOOL_ID:String = "material_brush" + +var cmd:CommandSetMaterial + +var settings:ToolMaterialBrushSettings = ToolMaterialBrushSettings.new() +var material_viewer_state:MaterialViewerState = preload("res://addons/cyclops_level_builder/docks/material_palette/material_viewer/material_viewer_state_res.tres") + +var last_mouse_pos:Vector2 + +func _get_tool_id()->String: + return TOOL_ID + +func _draw_tool(viewport_camera:Camera3D): + super._draw_tool(viewport_camera) + + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + +func _get_tool_properties_editor()->Control: + var ed:ToolMaterialBrushSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_X: + if e.shift_pressed: + if e.is_pressed(): + var origin:Vector3 = viewport_camera.project_ray_origin(last_mouse_pos) + var dir:Vector3 = viewport_camera.project_ray_normal(last_mouse_pos) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + var block:CyclopsBlock = result.object + result.face_index + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(block.mesh_vector_data) + + var face:ConvexVolume.FaceInfo = vol.faces[result.face_index] + + #Sample under cursor + if settings.paint_materials: + if face.material_id != -1: + #Pick this material + #print("face.material_id ", face.material_id) + var mat:Material = block.materials[face.material_id] \ + if face.material_id >= 0 && face.material_id < block.materials.size() \ + else null + settings.material_path = mat.resource_path if mat else NodePath() + #print("settings.material_path ", settings.material_path) + + + if settings.paint_color: + settings.color = face.color + + if settings.paint_visibility: + settings.visibility = face.visible + + if settings.paint_uv: + settings.uv_matrix = face.uv_transform + + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + if tool_state == ToolState.READY: + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + cmd = CommandSetMaterial.new() + cmd.builder = builder + + #print("settings.paint_materials ", settings.paint_materials) + cmd.setting_material = settings.paint_materials + + cmd.material_path = settings.material_path \ + if !settings.erase_material else "" + + cmd.setting_color = settings.paint_color + cmd.color = settings.color + + cmd.setting_visibility = settings.paint_visibility + cmd.visibility = settings.visibility + + cmd.painting_uv = settings.paint_uv + cmd.uv_matrix = settings.uv_matrix + + var block:CyclopsBlock = result.object + + if settings.individual_faces: + cmd.add_target(block.get_path(), [result.face_index]) + + else: + cmd.add_target(block.get_path(), block.control_mesh.get_face_indices()) + + tool_state = ToolState.PAINTING + + else: + + if tool_state == ToolState.PAINTING: + cmd.undo_it() + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.READY + + return true + + + elif event is InputEventMouseMotion: + + var e:InputEventMouseMotion = event + + last_mouse_pos = e.position + + if tool_state == ToolState.PAINTING: + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + #print ("hit ", result.object.name) + cmd.undo_it() + var block:CyclopsBlock = result.object + if settings.individual_faces: + cmd.add_target(block.get_path(), [result.face_index]) + + else: + cmd.add_target(block.get_path(), block.control_mesh.get_face_indices()) + cmd.do_it() + + return true + + return false + + +func on_material_viewer_state_changed(): + #print("mat changed to ", material_viewer_state.active_material_path) + settings.material_path = material_viewer_state.active_material_path + + +func _init(): + material_viewer_state.changed.connect(on_material_viewer_state_changed) + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + settings.material_path = material_viewer_state.active_material_path + +# material_viewer_state.changed.connect(on_material_viewer_state_changed) + +func _deactivate(): +# material_viewer_state.changed.disconnect(on_material_viewer_state_changed) + + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + + + diff --git a/addons/cyclops_level_builder/tools/tool_material_brush_settings.gd b/addons/cyclops_level_builder/tools/tool_material_brush_settings.gd new file mode 100644 index 0000000..2854bb9 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_material_brush_settings.gd @@ -0,0 +1,91 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolMaterialBrushSettings + +@export var paint_materials:bool = true +@export var paint_color:bool = false +@export var paint_visibility:bool = false +@export var paint_uv:bool = false +@export var uv_matrix:Transform2D = Transform2D.IDENTITY: + set(value): + if value != uv_matrix: + uv_matrix = value + emit_changed() + +#@export var component_type:GeometryComponentType.Type = GeometryComponentType.Type.OBJECT +@export var individual_faces:bool = false +@export var erase_material:bool = false + +@export var color:Color = Color.WHITE: + set(value): + if value != color: + color = value + emit_changed() + +@export var material_path:NodePath: + set(value): + if value != material_path: + material_path = value + emit_changed() + +@export var visibility:bool = true: + set(value): + if value != visibility: + visibility = value + emit_changed() + +func load_from_cache(cache:Dictionary): + paint_materials = cache.get("paint_materials", true) + paint_color = cache.get("paint_color", false) + paint_visibility = cache.get("paint_visibility", false) + individual_faces = cache.get("individual_faces", false) + #component_type = cache.get("component_type", GeometryComponentType.Type.OBJECT) + erase_material = cache.get("erase_material", false) + material_path = str_to_var(cache.get("material_path", NodePath())) + color = str_to_var(cache.get("color", var_to_str(Color.WHITE))) + visibility = cache.get("visibility", false) + paint_uv = cache.get("paint_uv", false) + uv_matrix = str_to_var(cache.get("uv_matrix", var_to_str(Transform2D.IDENTITY))) + +func save_to_cache(): + return { + "paint_materials": paint_materials, + "paint_color": paint_color, + "paint_visibility": paint_visibility, + "individual_faces": individual_faces, + #"component_type": component_type, + "erase_material": erase_material, + "material_path": var_to_str(material_path), + "color": var_to_str(color), + "visibility": visibility, + "paint_uv": paint_uv, + "uv_matrix": var_to_str(uv_matrix) + } + + + + + diff --git a/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.gd new file mode 100644 index 0000000..cb01a86 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.gd @@ -0,0 +1,142 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolMaterialBrushSettingsEditor + +@export var settings:ToolMaterialBrushSettings: + get: + return settings + + set(value): + if settings == value: + return + + if settings: + settings.changed.disconnect(on_settings_changed) + + settings = value + + if settings: + settings.changed.connect(on_settings_changed) + + update() + +func on_settings_changed(): + update() + +func update(): + if !settings: + %check_paint_material.disabled = true + %check_individual_faces.disabled = true + %check_erase_material.disabled = true + + %check_paint_color.disabled = true + %color_button.disabled = true + + %check_paint_visibility.disabled = true + %check_visibility.disabled = true + + %check_paint_uv.disabled = true + + return + + %check_paint_material.disabled = false + %check_paint_color.disabled = false + %check_paint_visibility.disabled = false + %check_individual_faces.disabled = false + + %check_individual_faces.button_pressed = settings.individual_faces + #%opbn_geom_component.selected = settings.component_type + + %check_paint_material.button_pressed = settings.paint_materials + %check_erase_material.button_pressed = settings.erase_material + %check_erase_material.disabled = !settings.paint_materials + + %check_paint_color.button_pressed = settings.paint_color + %color_button.color = settings.color + %color_button.disabled = !settings.paint_color + + %check_paint_visibility.button_pressed = settings.paint_visibility + %check_visibility.button_pressed = settings.visibility + %check_visibility.disabled = !settings.paint_visibility + + %check_paint_uv.button_pressed = settings.paint_uv + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_check_paint_material_toggled(button_pressed:bool): + #print("_on_check_paint_material_toggled ", button_pressed) + settings.paint_materials = button_pressed + %check_erase_material.disabled = !settings.paint_materials + + + +func _on_check_erase_material_toggled(button_pressed:bool): + settings.erase_material = button_pressed + + +func _on_check_paint_color_toggled(button_pressed:bool): + settings.paint_color = button_pressed + %color_button.disabled = !settings.paint_color + + +func _on_color_button_color_changed(color:Color): + settings.color = color + + +func _on_check_paint_visibility_toggled(button_pressed:bool): + settings.paint_visibility = button_pressed + %check_visibility.disabled = !settings.paint_visibility + + +func _on_check_visibility_toggled(button_pressed:bool): + settings.visibility = button_pressed + + +func _on_check_paint_uv_toggled(button_pressed:bool): + settings.paint_uv = button_pressed + + +func _on_check_individual_faces_toggled(button_pressed:bool): + settings.individual_faces = button_pressed + +#func _on_opbn_geom_component_item_selected(index): + #match index: + #0: + #settings.component_type = GeometryComponentType.Type.OBJECT + #1: + #settings.component_type = GeometryComponentType.Type.VERTEX + #2: + #settings.component_type = GeometryComponentType.Type.FACE + #3: + #settings.component_type = GeometryComponentType.Type.FACE_VERTEX diff --git a/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.tscn new file mode 100644 index 0000000..8914f83 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=2 format=3 uid="uid://dugi0xh84150p"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_material_brush_settings_editor.gd" id="1_q0hdg"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 317.0 +offset_bottom = 269.0 +script = ExtResource("1_q0hdg") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="check_individual_faces" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Individual faces" + +[node name="check_paint_material" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Paint materials" + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="check_erase_material" type="CheckBox" parent="VBoxContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Erase materials" + +[node name="check_paint_color" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Paint color" + +[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/MarginContainer2"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/MarginContainer2/HBoxContainer"] +layout_mode = 2 +text = "Color" + +[node name="color_button" type="ColorPickerButton" parent="VBoxContainer/MarginContainer2/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="check_paint_visibility" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Paint visibility" + +[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="check_visibility" type="CheckBox" parent="VBoxContainer/MarginContainer3"] +unique_name_in_owner = true +layout_mode = 2 +text = "Visible" + +[node name="check_paint_uv" type="CheckBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Set the face's UV transform to the identity matrix." +text = "Paint UV" + +[connection signal="toggled" from="VBoxContainer/check_individual_faces" to="." method="_on_check_individual_faces_toggled"] +[connection signal="toggled" from="VBoxContainer/check_paint_material" to="." method="_on_check_paint_material_toggled"] +[connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/check_erase_material" to="." method="_on_check_erase_material_toggled"] +[connection signal="toggled" from="VBoxContainer/check_paint_color" to="." method="_on_check_paint_color_toggled"] +[connection signal="color_changed" from="VBoxContainer/MarginContainer2/HBoxContainer/color_button" to="." method="_on_color_button_color_changed"] +[connection signal="toggled" from="VBoxContainer/check_paint_visibility" to="." method="_on_check_paint_visibility_toggled"] +[connection signal="toggled" from="VBoxContainer/MarginContainer3/check_visibility" to="." method="_on_check_visibility_toggled"] +[connection signal="toggled" from="VBoxContainer/check_paint_uv" to="." method="_on_check_paint_uv_toggled"] diff --git a/addons/cyclops_level_builder/tools/tool_move.gd b/addons/cyclops_level_builder/tools/tool_move.gd new file mode 100644 index 0000000..7506cd7 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_move.gd @@ -0,0 +1,451 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolMove + +const TOOL_ID:String = "move" + + +enum ToolState { NONE, READY, MOVE_BLOCK, MOVE_BLOCK_CLICK, DRAG_SELECTION } +var tool_state:ToolState = ToolState.NONE + +#enum MoveConstraint { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +#var viewport_camera_start:Camera3D +var event_start:InputEventMouseButton + +var block_drag_cur:Vector3 +var block_drag_p0:Vector3 + +var drag_select_start_pos:Vector2 +var drag_select_to_pos:Vector2 + +var mouse_hover_pos:Vector2 + +#Keep a copy of move command here while we are building it +#var cmd_move_blocks:CommandMoveBlocks +var cmd_xform_blocks:CommandTransformBlocks + +var base_points:PackedVector3Array + +var gizmo_translate:GizmoTranslate + +var settings:ToolMoveSettings = ToolMoveSettings.new() + +func _get_tool_properties_editor()->Control: + var ed:ToolMoveSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_move_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func _get_tool_id()->String: + return TOOL_ID + +func draw_gizmo(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + if !gizmo_translate: + gizmo_translate = preload("res://addons/cyclops_level_builder/tools/gizmos/gizmo_translate.tscn").instantiate() + + var blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + var active_block:Node3D = builder.get_active_block() + + if blocks.is_empty(): + global_scene.set_custom_gizmo(null) + else: + var origin:Vector3 + for block in blocks: + origin += block.global_transform.origin + origin /= blocks.size() + global_scene.set_custom_gizmo(gizmo_translate) + + match settings.transform_space: + TransformSpace.Type.GLOBAL: + var xform:Transform3D = Transform3D.IDENTITY + xform.origin = origin + gizmo_translate.global_transform = xform + TransformSpace.Type.LOCAL: + var xform:Transform3D = active_block.global_transform + gizmo_translate.global_transform = xform + TransformSpace.Type.NORMAL: + var xform:Transform3D = active_block.global_transform + gizmo_translate.global_transform = xform + TransformSpace.Type.VIEW: + gizmo_translate.global_basis = viewport_camera.global_basis + gizmo_translate.global_position = origin + TransformSpace.Type.PARENT: + var xform:Transform3D = active_block.get_parent_node_3d().global_transform + gizmo_translate.global_transform = xform + + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if tool_state == ToolState.DRAG_SELECTION: + #print("draw sel %s " % drag_select_to_pos) + global_scene.draw_screen_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos, global_scene.selection_rect_material) + + draw_gizmo(viewport_camera) + + + +func start_drag(viewport_camera:Camera3D, event:InputEvent): + var blocks_root:Node = builder.get_block_add_parent() + var e:InputEventMouseButton = event + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + move_constraint = MoveConstraint.Type.NONE + + if gizmo_translate: + var part_res:GizmoTranslate.IntersectResult = gizmo_translate.intersect(origin, dir, viewport_camera) + if part_res: + #print("Gizmo hit ", part_res.part) + match part_res.part: + GizmoTranslate.Part.AXIS_X: + move_constraint = MoveConstraint.Type.AXIS_X + GizmoTranslate.Part.AXIS_Y: + move_constraint = MoveConstraint.Type.AXIS_Y + GizmoTranslate.Part.AXIS_Z: + move_constraint = MoveConstraint.Type.AXIS_Z + GizmoTranslate.Part.PLANE_XY: + move_constraint = MoveConstraint.Type.PLANE_XY + GizmoTranslate.Part.PLANE_XZ: + move_constraint = MoveConstraint.Type.PLANE_XZ + GizmoTranslate.Part.PLANE_YZ: + move_constraint = MoveConstraint.Type.PLANE_YZ + + var start_pos:Vector3 = part_res.pos_world +# var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + #block_drag_p0 = MathUtil.snap_to_grid(start_pos, grid_step_size) + block_drag_p0 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + + # print("res obj %s" % result.object.get_path()) + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.MOVE_BLOCK + #print("Move block") + + cmd_xform_blocks = CommandTransformBlocks.new() + cmd_xform_blocks.builder = builder + cmd_xform_blocks.lock_uvs = settings.correct_uvs + for child in sel_blocks: + cmd_xform_blocks.add_block(child.get_path()) + + return + + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) +# print("result %s" % result) + + if result: + + if e.alt_pressed: + move_constraint = MoveConstraint.Type.AXIS_Y + else: + move_constraint = MoveConstraint.Type.PLANE_XZ + + var start_pos:Vector3 = result.get_world_position() + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + #block_drag_p0 = MathUtil.snap_to_grid(start_pos, grid_step_size) + block_drag_p0 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + + #print("block_drag_p0 %s" % block_drag_p0) + +# print("res obj %s" % result.object.get_path()) + if builder.is_selected(result.object): + + tool_state = ToolState.MOVE_BLOCK + + cmd_xform_blocks = CommandTransformBlocks.new() + cmd_xform_blocks.builder = builder + cmd_xform_blocks.lock_uvs = settings.correct_uvs + for child in builder.get_selected_blocks(): + cmd_xform_blocks.add_block(child.get_path()) + + return + + tool_state = ToolState.DRAG_SELECTION + drag_select_start_pos = e.position + drag_select_to_pos = e.position + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.NONE + if cmd_xform_blocks: + cmd_xform_blocks.undo_it() + cmd_xform_blocks = null + + return true + + elif e.keycode == KEY_G: + if e.is_pressed() && tool_state == ToolState.NONE: + tool_state = ToolState.MOVE_BLOCK_CLICK + move_constraint = MoveConstraint.Type.PLANE_VIEWPORT +# block_drag_p0 = MathUtil.intersect_plane(origin, dir, block_drag_p0, viewport_camera.global_transform.basis.z) +# block_drag_p0 = origin + dir * 20 + block_drag_p0 = Vector3.INF + + cmd_xform_blocks = CommandTransformBlocks.new() + cmd_xform_blocks.builder = builder + cmd_xform_blocks.lock_uvs = settings.correct_uvs + for child in builder.get_selected_blocks(): + cmd_xform_blocks.add_block(child.get_path()) + + return true + + elif e.keycode == KEY_X: + if tool_state == ToolState.MOVE_BLOCK_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_YZ + else: + move_constraint = MoveConstraint.Type.AXIS_X + return true + + elif e.keycode == KEY_Y: + if tool_state == ToolState.MOVE_BLOCK_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XZ + else: + move_constraint = MoveConstraint.Type.AXIS_Y + return true + + elif e.keycode == KEY_Z: + if tool_state == ToolState.MOVE_BLOCK_CLICK: + if e.shift_pressed: + move_constraint = MoveConstraint.Type.PLANE_XY + else: + move_constraint = MoveConstraint.Type.AXIS_Z + return true + + if e.keycode == KEY_Q && e.alt_pressed: + if e.is_pressed(): + var origin:Vector3 = viewport_camera.project_ray_origin(mouse_hover_pos) + var dir:Vector3 = viewport_camera.project_ray_normal(mouse_hover_pos) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result: + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.block_paths.append(result.object.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + _deactivate() + _activate(builder) + + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + if tool_state == ToolState.NONE: + event_start = event + + tool_state = ToolState.READY + + elif tool_state == ToolState.MOVE_BLOCK_CLICK: + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_xform_blocks.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + else: + if tool_state == ToolState.READY: + + #We just clicked with the mouse + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + #print("Invokke select %s" % result) + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if result: + cmd.block_paths.append(result.object.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + elif tool_state == ToolState.MOVE_BLOCK: + + #Finish moving blocks + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_xform_blocks.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + elif tool_state == ToolState.DRAG_SELECTION: + + var frustum:Array[Plane] = MathUtil.calc_frustum_camera_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos) + + var result:Array[CyclopsBlock] = builder.intersect_frustum_all(frustum) + + if !result.is_empty(): + + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + for r in result: + cmd.block_paths.append(r.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + return true + + elif e.button_index == MOUSE_BUTTON_RIGHT: + if e.is_pressed(): + #Right click cancel + if tool_state == ToolState.MOVE_BLOCK || tool_state == ToolState.MOVE_BLOCK_CLICK: + tool_state = ToolState.NONE + if cmd_xform_blocks: + cmd_xform_blocks.undo_it() + cmd_xform_blocks = null + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + mouse_hover_pos = e.position + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return super._gui_input(viewport_camera, event) + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + #print("tool_state %s" % tool_state) + + if tool_state == ToolState.READY: + var offset:Vector2 = e.position - event_start.position + if offset.length_squared() > MathUtil.square(builder.drag_start_radius): + start_drag(viewport_camera, event_start) + + return true + + elif tool_state == ToolState.MOVE_BLOCK || tool_state == ToolState.MOVE_BLOCK_CLICK: + if !block_drag_p0.is_finite(): + block_drag_p0 = origin + dir * 20 + + var xform_basis:Basis + + match settings.transform_space: + TransformSpace.Type.GLOBAL: + xform_basis = Basis.IDENTITY + TransformSpace.Type.LOCAL: + var active_block:Node3D = builder.get_active_block() + xform_basis = active_block.basis + TransformSpace.Type.NORMAL: + var active_block:Node3D = builder.get_active_block() + xform_basis = active_block.basis + TransformSpace.Type.VIEW: + xform_basis = viewport_camera.global_basis + TransformSpace.Type.PARENT: + var active_block:Node3D = builder.get_active_block().get_parent_node_3d() + xform_basis = active_block.basis + + + match move_constraint: + MoveConstraint.Type.AXIS_X: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, block_drag_p0, xform_basis.x) + MoveConstraint.Type.AXIS_Y: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, block_drag_p0, xform_basis.y) + MoveConstraint.Type.AXIS_Z: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, block_drag_p0, xform_basis.z) + MoveConstraint.Type.PLANE_XY: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, xform_basis.z) + MoveConstraint.Type.PLANE_XZ: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, xform_basis.y) + MoveConstraint.Type.PLANE_YZ: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, xform_basis.x) + MoveConstraint.Type.PLANE_VIEWPORT: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, viewport_camera.global_transform.basis.z) + + #print("dragging move_constraint %s block_drag_cur %s" % [move_constraint, block_drag_cur]) + + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + #block_drag_cur = MathUtil.snap_to_grid(block_drag_cur, grid_step_size) + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + #cmd_move_blocks.move_offset = block_drag_cur - block_drag_p0 + cmd_xform_blocks.transform = Transform3D(Basis.IDENTITY, block_drag_cur - block_drag_p0) + #print("cmd_move_blocks.move_offset %s" % cmd_move_blocks.move_offset) + cmd_xform_blocks.do_it() + + return true + + elif tool_state == ToolState.DRAG_SELECTION: + drag_select_to_pos = e.position + return true + + + return super._gui_input(viewport_camera, event) + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.set_custom_gizmo(null) + + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + diff --git a/addons/cyclops_level_builder/tools/tool_move_settings.gd b/addons/cyclops_level_builder/tools/tool_move_settings.gd new file mode 100644 index 0000000..69db0a3 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_move_settings.gd @@ -0,0 +1,39 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolMoveSettings + +@export var transform_space:TransformSpace.Type = TransformSpace.Type.GLOBAL +@export var correct_uvs:bool = true + +func load_from_cache(cache:Dictionary): + transform_space = cache.get("transform_space", TransformSpace.Type.GLOBAL) + correct_uvs = cache.get("correct_uvs", true) + +func save_to_cache(): + return { + "transform_space": transform_space, + "correct_uvs": correct_uvs, + } diff --git a/addons/cyclops_level_builder/tools/tool_move_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_move_settings_editor.gd new file mode 100644 index 0000000..4c2e51a --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_move_settings_editor.gd @@ -0,0 +1,60 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolMoveSettingsEditor + +var settings:ToolMoveSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + + +func _ready(): + %transform_space.clear() + for text in TransformSpace.Type.keys(): + %transform_space.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + %transform_space.selected = settings.transform_space + %check_correct_uvs.button_pressed = settings.correct_uvs + + pass + + +func _on_transform_space_item_selected(index): + settings.transform_space = index + + +func _on_check_correct_uvs_toggled(toggled_on): + settings.correct_uvs = toggled_on diff --git a/addons/cyclops_level_builder/tools/tool_move_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_move_settings_editor.tscn new file mode 100644 index 0000000..37b1459 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_move_settings_editor.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=2 format=3 uid="uid://c83wthlpyd7dm"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_move_settings_editor.gd" id="1_w2n7n"] + +[node name="ToolMoveSettingsEditor" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -929.0 +offset_bottom = -415.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_w2n7n") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Transform space" + +[node name="transform_space" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 5 +selected = 0 +popup/item_0/text = "GLOBAL" +popup/item_0/id = 0 +popup/item_1/text = "LOCAL" +popup/item_1/id = 1 +popup/item_2/text = "NORMAL" +popup/item_2/id = 2 +popup/item_3/text = "VIEW" +popup/item_3/id = 3 +popup/item_4/text = "PARENT" +popup/item_4/id = 4 + +[node name="Label2" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Triplanar Lock UVs" + +[node name="check_correct_uvs" type="CheckBox" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "On" + +[connection signal="item_selected" from="GridContainer/transform_space" to="." method="_on_transform_space_item_selected"] +[connection signal="toggled" from="GridContainer/check_correct_uvs" to="." method="_on_check_correct_uvs_toggled"] diff --git a/addons/cyclops_level_builder/tools/tool_prism.gd b/addons/cyclops_level_builder/tools/tool_prism.gd new file mode 100644 index 0000000..6d7ffca --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_prism.gd @@ -0,0 +1,265 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolPrism + +const TOOL_ID:String = "prism" + +enum ToolState { READY, BASE_POINTS, DRAG_HEIGHT } +var tool_state:ToolState = ToolState.READY + +var floor_normal:Vector3 +var base_points:PackedVector3Array +var block_drag_cur:Vector3 +var drag_offset:Vector3 +var preview_point:Vector3 + +var settings:ToolPrismSettings = ToolPrismSettings.new() + +func _get_tool_properties_editor()->Control: + var ed:ToolPrismSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_prism_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if tool_state == ToolState.BASE_POINTS: + var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + global_scene.draw_loop(bounding_points, true, global_scene.tool_material) + global_scene.draw_points(bounding_points, global_scene.vertex_tool_material) + + global_scene.draw_vertex(preview_point, global_scene.vertex_tool_material) + + if tool_state == ToolState.DRAG_HEIGHT: + var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + global_scene.draw_prism(bounding_points, drag_offset, global_scene.tool_material, global_scene.vertex_tool_material) + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + var blocks_root:Node = builder.get_block_add_parent() + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ENTER: + if e.is_pressed(): + if tool_state == ToolState.BASE_POINTS: + var camera_dir:Vector3 = viewport_camera.global_transform.basis.z + var angle_with_base:float = acos(floor_normal.dot(camera_dir)) + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + var height = settings.default_block_height + + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(base_points[0], floor_normal) + + drag_offset = floor_normal * height + block_drag_cur = base_points[0] + drag_offset + + create_block() + + tool_state = ToolState.READY + else: + + drag_offset = Vector3.ZERO + tool_state = ToolState.DRAG_HEIGHT + return true + + elif e.keycode == KEY_BACKSPACE: + if e.is_pressed(): + base_points.remove_at(base_points.size() - 1) + return true + + elif e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.READY + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + if tool_state == ToolState.READY: + base_points.clear() + tool_state = ToolState.BASE_POINTS + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result && settings.block_alignment == BlockAlignment.Type.ALIGN_TO_SURFACE: + floor_normal = result.get_world_normal() + + var p:Vector3 = builder.get_snapping_manager().snap_point(result.get_world_position(), SnappingQuery.new(viewport_camera)) + + base_points.append(p) + preview_point = p + + return true + + else: + #print("init base point empty space") + var draw_plane_point:Vector3 = Vector3.ZERO + var draw_plane_normal:Vector3 = BlockAlignment.get_plane_normal(settings.block_alignment) + if settings.match_selected_block: + draw_plane_point = calc_empty_space_draw_plane_origin(viewport_camera, draw_plane_point, draw_plane_normal) + + var hit_result = calc_hit_point_empty_space(origin, dir, viewport_camera, draw_plane_point, draw_plane_normal) + var start_pos:Vector3 = hit_result[0] + floor_normal = hit_result[1] + + +# var p:Vector3 = MathUtil.snap_to_grid(start_pos, grid_step_size) + var p:Vector3 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + base_points.append(p) + + return true + + elif tool_state == ToolState.BASE_POINTS: + #print("add base point") + if e.double_click: + if e.is_pressed(): + var camera_dir:Vector3 = viewport_camera.global_transform.basis.z + var angle_with_base:float = acos(floor_normal.dot(camera_dir)) + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + var height = settings.default_block_height + + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(base_points[0], floor_normal) + + drag_offset = floor_normal * height + block_drag_cur = base_points[0] + drag_offset + + create_block() + + tool_state = ToolState.READY + else: + drag_offset = Vector3.ZERO + tool_state = ToolState.DRAG_HEIGHT + return true + + var p_isect:Vector3 = MathUtil.intersect_plane(origin, dir, base_points[0], floor_normal) + var p:Vector3 = builder.get_snapping_manager().snap_point(p_isect, SnappingQuery.new(viewport_camera)) + base_points.append(p) + + var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + return true + + elif tool_state == ToolState.DRAG_HEIGHT: + create_block() + + tool_state = ToolState.READY + return true + + if e.button_index == MOUSE_BUTTON_RIGHT: + + if tool_state == ToolState.BASE_POINTS: + if e.is_pressed(): + for p_idx in base_points.size(): + var screen_pos:Vector2 = viewport_camera.unproject_position(base_points[p_idx]) + if screen_pos.distance_to(e.position) < builder.handle_screen_radius: + base_points.remove_at(p_idx) + break + return true + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var start_pos:Vector3 = origin + builder.block_create_distance * dir + var w2l = blocks_root.global_transform.inverse() + var origin_local:Vector3 = w2l * origin + var dir_local:Vector3 = w2l.basis * dir + + if tool_state == ToolState.BASE_POINTS: + var p_isect:Vector3 = MathUtil.intersect_plane(origin, dir, base_points[0], floor_normal) + preview_point = builder.get_snapping_manager().snap_point(p_isect, SnappingQuery.new(viewport_camera)) + + + elif tool_state == ToolState.DRAG_HEIGHT: + block_drag_cur = MathUtil.closest_point_on_line(origin_local, dir_local, base_points[0], floor_normal) + + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + drag_offset = block_drag_cur - base_points[0] + var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + + return true + + return super._gui_input(viewport_camera, event) + +func create_block(): + var blocks_root:Node = builder.get_block_add_parent() + + var bounding_points:PackedVector3Array = MathUtil.bounding_polygon_3d(base_points, floor_normal) + drag_offset = block_drag_cur - base_points[0] + + var cmd:CommandAddPrism = CommandAddPrism.new() + cmd.builder = builder + cmd.block_name = GeneralUtil.find_unique_name(blocks_root, "Block_") + cmd.blocks_root_path = blocks_root.get_path() + cmd.base_polygon = bounding_points + #cmd.local_transform = local_xform + cmd.extrude = drag_offset + cmd.uv_transform = builder.tool_uv_transform + cmd.material_path = builder.tool_material_path + cmd.collision_type = settings.collision_type + cmd.collision_layers = settings.collision_layer + cmd.collision_mask = settings.collision_mask + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/tools/tool_prism_settings.gd b/addons/cyclops_level_builder/tools/tool_prism_settings.gd new file mode 100644 index 0000000..a935c09 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_prism_settings.gd @@ -0,0 +1,56 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolPrismSettings + + +@export var block_alignment:BlockAlignment.Type = BlockAlignment.Type.ALIGN_TO_SURFACE +@export var match_selected_block:bool = true +@export var default_block_elevation:float = 0 +@export var default_block_height:float = 1 +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export_flags_3d_physics var collision_layer:int = 1 +@export_flags_3d_physics var collision_mask:int = 1 + +func load_from_cache(cache:Dictionary): + block_alignment = cache.get("block_alignment", BlockAlignment.Type.ALIGN_TO_SURFACE) + match_selected_block = cache.get("match_selected_block", true) + default_block_elevation = cache.get("default_block_elevation", 0) + default_block_height = cache.get("default_block_height", 1) + collision_type = cache.get("collision_type", Collision.Type.STATIC) + collision_layer = cache.get("collision_layer", 1) + collision_mask = cache.get("collision_mask", 1) + +func save_to_cache(): + return { + "block_alignment": block_alignment, + "match_selected_block": match_selected_block, + "default_block_elevation": default_block_elevation, + "default_block_height": default_block_height, + "collision_type": collision_type, + "collision_layer": collision_layer, + "collision_mask": collision_mask, + } + diff --git a/addons/cyclops_level_builder/tools/tool_prism_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_prism_settings_editor.gd new file mode 100644 index 0000000..1679ff7 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_prism_settings_editor.gd @@ -0,0 +1,90 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolPrismSettingsEditor + +var settings:ToolPrismSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + +func _ready(): + %collision_type.clear() + for text in Collision.Type.keys(): + %collision_type.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + +func update(): + if !settings: + %check_match_selected_block.disabled = true + %default_block_elevation.disabled = true + %default_block_height.disabled = true + return + + %check_match_selected_block.disabled = false + %check_match_selected_block.button_pressed = settings.match_selected_block + %default_block_elevation.disabled = false + %default_block_elevation.value = settings.default_block_elevation + %default_block_height.disabled = false + %default_block_height.value = settings.default_block_height + + %alignment_type.selected = settings.block_alignment + + %collision_type.selected = settings.collision_type + %collision_layers.value = settings.collision_layer + %collision_mask.value = settings.collision_mask + +func _on_default_block_height_value_changed(value:float): + settings.default_block_height = value + + +func _on_default_block_elevation_value_changed(value:float): + settings.default_block_elevation = value + + +func _on_check_match_selected_block_toggled(value:bool): + settings.match_selected_block = value + +func _on_collision_layers_value_changed(value): + settings.collision_layer = value + + +func _on_collision_mask_value_changed(value): + settings.collision_mask = value + +func _on_collision_type_item_selected(index): + settings.collision_type = index + + +func _on_alignment_type_item_selected(index): + settings.block_alignment = index diff --git a/addons/cyclops_level_builder/tools/tool_prism_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_prism_settings_editor.tscn new file mode 100644 index 0000000..544a2a7 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_prism_settings_editor.tscn @@ -0,0 +1,132 @@ +[gd_scene load_steps=3 format=3 uid="uid://b7vyy46r72h0d"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_prism_settings_editor.gd" id="1_rbt26"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_aysnj"] + +[node name="ToolPrismSettings" type="PanelContainer"] +offset_right = 315.0 +offset_bottom = 70.0 +script = ExtResource("1_rbt26") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Type" + +[node name="collision_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "NONE" +popup/item_0/id = 0 +popup/item_1/text = "STATIC" +popup/item_1/id = 1 +popup/item_2/text = "KINEMATIC" +popup/item_2/id = 2 +popup/item_3/text = "RIGID" +popup/item_3/id = 3 + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Layers" + +[node name="collision_layers" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label3" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Mask" + +[node name="collision_mask" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label5" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Alignment" + +[node name="alignment_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "Align to surface" +popup/item_0/id = 0 +popup/item_1/text = "XY Plane" +popup/item_1/id = 1 +popup/item_2/text = "XZ Plane" +popup/item_2/id = 2 +popup/item_3/text = "YZ Plane" +popup/item_3/id = 3 + +[node name="Label4" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Match Active Block" + +[node name="check_match_selected_block" type="CheckBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "When drawing in empty space, copy elevation and height properties from currently selected block." +disabled = true +text = "Match selected block" + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Orthogonal Viewport:" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Elevation" + +[node name="default_block_elevation" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_aysnj")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Height" + +[node name="default_block_height" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_aysnj")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true + +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/collision_type" to="." method="_on_collision_type_item_selected"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_layers" to="." method="_on_collision_layers_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_mask" to="." method="_on_collision_mask_value_changed"] +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/alignment_type" to="." method="_on_alignment_type_item_selected"] +[connection signal="toggled" from="PanelContainer/VBoxContainer/GridContainer/check_match_selected_block" to="." method="_on_check_match_selected_block_toggled"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_elevation" to="." method="_on_default_block_elevation_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_height" to="." method="_on_default_block_height_value_changed"] diff --git a/addons/cyclops_level_builder/tools/tool_rotate.gd b/addons/cyclops_level_builder/tools/tool_rotate.gd new file mode 100644 index 0000000..fb0e382 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_rotate.gd @@ -0,0 +1,336 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolRotate + +const TOOL_ID:String = "rotate" + + +enum ToolState { NONE, READY, ROTATE_BLOCK, DRAG_SELECTION } +var tool_state:ToolState = ToolState.NONE + +#enum MoveConstraint { NONE, AXIS_X, AXIS_Y, AXIS_Z, PLANE_XY, PLANE_XZ, PLANE_YZ, PLANE_VIEWPORT } +var move_constraint:MoveConstraint.Type = MoveConstraint.Type.NONE + +#var viewport_camera_start:Camera3D +var event_start:InputEventMouseButton + +var drag_select_start_pos:Vector2 +var drag_select_to_pos:Vector2 + +var block_drag_cur:Vector3 +var block_drag_p0:Vector3 +var block_drag_origin:Vector3 + +var gizmo_rotate:GizmoRotate + +var mouse_hover_pos:Vector2 + +#Keep a copy of move command here while we are building it +var cmd_transform_blocks:CommandTransformBlocks + +func _get_tool_id()->String: + return TOOL_ID + +func draw_gizmo(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + if !gizmo_rotate: + gizmo_rotate = preload("res://addons/cyclops_level_builder/tools/gizmos/gizmo_rotate.tscn").instantiate() + + var blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if blocks.is_empty(): + global_scene.set_custom_gizmo(null) + else: + var origin:Vector3 + for block in blocks: + origin += block.global_transform.origin + origin /= blocks.size() + global_scene.set_custom_gizmo(gizmo_rotate) + gizmo_rotate.global_transform.origin = origin + + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + if tool_state == ToolState.DRAG_SELECTION: + #print("draw sel %s " % drag_select_to_pos) + global_scene.draw_screen_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos, global_scene.selection_rect_material) + + draw_gizmo(viewport_camera) + + + +func start_drag(viewport_camera:Camera3D, event:InputEvent): + var blocks_root:Node = builder.get_block_add_parent() + var e:InputEventMouseButton = event + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + move_constraint = MoveConstraint.Type.NONE + + if gizmo_rotate: + var part_res:GizmoRotate.IntersectResult = gizmo_rotate.intersect(origin, dir, viewport_camera) + if part_res: + #print("Gizmo hit ", part_res.part) + match part_res.part: + GizmoRotate.Part.PLANE_XY: + move_constraint = MoveConstraint.Type.PLANE_XY + GizmoRotate.Part.PLANE_XZ: + move_constraint = MoveConstraint.Type.PLANE_XZ + GizmoRotate.Part.PLANE_YZ: + move_constraint = MoveConstraint.Type.PLANE_YZ + + var start_pos:Vector3 = part_res.pos_world +# var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + #block_drag_p0 = MathUtil.snap_to_grid(start_pos, grid_step_size) + block_drag_p0 = start_pos + + var blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + #var blocks_origin:Vector3 + block_drag_origin = Vector3.ZERO + for block in blocks: + block_drag_origin += block.global_transform.origin + block_drag_origin /= blocks.size() + + # print("res obj %s" % result.object.get_path()) + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() + if !sel_blocks.is_empty(): + + tool_state = ToolState.ROTATE_BLOCK + #print("Move block") + + cmd_transform_blocks = CommandTransformBlocks.new() + cmd_transform_blocks.builder = builder + cmd_transform_blocks.lock_uvs = builder.lock_uvs + for child in sel_blocks: + cmd_transform_blocks.add_block(child.get_path()) + + return + + + tool_state = ToolState.DRAG_SELECTION + drag_select_start_pos = e.position + drag_select_to_pos = e.position + + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.NONE + if cmd_transform_blocks: + cmd_transform_blocks.undo_it() + cmd_transform_blocks = null + + return true + + + if e.keycode == KEY_Q && e.alt_pressed: + if e.is_pressed(): + var origin:Vector3 = viewport_camera.project_ray_origin(mouse_hover_pos) + var dir:Vector3 = viewport_camera.project_ray_normal(mouse_hover_pos) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result: + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.block_paths.append(result.object.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + _deactivate() + _activate(builder) + + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + if tool_state == ToolState.NONE: + event_start = event + + tool_state = ToolState.READY + + else: + if tool_state == ToolState.READY: + + #We just clicked with the mouse + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + #print("Invoke select %s" % result) + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + if result: + cmd.block_paths.append(result.object.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + elif tool_state == ToolState.ROTATE_BLOCK: + + #Finish moving blocks + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd_transform_blocks.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + elif tool_state == ToolState.DRAG_SELECTION: + + var frustum:Array[Plane] = MathUtil.calc_frustum_camera_rect(viewport_camera, drag_select_start_pos, drag_select_to_pos) + + var result:Array[CyclopsBlock] = builder.intersect_frustum_all(frustum) + + if !result.is_empty(): + + var cmd:CommandSelectBlocks = CommandSelectBlocks.new() + cmd.builder = builder + cmd.selection_type = Selection.choose_type(e.shift_pressed, e.ctrl_pressed) + + for r in result: + cmd.block_paths.append(r.get_path()) + + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.NONE + + return true + + elif e.button_index == MOUSE_BUTTON_RIGHT: + if e.is_pressed(): + #Right click cancel + if tool_state == ToolState.ROTATE_BLOCK: + tool_state = ToolState.NONE + if cmd_transform_blocks: + cmd_transform_blocks.undo_it() + cmd_transform_blocks = null + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + mouse_hover_pos = e.position + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return super._gui_input(viewport_camera, event) + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + #print("tool_state %s" % tool_state) + + if tool_state == ToolState.READY: + var offset:Vector2 = e.position - event_start.position + if offset.length_squared() > MathUtil.square(builder.drag_start_radius): + start_drag(viewport_camera, event_start) + + return true + + elif tool_state == ToolState.ROTATE_BLOCK: + if !block_drag_p0.is_finite(): + block_drag_p0 = origin + dir * 20 + + var rot_axis:Vector3 + match move_constraint: + MoveConstraint.Type.PLANE_XY: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, Vector3.BACK) + rot_axis = Vector3.BACK + MoveConstraint.Type.PLANE_XZ: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, Vector3.UP) + rot_axis = Vector3.UP + MoveConstraint.Type.PLANE_YZ: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, Vector3.RIGHT) + rot_axis = Vector3.RIGHT + MoveConstraint.Type.PLANE_VIEWPORT: + block_drag_cur = MathUtil.intersect_plane(origin, dir, block_drag_p0, viewport_camera.global_transform.basis.z) + rot_axis = viewport_camera.global_transform.basis.z + + #print("dragging move_constraint %s block_drag_cur %s" % [move_constraint, block_drag_cur]) + + var v0:Vector3 = (block_drag_p0 - block_drag_origin).normalized() + var v1:Vector3 = (block_drag_cur - block_drag_origin).normalized() + var binorm:Vector3 = v0.cross(rot_axis) + + var angle:float = atan2(v1.dot(binorm), v1.dot(v0)) + var snapped_angle = builder.get_snapping_manager().snap_angle(rad_to_deg(angle), SnappingQuery.new(viewport_camera)) + angle = deg_to_rad(snapped_angle) + + var xform:Transform3D = Transform3D.IDENTITY + xform = xform.translated_local(block_drag_origin) + xform = xform.rotated_local(rot_axis, -angle) + xform = xform.translated_local(-block_drag_origin) + #var rot_basis:Basis + #rot_basis = rot_basis.rotated(rot_axis, angle) + + + + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + cmd_transform_blocks.transform = xform + #print("cmd_move_blocks.move_offset %s" % cmd_move_blocks.move_offset) + cmd_transform_blocks.do_it() + + return true + + elif tool_state == ToolState.DRAG_SELECTION: + drag_select_to_pos = e.position + return true + + + return super._gui_input(viewport_camera, event) + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + +func _deactivate(): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.set_custom_gizmo(null) + diff --git a/addons/cyclops_level_builder/tools/tool_stairs.gd b/addons/cyclops_level_builder/tools/tool_stairs.gd new file mode 100644 index 0000000..17fc3a8 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_stairs.gd @@ -0,0 +1,319 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolStairs + +const TOOL_ID:String = "stairs" + +enum ToolState { READY, DRAG_BASE, DRAG_HEIGHT } +var tool_state:ToolState = ToolState.READY + +var settings:ToolStairsSettings = ToolStairsSettings.new() + +var floor_normal:Vector3 +var drag_origin:Vector3 +var base_drag_cur:Vector3 +var block_drag_cur:Vector3 + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + builder.mode = CyclopsLevelBuilder.Mode.OBJECT + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) + +func _get_tool_properties_editor()->Control: + #var res_insp:ResourceInspector = preload("res://addons/cyclops_level_builder/controls/resource_inspector/resource_inspector.tscn").instantiate() + # + #res_insp.target = settings + # + #return res_insp + var ed:ToolStairsSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_stairs_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + + if tool_state == ToolState.DRAG_BASE: + var p01:Vector3 + var p10:Vector3 + var axis:MathUtil.Axis = MathUtil.get_longest_axis(floor_normal) + match axis: + MathUtil.Axis.X: + p01 = Vector3(drag_origin.x, drag_origin.y, base_drag_cur.z) + p10 = Vector3(drag_origin.x, base_drag_cur.y, drag_origin.z) + MathUtil.Axis.Y: + p01 = Vector3(drag_origin.x, drag_origin.y, base_drag_cur.z) + p10 = Vector3(base_drag_cur.x, drag_origin.y, drag_origin.z) + MathUtil.Axis.Z: + p01 = Vector3(drag_origin.x, base_drag_cur.y, drag_origin.z) + p10 = Vector3(base_drag_cur.x, drag_origin.y, drag_origin.z) + + var base_points:PackedVector3Array = [drag_origin, p01, base_drag_cur, p10] + + global_scene.draw_loop(base_points, true, global_scene.tool_material) + global_scene.draw_points(base_points, global_scene.vertex_tool_material) + + if tool_state == ToolState.DRAG_HEIGHT: + var tan_bi:Array[Vector3] = MathUtil.get_axis_aligned_tangent_and_binormal(floor_normal) + var u_normal:Vector3 = tan_bi[0] + var v_normal:Vector3 = tan_bi[1] + + #Rotate ccw by 90 degree increments + match settings.direction: + 1: + var tmp:Vector3 = u_normal + u_normal = -v_normal + v_normal = tmp + 2: + u_normal = -u_normal + v_normal = -v_normal + 3: + var tmp:Vector3 = -u_normal + u_normal = v_normal + v_normal = tmp + + var u_span:Vector3 = (base_drag_cur - drag_origin).project(u_normal) + var v_span:Vector3 = (base_drag_cur - drag_origin).project(v_normal) + + var stairs_origin:Vector3 = drag_origin + if u_span.dot(u_normal) < 0: + stairs_origin += u_span + u_span = -u_span + if v_span.dot(v_normal) < 0: + stairs_origin += v_span + v_span = -v_span + + #Stairs should ascend along v axis + global_scene.draw_cube(drag_origin, base_drag_cur, block_drag_cur, global_scene.tool_material, global_scene.vertex_tool_material) + + var height_offset = block_drag_cur - base_drag_cur + if height_offset.dot(floor_normal) < 0: + return + var num_steps:int = min(v_span.length() / settings.step_depth, height_offset.length() / settings.step_height) + + var max_height:float = floor(height_offset.length() / settings.step_height) * settings.step_height + + var step_span:Vector3 = v_normal * settings.step_depth + for i in num_steps: + var base_points:PackedVector3Array = [stairs_origin + step_span * i, \ + stairs_origin + u_span + step_span * i, \ + stairs_origin + u_span + step_span * (i + 1), \ + stairs_origin + step_span * (i + 1)] + global_scene.draw_prism(base_points, \ + floor_normal * (max_height - settings.step_height * i), \ + global_scene.tool_material, \ + global_scene.vertex_tool_material) + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + var blocks_root:Node = builder.get_block_add_parent() + #var grid_step_size:float = pow(2, builder.get_global_scene().grid_size) + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_ESCAPE: + if e.is_pressed(): + tool_state = ToolState.READY + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + if tool_state == ToolState.READY: + tool_state = ToolState.DRAG_BASE + + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + if result && settings.block_alignment == BlockAlignment.Type.ALIGN_TO_SURFACE: + #print("init base point block") + floor_normal = result.get_world_normal() + +# var p:Vector3 = MathUtil.snap_to_grid(result.get_world_position(), grid_step_size) + var p:Vector3 = builder.get_snapping_manager().snap_point(result.get_world_position(), SnappingQuery.new(viewport_camera)) + drag_origin = p + base_drag_cur = p + + return true + + else: + #print("init base point empty space") + var draw_plane_point:Vector3 = Vector3.ZERO + var draw_plane_normal:Vector3 = BlockAlignment.get_plane_normal(settings.block_alignment) + if settings.match_selected_block: + draw_plane_point = calc_empty_space_draw_plane_origin(viewport_camera, draw_plane_point, draw_plane_normal) + + var hit_result = calc_hit_point_empty_space(origin, dir, viewport_camera, draw_plane_point, draw_plane_normal) + var start_pos:Vector3 = hit_result[0] + floor_normal = hit_result[1] + + #var p:Vector3 = MathUtil.snap_to_grid(start_pos, grid_step_size) + var p:Vector3 = builder.get_snapping_manager().snap_point(start_pos, SnappingQuery.new(viewport_camera)) + drag_origin = p + base_drag_cur = p + + return true + + else: + if tool_state == ToolState.DRAG_BASE: + var camera_dir:Vector3 = viewport_camera.project_ray_normal(e.position) + var angle_with_base:float = acos(floor_normal.dot(camera_dir)) + var drag_angle_limit:float = builder.get_global_scene().drag_angle_limit + if angle_with_base < drag_angle_limit || angle_with_base > PI - drag_angle_limit: + var height = settings.default_block_height + if settings.match_selected_block: + height = calc_active_block_orthogonal_height(base_drag_cur, floor_normal) + + block_drag_cur = base_drag_cur + floor_normal * height + + create_block() + + tool_state = ToolState.READY + else: + tool_state = ToolState.DRAG_HEIGHT + block_drag_cur = base_drag_cur + return true + + elif tool_state == ToolState.DRAG_HEIGHT: + #Create shape + create_block() + + tool_state = ToolState.READY + return true + + #elif e.button_index == MOUSE_BUTTON_RIGHT: + #if tool_state == ToolState.DRAG_BASE || tool_state == ToolState.DRAG_HEIGHT: + #if e.is_pressed(): + #tool_state = ToolState.READY + #return true + + elif e.button_index == MOUSE_BUTTON_WHEEL_UP: + if tool_state == ToolState.DRAG_BASE || tool_state == ToolState.DRAG_HEIGHT: + if e.pressed: + if e.ctrl_pressed: + if e.shift_pressed: + var size = log(settings.step_depth) / log(2) + settings.step_depth = pow(2, size + 1) + else: + var size = log(settings.step_height) / log(2) + settings.step_height = pow(2, size + 1) + else: + settings.direction = wrap(settings.direction + 1, 0, 4) + return true + + elif e.button_index == MOUSE_BUTTON_WHEEL_DOWN: + if tool_state == ToolState.DRAG_BASE || tool_state == ToolState.DRAG_HEIGHT: + if e.pressed: + if e.ctrl_pressed: + if e.shift_pressed: + var size = log(settings.step_depth) / log(2) + settings.step_depth = pow(2, size - 1) + else: + var size = log(settings.step_height) / log(2) + settings.step_height = pow(2, size - 1) + else: + settings.direction = wrap(settings.direction - 1, 0, 4) + return true + + + elif event is InputEventMouseMotion: + var e:InputEventMouseMotion = event + + if (e.button_mask & MOUSE_BUTTON_MASK_MIDDLE): + return false + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var start_pos:Vector3 = origin + builder.block_create_distance * dir +# var w2l = blocks_root.global_transform.inverse() +# var origin_local:Vector3 = w2l * origin +# var dir_local:Vector3 = w2l.basis * dir + + if tool_state == ToolState.DRAG_BASE: + var p_isect:Vector3 = MathUtil.intersect_plane(origin, dir, drag_origin, floor_normal) + #var p_snapped = to_local(p_isect, blocks_root.global_transform.inverse(), grid_step_size) +# var p_snapped:Vector3 = MathUtil.snap_to_grid(p_isect, grid_step_size) + var p_snapped:Vector3 = builder.get_snapping_manager().snap_point(p_isect, SnappingQuery.new(viewport_camera)) + base_drag_cur = p_snapped + + return true + + elif tool_state == ToolState.DRAG_HEIGHT: + block_drag_cur = MathUtil.closest_point_on_line(origin, dir, base_drag_cur, floor_normal) + + #block_drag_cur = to_local(block_drag_cur, blocks_root.global_transform.inverse(), grid_step_size) + block_drag_cur = builder.get_snapping_manager().snap_point(block_drag_cur, SnappingQuery.new(viewport_camera)) + + return true + + return super._gui_input(viewport_camera, event) + +func create_block(): + var blocks_root:Node = builder.get_block_add_parent() + + var cmd:CommandAddStairs = CommandAddStairs.new() + cmd.builder = builder + cmd.blocks_root_path = blocks_root.get_path() + cmd.block_name_prefix = "Block_" + cmd.floor_normal = floor_normal + cmd.drag_origin = drag_origin + cmd.base_drag_cur = base_drag_cur + cmd.block_drag_cur = block_drag_cur + cmd.step_height = settings.step_height + cmd.step_depth = settings.step_depth + cmd.direction = settings.direction + cmd.uv_transform = builder.tool_uv_transform + cmd.material_path = builder.tool_material_path + cmd.collision_type = settings.collision_type + cmd.collision_layers = settings.collision_layer + cmd.collision_mask = settings.collision_mask + + var undo:EditorUndoRedoManager = builder.get_undo_redo() + + cmd.add_to_undo_manager(undo) diff --git a/addons/cyclops_level_builder/tools/tool_stairs_settings.gd b/addons/cyclops_level_builder/tools/tool_stairs_settings.gd new file mode 100644 index 0000000..34f2c9e --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_stairs_settings.gd @@ -0,0 +1,66 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolStairsSettings + +@export var block_alignment:BlockAlignment.Type = BlockAlignment.Type.ALIGN_TO_SURFACE +@export var match_selected_block:bool = true +@export var default_block_elevation:float = 0 +@export var default_block_height:float = 1 +@export var collision_type:Collision.Type = Collision.Type.STATIC +@export_flags_3d_physics var collision_layer:int = 1 +@export_flags_3d_physics var collision_mask:int = 1 + +@export var step_height:float = .25 +@export var step_depth:float = .5 +@export var direction:int = 0 + +func load_from_cache(cache:Dictionary): + block_alignment = cache.get("block_alignment", BlockAlignment.Type.ALIGN_TO_SURFACE) + match_selected_block = cache.get("match_selected_block", true) + default_block_elevation = cache.get("default_block_elevation", 0) + default_block_height = cache.get("default_block_height", 1) + collision_type = cache.get("collision_type", Collision.Type.STATIC) + collision_layer = cache.get("collision_layer", 1) + collision_mask = cache.get("collision_mask", 1) + + step_height = cache.get("step_height", .25) + step_depth = cache.get("step_depth", .5) + direction = cache.get("direction", 0) + +func save_to_cache(): + return { + "block_alignment": block_alignment, + "match_selected_block": match_selected_block, + "default_block_elevation": default_block_elevation, + "default_block_height": default_block_height, + "collision_type": collision_type, + "collision_layer": collision_layer, + "collision_mask": collision_mask, + "step_height": step_height, + "step_depth": step_depth, + "direction": direction, + } + diff --git a/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.gd new file mode 100644 index 0000000..44a6b55 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.gd @@ -0,0 +1,116 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolStairsSettingsEditor + + +var settings:ToolStairsSettings: + get: + return settings + set(value): + settings = value + dirty = true + +var dirty:bool = true + +func _ready(): + %collision_type.clear() + for text in Collision.Type.keys(): + %collision_type.add_item(text) + +func _process(delta): + if dirty: + update() + dirty = false + + +func update(): + if !settings: + #%default_block_height.value = 0 +# %step_height.disabled = true +# %step_depth.disabled = true + %spin_direction.disabled = true + %check_match_selected_block.disabled = true + %default_block_elevation.disabled = true + %default_block_height.disabled = true + return + +# %step_height.disabled = false + %step_height.value = settings.step_height +# %step_depth.disabled = false + %step_depth.value = settings.step_depth + #%spin_direction.disabled = false + %spin_direction.value = settings.direction + %check_match_selected_block.disabled = false + %check_match_selected_block.button_pressed = settings.match_selected_block + %default_block_elevation.disabled = false + %default_block_elevation.value = settings.default_block_elevation + %default_block_height.disabled = false + %default_block_height.value = settings.default_block_height + + %alignment_type.selected = settings.block_alignment + + %collision_type.selected = settings.collision_type + %collision_layers.value = settings.collision_layer + %collision_mask.value = settings.collision_mask + + +func _on_check_match_selected_block_toggled(value): + settings.match_selected_block = value + + +func _on_default_block_elevation_value_changed(value): + settings.default_block_elevation = value + + +func _on_default_block_height_value_changed(value): + settings.default_block_height = value + + + +func _on_step_height_value_changed(value): + settings.step_height + + +func _on_step_depth_value_changed(value): + settings.step_depth + + +func _on_spin_direction_value_changed(value): + settings.direction + +func _on_collision_layers_value_changed(value): + settings.collision_layer = value + + +func _on_collision_mask_value_changed(value): + settings.collision_mask = value + +func _on_collision_type_item_selected(index): + settings.collision_type = index + + +func _on_alignment_type_item_selected(index): + settings.block_alignment = index diff --git a/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.tscn new file mode 100644 index 0000000..5dc96c8 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_stairs_settings_editor.tscn @@ -0,0 +1,163 @@ +[gd_scene load_steps=3 format=3 uid="uid://bhbo7hbko5myp"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_stairs_settings_editor.gd" id="1_flcpk"] +[ext_resource type="PackedScene" uid="uid://diibmlqy1mpqb" path="res://addons/cyclops_level_builder/controls/numeric_line_edit.tscn" id="2_hqkby"] + +[node name="ToolStairsSettings" type="PanelContainer"] +offset_right = 413.0 +offset_bottom = 232.0 +script = ExtResource("1_flcpk") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label5" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Type" + +[node name="collision_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "NONE" +popup/item_0/id = 0 +popup/item_1/text = "STATIC" +popup/item_1/id = 1 +popup/item_2/text = "KINEMATIC" +popup/item_2/id = 2 +popup/item_3/text = "RIGID" +popup/item_3/id = 3 + +[node name="Label6" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Layers" + +[node name="collision_layers" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label7" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Collision Mask" + +[node name="collision_mask" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Step Height" + +[node name="step_height" parent="PanelContainer/VBoxContainer/GridContainer" instance=ExtResource("2_hqkby")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label4" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Step Depth" + +[node name="step_depth" parent="PanelContainer/VBoxContainer/GridContainer" instance=ExtResource("2_hqkby")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Direction" + +[node name="spin_direction" type="SpinBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +max_value = 3.0 +value = 3.0 +rounded = true +allow_greater = true +allow_lesser = true + +[node name="Label8" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Alignment" + +[node name="alignment_type" type="OptionButton" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "Align to surface" +popup/item_0/id = 0 +popup/item_1/text = "XY Plane" +popup/item_1/id = 1 +popup/item_2/text = "XZ Plane" +popup/item_2/id = 2 +popup/item_3/text = "YZ Plane" +popup/item_3/id = 3 + +[node name="Label3" type="Label" parent="PanelContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Match selected block" + +[node name="check_match_selected_block" type="CheckBox" parent="PanelContainer/VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "When drawing in empty space, copy elevation and height properties from currently selected block." +text = "On" + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +text = "Orthogonal Viewport:" + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Elevation" + +[node name="default_block_elevation" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_hqkby")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Default Block Height" + +[node name="default_block_height" parent="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer" instance=ExtResource("2_hqkby")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/collision_type" to="." method="_on_collision_type_item_selected"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_layers" to="." method="_on_collision_layers_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/collision_mask" to="." method="_on_collision_mask_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/step_height" to="." method="_on_step_height_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/step_depth" to="." method="_on_step_depth_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/GridContainer/spin_direction" to="." method="_on_spin_direction_value_changed"] +[connection signal="item_selected" from="PanelContainer/VBoxContainer/GridContainer/alignment_type" to="." method="_on_alignment_type_item_selected"] +[connection signal="toggled" from="PanelContainer/VBoxContainer/GridContainer/check_match_selected_block" to="." method="_on_check_match_selected_block_toggled"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_elevation" to="." method="_on_default_block_elevation_value_changed"] +[connection signal="value_changed" from="PanelContainer/VBoxContainer/MarginContainer/VBoxContainer/GridContainer/default_block_height" to="." method="_on_default_block_height_value_changed"] diff --git a/addons/cyclops_level_builder/tools/tool_vertex_color_brush.gd b/addons/cyclops_level_builder/tools/tool_vertex_color_brush.gd new file mode 100644 index 0000000..1c71ade --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_vertex_color_brush.gd @@ -0,0 +1,198 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends CyclopsTool +class_name ToolVertexColorBrush + +enum ToolState { READY, PAINTING } +var tool_state:ToolState = ToolState.READY + +const TOOL_ID:String = "vertex_color_brush" + +var cmd:CommandVertexPaintStroke + +var settings:ToolVertexColorBrushSettings = ToolVertexColorBrushSettings.new() + +var last_mouse_pos:Vector2 + +var brush_over_mesh:bool = false +var last_hit_pos:Vector3 + +func _get_tool_id()->String: + return TOOL_ID + +func _draw_tool(viewport_camera:Camera3D): + var global_scene:CyclopsGlobalScene = builder.get_global_scene() + global_scene.clear_tool_mesh() + global_scene.draw_selected_blocks(viewport_camera) + + #super._draw_tool(viewport_camera) + + if brush_over_mesh: + var view_dir:Vector3 = viewport_camera.global_transform.basis.z + var bounding_points:PackedVector3Array = \ + MathUtil.create_circle_points(last_hit_pos, view_dir.normalized(), settings.radius, 16) + global_scene.draw_loop(bounding_points, true, global_scene.tool_material) + + +func _get_tool_properties_editor()->Control: + var ed:ToolVertexColorBrushSettingsEditor = preload("res://addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.tscn").instantiate() + + ed.settings = settings + + return ed + + +func _gui_input(viewport_camera:Camera3D, event:InputEvent)->bool: + + if event is InputEventKey: + var e:InputEventKey = event + + if e.keycode == KEY_X: + if e.shift_pressed: + if e.is_pressed(): + #Pick closest vertex color + var origin:Vector3 = viewport_camera.project_ray_origin(last_mouse_pos) + var dir:Vector3 = viewport_camera.project_ray_normal(last_mouse_pos) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + var block:CyclopsBlock = result.object + result.face_index + + var vol:ConvexVolume = ConvexVolume.new() + vol.init_from_mesh_vector_data(block.mesh_vector_data) + + var face:ConvexVolume.FaceInfo = vol.faces[result.face_index] + var v_idx:int = face.get_closest_vertex(result.position) + var vert:ConvexVolume.VertexInfo = vol.vertices[v_idx] + + var fv:ConvexVolume.FaceVertexInfo = vol.get_face_vertex(result.face_index, v_idx) + #print("sample color ", fv.color) + + settings.color = fv.color + + return true + + + elif e.keycode == KEY_Q: + + if e.is_pressed(): + select_block_under_cursor(viewport_camera, last_mouse_pos) + + return true + + elif event is InputEventMouseButton: + + var e:InputEventMouseButton = event + if e.button_index == MOUSE_BUTTON_LEFT: + + if e.is_pressed(): + + if tool_state == ToolState.READY: + #print("vertex color brush bn down") + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + var sel_blocks:Array[CyclopsBlock] = builder.get_selected_blocks() +# if result && result.object == builder.get_active_block(): + if result && sel_blocks.has(result.object): + #print("starting paint") + cmd = CommandVertexPaintStroke.new() + cmd.builder = builder + + cmd.append_block(result.object.get_path()) + cmd.color = settings.color + cmd.strength = settings.strength + cmd.radius = settings.radius + cmd.falloff_curve = settings.falloff_curve.duplicate() + cmd.mask = settings.mask_type + + var pos:Vector3 = result.get_world_position() + #print("pos ", pos) + cmd.append_stroke_point(pos, 1) + + + cmd.do_it() + tool_state = ToolState.PAINTING + + else: + + if tool_state == ToolState.PAINTING: + cmd.undo_it() + if cmd.will_change_anything(): + var undo:EditorUndoRedoManager = builder.get_undo_redo() + cmd.add_to_undo_manager(undo) + + tool_state = ToolState.READY + + return true + + + elif event is InputEventMouseMotion: + + var e:InputEventMouseMotion = event + + last_mouse_pos = e.position + + var origin:Vector3 = viewport_camera.project_ray_origin(e.position) + var dir:Vector3 = viewport_camera.project_ray_normal(e.position) + + var result:IntersectResults = builder.intersect_ray_closest(origin, dir) + + if result: + brush_over_mesh = true + last_hit_pos = result.object.global_transform * result.position + else: + brush_over_mesh = false + + if tool_state == ToolState.PAINTING: + + if result: + #print ("hit ", result.object.name) + cmd.undo_it() + + cmd.append_stroke_point(result.get_world_position(), \ + e.pressure if settings.pen_pressure_strength else 1) + + cmd.do_it() + + return true + + return false + + +func _activate(builder:CyclopsLevelBuilder): + super._activate(builder) + + var cache:Dictionary = builder.get_tool_cache(TOOL_ID) + settings.load_from_cache(cache) + +func _deactivate(): + var cache:Dictionary = settings.save_to_cache() + builder.set_tool_cache(TOOL_ID, cache) diff --git a/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings.gd b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings.gd new file mode 100644 index 0000000..d27fa83 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings.gd @@ -0,0 +1,94 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name ToolVertexColorBrushSettings + + +@export var component_type:GeometryComponentType.Type = GeometryComponentType.Type.OBJECT + +@export var mask_type:CommandVertexPaintStroke.MaskType = CommandVertexPaintStroke.MaskType.NONE: + set(value): + if value != mask_type: + mask_type = value + emit_changed() + +@export var color:Color = Color.WHITE: + set(value): + if value != color: + color = value + emit_changed() + +@export var radius:float: + set(value): + if value != radius: + radius = value + emit_changed() + +@export var strength:float: + set(value): + if value != strength: + strength = value + emit_changed() + +@export var pen_pressure_strength:bool: + set(value): + if value != pen_pressure_strength: + pen_pressure_strength = value + emit_changed() + +@export var falloff_curve:Curve: + set(value): + if value != falloff_curve: + falloff_curve = value + emit_changed() + +func load_from_cache(cache:Dictionary): + component_type = cache.get("component_type", GeometryComponentType.Type.OBJECT) + color = str_to_var(cache.get("color", var_to_str(Color.WHITE))) + radius = cache.get("radius", 1) + strength = cache.get("strength", 1) + pen_pressure_strength = cache.get("pen_pressure_strength", false) + + if cache.has("falloff_curve"): + falloff_curve = str_to_var(cache.get("falloff_curve")) + else: + falloff_curve = Curve.new() + falloff_curve.add_point(Vector2(0, 0)) + falloff_curve.add_point(Vector2(1, 1)) + +func save_to_cache(): + return { + "component_type": component_type, + "color": var_to_str(color), + "radius": radius, + "strength": strength, + "pen_pressure_strength": pen_pressure_strength, + "falloff_curve": var_to_str(falloff_curve) + } + + + + + diff --git a/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.gd b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.gd new file mode 100644 index 0000000..5facaae --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.gd @@ -0,0 +1,104 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends PanelContainer +class_name ToolVertexColorBrushSettingsEditor + +@export var settings:ToolVertexColorBrushSettings: + get: + return settings + + set(value): + if settings == value: + return + + if settings: + settings.changed.disconnect(on_settings_changed) + + settings = value + + if settings: + settings.changed.connect(on_settings_changed) + + update() + +func on_settings_changed(): + update() + +func update(): + + %opbn_mask_type.selected = settings.mask_type + + %color_button.color = settings.color + %spin_strength.value = settings.strength + %spin_radius.value = settings.radius + %check_pen_pressure_str.button_pressed = settings.pen_pressure_strength + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + pass + + +func _on_color_button_color_changed(color:Color): + settings.color = color + + +#func _on_opbn_geom_component_item_selected(index): + #match index: + #0: + #settings.component_type = GeometryComponentType.Type.OBJECT + #1: + #settings.component_type = GeometryComponentType.Type.VERTEX + #2: + #settings.component_type = GeometryComponentType.Type.FACE + #3: + #settings.component_type = GeometryComponentType.Type.FACE_VERTEX + + +func _on_spin_strength_value_changed(value): + settings.strength = value + + +func _on_check_pen_pressure_str_toggled(toggled_on): + settings.pen_pressure_strength = toggled_on + + +func _on_spin_radius_value_changed(value): + settings.radius = value + + +func _on_opbn_mask_type_item_selected(index): + match index: + 0: + settings.mask_type = CommandVertexPaintStroke.MaskType.NONE + 1: + settings.mask_type = CommandVertexPaintStroke.MaskType.VERTICES + 2: + settings.mask_type = CommandVertexPaintStroke.MaskType.FACES diff --git a/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.tscn b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.tscn new file mode 100644 index 0000000..6f04650 --- /dev/null +++ b/addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.tscn @@ -0,0 +1,80 @@ +[gd_scene load_steps=2 format=3 uid="uid://djlowj2pi405u"] + +[ext_resource type="Script" path="res://addons/cyclops_level_builder/tools/tool_vertex_color_brush_settings_editor.gd" id="1_1a32u"] + +[node name="PanelContainer" type="PanelContainer"] +offset_right = 317.0 +offset_bottom = 269.0 +script = ExtResource("1_1a32u") + +[node name="MarginContainer2" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 16 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer2"] +layout_mode = 2 +columns = 2 + +[node name="Label3" type="Label" parent="MarginContainer2/GridContainer"] +layout_mode = 2 +text = "Component type" + +[node name="opbn_mask_type" type="OptionButton" parent="MarginContainer2/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 3 +selected = 0 +popup/item_0/text = "None" +popup/item_0/id = 0 +popup/item_1/text = "Vertex" +popup/item_1/id = 1 +popup/item_2/text = "Face" +popup/item_2/id = 2 + +[node name="Label" type="Label" parent="MarginContainer2/GridContainer"] +layout_mode = 2 +text = "Color" + +[node name="color_button" type="ColorPickerButton" parent="MarginContainer2/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label5" type="Label" parent="MarginContainer2/GridContainer"] +layout_mode = 2 +text = "Radius" + +[node name="spin_radius" type="SpinBox" parent="MarginContainer2/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.1 +value = 1.0 +allow_greater = true + +[node name="Label2" type="Label" parent="MarginContainer2/GridContainer"] +layout_mode = 2 +text = "Strength" + +[node name="spin_strength" type="SpinBox" parent="MarginContainer2/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.1 +value = 1.0 +allow_greater = true + +[node name="Label4" type="Label" parent="MarginContainer2/GridContainer"] +layout_mode = 2 +text = "Pen Pressure" + +[node name="check_pen_pressure_str" type="CheckBox" parent="MarginContainer2/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "On" + +[connection signal="item_selected" from="MarginContainer2/GridContainer/opbn_mask_type" to="." method="_on_opbn_mask_type_item_selected"] +[connection signal="color_changed" from="MarginContainer2/GridContainer/color_button" to="." method="_on_color_button_color_changed"] +[connection signal="value_changed" from="MarginContainer2/GridContainer/spin_radius" to="." method="_on_spin_radius_value_changed"] +[connection signal="value_changed" from="MarginContainer2/GridContainer/spin_strength" to="." method="_on_spin_strength_value_changed"] +[connection signal="toggled" from="MarginContainer2/GridContainer/check_pen_pressure_str" to="." method="_on_check_pen_pressure_str_toggled"] diff --git a/addons/cyclops_level_builder/util/collision.gd b/addons/cyclops_level_builder/util/collision.gd new file mode 100644 index 0000000..cb172e9 --- /dev/null +++ b/addons/cyclops_level_builder/util/collision.gd @@ -0,0 +1,27 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name Collision + +enum Type { NONE, STATIC, KINEMATIC, RIGID } diff --git a/addons/cyclops_level_builder/util/cyclops_logger.gd b/addons/cyclops_level_builder/util/cyclops_logger.gd new file mode 100644 index 0000000..5eb670b --- /dev/null +++ b/addons/cyclops_level_builder/util/cyclops_logger.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name CyclopsLogger + +enum LogLevel { ERROR, WARNING, INFO } + +func log(message:String, level:LogLevel = LogLevel.ERROR): + print(message) diff --git a/addons/cyclops_level_builder/util/cyclops_settings.gd b/addons/cyclops_level_builder/util/cyclops_settings.gd new file mode 100644 index 0000000..128a892 --- /dev/null +++ b/addons/cyclops_level_builder/util/cyclops_settings.gd @@ -0,0 +1,228 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name CyclopsSettings + +@export var definition_map:Dictionary +@export var lookup:Dictionary + +var float_regex_strn:String = "[+-]?([0-9]*[.])?[0-9]+" +var regex_int = RegEx.create_from_string("[0-9]+") +var regex_float = RegEx.create_from_string(float_regex_strn) +var regex_color = RegEx.create_from_string("color\\(" + float_regex_strn + "\\)") + +class SettingDef: + var name:String + var default_value + var type:Variant.Type + var hint:PropertyHint + var hint_string:String + + +func value_to_text(value, type:int)->String: + match type: + TYPE_BOOL: + return "true" if value else "false" + + TYPE_COLOR: + return JSON.stringify([value.r, value.g, value.b, value.a]) + + TYPE_FLOAT: + return str(value) + + TYPE_INT: + return str(value) + + TYPE_NODE_PATH: + return str(value) + + TYPE_STRING: + return "\"" + value + "\"" + + TYPE_TRANSFORM2D: + var a:Transform2D = value + return JSON.stringify({"x": [a.x.x, a.x.y], + "y": [a.y.x, a.y.y], + "o": [a.origin.x, a.origin.y], + }) + + TYPE_TRANSFORM3D: + var a:Transform3D = value + return JSON.stringify({"x": [a.basis.x.x, a.basis.x.y, a.basis.x.z], + "y": [a.basis.y.x, a.basis.y.y, a.basis.y.z], + "z": [a.basis.z.x, a.basis.z.y, a.basis.z.z], + "o": [a.origin.x, a.origin.y, a.origin.z], + }) + + TYPE_VECTOR2: + var a:Vector2 = value + return JSON.stringify([a.x, a.y]) + + TYPE_VECTOR3: + var a:Vector3 = value + return JSON.stringify([a.x, a.y, a.z]) + + TYPE_VECTOR4: + var a:Vector4 = value + return JSON.stringify([a.x, a.y, a.z, a.w]) + + _: + return "" + +func text_to_value(text:String, type:int): + text = text.lstrip(" ").rstrip(" ") + + match type: + TYPE_BOOL: + return text.to_lower() == "true" + + TYPE_COLOR: + var a:Array = JSON.parse_string(text) + return Color(a[0], a[1], a[2], a[3]) + + TYPE_FLOAT: + return float(text) + + TYPE_INT: + return int(text) + + TYPE_NODE_PATH: + return NodePath(text) + + TYPE_STRING: + #Trim starting and ending quotes + return text.substr(1, text.length() - 2) + + TYPE_TRANSFORM2D: + var a:Dictionary = JSON.parse_string(text) + return Transform2D(Vector2(a["x"][0], a["x"][1]), + Vector2(a["y"][0], a["y"][1]), + Vector2(a["o"][0], a["o"][1])) + + TYPE_TRANSFORM3D: + var a:Dictionary = JSON.parse_string(text) + return Transform3D(Vector3(a["x"][0], a["x"][1], a["x"][2]), + Vector3(a["y"][0], a["y"][1], a["y"][2]), + Vector3(a["z"][0], a["z"][1], a["z"][2]), + Vector3(a["o"][0], a["o"][1], a["o"][2])) + + TYPE_VECTOR2: + var a:Array = JSON.parse_string(text) + return Vector2(a[0], a[1]) + + TYPE_VECTOR3: + var a:Array = JSON.parse_string(text) + return Vector3(a[0], a[1], a[2]) + + TYPE_VECTOR4: + var a:Array = JSON.parse_string(text) + return Vector4(a[0], a[1], a[2], a[3]) + + _: + return null + + +func save_to_file(path:String): + var keys:Array = lookup.keys() + keys.sort() + + var f:FileAccess = FileAccess.open(path, FileAccess.WRITE) + if !f: + return + + for key in keys: + var def:SettingDef = definition_map[key] + f.store_line("%s=%s" % [key, value_to_text(lookup[key], def.type)]) + + f.close() + +func load_from_file(path:String): + lookup.clear() + + var f:FileAccess = FileAccess.open(path, FileAccess.READ) + + while !f.eof_reached(): + var line:String = f.get_line() + line = line.lstrip(" ") + if line.is_empty() || line[0] == "#": + continue + + var idx = line.find("=") + if idx == -1: + continue + + var name:String = line.substr(0, idx) + var value_text:String = line.substr(idx + 1) + + if !definition_map.has(name): + continue + + var def:SettingDef = definition_map[name] + set_property(name, text_to_value(value_text, def.type)) + + + +func add_setting(name:String, default_value, type:Variant.Type, hint:PropertyHint = PROPERTY_HINT_NONE, hint_string:String = ""): + var def:SettingDef = SettingDef.new() + def.name = name + def.default_value = default_value + def.type = type + def.hint = hint + def.hint_string = hint_string + + definition_map[name] = def + + +func set_property(name:String, value): + if !definition_map.has(name): + push_error("Unknown setting name " + name) + return + + var def:SettingDef = definition_map[name] + var var_type:int = typeof(value) + if var_type != def.type: + push_error("Settings error: Bad setting type. Needed %s but got %s" % [def.type, var_type]) + return + + lookup[name] = value + + +func has_property(name:String)->bool: + return definition_map.has(name) + +func get_property(name:String): + #print("lookup ", name) + if !definition_map.has(name): + push_error("Unknown setting name " + name) + return null + + #print("is defined ", name) + if lookup.has(name): + return lookup[name] + + #print("returning default ", name) + var def:SettingDef = definition_map[name] + return def.default_value + diff --git a/addons/cyclops_level_builder/util/display_mode.gd b/addons/cyclops_level_builder/util/display_mode.gd new file mode 100644 index 0000000..69d2616 --- /dev/null +++ b/addons/cyclops_level_builder/util/display_mode.gd @@ -0,0 +1,28 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name DisplayMode + +enum Type { WIRE, MESH, MATERIAL } + diff --git a/addons/cyclops_level_builder/util/general_util.gd b/addons/cyclops_level_builder/util/general_util.gd new file mode 100644 index 0000000..2409500 --- /dev/null +++ b/addons/cyclops_level_builder/util/general_util.gd @@ -0,0 +1,71 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name GeneralUtil + +static func find_unique_name(parent:Node, base_name:String)->String: + #Check if numeric suffix already exists + var regex = RegEx.new() + regex.compile("(\\d+)") + var match_res:RegExMatch = regex.search(base_name) + + var name_idx:int = 0 + + if match_res: + var suffix:String = match_res.get_string(1) + name_idx = int(suffix) + 1 + base_name = base_name.substr(0, base_name.length() - suffix.length()) + + #Search for free index + while true: + var name = base_name + str(name_idx) + if !parent.find_child(name, false): + return name + + name_idx += 1 + + return "" + +static func calc_resource_name(res:Resource)->String: + var name:String = res.resource_name + + if name.is_empty(): + name = res.resource_path.get_file() + var idx:int = name.rfind(".") + if idx != -1: + name = name.substr(0, idx) + + return name + +static func format_planes_string(planes:Array[Plane])->String: + var result:String = "" + for p in planes: + result = result + "(%s, %s, %s, %s)," % [p.x, p.y, p.z, p.d] + return result + + +static func dump_properties(obj): + for prop in obj.get_property_list(): + var name:String = prop["name"] + print ("%s: %s" % [name, str(obj.get(name))]) diff --git a/addons/cyclops_level_builder/util/geometry_component_type.gd b/addons/cyclops_level_builder/util/geometry_component_type.gd new file mode 100644 index 0000000..0429c90 --- /dev/null +++ b/addons/cyclops_level_builder/util/geometry_component_type.gd @@ -0,0 +1,28 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name GeometryComponentType + +enum Type { OBJECT, VERTEX, FACE, FACE_VERTEX } + diff --git a/addons/cyclops_level_builder/util/selection.gd b/addons/cyclops_level_builder/util/selection.gd new file mode 100644 index 0000000..46e40e3 --- /dev/null +++ b/addons/cyclops_level_builder/util/selection.gd @@ -0,0 +1,37 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name Selection + +enum Type { REPLACE, ADD, SUBTRACT, TOGGLE } + +static func choose_type(shift_pressed:bool, ctrl_pressed)->Type: + if !shift_pressed and !ctrl_pressed: + return Type.REPLACE + elif shift_pressed and !ctrl_pressed: + return Type.TOGGLE + elif !shift_pressed and ctrl_pressed: + return Type.ADD + else: + return Type.SUBTRACT diff --git a/addons/cyclops_level_builder/util/selection_list.gd b/addons/cyclops_level_builder/util/selection_list.gd new file mode 100644 index 0000000..453a4dc --- /dev/null +++ b/addons/cyclops_level_builder/util/selection_list.gd @@ -0,0 +1,37 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name SelectionList + +enum Type { REPLACE, RANGE, TOGGLE } + +static func choose_type(shift_pressed:bool, ctrl_pressed)->Type: + if !shift_pressed and !ctrl_pressed: + return Type.REPLACE + elif shift_pressed and !ctrl_pressed: + return Type.RANGE + elif !shift_pressed and ctrl_pressed: + return Type.TOGGLE + else: + return Type.REPLACE diff --git a/addons/cyclops_level_builder/util/serial_util.gd b/addons/cyclops_level_builder/util/serial_util.gd new file mode 100644 index 0000000..ed8a6f7 --- /dev/null +++ b/addons/cyclops_level_builder/util/serial_util.gd @@ -0,0 +1,72 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name SerialUtil + +static func save_cache_vector3(value:Vector3)->Dictionary: + return { + "value": [value.x, value.y, value.z] + } + +static func load_cache_vector3(cache:Dictionary, default_value:Vector3 = Vector3.ZERO)->Vector3: + if !cache: + return default_value + + return Vector3(cache.value[0], cache.value[1], cache.value[2]) + +static func save_cache_color(value:Color)->Dictionary: + return { + "color": [value.r, value.g, value.b, value.a] + } + +static func load_cache_color(cache:Dictionary, default_value:Color = Color.BLACK)->Color: + if !cache: + return default_value + + return Color(cache.color[0], cache.color[1], cache.color[2], cache.color[3]) + +static func save_cache_transform_3d(t:Transform3D)->String: + return var_to_str(t) + #var dict:Dictionary = { + #"x": [t.basis.x.x, t.basis.x.y, t.basis.x.z], + #"y": [t.basis.y.x, t.basis.y.y, t.basis.y.z], + #"z": [t.basis.z.x, t.basis.z.y, t.basis.z.z], + #"o": [t.origin.x, t.origin.y, t.origin.z], + #} + #return JSON.stringify(dict) + +static func load_cache_transform_3d(text:String, default_value:Transform3D = Transform3D.IDENTITY)->Transform3D: + if text.is_empty(): + return default_value + + return str_to_var(text) + + #var cache:Dictionary = JSON.parse_string(text) + #var x:Vector3 = Vector3(cache.x[0], cache.x[1], cache.x[2]) + #var y:Vector3 = Vector3(cache.y[0], cache.y[1], cache.y[2]) + #var z:Vector3 = Vector3(cache.z[0], cache.z[1], cache.z[2]) + #var o:Vector3 = Vector3(cache.o[0], cache.o[1], cache.o[2]) + # + #return Transform3D(x, y, z, o) + diff --git a/addons/cyclops_level_builder/util/transform_space.gd b/addons/cyclops_level_builder/util/transform_space.gd new file mode 100644 index 0000000..b3bbd68 --- /dev/null +++ b/addons/cyclops_level_builder/util/transform_space.gd @@ -0,0 +1,29 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + + +@tool + +class_name TransformSpace + +enum Type { GLOBAL, LOCAL, NORMAL, VIEW, PARENT } diff --git a/addons/cyclops_level_builder/util/tree_vistor.gd b/addons/cyclops_level_builder/util/tree_vistor.gd new file mode 100644 index 0000000..598e604 --- /dev/null +++ b/addons/cyclops_level_builder/util/tree_vistor.gd @@ -0,0 +1,35 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends RefCounted +class_name TreeVisitor + +static func visit(root:Node, callback:Callable): + visit_recursive(root, callback) + +static func visit_recursive(node:Node, callback:Callable): + callback.call(node) + + for child in node.get_children(): + visit_recursive(child, callback) diff --git a/addons/cyclops_level_builder/util/unit_system.gd b/addons/cyclops_level_builder/util/unit_system.gd new file mode 100644 index 0000000..de44a13 --- /dev/null +++ b/addons/cyclops_level_builder/util/unit_system.gd @@ -0,0 +1,28 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +class_name UnitSystem + +enum Type { NONE, METRIC, IMPERIAL } + diff --git a/addons/cyclops_level_builder/util/xml/XML_attribute.gd b/addons/cyclops_level_builder/util/xml/XML_attribute.gd new file mode 100644 index 0000000..42c9569 --- /dev/null +++ b/addons/cyclops_level_builder/util/xml/XML_attribute.gd @@ -0,0 +1,33 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends XMLNode +class_name XMLAttribute + +@export var name:String +@export var value:String + +func _init(name:String = "", value:String = ""): + self.name = name + self.value = value diff --git a/addons/cyclops_level_builder/util/xml/XML_document.gd b/addons/cyclops_level_builder/util/xml/XML_document.gd new file mode 100644 index 0000000..376f05f --- /dev/null +++ b/addons/cyclops_level_builder/util/xml/XML_document.gd @@ -0,0 +1,35 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends XMLNode +class_name XMLDocument + +@export var root:XMLElement + +func format_document(indent:String = "")->String: + return root.format_document_recursive("", indent) if root else "" + +func format_document_recursive(cur_indent:String = "", indent_increment:String = "")->String: + assert(false, "Call to_string()") + return "" diff --git a/addons/cyclops_level_builder/util/xml/XML_element.gd b/addons/cyclops_level_builder/util/xml/XML_element.gd new file mode 100644 index 0000000..b2bce20 --- /dev/null +++ b/addons/cyclops_level_builder/util/xml/XML_element.gd @@ -0,0 +1,82 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends XMLNode +class_name XMLElement + +@export var name:String +@export var attributes:Array[XMLAttribute] +@export var children:Array[XMLNode] + +func _init(name:String = ""): + self.name = name + + +func format_document_recursive(cur_indent:String = "", indent_increment:String = " ")->String: + var result = cur_indent + "<" + name + for attr in attributes: + result += " " + attr.name + "=\"" + attr.value + "\"" + if children.is_empty(): + result += "/>" + else: + result += ">" + for child in children: + result += child.to_string_recursive(cur_indent + indent_increment, indent_increment) + result += "" + return result + + +func add_child(node:XMLNode): + children.append(node) + +func get_attribute(name:String)->XMLAttribute: + for attr in attributes: + if attr.name == name: + return attr + return null + +func get_attribute_value(name:String, default_value:String = "")->String: + for attr in attributes: + if attr.name == name: + return attr.value + return default_value + +func get_attribute_index(nane:String)->int: + for attr_idx in attributes.size(): + if attributes[attr_idx].name == name: + return attr_idx + return -1 + +func set_attribute(name:String, value:String): + var idx = get_attribute_index(name) + if idx != -1: + attributes[idx].value = value + else: + attributes.append(XMLAttribute.new(name, value)) + +#func set_attribute_bool(name:String, value:bool): + #set_attribute(name, str(value)) +# +#func set_attribute_int(name:String, value:int): + #set_attribute(name, str(value)) diff --git a/addons/cyclops_level_builder/util/xml/XML_node.gd b/addons/cyclops_level_builder/util/xml/XML_node.gd new file mode 100644 index 0000000..8f4ec17 --- /dev/null +++ b/addons/cyclops_level_builder/util/xml/XML_node.gd @@ -0,0 +1,29 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends Resource +class_name XMLNode + +func format_document_recursive(cur_indent:String = "", indent_increment:String = " ")->String: + return "" diff --git a/addons/cyclops_level_builder/util/xml/XML_text.gd b/addons/cyclops_level_builder/util/xml/XML_text.gd new file mode 100644 index 0000000..1e2f1bb --- /dev/null +++ b/addons/cyclops_level_builder/util/xml/XML_text.gd @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2023 Mark McKay +# https://github.com/blackears/cyclopsLevelBuilder +# +# 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. + +@tool +extends XMLNode +class_name XMLText + +@export var value:String + +func format_document_recursive(cur_indent:String = "", indent_increment:String = " ")->String: + return value diff --git a/addons/reactivex/__gdrxsingleton__.gd b/addons/reactivex/__gdrxsingleton__.gd index a46be74..9d7a30f 100644 --- a/addons/reactivex/__gdrxsingleton__.gd +++ b/addons/reactivex/__gdrxsingleton__.gd @@ -40,25 +40,25 @@ var pipe = __init__.Pipe_.new() # =========================================================================== # func _init(): - ## Insert Main Thread - if true: - var reglock : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) - reglock.w_lock() - self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[MAIN_THREAD_ID] = MAIN_THREAD - reglock.w_unlock() - - ## Init [SceneTreeScheduler] singleton - for i in range(8): - var process_always : bool = bool(i & 0b1) - var process_in_physics : bool = bool(i & 0b10) - var ignore_time_scale : bool = bool(i & 0b100) - self.SceneTreeTimeoutScheduler_.push_back(SceneTreeTimeoutScheduler.new( - "GDRx", process_always, process_in_physics, ignore_time_scale)) + ## Insert Main Thread + if true: + var reglock : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) + reglock.w_lock() + self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[MAIN_THREAD_ID] = MAIN_THREAD + reglock.w_unlock() + + ## Init [SceneTreeScheduler] singleton + for i in range(8): + var process_always : bool = bool(i & 0b1) + var process_in_physics : bool = bool(i & 0b10) + var ignore_time_scale : bool = bool(i & 0b100) + self.SceneTreeTimeoutScheduler_.push_back(SceneTreeTimeoutScheduler.new( + "GDRx", process_always, process_in_physics, ignore_time_scale)) func _notification(what): - if what == NOTIFICATION_PREDELETE: - self.THREAD_MANAGER.stop_and_join() - self.THREAD_MANAGER.free() + if what == NOTIFICATION_PREDELETE: + self.THREAD_MANAGER.stop_and_join() + self.THREAD_MANAGER.free() # =========================================================================== # # Multi-Threading @@ -74,12 +74,12 @@ var THREAD_MANAGER = concur.ThreadManager.new() ## Returns the caller's current [Thread] ## If the caller thread is the main thread, it returns [b]MAIN_THREAD[/b]. func get_current_thread() -> Thread: - var id = OS.get_thread_caller_id() - var l : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) - l.r_lock() - id = self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[id] - l.r_unlock() - return id + var id = OS.get_thread_caller_id() + var l : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) + l.r_lock() + id = self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[id] + l.r_unlock() + return id # =========================================================================== # # Scheduler Singletons @@ -113,93 +113,93 @@ var ErrorHandler_ : WeakKeyDictionary = WeakKeyDictionary.new() ## Equality func eq(x, y) -> bool: - return GDRx.basic.default_comparer(x, y) + return GDRx.basic.default_comparer(x, y) ## Negated equality func neq(x, y) -> bool: - return !eq(x, y) + return !eq(x, y) ## Less than operator func lt(x, y) -> bool: - return x.lt(y) if (x is Object and x.has_method("lt")) else x < y + return x.lt(y) if (x is Object and x.has_method("lt")) else x < y ## Greater than operator func gt(x, y) -> bool: - return x.gt(y) if (x is Object and x.has_method("gt")) else x > y + return x.gt(y) if (x is Object and x.has_method("gt")) else x > y ## Greater than equals operator func gte(x, y) -> bool: - return x.gte(y) if (x is Object and x.has_method("gte")) else x >= y + return x.gte(y) if (x is Object and x.has_method("gte")) else x >= y ## Less than equals operator func lte(x, y) -> bool: - return x.lte(y) if (x is Object and x.has_method("lte")) else x <= y + return x.lte(y) if (x is Object and x.has_method("lte")) else x <= y ## Raises a [AssertionFailedError] and returns [b]true[/b] ## should the assertion fail. func assert_(assertion : bool, message : String = "Assertion failed!") -> bool: - if not assertion: - AssertionFailedError.new(message).throw() - return not assertion + if not assertion: + AssertionFailedError.new(message).throw() + return not assertion ## Creates a new [TryCatch] Statement func try(fun : Callable) -> TryCatch: - return TryCatch.new(fun) + return TryCatch.new(fun) ## Raises a [ThrowableBase] func raise(exc_ : ThrowableBase, default = null) -> Variant: - return ErrorHandler.singleton().raise(exc_, default) + return ErrorHandler.singleton().raise(exc_, default) ## Raises a [RxBaseError] containing the given message func raise_message(msg : String, default = null): - return RxBaseError.raise(default, msg) + return RxBaseError.raise(default, msg) ## Construct an [IterableBase] onto x. func to_iterable(x) -> IterableBase: - return Iterator.to_iterable(x) + return Iterator.to_iterable(x) ### Construct an [Iterator] onto x. func iter(x) -> Iterator: - return Iterator.iter(x) + return Iterator.iter(x) ### Creates a [WhileIterable] from a given condition and another [IterableBase] func take_while(cond : Callable, it : IterableBase) -> IterableBase: - return WhileIterable.new(it, cond) + return WhileIterable.new(it, cond) ### Generates an [InfiniteIterable] sequence of a given value. func infinite(infval = NOT_SET) -> IterableBase: - return InfiniteIterable.new(infval) + return InfiniteIterable.new(infval) ### NOT Set value var NOT_SET: - get: return util.NOT_SET + get: return util.NOT_SET ## Is NOT Set value func not_set(value) -> bool: - return NOT_SET.eq(value) + return NOT_SET.eq(value) ## Unit item var UNIT: - get: return StreamItem.Unit() + get: return StreamItem.Unit() ## Alias for [code]GDRx.util.add_ref()[/code] func add_ref(xs : Observable, r : RefCountDisposable) -> Observable: - return util.add_ref(xs, r) + return util.add_ref(xs, r) ## Create an observable sequence from an array func of(data : Array, scheduler : SchedulerBase = null) -> Observable: - return self.from_array(data, scheduler) + return self.from_array(data, scheduler) ## Create an observable on any given iterable sequence func from(seq, scheduler : SchedulerBase = null) -> Observable: - return self.from_iterable(to_iterable(seq), scheduler) + return self.from_iterable(to_iterable(seq), scheduler) ## Alias for [code]GDRx.obs.return_value[/code] func just(value, scheduler : SchedulerBase = null) -> Observable: - return self.return_value(value, scheduler) + return self.return_value(value, scheduler) ## Empty operation as defined in [code]GDRx.basic.noop[/code] func noop(__ = null, ___ = null): - GDRx.basic.noop(__, ___) + GDRx.basic.noop(__, ___) ## Identity mapping as defined in [code]GDRx.basic.identity[/code] func identity(x, __ = null): - return GDRx.basic.identity(x, __) + return GDRx.basic.identity(x, __) # =========================================================================== # # Observable Constructors @@ -207,131 +207,131 @@ func identity(x, __ = null): ## See: [b]res://addons/reactivex/observable/amb.gd[/b] func amb(sources) -> Observable: - return obs.amb(sources) + return obs.amb(sources) ## See: [b]res://addons/reactivex/observable/case.gd[/b] func case(mapper : Callable, sources : Dictionary, default_source : Observable = null) -> Observable: - return obs.case(mapper, sources, default_source) + return obs.case(mapper, sources, default_source) ## Create a new observable func create(subscribe : Callable = func(_observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: return Disposable.new()) -> Observable: - return Observable.new(subscribe) + return Observable.new(subscribe) ## See: [b]res://addons/reactivex/observable/catch.gd[/b] func catch(sources : Array[Observable]) -> Observable: - return obs.catch_with_iterable(to_iterable(sources)) + return obs.catch_with_iterable(to_iterable(sources)) ## See: [b]res://addons/reactivex/observable/catch.gd[/b] func catch_with_iterable(sources : IterableBase) -> Observable: - return obs.catch_with_iterable(sources) + return obs.catch_with_iterable(sources) ## See: [b]res://addons/reactivex/observable/combinelatest.gd[/b] func combine_latest(sources) -> Observable: - return obs.combine_latest(sources) + return obs.combine_latest(sources) ## See: [b]res://addons/reactivex/observable/concat.gd[/b] func concat_streams(sources : Array[Observable]) -> Observable: - return obs.concat_with_iterable(to_iterable(sources)) + return obs.concat_with_iterable(to_iterable(sources)) ## See: [b]res://addons/reactivex/observable/concat.gd[/b] func concat_with_iterable(sources : IterableBase) -> Observable: - return obs.concat_with_iterable(sources) + return obs.concat_with_iterable(sources) ## See: [b]res://addons/reactivex/observable/defer.gd[/b] func defer(factory : Callable = GDRx.basic.default_factory) -> Observable: - return obs.defer(factory) + return obs.defer(factory) ## See: [b]res://addons/reactivex/observable/empty.gd[/b] func empty(scheduler : SchedulerBase = null) -> Observable: - return obs.empty(scheduler) + return obs.empty(scheduler) ## See: [b]res://addons/reactivex/observable/forkjoin.gd[/b] func fork_join(sources) -> Observable: - return obs.fork_join(sources) + return obs.fork_join(sources) ## See: [b]res://addons/reactivex/observable/fromcallback.gd[/b] func from_callback(fun : Callable = func(_args : Array, _cb : Callable): return, mapper = null) -> Callable: - return obs.from_callback(fun, mapper) + return obs.from_callback(fun, mapper) ## Transforms an array into an observable sequence. func from_array(array : Array, scheduler : SchedulerBase = null) -> Observable: - return obs.from_iterable(to_iterable(array), scheduler) + return obs.from_iterable(to_iterable(array), scheduler) ## See: [b]res://addons/reactivex/observable/fromiterable.gd[/b] func from_iterable(iterable : IterableBase, scheduler : SchedulerBase = null) -> Observable: - return obs.from_iterable(iterable, scheduler) + return obs.from_iterable(iterable, scheduler) ## See: [b]res://addons/reactivex/observable/generate.gd[/b] func generate(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity) -> Observable: - return obs.generate(initial_state, condition, iterate) + return obs.generate(initial_state, condition, iterate) ## See: [b]res://addons/reactivex/observable/generatewithrealtivetime.gd[/b] func generate_with_relative_time(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity, time_mapper : Callable = func(_state) -> float: return 1.0) -> Observable: - return obs.generate_with_relative_time(initial_state, condition, iterate, time_mapper) + return obs.generate_with_relative_time(initial_state, condition, iterate, time_mapper) ## See: [b]res://addons/reactivex/observable/ifthen.gd[/b] func if_then(condition : Callable = GDRx.basic.default_condition, then_source : Observable = null, else_source : Observable = null) -> Observable: - return obs.if_then(condition, then_source, else_source) + return obs.if_then(condition, then_source, else_source) ## See: [b]res://addons/reactivex/observable/interval.gd[/b] func interval(period : float, scheduler : SchedulerBase = null) -> ObservableBase: - return obs.interval(period, scheduler) + return obs.interval(period, scheduler) ## See: [b]res://addons/reactivex/observable/merge.gd[/b] func merge(sources) -> Observable: - return obs.merge(sources) + return obs.merge(sources) ## See: [b]res://addons/reactivex/observable/never.gd[/b] func never() -> Observable: - return obs.never() + return obs.never() ## See: [b]res://addons/reactivex/observable/onerrorresumenext.gd[/b] func on_error_resume_next(sources : Array) -> Observable: - return obs.on_error_resume_next(sources) + return obs.on_error_resume_next(sources) ## See: [b]res://addons/reactivex/observable/range.gd[/b] @warning_ignore("shadowed_global_identifier") func range(start : int, stop = null, step = null, scheduler : SchedulerBase = null) -> Observable: - return obs.range(start, stop, step, scheduler) + return obs.range(start, stop, step, scheduler) ## See: [b]res://addons/reactivex/observable/repeat.gd[/b] func repeat_value(value, repeat_count = null) -> Observable: - return obs.repeat_value(value, repeat_count) + return obs.repeat_value(value, repeat_count) ## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] func return_value(value, scheduler : SchedulerBase = null) -> Observable: - return obs.return_value(value, scheduler) + return obs.return_value(value, scheduler) ## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] func from_callable(supplier : Callable, scheduler : SchedulerBase = null) -> Observable: - return obs.from_callable(supplier, scheduler) + return obs.from_callable(supplier, scheduler) ## See: [b]res://addons/reactivex/observable/throw.gd[/b] func throw(err, scheduler : SchedulerBase = null) -> Observable: - return obs.throw(err, scheduler) + return obs.throw(err, scheduler) ## See: [b]res://addons/reactivex/observable/timer.gd[/b] func timer(duetime : float, time_absolute : bool, period = null, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(duetime, time_absolute, period, scheduler) + return obs.timer(duetime, time_absolute, period, scheduler) ## See: [b]res://addons/reactivex/observable/toasync.gd[/b] func to_async(fun : Callable, scheduler : SchedulerBase = null) -> Callable: - return obs.to_async(fun, scheduler) + return obs.to_async(fun, scheduler) ## See: [b]res://addons/reactivex/observable/using.gd[/b] func using(resource_factory : Callable, observable_factory : Callable) -> Observable: - return obs.using(resource_factory, observable_factory) + return obs.using(resource_factory, observable_factory) ## See: [b]res://addons/reactivex/observable/withlatestfrom.gd[/b] func with_latest_from(sources) -> Observable: - var _sources : Array[Observable] = util.unpack_arg(sources) - assert_(_sources.size() > 0) - var parent = _sources.pop_front() - return obs.with_latest_from(parent, _sources) + var _sources : Array[Observable] = util.unpack_arg(sources) + assert_(_sources.size() > 0) + var parent = _sources.pop_front() + return obs.with_latest_from(parent, _sources) ## See: [b]res://addons/reactivex/observable/zip.gd[/b] func zip(sources) -> Observable: - return obs.zip(sources) + return obs.zip(sources) # =========================================================================== # # Timers @@ -339,23 +339,23 @@ func zip(sources) -> Observable: ## Creates an observable timer func start_timer(timespan_sec : float, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(timespan_sec, false, null, scheduler) + return obs.timer(timespan_sec, false, null, scheduler) ## Creates an observable periodic timer func start_periodic_timer(period_sec : float, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(period_sec, false, period_sec, scheduler) + return obs.timer(period_sec, false, period_sec, scheduler) ## Creates an observable periodic timer which starts after a timespan has passed func start_periodic_timer_after_timespan(timespan_sec : float, period_sec : float, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(timespan_sec, false, period_sec, scheduler) + return obs.timer(timespan_sec, false, period_sec, scheduler) ## Creates an observable timer func schedule_datetime(datetime_sec : float, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(datetime_sec, true, null, scheduler) + return obs.timer(datetime_sec, true, null, scheduler) ## Creates an observable periodic timer which starts at a given timestamp. func start_periodic_timer_at_datetime(datetime_sec : float, period_sec : float, scheduler : SchedulerBase = null) -> Observable: - return obs.timer(datetime_sec, true, period_sec, scheduler) + return obs.timer(datetime_sec, true, period_sec, scheduler) # =========================================================================== # # Godot-specific Observable Constructors @@ -363,59 +363,59 @@ func start_periodic_timer_at_datetime(datetime_sec : float, period_sec : float, ## Creates an observable from a Godot Signal func from_signal(sig : Signal) -> Observable: - return gd.from_godot_signal(sig) + return gd.from_godot_signal(sig) ## Creates an observable from a Coroutine func from_coroutine(fun : Callable, bindings : Array = [], scheduler : SchedulerBase = null) -> Observable: - return gd.from_godot_coroutine(fun, bindings, scheduler) + return gd.from_godot_coroutine(fun, bindings, scheduler) ## Emits items from [method Node._process]. func on_process_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 0) + return gd.from_godot_node_lifecycle_event(conn, 0) ## Emits items from [method Node._physics_process]. func on_physics_process_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 1) + return gd.from_godot_node_lifecycle_event(conn, 1) ## Emits items from [method Node._input]. func on_input_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 2) + return gd.from_godot_node_lifecycle_event(conn, 2) ## Emits items from [method Node._shortcut_input]. func on_shortcut_input_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 3) + return gd.from_godot_node_lifecycle_event(conn, 3) ## Emits items from [method Node._unhandled_input]. func on_unhandled_input_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 4) + return gd.from_godot_node_lifecycle_event(conn, 4) ## Emits items from [method Node._unhandled_key_input]. func on_unhandled_key_input_as_observable(conn : Node) -> Observable: - return gd.from_godot_node_lifecycle_event(conn, 5) + return gd.from_godot_node_lifecycle_event(conn, 5) ## Tranforms an input action into an observable sequence emiting items on check. func input_action(input_action_ : String, checks : Observable) -> Observable: - return gd.from_godot_input_action(input_action_, checks) + return gd.from_godot_input_action(input_action_, checks) ## Creates a new Compute Shader as [Observable]. func from_compute_shader(shader_path : String, rd : RenderingDevice, work_groups : Vector3i, uniform_sets : Array = [], scheduler : SchedulerBase = null) -> Observable: - return gd.from_compute_shader(shader_path, rd, work_groups, uniform_sets, scheduler) + return gd.from_compute_shader(shader_path, rd, work_groups, uniform_sets, scheduler) ## Emits items when the node enters the scene tree func on_tree_enter_as_observable(node : Node) -> Observable: - return from_signal(node.tree_entered) + return from_signal(node.tree_entered) ## Emits items when the node just exited the scene tree func on_tree_exit_as_observable(node : Node) -> Observable: - return from_signal(node.tree_exited) + return from_signal(node.tree_exited) ## Emits items when the node is about to exit the scene tree func on_tree_exiting_as_observable(node : Node) -> Observable: - return from_signal(node.tree_exiting) + return from_signal(node.tree_exiting) ## Creates an HTTP Request func from_http_request(url : String, request_data = "", raw : bool = false, encoding : String = "", requester : HTTPRequest = null, custom_headers : PackedStringArray = PackedStringArray(), method : HTTPClient.Method = HTTPClient.METHOD_GET) -> Observable: - return gd.from_http_request(url, request_data, raw, encoding, requester, custom_headers, method) + return gd.from_http_request(url, request_data, raw, encoding, requester, custom_headers, method) # =========================================================================== # # Some useful Input Observables @@ -423,83 +423,83 @@ func from_http_request(url : String, request_data = "", raw : bool = false, enco ## Emits item when mouse button is just pressed func on_mouse_down() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ - .filter(func(ev : InputEventMouseButton): return ev.is_pressed()) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return ev.is_pressed()) ## Emits item when mouse button is just released func on_mouse_up() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ - .filter(func(ev : InputEventMouseButton): return not ev.is_pressed()) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return not ev.is_pressed()) ## Emits item on mouse double-click func on_mouse_double_click() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ - .filter(func(ev : InputEventMouseButton): return ev.is_pressed() and ev.double_click) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return ev.is_pressed() and ev.double_click) ## Emits items on mouse motion func on_mouse_motion() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) ## Emits the relative mouse motion as a [Vector2]. func relative_mouse_movement_as_observable() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) \ - .map(func(ev : InputEventMouseMotion): return ev.relative) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) \ + .map(func(ev : InputEventMouseMotion): return ev.relative) ## Emits an item when the given keycode is just pressed. func on_key_just_pressed(key : int) -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventKey) \ - .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed and not ev.echo) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed and not ev.echo) ## Emits an item when the given keycode is pressed. func on_key_pressed(key : int) -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventKey) \ - .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed) ## Emits an item when the given keycode is just released. func on_key_just_released(key : int) -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventKey) \ - .filter(func(ev : InputEventKey): return ev.keycode == key and not ev.pressed) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and not ev.pressed) ## Emits an item, when the screen is touched (touch devices). func on_screen_touch() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventScreenTouch) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventScreenTouch) ## Emits an item, when the touch screen notices a drag gesture (touch devices) func on_screen_drag() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventScreenDrag) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventScreenDrag) ## Emits an item on Midi event. func on_midi_event() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventMIDI) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMIDI) ## Emits an item, when a joypad button is just pressed. func on_joypad_button_down() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ - .filter(func(ev : InputEventJoypadButton): return not ev.is_echo() and ev.is_pressed()) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return not ev.is_echo() and ev.is_pressed()) ## Emits an item, when a joypad button is pressed. func on_joypad_button_pressed() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ - .filter(func(ev : InputEventJoypadButton): return ev.is_pressed()) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return ev.is_pressed()) ## Emits an item, when a joypad button is just released. func on_joypad_button_released() -> Observable: - return on_input_as_observable(self) \ - .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ - .filter(func(ev : InputEventJoypadButton): return not ev.is_pressed()) + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return not ev.is_pressed()) # =========================================================================== # # Frame Events @@ -507,22 +507,22 @@ func on_joypad_button_released() -> Observable: ## Emits items on idle frame events. func on_idle_frame() -> Observable: - return from_signal(self.get_tree().process_frame) \ - .map(func(__): return get_process_delta_time()) + return from_signal(self.get_tree().process_frame) \ + .map(func(__): return get_process_delta_time()) ## Emits items on physics frame events. func on_physics_step() -> Observable: - return from_signal(self.get_tree().physics_frame) \ - .map(func(__): return get_physics_process_delta_time()) + return from_signal(self.get_tree().physics_frame) \ + .map(func(__): return get_physics_process_delta_time()) ## Emits an item when the scene tree has changed. func on_tree_changed() -> Observable: - return from_signal(self.get_tree().tree_changed) + return from_signal(self.get_tree().tree_changed) ## Emits an item at post-draw frame event. func on_frame_post_draw() -> Observable: - return from_signal(RenderingServer.frame_post_draw) + return from_signal(RenderingServer.frame_post_draw) ## Emits an item at pre-draw frame event. func on_frame_pre_draw() -> Observable: - return from_signal(RenderingServer.frame_pre_draw) + return from_signal(RenderingServer.frame_pre_draw) diff --git a/addons/reactivex/engine/__gdrxengine__.gd b/addons/reactivex/engine/__gdrxengine__.gd index f387434..fe07665 100644 --- a/addons/reactivex/engine/__gdrxengine__.gd +++ b/addons/reactivex/engine/__gdrxengine__.gd @@ -17,32 +17,32 @@ var _ProcessTimeInterval_ = load("res://addons/reactivex/engine/operators/_proce ## See: [b]res://addons/reactivex/engine/observable/godotsignal.gd[/b] func from_godot_signal(sig : Signal, scheduler : SchedulerBase = null) -> Observable: - return _GodotSignal_.from_godot_signal_(sig, scheduler) + return _GodotSignal_.from_godot_signal_(sig, scheduler) ## See: [b]res://addons/reactivex/engine/observable/godotnodelifecycle.gd[/b] func from_godot_node_lifecycle_event(conn : Node, type : int) -> Observable: - return _GodotLifecycle_.from_godot_node_lifecycle_event_(conn, type) + return _GodotLifecycle_.from_godot_node_lifecycle_event_(conn, type) ## See: [b]res://addons/reactivex/engine/observable/godotinputaction.gd[/b] func from_godot_input_action(input_action : String, checks : Observable) -> Observable: - return _GodotInputAction_.from_godot_input_action_(input_action, checks) + return _GodotInputAction_.from_godot_input_action_(input_action, checks) ## See: [b]res://addons/reactivex/engine/observable/godotcoroutine.gd[/b] func from_godot_coroutine(fun : Callable, bindings : Array = [], scheduler : SchedulerBase = null) -> Observable: - return _GodotCoroutine_.from_godot_coroutine_(fun, bindings, scheduler) + return _GodotCoroutine_.from_godot_coroutine_(fun, bindings, scheduler) ## See: [b]"res://addons/reactivex/engine/observable/computeshader.gd"[/b] func from_compute_shader(shader_path : String, rd : RenderingDevice, work_groups : Vector3i, uniform_sets : Array = [], scheduler : SchedulerBase = null) -> Observable: - return _ComputeShader_.from_compute_shader_(shader_path, rd, work_groups, uniform_sets, scheduler) + return _ComputeShader_.from_compute_shader_(shader_path, rd, work_groups, uniform_sets, scheduler) ## See: [b]"res://addons/reactivex/engine/observable/httprequest.gd"[/b] func from_http_request(url : String, request_data = "", raw : bool = false, encoding : String = "", requester : HTTPRequest = null, custom_headers : PackedStringArray = PackedStringArray(), method : HTTPClient.Method = HTTPClient.METHOD_GET) -> Observable: - return _HttpRequest_.from_http_request_(url, request_data, raw, encoding, requester, custom_headers, method) + return _HttpRequest_.from_http_request_(url, request_data, raw, encoding, requester, custom_headers, method) ## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] func process_time_interval(initial_time : float = 0.0) -> Callable: - return _ProcessTimeInterval_.process_time_interval_(initial_time) + return _ProcessTimeInterval_.process_time_interval_(initial_time) ## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] func physics_time_interval(initial_time : float = 0.0) -> Callable: - return _ProcessTimeInterval_.physics_time_interval_(initial_time) + return _ProcessTimeInterval_.physics_time_interval_(initial_time) diff --git a/addons/reactivex/engine/observable/godotsignal.gd b/addons/reactivex/engine/observable/godotsignal.gd index 24c0ba1..01e8c6e 100644 --- a/addons/reactivex/engine/observable/godotsignal.gd +++ b/addons/reactivex/engine/observable/godotsignal.gd @@ -1,69 +1,69 @@ ## Represents a Godot [Signal] as an observable sequence static func from_godot_signal_( - sig : Signal, - scheduler : SchedulerBase = null + sig : Signal, + scheduler : SchedulerBase = null ) -> Observable: - var subscribe = func( - observer : ObserverBase, - scheduler_ : SchedulerBase = null - ) -> DisposableBase: - var _scheduler : SchedulerBase = null - if scheduler != null: _scheduler = scheduler - elif scheduler_ != null: _scheduler = scheduler_ - else: _scheduler = GodotSignalScheduler.singleton() - - var obj = instance_from_id(sig.get_object_id()) - if obj == null: - GDRx.raise(NullReferenceError.new()) - return Disposable.new() - var n_args = -1 - var sig_lst = obj.get_signal_list() - for dict in sig_lst: - if dict["name"] == sig.get_name(): - n_args = dict["args"].size() - break - - var action : Callable - match n_args: - 0: - action = func(): - observer.on_next(Tuple.new([])) - 1: - action = func(arg1): - if arg1 is Array: - observer.on_next(Tuple.new(arg1)) - else: - observer.on_next(arg1) - 2: - action = func(arg1, arg2): - observer.on_next(Tuple.new([arg1, arg2])) - 3: - action = func(arg1, arg2, arg3): - observer.on_next(Tuple.new([arg1, arg2, arg3])) - 4: - action = func(arg1, arg2, arg3, arg4): - observer.on_next(Tuple.new([arg1, arg2, arg3, arg4])) - 5: - action = func(arg1, arg2, arg3, arg4, arg5): - observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5])) - 6: - action = func(arg1, arg2, arg3, arg4, arg5, arg6): - observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6])) - 7: - action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7): - observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7])) - 8: - action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): - observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8])) - _: - GDRx.raise(TooManyArgumentsError.new( - "Only up to 8 signal parameters supported! Use lists instead!")) - return Disposable.new() - - if not _scheduler is GodotSignalScheduler: - _scheduler = GodotSignalScheduler.singleton() - - var godot_signal_scheduler : GodotSignalScheduler = _scheduler - return godot_signal_scheduler.schedule_signal(sig, n_args, action) - - return Observable.new(subscribe) + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = GodotSignalScheduler.singleton() + + var obj = instance_from_id(sig.get_object_id()) + if obj == null: + GDRx.raise(NullReferenceError.new()) + return Disposable.new() + var n_args = -1 + var sig_lst = obj.get_signal_list() + for dict in sig_lst: + if dict["name"] == sig.get_name(): + n_args = dict["args"].size() + break + + var action : Callable + match n_args: + 0: + action = func(): + observer.on_next(Tuple.new([])) + 1: + action = func(arg1): + if arg1 is Array: + observer.on_next(Tuple.new(arg1)) + else: + observer.on_next(arg1) + 2: + action = func(arg1, arg2): + observer.on_next(Tuple.new([arg1, arg2])) + 3: + action = func(arg1, arg2, arg3): + observer.on_next(Tuple.new([arg1, arg2, arg3])) + 4: + action = func(arg1, arg2, arg3, arg4): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4])) + 5: + action = func(arg1, arg2, arg3, arg4, arg5): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5])) + 6: + action = func(arg1, arg2, arg3, arg4, arg5, arg6): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6])) + 7: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7])) + 8: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8])) + _: + GDRx.raise(TooManyArgumentsError.new( + "Only up to 8 signal parameters supported! Use lists instead!")) + return Disposable.new() + + if not _scheduler is GodotSignalScheduler: + _scheduler = GodotSignalScheduler.singleton() + + var godot_signal_scheduler : GodotSignalScheduler = _scheduler + return godot_signal_scheduler.schedule_signal(sig, n_args, action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/observable.gd b/addons/reactivex/observable/observable.gd index 114200f..4133e9b 100644 --- a/addons/reactivex/observable/observable.gd +++ b/addons/reactivex/observable/observable.gd @@ -7,16 +7,16 @@ var lock : RLock var _subscribe : Callable func _init(subscribe_ : Callable = func(_observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: - return Disposable.new()): - super._init() - - self.lock = RLock.new() - self._subscribe = subscribe_ + return Disposable.new()): + super._init() + + self.lock = RLock.new() + self._subscribe = subscribe_ func _subscribe_core( - observer : ObserverBase, - scheduler : SchedulerBase = null) -> DisposableBase: - return self._subscribe.call(observer, scheduler) + observer : ObserverBase, + scheduler : SchedulerBase = null) -> DisposableBase: + return self._subscribe.call(observer, scheduler) ## Subscribe an observer to the observable sequence. ## [br] @@ -56,55 +56,55 @@ func _subscribe_core( ## Disposable object representing an observer's subscription to ## the observable sequence. func subscribe( - on_next = null, # Callable or Observer or Object with callbacks - on_error : Callable = GDRx.basic.noop, - on_completed : Callable = GDRx.basic.noop, - scheduler : SchedulerBase = null) -> DisposableBase: - if on_next == null: - on_next = GDRx.basic.noop - - if on_next is ObserverBase: - var obv : ObserverBase = on_next - on_next = func(i): obv.on_next.call(i) - on_error = func(e): obv.on_error.call(e) - on_completed = func(): obv.on_completed.call() - elif on_next is Object and on_next.has_method("on_next"): - var obv : Object = on_next - if obv.has_method("on_next"): - on_next = func(i): obv.on_next.call(i) - if obv.has_method("on_error"): - on_error = func(e): obv.on_error.call(e) - if obv.has_method("on_completed"): - on_completed = func(): obv.on_completed.call() - - var auto_detach_observer : AutoDetachObserver = AutoDetachObserver.new( - on_next, on_error, on_completed - ) - - var fix_subscriber = func(subscriber) -> DisposableBase: - if subscriber is DisposableBase or subscriber.has_method("dispose"): - return subscriber - return Disposable.new(subscriber) - - var set_disposable = func(__ : SchedulerBase = null, ___ = null): - var subscriber = RefValue.Null() - if not GDRx.try(func(): - subscriber.v = self._subscribe_core(auto_detach_observer, scheduler) - ) \ - .catch("Error", func(ex): - if not auto_detach_observer.fail(ex): - GDRx.raise(ex) - ) \ - .end_try_catch(): - auto_detach_observer.subscription = fix_subscriber.call(subscriber.v) - - var current_thread_scheduler = CurrentThreadScheduler.singleton() - if current_thread_scheduler.schedule_required(): - current_thread_scheduler.schedule(set_disposable) - else: - set_disposable.call() - - return Disposable.new(func(): auto_detach_observer.dispose()) + on_next = null, # Callable or Observer or Object with callbacks + on_error : Callable = GDRx.basic.noop, + on_completed : Callable = GDRx.basic.noop, + scheduler : SchedulerBase = null) -> DisposableBase: + if on_next == null: + on_next = GDRx.basic.noop + + if on_next is ObserverBase: + var obv : ObserverBase = on_next + on_next = func(i): obv.on_next.call(i) + on_error = func(e): obv.on_error.call(e) + on_completed = func(): obv.on_completed.call() + elif on_next is Object and on_next.has_method("on_next"): + var obv : Object = on_next + if obv.has_method("on_next"): + on_next = func(i): obv.on_next.call(i) + if obv.has_method("on_error"): + on_error = func(e): obv.on_error.call(e) + if obv.has_method("on_completed"): + on_completed = func(): obv.on_completed.call() + + var auto_detach_observer : AutoDetachObserver = AutoDetachObserver.new( + on_next, on_error, on_completed + ) + + var fix_subscriber = func(subscriber) -> DisposableBase: + if subscriber is DisposableBase or subscriber.has_method("dispose"): + return subscriber + return Disposable.new(subscriber) + + var set_disposable = func(__ : SchedulerBase = null, ___ = null): + var subscriber = RefValue.Null() + if not GDRx.try(func(): + subscriber.v = self._subscribe_core(auto_detach_observer, scheduler) + ) \ + .catch("Error", func(ex): + if not auto_detach_observer.fail(ex): + GDRx.raise(ex) + ) \ + .end_try_catch(): + auto_detach_observer.subscription = fix_subscriber.call(subscriber.v) + + var current_thread_scheduler = CurrentThreadScheduler.singleton() + if current_thread_scheduler.schedule_required(): + current_thread_scheduler.schedule(set_disposable) + else: + set_disposable.call() + + return Disposable.new(func(): auto_detach_observer.dispose()) # ============================================================================ # # AWAIT # @@ -114,19 +114,19 @@ func subscribe( ## [br][br] ## [code]var item = await obs.next()[/code] func next() -> Variant: - return await ObservableAwait.new().on_next(self) + return await ObservableAwait.new().on_next(self) ## Coroutine which finishes when sequence terminates with error. ## [br][br] ## [code]var err = await obs.error()[/code] func error() -> Variant: - return await ObservableAwait.new().on_error(self) + return await ObservableAwait.new().on_error(self) ## Coroutine which finishes when sequence terminates gracefully. ## [br][br] ## [code]await obs.completed()[/code] func completed(): - return await ObservableAwait.new().on_completed(self) + return await ObservableAwait.new().on_completed(self) # ============================================================================ # # PIPE # @@ -134,99 +134,99 @@ func completed(): ## Pipe operator func pipe0() -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([])) ## Pipe operator func pipe1(__fn1 : Callable) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1])) ## Pipe operator func pipe2( - __fn1 : Callable, - __fn2 : Callable + __fn1 : Callable, + __fn2 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2])) ## Pipe operator func pipe3( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3])) ## Pipe operator func pipe4( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4])) ## Pipe operator func pipe5( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable, - __fn5 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5])) ## Pipe operator func pipe6( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable, - __fn5 : Callable, - __fn6 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6])) ## Pipe operator func pipe7( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable, - __fn5 : Callable, - __fn6 : Callable, - __fn7 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7])) ## Pipe operator func pipe8( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable, - __fn5 : Callable, - __fn6 : Callable, - __fn7 : Callable, - __fn8 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8])) ## Pipe operator func pipe9( - __fn1 : Callable, - __fn2 : Callable, - __fn3 : Callable, - __fn4 : Callable, - __fn5 : Callable, - __fn6 : Callable, - __fn7 : Callable, - __fn8 : Callable, - __fn9 : Callable + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable, + __fn9 : Callable ) -> Variant: - return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8, __fn9])) + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8, __fn9])) ## Pipe operator taking a list func pipea(arr : Array): - return GDRx.pipe.pipe(self, GDRx.util.Iter(arr)) + return GDRx.pipe.pipe(self, GDRx.util.Iter(arr)) ## Compose multiple operators left to right. ## [br] @@ -250,7 +250,7 @@ func pipea(arr : Array): ## [br] ## The composed observable. func pipe(fns : IterableBase) -> Variant: - return GDRx.pipe.compose(fns).call(self) + return GDRx.pipe.compose(fns).call(self) # ============================================================================ # # OPERATORS # @@ -258,550 +258,550 @@ func pipe(fns : IterableBase) -> Variant: ## See: [b]res://addons/reactivex/operators/connectable/_refcount.gd[/b] func ref_count() -> Observable: - return GDRx.op.ref_count().call(self) + return GDRx.op.ref_count().call(self) ## See: [b]res://addons/reactivex/operators/_all.gd[/b] func all(predicate : Callable) -> Observable: - return GDRx.op.all(predicate).call(self) + return GDRx.op.all(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_amb.gd[/b] func amb(right_source : Observable) -> Observable: - return GDRx.op.amb(right_source).call(self) + return GDRx.op.amb(right_source).call(self) ## See: [b]res://addons/reactivex/operators/_asobservable.gd[/b] func as_observable() -> Observable: - return GDRx.op.as_observable().call(self) + return GDRx.op.as_observable().call(self) ## See: [b]res://addons/reactivex/operators/_average.gd[/b] func average(key_mapper = null) -> Observable: - return GDRx.op.average(key_mapper).call(self) + return GDRx.op.average(key_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] func buffer(boundaries : Observable) -> Observable: - return GDRx.op.buffer(boundaries).call(self) + return GDRx.op.buffer(boundaries).call(self) ## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] func buffer_when(closing_mapper : Callable) -> Observable: - return GDRx.op.buffer_when(closing_mapper).call(self) + return GDRx.op.buffer_when(closing_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] func buffer_toggle(openings : Observable, closing_mapper : Callable) -> Observable: - return GDRx.op.buffer_toggle(openings, closing_mapper).call(self) + return GDRx.op.buffer_toggle(openings, closing_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] func buffer_with_count(count_ : int, skip_ = null) -> Observable: - return GDRx.op.buffer_with_count(count_, skip_).call(self) + return GDRx.op.buffer_with_count(count_, skip_).call(self) ## See: [b]res://addons/reactivex/operators/_bufferwithtime.gd[/b] func buffer_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.buffer_with_time(timespan, timeshift, scheduler).call(self) + return GDRx.op.buffer_with_time(timespan, timeshift, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_bufferwithtimeourcount.gd[/b] func buffer_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.buffer_with_time_or_count(timespan, count_, scheduler).call(self) + return GDRx.op.buffer_with_time_or_count(timespan, count_, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_catch.gd[/b] func catch_handler(handler : Callable) -> Observable: - return GDRx.op.catch_handler(self, handler) + return GDRx.op.catch_handler(self, handler) ## See: [b]res://addons/reactivex/operators/_catch.gd[/b] func catch(handler) -> Observable: - return GDRx.op.catch(handler).call(self) + return GDRx.op.catch(handler).call(self) ## See: [b]res://addons/reactivex/operators/_combinelatest.gd[/b] func combine_latest(others) -> Observable: - return GDRx.op.combine_latest(others).call(self) + return GDRx.op.combine_latest(others).call(self) ## See: [b]res://addons/reactivex/operators/_concat.gd[/b] func concat(sources) -> Observable: - return GDRx.op.concat(sources).call(self) + return GDRx.op.concat(sources).call(self) ## See: [b]res://addons/reactivex/operators/_contains.gd[/b] func contains(value, comparer = GDRx.basic.default_comparer) -> Observable: - return GDRx.op.contains(value, comparer).call(self) + return GDRx.op.contains(value, comparer).call(self) ## See: [b]res://addons/reactivex/operators/_count.gd[/b] func count(predicate = null) -> Observable: - return GDRx.op.count(predicate).call(self) + return GDRx.op.count(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] func debounce(duetime : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.debounce(duetime, scheduler).call(self) + return GDRx.op.debounce(duetime, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] func throttle_with_mapper(throttle_duration_mapper : Callable) -> Observable: - return GDRx.op.throttle_with_mapper(throttle_duration_mapper).call(self) + return GDRx.op.throttle_with_mapper(throttle_duration_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_defaultifempty.gd[/b] func default_if_empty(default_value = null) -> Observable: - return GDRx.op.default_if_empty(default_value).call(self) + return GDRx.op.default_if_empty(default_value).call(self) ## See: [b]res://addons/reactivex/operators/_delay.gd[/b] func observable_delay_timespan(duetime : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.observable_delay_timespan(self, duetime, scheduler) + return GDRx.op.observable_delay_timespan(self, duetime, scheduler) ## See: [b]res://addons/reactivex/operators/_delay.gd[/b] func delay(duetime : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.delay(duetime, scheduler).call(self) + return GDRx.op.delay(duetime, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_delaysubscription.gd[/b] func delay_subscription(duetime : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.delay_subscription(duetime, time_absolute, scheduler).call(self) + return GDRx.op.delay_subscription(duetime, time_absolute, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_delaywithmapper.gd[/b] func delay_with_mapper(subscription_delay = null, delay_duration_mapper = null) -> Observable: - return GDRx.op.delay_with_mapper(subscription_delay, delay_duration_mapper).call(self) + return GDRx.op.delay_with_mapper(subscription_delay, delay_duration_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_dematerialize.gd[/b] func dematerialize() -> Observable: - return GDRx.op.dematerialize().call(self) + return GDRx.op.dematerialize().call(self) ## See: [b]res://addons/reactivex/operators/_distinct.gd[/b] func distinct(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: - return GDRx.op.distinct(key_mapper, comparer).call(self) + return GDRx.op.distinct(key_mapper, comparer).call(self) ## See: [b]res://addons/reactivex/operators/_distinctuntilchanged.gd[/b] func distinct_until_changed(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: - return GDRx.op.distinct_until_changed(key_mapper, comparer).call(self) + return GDRx.op.distinct_until_changed(key_mapper, comparer).call(self) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_action(on_next = null, on_error = null, on_completed = null) -> Observable: - return GDRx.op.do_action(on_next, on_error, on_completed).call(self) + return GDRx.op.do_action(on_next, on_error, on_completed).call(self) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do(observer : ObserverBase) -> Observable: - return GDRx.op.do(observer).call(self) + return GDRx.op.do(observer).call(self) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_after_next(after_next : Callable) -> Observable: - return GDRx.op.do_after_next(self, after_next) + return GDRx.op.do_after_next(self, after_next) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_on_subscribe(on_subscribe : Callable) -> Observable: - return GDRx.op.do_on_subscribe(self, on_subscribe) + return GDRx.op.do_on_subscribe(self, on_subscribe) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_on_dispose(on_dispose : Callable) -> Observable: - return GDRx.op.do_on_dispose(self, on_dispose) + return GDRx.op.do_on_dispose(self, on_dispose) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_on_terminate(on_terminate : Callable) -> Observable: - return GDRx.op.do_on_terminate(self, on_terminate) + return GDRx.op.do_on_terminate(self, on_terminate) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_after_terminate(after_terminate : Callable) -> Observable: - return GDRx.op.do_after_terminate(self, after_terminate) + return GDRx.op.do_after_terminate(self, after_terminate) ## See: [b]res://addons/reactivex/operators/_do.gd[/b] func do_finally(finally_action_ : Callable) -> Observable: - return GDRx.op.do_finally(finally_action_).call(self) + return GDRx.op.do_finally(finally_action_).call(self) ## See: [b]res://addons/reactivex/operators/_dowhile.gd[/b] func do_while(condition : Callable) -> Observable: - return GDRx.op.do_while(condition).call(self) + return GDRx.op.do_while(condition).call(self) ## See: [b]res://addons/reactivex/operators/_elementordefault.gd[/b] func element_at_or_default(index : int, has_default : bool = false, default_value = GDRx.util.GetNotSet()) -> Observable: - return GDRx.op.element_at_or_default(index, has_default, default_value).call(self) + return GDRx.op.element_at_or_default(index, has_default, default_value).call(self) ## See: [b]res://addons/reactivex/operators/_exclusive.gd[/b] func exclusive() -> Observable: - return GDRx.op.exclusive().call(self) + return GDRx.op.exclusive().call(self) ## See: [b]res://addons/reactivex/operators/_expand.gd[/b] func expand(mapper : Callable) -> Observable: - return GDRx.op.expand(mapper).call(self) + return GDRx.op.expand(mapper).call(self) ## See: [b]res://addons/reactivex/operators/_filter.gd[/b] func filter(predicate : Callable = GDRx.basic.default_condition) -> Observable: - return GDRx.op.filter(predicate).call(self) + return GDRx.op.filter(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_filter.gd[/b] func filter_indexed(predicate : Callable = GDRx.basic.default_condition) -> Observable: - return GDRx.op.filter_indexed(predicate).call(self) + return GDRx.op.filter_indexed(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_finallyaction.gd[/b] func finally_action(action : Callable) -> Observable: - return GDRx.op.finally_action(action).call(self) + return GDRx.op.finally_action(action).call(self) ## See: [b]res://addons/reactivex/operators/_find.gd[/b] func find_value(predicate : Callable, yield_index : bool) -> Observable: - return GDRx.op.find_value(predicate, yield_index).call(self) + return GDRx.op.find_value(predicate, yield_index).call(self) ## See: [b]res://addons/reactivex/operators/_first.gd[/b] func first(predicate = null) -> Observable: - return GDRx.op.first(predicate).call(self) + return GDRx.op.first(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] func first_or_default_async(has_default : bool = false, default_value = null) -> Observable: - return GDRx.op.first_or_default_async(has_default, default_value).call(self) + return GDRx.op.first_or_default_async(has_default, default_value).call(self) ## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] func first_or_default(predicate = null, default_value = null) -> Observable: - return GDRx.op.first_or_default(predicate, default_value).call(self) + return GDRx.op.first_or_default(predicate, default_value).call(self) ## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] func flat_map(mapper = null) -> Observable: - return GDRx.op.flat_map(mapper).call(self) + return GDRx.op.flat_map(mapper).call(self) ## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] func flat_map_indexed(mapper_indexed = null) -> Observable: - return GDRx.op.flat_map_indexed(mapper_indexed).call(self) + return GDRx.op.flat_map_indexed(mapper_indexed).call(self) ## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] func flat_map_latest(mapper = null) -> Observable: - return GDRx.op.flat_map_latest(mapper).call(self) + return GDRx.op.flat_map_latest(mapper).call(self) ## See: [b]res://addons/reactivex/operators/_forkjoin.gd[/b] func fork_join(args) -> Observable: - return GDRx.op.fork_join(args).call(self) + return GDRx.op.fork_join(args).call(self) ## See: [b]res://addons/reactivex/operators/_groupby.gd[/b] func group_by(key_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: - return GDRx.op.group_by(key_mapper, element_mapper, subject_mapper).call(self) + return GDRx.op.group_by(key_mapper, element_mapper, subject_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_groupbyuntil.gd[/b] func group_by_until(key_mapper : Callable, duration_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: - return GDRx.op.group_by_until(key_mapper, duration_mapper, element_mapper, subject_mapper).call(self) + return GDRx.op.group_by_until(key_mapper, duration_mapper, element_mapper, subject_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_groupjoin.gd[/b] func group_join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: - return GDRx.op.group_join(right, left_duration_mapper, right_duration_mapper).call(self) + return GDRx.op.group_join(right, left_duration_mapper, right_duration_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_ignoreelements.gd[/b] func ignore_elements() -> Observable: - return GDRx.op.ignore_elements().call(self) + return GDRx.op.ignore_elements().call(self) ## See: [b]res://addons/reactivex/operators/_isempty.gd[/b] func is_empty() -> Observable: - return GDRx.op.is_empty().call(self) + return GDRx.op.is_empty().call(self) ## See: [b]res://addons/reactivex/operators/_join.gd[/b] func join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: - return GDRx.op.join(right, left_duration_mapper, right_duration_mapper).call(self) + return GDRx.op.join(right, left_duration_mapper, right_duration_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_last.gd[/b] func last(predicate = null) -> Observable: - return GDRx.op.last(predicate).call(self) + return GDRx.op.last(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] func last_or_default_async(has_default : bool = false, default_value = null) -> Observable: - return GDRx.op.last_or_default_async(self, has_default, default_value) + return GDRx.op.last_or_default_async(self, has_default, default_value) ## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] func last_or_default(default_value = null, predicate = null) -> Observable: - return GDRx.op.last_or_default(default_value, predicate).call(self) + return GDRx.op.last_or_default(default_value, predicate).call(self) ## See: [b]res://addons/reactivex/operators/_map.gd[/b] func map(mapper : Callable = GDRx.basic.identity) -> Observable: - return GDRx.op.map(mapper).call(self) + return GDRx.op.map(mapper).call(self) ## See: [b]res://addons/reactivex/operators/_map.gd[/b] func map_indexed(mapper_indexed : Callable = GDRx.basic.identity) -> Observable: - return GDRx.op.map_indexed(mapper_indexed).call(self) + return GDRx.op.map_indexed(mapper_indexed).call(self) ## See: [b]res://addons/reactivex/operators/_materialize.gd[/b] func materialize() -> Observable: - return GDRx.op.materialize().call(self) + return GDRx.op.materialize().call(self) ## See: [b]res://addons/reactivex/operators/_max.gd[/b] @warning_ignore("shadowed_global_identifier") func max(comparer = null) -> Observable: - return GDRx.op.max(comparer).call(self) + return GDRx.op.max(comparer).call(self) ## See: [b]res://addons/reactivex/operators/_maxby.gd[/b] func max_by(key_mapper : Callable, comparer = null) -> Observable: - return GDRx.op.max_by(key_mapper, comparer).call(self) + return GDRx.op.max_by(key_mapper, comparer).call(self) ## See: [b]res://addons/reactivex/operators/_merge.gd[/b] func merge(sources, max_concorrent : int = -1) -> Observable: - return GDRx.op.merge(sources, max_concorrent).call(self) + return GDRx.op.merge(sources, max_concorrent).call(self) ## See: [b]res://addons/reactivex/operators/_merge.gd[/b] func merge_all() -> Observable: - return GDRx.op.merge_all().call(self) + return GDRx.op.merge_all().call(self) ## See: [b]res://addons/reactivex/operators/_min.gd[/b] @warning_ignore("shadowed_global_identifier") func min(comparer = null) -> Observable: - return GDRx.op.min(comparer).call(self) + return GDRx.op.min(comparer).call(self) ## See: [b]res://addons/reactivex/operators/_minby.gd[/b] func extrema_by(key_mapper : Callable, comparer : Callable) -> Observable: - return GDRx.op.extrema_by(self, key_mapper, comparer) + return GDRx.op.extrema_by(self, key_mapper, comparer) ## See: [b]res://addons/reactivex/operators/_minby.gd[/b] func min_by(key_mapper : Callable, comparer = null) -> Observable: - return GDRx.op.min_by(key_mapper, comparer).call(self) + return GDRx.op.min_by(key_mapper, comparer).call(self) ## See: [b]res://addons/reactivex/operators/_multicast.gd[/b] func multicast(subject : SubjectBase = null, subject_factory = null, mapper = null) -> Observable: - return GDRx.op.multicast(subject, subject_factory, mapper).call(self) + return GDRx.op.multicast(subject, subject_factory, mapper).call(self) ## See: [b]res://addons/reactivex/operators/_observeon.gd[/b] func observe_on(scheduler : SchedulerBase) -> Observable: - return GDRx.op.observe_on(scheduler).call(self) + return GDRx.op.observe_on(scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_oftype.gd[/b] func oftype(type, push_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality) -> Observable: - return GDRx.op.oftype(type, push_err, type_equality).call(self) + return GDRx.op.oftype(type, push_err, type_equality).call(self) ## See: [b]res://addons/reactivex/operators/_onerrorresumenext.gd[/b] func on_error_resume_next(second : Observable) -> Observable: - return GDRx.op.on_error_resume_next(second).call(self) + return GDRx.op.on_error_resume_next(second).call(self) ## See: [b]res://addons/reactivex/operators/_pairwise.gd[/b] func pairwise() -> Observable: - return GDRx.op.pairwise().call(self) + return GDRx.op.pairwise().call(self) ## See: [b]res://addons/reactivex/operators/_partiton.gd[/b] func partition(predicate : Callable = GDRx.basic.default_condition) -> Array[Observable]: - return GDRx.op.partition(predicate).call(self) + return GDRx.op.partition(predicate).call(self) ## Alternative to [method partition] but returning type an [IterableBase] containing. ## the partitioned [Observable]s. func partitionit(predicate : Callable = GDRx.basic.default_condition) -> IterableBase: - return Iterator.to_iterable(self.partition(predicate)) + return Iterator.to_iterable(self.partition(predicate)) ## See: [b]res://addons/reactivex/operators/_partition.gd[/b] func partition_indexed(predicate_indexed : Callable = GDRx.basic.default_condition) -> Observable: - return GDRx.op.partition_indexed(predicate_indexed).call(self) + return GDRx.op.partition_indexed(predicate_indexed).call(self) ## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] func pluck(key) -> Observable: - return GDRx.op.pluck(key).call(self) + return GDRx.op.pluck(key).call(self) ## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] func pluck_attr(prop : String) -> Observable: - return GDRx.op.pluck_attr(prop).call(self) + return GDRx.op.pluck_attr(prop).call(self) ## See: [b]res://addons/reactivex/operators/_publish.gd[/b] func publish(mapper = null) -> Observable: - return GDRx.op.publish(mapper).call(self) + return GDRx.op.publish(mapper).call(self) ## See: [b]res://addons/reactivex/operators/_publish.gd[/b] func share() -> Observable: - return GDRx.op.share().call(self) + return GDRx.op.share().call(self) ## See: [b]res://addons/reactivex/operators/_publishvalue.gd[/b] func publish_value(initial_value, mapper = null) -> Observable: - return GDRx.op.publish_value(initial_value, mapper).call(self) + return GDRx.op.publish_value(initial_value, mapper).call(self) ## See: [b]res://addons/reactivex/operators/_reduce.gd[/b] func reduce(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: - return GDRx.op.reduce(accumulator, seed_).call(self) + return GDRx.op.reduce(accumulator, seed_).call(self) ## See: [b]res://addons/reactivex/operators/_repeat.gd[/b] func repeat(repeat_count = null) -> Observable: - return GDRx.op.repeat(repeat_count).call(self) + return GDRx.op.repeat(repeat_count).call(self) ## See: [b]res://addons/reactivex/operators/_replay.gd[/b] func replay(mapper = null, buffer_size = null, window_ = null, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.replay(mapper, buffer_size, window_, scheduler).call(self) + return GDRx.op.replay(mapper, buffer_size, window_, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_retry.gd[/b] func retry(retry_count : int = -1) -> Observable: - return GDRx.op.retry(retry_count).call(self) + return GDRx.op.retry(retry_count).call(self) ## See: [b]res://addons/reactivex/operators/_sample.gd[/b] func sample_observable(sampler : Observable) -> Observable: - return GDRx.op.sample_observable(self, sampler) + return GDRx.op.sample_observable(self, sampler) ## See: [b]res://addons/reactivex/operators/_sample.gd[/b] func sample(sampler : Observable, sampler_time : float = NAN, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.sample(sampler, sampler_time, scheduler).call(self) + return GDRx.op.sample(sampler, sampler_time, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_scan.gd[/b] func scan(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: - return GDRx.op.scan(accumulator, seed_).call(self) + return GDRx.op.scan(accumulator, seed_).call(self) ## See: [b]res://addons/reactivex/operators/_sequenceequal.gd[/b] func sequence_equal(second, comparer = null, second_it : IterableBase = null) -> Observable: - return GDRx.op.sequence_equal(second, comparer, second_it).call(self) + return GDRx.op.sequence_equal(second, comparer, second_it).call(self) ## See: [b]res://addons/reactivex/operators/_single.gd[/b] func single(predicate = null) -> Observable: - return GDRx.op.single(predicate).call(self) + return GDRx.op.single(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] func single_or_default_async(has_default : bool = false, default_value = null) -> Observable: - return GDRx.op.single_or_default_async(has_default, default_value).call(self) + return GDRx.op.single_or_default_async(has_default, default_value).call(self) ## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] func single_or_default(predicate = null, default_value = null) -> Observable: - return GDRx.op.single_or_default(predicate, default_value).call(self) + return GDRx.op.single_or_default(predicate, default_value).call(self) ## See: [b]res://addons/reactivex/operators/_skip.gd[/b] func skip(count_ : int) -> Observable: - return GDRx.op.skip(count_).call(self) + return GDRx.op.skip(count_).call(self) ## See: [b]res://addons/reactivex/operators/_skiplast.gd[/b] func skip_last(count_ : int) -> Observable: - return GDRx.op.skip_last(count_).call(self) + return GDRx.op.skip_last(count_).call(self) ## See: [b]res://addons/reactivex/operators/_skiplastwithtime.gd[/b] func skip_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.skip_last_with_time(duration, scheduler).call(self) + return GDRx.op.skip_last_with_time(duration, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_skipuntil.gd[/b] func skip_until(other : Observable) -> Observable: - return GDRx.op.skip_until(other).call(self) + return GDRx.op.skip_until(other).call(self) ## See: [b]res://addons/reactivex/operators/_skipuntilwithtime.gd[/b] func skip_until_with_time(start_time : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.skip_until_with_time(start_time, time_absolute, scheduler).call(self) + return GDRx.op.skip_until_with_time(start_time, time_absolute, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] func skip_while(predicate : Callable) -> Observable: - return GDRx.op.skip_while(predicate).call(self) + return GDRx.op.skip_while(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] func skip_while_indexed(predicate : Callable) -> Observable: - return GDRx.op.skip_while_indexed(predicate).call(self) + return GDRx.op.skip_while_indexed(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_skipwithtime.gd[/b] func skip_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.skip_with_time(duration, scheduler).call(self) + return GDRx.op.skip_with_time(duration, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_slice.gd[/b] func slice(start : int = 0, stop : int = GDRx.util.MAX_SIZE, step : int = 1) -> Observable: - return GDRx.op.slice(start, stop, step).call(self) + return GDRx.op.slice(start, stop, step).call(self) ## See: [b]res://addons/reactivex/operators/_some.gd[/b] func some(predicate = null) -> Observable: - return GDRx.op.some(predicate).call(self) + return GDRx.op.some(predicate).call(self) ## See: [b]res://addons/reactivex/operators/_startswith.gd[/b] func start_with(args) -> Observable: - return GDRx.op.start_with(args).call(self) + return GDRx.op.start_with(args).call(self) ## See: [b]res://addons/reactivex/operators/_subscribeon.gd[/b] func subscribe_on(scheduler : SchedulerBase) -> Observable: - return GDRx.op.subscribe_on(scheduler).call(self) + return GDRx.op.subscribe_on(scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_sum.gd[/b] func sum(key_mapper = null) -> Observable: - return GDRx.op.sum(key_mapper).call(self) + return GDRx.op.sum(key_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_switchlatest.gd[/b] func switch_latest() -> Observable: - return GDRx.op.switch_latest().call(self) + return GDRx.op.switch_latest().call(self) ## See: [b]res://addons/reactivex/operators/_take.gd[/b] func take(count_ : int) -> Observable: - return GDRx.op.take(count_).call(self) + return GDRx.op.take(count_).call(self) ## See: [b]res://addons/reactivex/operators/_takelast.gd[/b] func take_last(count_ : int) -> Observable: - return GDRx.op.take_last(count_).call(self) + return GDRx.op.take_last(count_).call(self) ## See: [b]res://addons/reactivex/operators/_takelastbuffer.gd[/b] func take_last_buffer(count_ : int) -> Observable: - return GDRx.op.take_last_buffer(count_).call(self) + return GDRx.op.take_last_buffer(count_).call(self) ## See: [b]res://addons/reactivex/operators/_takelastwithtime.gd[/b] func take_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.take_last_with_time(duration, scheduler).call(self) + return GDRx.op.take_last_with_time(duration, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_takeuntil.gd[/b] func take_until(other : Observable) -> Observable: - return GDRx.op.take_until(other).call(self) + return GDRx.op.take_until(other).call(self) ## See: [b]res://addons/reactivex/operators/_takeuntilwithtime.gd[/b] func take_until_with_time(end_time : float, absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.take_until_with_time(end_time, absolute, scheduler).call(self) + return GDRx.op.take_until_with_time(end_time, absolute, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] func take_while(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: - return GDRx.op.take_while(predicate, inclusive).call(self) + return GDRx.op.take_while(predicate, inclusive).call(self) ## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] func take_while_indexed(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: - return GDRx.op.take_while_indexed(predicate, inclusive).call(self) + return GDRx.op.take_while_indexed(predicate, inclusive).call(self) ## See: [b]res://addons/reactivex/operators/_takewithtime.gd[/b] func take_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.take_with_time(duration, scheduler).call(self) + return GDRx.op.take_with_time(duration, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_throttlefirst.gd[/b] func throttle_first(window_duration : float, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.throttle_first(window_duration, scheduler).call(self) + return GDRx.op.throttle_first(window_duration, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_timeinterval.gd[/b] func time_interval(scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.time_interval(scheduler).call(self) + return GDRx.op.time_interval(scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_timeout.gd[/b] func timeout(duetime : float, absolute : bool = false, other : Observable = null, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.timeout(duetime, absolute, other, scheduler).call(self) + return GDRx.op.timeout(duetime, absolute, other, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_timeoutwithmapper.gd[/b] func timeout_with_mapper(first_timeout : Observable = null, timeout_duration_mapper : Callable = func(__) -> Observable: return GDRx.obs.never(), other : Observable = null) -> Observable: - return GDRx.op.timeout_with_mapper(first_timeout, timeout_duration_mapper, other).call(self) + return GDRx.op.timeout_with_mapper(first_timeout, timeout_duration_mapper, other).call(self) ## See: [b]res://addons/reactivex/operators/_timestamp.gd[/b] func timestamp(scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.timestamp(scheduler).call(self) + return GDRx.op.timestamp(scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_todict.gd[/b] func to_dict(key_mapper : Callable, element_mapper : Callable = GDRx.basic.identity) -> Observable: - return GDRx.op.to_dict(key_mapper, element_mapper).call(self) + return GDRx.op.to_dict(key_mapper, element_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_toiterable.gd[/b] func to_iterable() -> Observable: - return GDRx.op.to_iterable().call(self) + return GDRx.op.to_iterable().call(self) ## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] func to_list() -> Observable: - return GDRx.op.to_list().call(self) + return GDRx.op.to_list().call(self) ## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] func to_array() -> Observable: - return GDRx.op.to_list().call(self) + return GDRx.op.to_list().call(self) ## See: [b]res://addons/reactivex/operators/_toset.gd[/b] func to_set() -> Observable: - return GDRx.op.to_set().call(self) + return GDRx.op.to_set().call(self) ## See: [b]res://addons/reactivex/operators/_whiledo.gd[/b] func while_do(condition : Callable = GDRx.basic.default_condition) -> Observable: - return GDRx.op.while_do(condition).call(self) + return GDRx.op.while_do(condition).call(self) ## See: [b]res://addons/reactivex/operators/_window.gd[/b] func window_toggle(openings : Observable, closing_mapper : Callable) -> Observable: - return GDRx.op.window_toggle(openings, closing_mapper).call(self) + return GDRx.op.window_toggle(openings, closing_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_window.gd[/b] func window(boundaries : Observable) -> Observable: - return GDRx.op.window(boundaries).call(self) + return GDRx.op.window(boundaries).call(self) ## See: [b]res://addons/reactivex/operators/_window.gd[/b] func window_when(closing_mapper : Callable) -> Observable: - return GDRx.op.window_when(closing_mapper).call(self) + return GDRx.op.window_when(closing_mapper).call(self) ## See: [b]res://addons/reactivex/operators/_windowwithcount.gd[/b] func window_with_count(count_ : int, skip_ = null) -> Observable: - return GDRx.op.window_with_count(count_, skip_).call(self) + return GDRx.op.window_with_count(count_, skip_).call(self) ## See: [b]res://addons/reactivex/operators/_windowwithtime.gd[/b] func window_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.window_with_time(timespan, timeshift, scheduler).call(self) + return GDRx.op.window_with_time(timespan, timeshift, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_windowwithtimeorcount.gd[/b] func window_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: - return GDRx.op.window_with_time_or_count(timespan, count_, scheduler).call(self) + return GDRx.op.window_with_time_or_count(timespan, count_, scheduler).call(self) ## See: [b]res://addons/reactivex/operators/_withlatestfrom.gd[/b] func with_latest_from(sources) -> Observable: - return GDRx.op.with_latest_from(sources).call(self) + return GDRx.op.with_latest_from(sources).call(self) ## See: [b]res://addons/reactivex/operators/_zip.gd[/b] func zip(args) -> Observable: - return GDRx.op.zip(args).call(self) + return GDRx.op.zip(args).call(self) ## See: [b]res://addons/reactivex/operators/_zip.gd[/b] func zip_with_iterable(seq : IterableBase) -> Observable: - return GDRx.op.zip_with_iterable(seq).call(self) + return GDRx.op.zip_with_iterable(seq).call(self) # =========================================================================== # # Godot-specific Operators @@ -809,12 +809,12 @@ func zip_with_iterable(seq : IterableBase) -> Observable: ## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] func process_time_interval(initial_time : float = 0.0) -> Observable: - return GDRx.gd.process_time_interval(initial_time).call(self) + return GDRx.gd.process_time_interval(initial_time).call(self) ## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] func physics_time_interval(initial_time : float = 0.0) -> Observable: - return GDRx.gd.physics_time_interval(initial_time).call(self) + return GDRx.gd.physics_time_interval(initial_time).call(self) ## Create a [ReadOnlyReactiveProperty] from the given sequence func to_reactive_property(initial_value, distinct_until_changed : bool = true, raise_latest_value_on_subscribe = true) -> ReadOnlyReactiveProperty: - return ReadOnlyReactiveProperty.new(self, initial_value, distinct_until_changed, raise_latest_value_on_subscribe) + return ReadOnlyReactiveProperty.new(self, initial_value, distinct_until_changed, raise_latest_value_on_subscribe) diff --git a/addons/reactivex/observer/autodetachobserver.gd b/addons/reactivex/observer/autodetachobserver.gd index 8f089c7..72ad82d 100644 --- a/addons/reactivex/observer/autodetachobserver.gd +++ b/addons/reactivex/observer/autodetachobserver.gd @@ -12,61 +12,61 @@ var is_stopped : bool var this : AutoDetachObserver func _init( - on_next_ : Callable = GDRx.basic.noop, - on_error_ : Callable = GDRx.basic.noop, - on_completed_ : Callable = GDRx.basic.noop): - this = self - this.unreference() - - self._on_next = on_next_ - self._on_error = on_error_ - self._on_completed = on_completed_ - - self._subscription = SingleAssignmentDisposable.new() - self.is_stopped = false + on_next_ : Callable = GDRx.basic.noop, + on_error_ : Callable = GDRx.basic.noop, + on_completed_ : Callable = GDRx.basic.noop): + this = self + this.unreference() + + self._on_next = on_next_ + self._on_error = on_error_ + self._on_completed = on_completed_ + + self._subscription = SingleAssignmentDisposable.new() + self.is_stopped = false func on_next(i): - if self.is_stopped: - return - self._on_next.call(i) + if self.is_stopped: + return + self._on_next.call(i) func on_error(e): - if self.is_stopped: - return - self.is_stopped = true - - GDRx.try(func(): - self._on_error.call(e) - ).end_try_catch() - self.dispose() + if self.is_stopped: + return + self.is_stopped = true + + GDRx.try(func(): + self._on_error.call(e) + ).end_try_catch() + self.dispose() func on_completed(): - if self.is_stopped: - return - self.is_stopped = true - - GDRx.try(func(): - self._on_completed.call() - ).end_try_catch() - self.dispose() + if self.is_stopped: + return + self.is_stopped = true + + GDRx.try(func(): + self._on_completed.call() + ).end_try_catch() + self.dispose() func set_disposable(value : DisposableBase): - self._subscription.disposable = value + self._subscription.disposable = value var subscription: set = set_disposable func dispose(): - this.is_stopped = true - this._subscription.dispose() + this.is_stopped = true + this._subscription.dispose() func fail(err) -> bool: - if self.is_stopped: - return false - - self.is_stopped = true - self._on_error.call(err) - return true + if self.is_stopped: + return false + + self.is_stopped = true + self._on_error.call(err) + return true func _notification(what): - if what == NOTIFICATION_PREDELETE: - this.dispose() + if what == NOTIFICATION_PREDELETE: + this.dispose() diff --git a/assets/characters/evilcoco.tscn b/assets/characters/evilcoco.tscn index 75b3ddd..b3b0535 100644 --- a/assets/characters/evilcoco.tscn +++ b/assets/characters/evilcoco.tscn @@ -32,7 +32,7 @@ max_value = 200.0 _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.56535, 83.9717), 420.727, 420.727, 0, 0, Vector2(0.729483, 200), 0.0, 0.0, 0, 0] point_count = 3 -[sub_resource type="ViewportTexture" id="ViewportTexture_hfgjs"] +[sub_resource type="ViewportTexture" id="ViewportTexture_pu4u6"] viewport_path = NodePath("MeshInstance3D/SubViewport") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_a31vv"] @@ -41,7 +41,7 @@ blend_mode = 4 no_depth_test = true diffuse_mode = 3 specular_mode = 1 -albedo_texture = SubResource("ViewportTexture_hfgjs") +albedo_texture = SubResource("ViewportTexture_pu4u6") texture_filter = 0 texture_repeat = false billboard_mode = 1 diff --git a/assets/characters/npc.tscn b/assets/characters/npc.tscn index b819498..d73a6ef 100644 --- a/assets/characters/npc.tscn +++ b/assets/characters/npc.tscn @@ -33,7 +33,7 @@ max_value = 200.0 _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.56535, 83.9717), 420.727, 420.727, 0, 0, Vector2(0.729483, 200), 0.0, 0.0, 0, 0] point_count = 3 -[sub_resource type="ViewportTexture" id="ViewportTexture_l5xb2"] +[sub_resource type="ViewportTexture" id="ViewportTexture_cmoyk"] viewport_path = NodePath("MeshInstance3D/SubViewport") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_a31vv"] @@ -43,7 +43,7 @@ blend_mode = 4 shading_mode = 0 diffuse_mode = 3 specular_mode = 1 -albedo_texture = SubResource("ViewportTexture_l5xb2") +albedo_texture = SubResource("ViewportTexture_cmoyk") texture_filter = 0 texture_repeat = false billboard_mode = 1 diff --git a/assets/characters/player.tscn b/assets/characters/player.tscn index 7575ece..4f92560 100644 --- a/assets/characters/player.tscn +++ b/assets/characters/player.tscn @@ -6,7 +6,7 @@ [ext_resource type="Script" path="res://src/camera_3d.gd" id="2_cjtsr"] [ext_resource type="Script" path="res://src/spine_sprite.gd" id="4_bwb5d"] -[sub_resource type="ViewportTexture" id="ViewportTexture_nt42y"] +[sub_resource type="ViewportTexture" id="ViewportTexture_8onqm"] viewport_path = NodePath("MeshInstance3D/SubViewport") [sub_resource type="ShaderMaterial" id="ShaderMaterial_5xxpu"] @@ -16,7 +16,7 @@ next_pass = SubResource("ShaderMaterial_5xxpu") transparency = 1 diffuse_mode = 3 specular_mode = 1 -albedo_texture = SubResource("ViewportTexture_nt42y") +albedo_texture = SubResource("ViewportTexture_8onqm") billboard_mode = 1 [sub_resource type="QuadMesh" id="QuadMesh_iwcmf"] diff --git a/assets/characters/tawna/tawna.atlas b/assets/characters/tawna/tawna.atlas index caf2c57..a9e7f59 100644 --- a/assets/characters/tawna/tawna.atlas +++ b/assets/characters/tawna/tawna.atlas @@ -1,6 +1,6 @@ tawna.png -size:505,1153 +size:505,1156 filter:Linear,Linear scale:0.5 tawna -bounds:2,2,501,1149 +bounds:2,2,501,1152 diff --git a/assets/characters/tawna/tawna.png b/assets/characters/tawna/tawna.png index 3c78adc..64045e8 100644 Binary files a/assets/characters/tawna/tawna.png and b/assets/characters/tawna/tawna.png differ diff --git a/assets/characters/tawna/tawna.skel b/assets/characters/tawna/tawna.skel index ec51f61..82c9217 100644 Binary files a/assets/characters/tawna/tawna.skel and b/assets/characters/tawna/tawna.skel differ diff --git a/assets/characters/tawna/tawna.tscn b/assets/characters/tawna/tawna.tscn index c2ed0e5..1f5401b 100644 --- a/assets/characters/tawna/tawna.tscn +++ b/assets/characters/tawna/tawna.tscn @@ -33,7 +33,7 @@ max_value = 200.0 _data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(0.56535, 83.9717), 420.727, 420.727, 0, 0, Vector2(0.729483, 200), 0.0, 0.0, 0, 0] point_count = 3 -[sub_resource type="ViewportTexture" id="ViewportTexture_7ojb1"] +[sub_resource type="ViewportTexture" id="ViewportTexture_un223"] viewport_path = NodePath("MeshInstance3D/SubViewport") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_a31vv"] @@ -41,7 +41,7 @@ next_pass = ExtResource("2_2icy7") transparency = 1 diffuse_mode = 3 specular_mode = 1 -albedo_texture = SubResource("ViewportTexture_7ojb1") +albedo_texture = SubResource("ViewportTexture_un223") billboard_mode = 1 [sub_resource type="QuadMesh" id="QuadMesh_iwcmf"] diff --git a/assets/levels/maps/debug1.tscn b/assets/levels/maps/debug1.tscn index ce0d347..a11874f 100644 --- a/assets/levels/maps/debug1.tscn +++ b/assets/levels/maps/debug1.tscn @@ -54,14 +54,14 @@ size = Vector2(10, 5) [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_flcen"] data = PackedVector3Array(5, 0, 5, -5, 0, 5, 5, 0, -5, -5, 0, 5, -5, 0, -5, 5, 0, -5) -[sub_resource type="ViewportTexture" id="ViewportTexture_lyl4a"] +[sub_resource type="ViewportTexture" id="ViewportTexture_vcg3v"] viewport_path = NodePath("MeshInstance3D/SubViewport") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mdr1l"] transparency = 2 alpha_scissor_threshold = 0.5 alpha_antialiasing_mode = 0 -albedo_texture = SubResource("ViewportTexture_lyl4a") +albedo_texture = SubResource("ViewportTexture_vcg3v") metallic_specular = 0.0 [sub_resource type="ShaderMaterial" id="ShaderMaterial_lrfyr"] diff --git a/build/game.pck b/build/game.pck deleted file mode 100644 index 600f45b..0000000 Binary files a/build/game.pck and /dev/null differ diff --git a/build/game.sh b/build/game.sh deleted file mode 100755 index ed54776..0000000 --- a/build/game.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -echo -ne '\033c\033]0;Test Project\a' -base_path="$(dirname "$(realpath "$0")")" -"$base_path/game.x86_64" "$@" diff --git a/build/game.x86_64 b/build/game.x86_64 deleted file mode 100755 index aa3efdc..0000000 Binary files a/build/game.x86_64 and /dev/null differ diff --git a/build/web/Test Project.apple-touch-icon.png b/build/web/Test Project.apple-touch-icon.png deleted file mode 100644 index e64341e..0000000 Binary files a/build/web/Test Project.apple-touch-icon.png and /dev/null differ diff --git a/build/web/Test Project.audio.worklet.js b/build/web/Test Project.audio.worklet.js deleted file mode 100644 index 3b94cab..0000000 --- a/build/web/Test Project.audio.worklet.js +++ /dev/null @@ -1,213 +0,0 @@ -/**************************************************************************/ -/* audio.worklet.js */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 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. */ -/**************************************************************************/ - -class RingBuffer { - constructor(p_buffer, p_state, p_threads) { - this.buffer = p_buffer; - this.avail = p_state; - this.threads = p_threads; - this.rpos = 0; - this.wpos = 0; - } - - data_left() { - return this.threads ? Atomics.load(this.avail, 0) : this.avail; - } - - space_left() { - return this.buffer.length - this.data_left(); - } - - read(output) { - const size = this.buffer.length; - let from = 0; - let to_write = output.length; - if (this.rpos + to_write > size) { - const high = size - this.rpos; - output.set(this.buffer.subarray(this.rpos, size)); - from = high; - to_write -= high; - this.rpos = 0; - } - if (to_write) { - output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); - } - this.rpos += to_write; - if (this.threads) { - Atomics.add(this.avail, 0, -output.length); - Atomics.notify(this.avail, 0); - } else { - this.avail -= output.length; - } - } - - write(p_buffer) { - const to_write = p_buffer.length; - const mw = this.buffer.length - this.wpos; - if (mw >= to_write) { - this.buffer.set(p_buffer, this.wpos); - this.wpos += to_write; - if (mw === to_write) { - this.wpos = 0; - } - } else { - const high = p_buffer.subarray(0, mw); - const low = p_buffer.subarray(mw); - this.buffer.set(high, this.wpos); - this.buffer.set(low); - this.wpos = low.length; - } - if (this.threads) { - Atomics.add(this.avail, 0, to_write); - Atomics.notify(this.avail, 0); - } else { - this.avail += to_write; - } - } -} - -class GodotProcessor extends AudioWorkletProcessor { - constructor() { - super(); - this.threads = false; - this.running = true; - this.lock = null; - this.notifier = null; - this.output = null; - this.output_buffer = new Float32Array(); - this.input = null; - this.input_buffer = new Float32Array(); - this.port.onmessage = (event) => { - const cmd = event.data['cmd']; - const data = event.data['data']; - this.parse_message(cmd, data); - }; - } - - process_notify() { - if (this.notifier) { - Atomics.add(this.notifier, 0, 1); - Atomics.notify(this.notifier, 0); - } - } - - parse_message(p_cmd, p_data) { - if (p_cmd === 'start' && p_data) { - const state = p_data[0]; - let idx = 0; - this.threads = true; - this.lock = state.subarray(idx, ++idx); - this.notifier = state.subarray(idx, ++idx); - const avail_in = state.subarray(idx, ++idx); - const avail_out = state.subarray(idx, ++idx); - this.input = new RingBuffer(p_data[1], avail_in, true); - this.output = new RingBuffer(p_data[2], avail_out, true); - } else if (p_cmd === 'stop') { - this.running = false; - this.output = null; - this.input = null; - this.lock = null; - this.notifier = null; - } else if (p_cmd === 'start_nothreads') { - this.output = new RingBuffer(p_data[0], p_data[0].length, false); - } else if (p_cmd === 'chunk') { - this.output.write(p_data); - } - } - - static array_has_data(arr) { - return arr.length && arr[0].length && arr[0][0].length; - } - - process(inputs, outputs, parameters) { - if (!this.running) { - return false; // Stop processing. - } - if (this.output === null) { - return true; // Not ready yet, keep processing. - } - const process_input = GodotProcessor.array_has_data(inputs); - if (process_input) { - const input = inputs[0]; - const chunk = input[0].length * input.length; - if (this.input_buffer.length !== chunk) { - this.input_buffer = new Float32Array(chunk); - } - if (!this.threads) { - GodotProcessor.write_input(this.input_buffer, input); - this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer }); - } else if (this.input.space_left() >= chunk) { - GodotProcessor.write_input(this.input_buffer, input); - this.input.write(this.input_buffer); - } else { - // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer. - } - } - const process_output = GodotProcessor.array_has_data(outputs); - if (process_output) { - const output = outputs[0]; - const chunk = output[0].length * output.length; - if (this.output_buffer.length !== chunk) { - this.output_buffer = new Float32Array(chunk); - } - if (this.output.data_left() >= chunk) { - this.output.read(this.output_buffer); - GodotProcessor.write_output(output, this.output_buffer); - if (!this.threads) { - this.port.postMessage({ 'cmd': 'read', 'data': chunk }); - } - } else { - // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer. - } - } - this.process_notify(); - return true; - } - - static write_output(dest, source) { - const channels = dest.length; - for (let ch = 0; ch < channels; ch++) { - for (let sample = 0; sample < dest[ch].length; sample++) { - dest[ch][sample] = source[sample * channels + ch]; - } - } - } - - static write_input(dest, source) { - const channels = source.length; - for (let ch = 0; ch < channels; ch++) { - for (let sample = 0; sample < source[ch].length; sample++) { - dest[sample * channels + ch] = source[ch][sample]; - } - } - } -} - -registerProcessor('godot-processor', GodotProcessor); diff --git a/build/web/Test Project.html b/build/web/Test Project.html deleted file mode 100644 index aba8be3..0000000 --- a/build/web/Test Project.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - Test Project - - - - - - - - Your browser does not support the canvas tag. - - - - -
- - -
-
- - - - - - diff --git a/build/web/Test Project.icon.png b/build/web/Test Project.icon.png deleted file mode 100644 index e8ed395..0000000 Binary files a/build/web/Test Project.icon.png and /dev/null differ diff --git a/build/web/Test Project.js b/build/web/Test Project.js deleted file mode 100644 index dd9977c..0000000 --- a/build/web/Test Project.js +++ /dev/null @@ -1,910 +0,0 @@ - -var Godot = (() => { - var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; - - return ( -function(Godot) { - Godot = Godot || {}; - -var Module=typeof Godot!="undefined"?Godot:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||false;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||33554432;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){___funcs_on_exit();callRuntimeCallbacks(__ATEXIT__);FS.quit();TTY.shutdown();IDBFS.quit();runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="godot.web.template_debug.wasm32.nothreads.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["rf"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["tf"];addOnInit(Module["asm"]["sf"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort("invalid type for getValue: "+type)}return null}function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort("invalid type for setValue: "+type)}}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function ___call_sighandler(fp,sig){getWasmTableEntry(fp)(sig)}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else return()=>abort("randomDevice")}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function zeroMemory(address,size){HEAPU8.fill(0,address,address+size);return address}function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var IDBFS={dbs:{},indexedDB:()=>{if(typeof indexedDB!="undefined")return indexedDB;var ret=null;if(typeof window=="object")ret=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;assert(ret,"IDBFS used, but indexedDB not supported");return ret},DB_VERSION:21,DB_STORE_NAME:"FILE_DATA",mount:function(mount){return MEMFS.mount.apply(null,arguments)},syncfs:(mount,populate,callback)=>{IDBFS.getLocalSet(mount,(err,local)=>{if(err)return callback(err);IDBFS.getRemoteSet(mount,(err,remote)=>{if(err)return callback(err);var src=populate?remote:local;var dst=populate?local:remote;IDBFS.reconcile(src,dst,callback)})})},quit:()=>{Object.values(IDBFS.dbs).forEach(value=>value.close());IDBFS.dbs={}},getDB:(name,callback)=>{var db=IDBFS.dbs[name];if(db){return callback(null,db)}var req;try{req=IDBFS.indexedDB().open(name,IDBFS.DB_VERSION)}catch(e){return callback(e)}if(!req){return callback("Unable to connect to IndexedDB")}req.onupgradeneeded=e=>{var db=e.target.result;var transaction=e.target.transaction;var fileStore;if(db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)){fileStore=transaction.objectStore(IDBFS.DB_STORE_NAME)}else{fileStore=db.createObjectStore(IDBFS.DB_STORE_NAME)}if(!fileStore.indexNames.contains("timestamp")){fileStore.createIndex("timestamp","timestamp",{unique:false})}};req.onsuccess=()=>{db=req.result;IDBFS.dbs[name]=db;callback(null,db)};req.onerror=e=>{callback(this.error);e.preventDefault()}},getLocalSet:(mount,callback)=>{var entries={};function isRealDir(p){return p!=="."&&p!==".."}function toAbsolute(root){return p=>{return PATH.join2(root,p)}}var check=FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint));while(check.length){var path=check.pop();var stat;try{stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){check.push.apply(check,FS.readdir(path).filter(isRealDir).map(toAbsolute(path)))}entries[path]={"timestamp":stat.mtime}}return callback(null,{type:"local",entries:entries})},getRemoteSet:(mount,callback)=>{var entries={};IDBFS.getDB(mount.mountpoint,(err,db)=>{if(err)return callback(err);try{var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readonly");transaction.onerror=e=>{callback(this.error);e.preventDefault()};var store=transaction.objectStore(IDBFS.DB_STORE_NAME);var index=store.index("timestamp");index.openKeyCursor().onsuccess=event=>{var cursor=event.target.result;if(!cursor){return callback(null,{type:"remote",db:db,entries:entries})}entries[cursor.primaryKey]={"timestamp":cursor.key};cursor.continue()}}catch(e){return callback(e)}})},loadLocalEntry:(path,callback)=>{var stat,node;try{var lookup=FS.lookupPath(path);node=lookup.node;stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){return callback(null,{"timestamp":stat.mtime,"mode":stat.mode})}else if(FS.isFile(stat.mode)){node.contents=MEMFS.getFileDataAsTypedArray(node);return callback(null,{"timestamp":stat.mtime,"mode":stat.mode,"contents":node.contents})}else{return callback(new Error("node type not supported"))}},storeLocalEntry:(path,entry,callback)=>{try{if(FS.isDir(entry["mode"])){FS.mkdirTree(path,entry["mode"])}else if(FS.isFile(entry["mode"])){FS.writeFile(path,entry["contents"],{canOwn:true})}else{return callback(new Error("node type not supported"))}FS.chmod(path,entry["mode"]);FS.utime(path,entry["timestamp"],entry["timestamp"])}catch(e){return callback(e)}callback(null)},removeLocalEntry:(path,callback)=>{try{var stat=FS.stat(path);if(FS.isDir(stat.mode)){FS.rmdir(path)}else if(FS.isFile(stat.mode)){FS.unlink(path)}}catch(e){return callback(e)}callback(null)},loadRemoteEntry:(store,path,callback)=>{var req=store.get(path);req.onsuccess=event=>{callback(null,event.target.result)};req.onerror=e=>{callback(this.error);e.preventDefault()}},storeRemoteEntry:(store,path,entry,callback)=>{try{var req=store.put(entry,path)}catch(e){callback(e);return}req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},removeRemoteEntry:(store,path,callback)=>{var req=store.delete(path);req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},reconcile:(src,dst,callback)=>{var total=0;var create=[];Object.keys(src.entries).forEach(function(key){var e=src.entries[key];var e2=dst.entries[key];if(!e2||e["timestamp"].getTime()!=e2["timestamp"].getTime()){create.push(key);total++}});var remove=[];Object.keys(dst.entries).forEach(function(key){if(!src.entries[key]){remove.push(key);total++}});if(!total){return callback(null)}var errored=false;var db=src.type==="remote"?src.db:dst.db;var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readwrite");var store=transaction.objectStore(IDBFS.DB_STORE_NAME);function done(err){if(err&&!errored){errored=true;return callback(err)}}transaction.onerror=e=>{done(this.error);e.preventDefault()};transaction.oncomplete=e=>{if(!errored){callback(null)}};create.sort().forEach(path=>{if(dst.type==="local"){IDBFS.loadRemoteEntry(store,path,(err,entry)=>{if(err)return done(err);IDBFS.storeLocalEntry(path,entry,done)})}else{IDBFS.loadLocalEntry(path,(err,entry)=>{if(err)return done(err);IDBFS.storeRemoteEntry(store,path,entry,done)})}});remove.sort().reverse().forEach(path=>{if(dst.type==="local"){IDBFS.removeLocalEntry(path,done)}else{IDBFS.removeRemoteEntry(store,path,done)}})}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"IDBFS":IDBFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;_fflush(0);for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;tempI64=[Math.floor(stat.atime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.atime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=0;tempI64=[Math.floor(stat.mtime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.mtime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=0;tempI64=[Math.floor(stat.ctime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.ctime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}function ___syscall_bind(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_chdir(path){try{path=SYSCALLS.getStr(path);FS.chdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_chmod(path,mode){try{path=SYSCALLS.getStr(path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fchmod(fd,mode){try{FS.fchmod(fd,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function ___syscall_ftruncate64(fd,length_low,length_high){try{var length=convertI32PairToI53Checked(length_low,length_high);if(isNaN(length))return-61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>>0,(tempDouble=id,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos>>2]=tempI64[0],HEAP32[dirp+pos+4>>2]=tempI64[1];tempI64=[(idx+1)*struct_size>>>0,(tempDouble=(idx+1)*struct_size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos+8>>2]=tempI64[0],HEAP32[dirp+pos+12>>2]=tempI64[1];HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen){try{err("__syscall_getsockname "+fd);var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_mknodat(dirfd,path,mode,dev){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~4352;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_readlinkat(dirfd,path,buf,bufsize){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_statfs64(path,size,buf){try{path=SYSCALLS.getStr(path);HEAP32[buf+4>>2]=4096;HEAP32[buf+40>>2]=4096;HEAP32[buf+8>>2]=1e6;HEAP32[buf+12>>2]=5e5;HEAP32[buf+16>>2]=5e5;HEAP32[buf+20>>2]=FS.nextInode;HEAP32[buf+24>>2]=1e6;HEAP32[buf+28>>2]=42;HEAP32[buf+44>>2]=2;HEAP32[buf+36>>2]=255;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_symlink(target,linkpath){try{target=SYSCALLS.getStr(target);linkpath=SYSCALLS.getStr(linkpath);FS.symlink(target,linkpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function __dlinit(main_dso_handle){}var dlopenMissingError="To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking";function __dlopen_js(filename,flag){abort(dlopenMissingError)}function __dlsym_js(handle,symbol){abort(dlopenMissingError)}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var __MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var __MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function __yday_from_date(date){var isLeapYear=__isLeapYear(date.getFullYear());var monthDaysCumulative=isLeapYear?__MONTH_DAYS_LEAP_CUMULATIVE:__MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}function runtimeKeepalivePush(){runtimeKeepaliveCounter+=1}function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(!Browser.mainLoop.running){runtimeKeepalivePush();Browser.mainLoop.running=true}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){var timeUntilNextTick=Math.max(0,Browser.mainLoop.tickStartTime+value-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,timeUntilNextTick)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(typeof setImmediate=="undefined"){var setImmediates=[];var emscriptenMainLoopMessageId="setimmediate";var Browser_setImmediate_messageHandler=event=>{if(event.data===emscriptenMainLoopMessageId||event.data.target===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}};addEventListener("message",Browser_setImmediate_messageHandler,true);setImmediate=function Browser_emulated_setImmediate(func){setImmediates.push(func);if(ENVIRONMENT_IS_WORKER){if(Module["setImmediates"]===undefined)Module["setImmediates"]=[];Module["setImmediates"].push(func);postMessage({target:emscriptenMainLoopMessageId})}else postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){setImmediate(Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function _emscripten_webgl_do_commit_frame(){if(!GL.currentContext||!GL.currentContext.GLctx){return-3}if(GL.currentContext.defaultFbo){GL.blitOffscreenFramebuffer(GL.currentContext);return 0}if(!GL.currentContext.attributes.explicitSwapControl){return-3}return 0}var _emscripten_webgl_commit_frame=_emscripten_webgl_do_commit_frame;function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;if(!keepRuntimeAlive()){exitRuntime()}_proc_exit(status)}var _exit=exitJS;function handleException(e){if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)}function maybeExit(){if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}}function runtimeKeepalivePop(){runtimeKeepaliveCounter-=1}function setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop,arg,noSetTiming){assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=browserIterationFunc;Browser.mainLoop.arg=arg;var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;function checkIsRunning(){if(thisMainLoopId0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}out('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();if(!checkIsRunning())return;setTimeout(Browser.mainLoop.runner,0);return}if(!checkIsRunning())return;Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0;if(Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else if(Browser.mainLoop.timingMode==0){Browser.mainLoop.tickStartTime=_emscripten_get_now()}Browser.mainLoop.runIter(browserIterationFunc);if(!checkIsRunning())return;if(typeof SDL=="object"&&SDL.audio&&SDL.audio.queueNewAudioData)SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler()};if(!noSetTiming){if(fps&&fps>0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"unwind"}}function callUserCallback(func){if(runtimeExited||ABORT){return}try{func();maybeExit()}catch(e){handleException(e)}}function safeSetTimeout(func,timeout){runtimeKeepalivePush();return setTimeout(function(){runtimeKeepalivePop();callUserCallback(func)},timeout)}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}var Browser={mainLoop:{running:false,scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;setMainLoop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()},updateStatus:function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining{assert(img.complete,"Image "+name+" could not be decoded");var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);preloadedImages[name]=canvas;Browser.URLObject.revokeObjectURL(url);if(onload)onload(byteArray)};img.onerror=event=>{out("Image "+url+" could not be decoded");if(onerror)onerror()};img.src=url};Module["preloadPlugins"].push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=function audioPlugin_canHandle(name){return!Module.noAudioDecoding&&name.substr(-4)in{".ogg":1,".wav":1,".mp3":1}};audioPlugin["handle"]=function audioPlugin_handle(byteArray,name,onload,onerror){var done=false;function finish(audio){if(done)return;done=true;preloadedAudios[name]=audio;if(onload)onload(byteArray)}function fail(){if(done)return;done=true;preloadedAudios[name]=new Audio;if(onerror)onerror()}if(Browser.hasBlobConstructor){try{var b=new Blob([byteArray],{type:Browser.getMimetype(name)})}catch(e){return fail()}var url=Browser.URLObject.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",()=>finish(audio),false);audio.onerror=function audio_onerror(event){if(done)return;err("warning: browser could not fully decode audio "+name+", trying slower base64 approach");function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;safeSetTimeout(function(){finish(audio)},1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===Module["canvas"]||document["mozPointerLockElement"]===Module["canvas"]||document["webkitPointerLockElement"]===Module["canvas"]||document["msPointerLockElement"]===Module["canvas"]}var canvas=Module["canvas"];if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||(()=>{});canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||(()=>{});canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",ev=>{if(!Browser.pointerLock&&Module["canvas"].requestPointerLock){Module["canvas"].requestPointerLock();ev.preventDefault()}},false)}}},handledByPreloadPlugin:function(byteArray,fullname,finish,onerror){Browser.init();var handled=false;Module["preloadPlugins"].forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled},createContext:function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false,majorVersion:typeof WebGL2RenderingContext!="undefined"?2:1};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}if(typeof GL!="undefined"){contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}}}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx=="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach(function(callback){callback()});Browser.init()}return ctx},destroyContext:function(canvas,useWebGL,setInModule){},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen:function(lockPointer,resizeCanvas){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;if(typeof Browser.lockPointer=="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas=="undefined")Browser.resizeCanvas=false;var canvas=Module["canvas"];function fullscreenChange(){Browser.isFullscreen=false;var canvasContainer=canvas.parentNode;if((document["fullscreenElement"]||document["mozFullScreenElement"]||document["msFullscreenElement"]||document["webkitFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.exitFullscreen=Browser.exitFullscreen;if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullscreen=true;if(Browser.resizeCanvas){Browser.setFullscreenCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas){Browser.setWindowedCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullscreen);if(Module["onFullscreen"])Module["onFullscreen"](Browser.isFullscreen)}if(!Browser.fullscreenHandlersInstalled){Browser.fullscreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullscreenChange,false);document.addEventListener("mozfullscreenchange",fullscreenChange,false);document.addEventListener("webkitfullscreenchange",fullscreenChange,false);document.addEventListener("MSFullscreenChange",fullscreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen=canvasContainer["requestFullscreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullscreen"]?()=>canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"]):null)||(canvasContainer["webkitRequestFullScreen"]?()=>canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"]):null);canvasContainer.requestFullscreen()},exitFullscreen:function(){if(!Browser.isFullscreen){return false}var CFS=document["exitFullscreen"]||document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["msExitFullscreen"]||document["webkitCancelFullScreen"]||function(){};CFS.apply(document,[]);return true},nextRAF:0,fakeRequestAnimationFrame:function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)},requestAnimationFrame:function(func){if(typeof requestAnimationFrame=="function"){requestAnimationFrame(func);return}var RAF=Browser.fakeRequestAnimationFrame;RAF(func)},safeSetTimeout:function(func,timeout){return safeSetTimeout(func,timeout)},safeRequestAnimationFrame:function(func){runtimeKeepalivePush();return Browser.requestAnimationFrame(function(){runtimeKeepalivePop();callUserCallback(func)})},getMimetype:function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]},getUserMedia:function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)},getMovementX:function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0},getMovementY:function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0},getMouseWheelDelta:function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail/3;break;case"mousewheel":delta=event.wheelDelta/120;break;case"wheel":delta=event.deltaY;switch(event.deltaMode){case 0:delta/=100;break;case 1:delta/=3;break;case 2:delta*=80;break;default:throw"unrecognized mouse wheel delta mode: "+event.deltaMode}break;default:throw"unrecognized mouse wheel event: "+event.type}return delta},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}},resizeListeners:[],updateResizeListeners:function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach(function(listener){listener(canvas.width,canvas.height)})},setCanvasSize:function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags|8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags&~8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},updateCanvasDimensions:function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var JSEvents={inEventHandler:0,removeAllEventListeners:function(){for(var i=JSEvents.eventHandlers.length-1;i>=0;--i){JSEvents._removeHandler(i)}JSEvents.eventHandlers=[];JSEvents.deferredCalls=[]},registerRemoveEventListeners:function(){if(!JSEvents.removeEventListenersRegistered){__ATEXIT__.push(JSEvents.removeAllEventListeners);JSEvents.removeEventListenersRegistered=true}},deferredCalls:[],deferCall:function(targetFunction,precedence,argsList){function arraysHaveEqualContent(arrA,arrB){if(arrA.length!=arrB.length)return false;for(var i in arrA){if(arrA[i]!=arrB[i])return false}return true}for(var i in JSEvents.deferredCalls){var call=JSEvents.deferredCalls[i];if(call.targetFunction==targetFunction&&arraysHaveEqualContent(call.argsList,argsList)){return}}JSEvents.deferredCalls.push({targetFunction:targetFunction,precedence:precedence,argsList:argsList});JSEvents.deferredCalls.sort(function(x,y){return x.precedence2?UTF8ToString(cString):cString}var specialHTMLTargets=[0,typeof document!="undefined"?document:0,typeof window!="undefined"?window:0];function findEventTarget(target){target=maybeCStringToJsString(target);var domElement=specialHTMLTargets[target]||(typeof document!="undefined"?document.querySelector(target):undefined);return domElement}function findCanvasEventTarget(target){return findEventTarget(target)}function _emscripten_set_canvas_element_size(target,width,height){var canvas=findCanvasEventTarget(target);if(!canvas)return-4;canvas.width=width;canvas.height=height;if(canvas.GLctxObject)GL.resizeOffscreenFramebuffer(canvas.GLctxObject);return 0}function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop){var browserIterationFunc=getWasmTableEntry(func);setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop)}function __webgl_enable_ANGLE_instanced_arrays(ctx){var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=function(index,divisor){ext["vertexAttribDivisorANGLE"](index,divisor)};ctx["drawArraysInstanced"]=function(mode,first,count,primcount){ext["drawArraysInstancedANGLE"](mode,first,count,primcount)};ctx["drawElementsInstanced"]=function(mode,count,type,indices,primcount){ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount)};return 1}}function __webgl_enable_OES_vertex_array_object(ctx){var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=function(){return ext["createVertexArrayOES"]()};ctx["deleteVertexArray"]=function(vao){ext["deleteVertexArrayOES"](vao)};ctx["bindVertexArray"]=function(vao){ext["bindVertexArrayOES"](vao)};ctx["isVertexArray"]=function(vao){return ext["isVertexArrayOES"](vao)};return 1}}function __webgl_enable_WEBGL_draw_buffers(ctx){var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=function(n,bufs){ext["drawBuffersWEBGL"](n,bufs)};return 1}}function __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},createContext:function(canvas,webGLContextAttributes){if(webGLContextAttributes.renderViaOffscreenBackBuffer)webGLContextAttributes["preserveDrawingBuffer"]=true;var ctx=webGLContextAttributes.majorVersion>1?canvas.getContext("webgl2",webGLContextAttributes):canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},enableOffscreenFramebufferAttributes:function(webGLContextAttributes){webGLContextAttributes.renderViaOffscreenBackBuffer=true;webGLContextAttributes.preserveDrawingBuffer=true},createOffscreenFramebuffer:function(context){var gl=context.GLctx;var fbo=gl.createFramebuffer();gl.bindFramebuffer(36160,fbo);context.defaultFbo=fbo;context.defaultFboForbidBlitFramebuffer=false;if(gl.getContextAttributes().antialias){context.defaultFboForbidBlitFramebuffer=true}else{var firefoxMatch=navigator.userAgent.toLowerCase().match(/firefox\/(\d\d)/);if(firefoxMatch!=null){var firefoxVersion=firefoxMatch[1];context.defaultFboForbidBlitFramebuffer=firefoxVersion<67}}context.defaultColorTarget=gl.createTexture();context.defaultDepthTarget=gl.createRenderbuffer();GL.resizeOffscreenFramebuffer(context);gl.bindTexture(3553,context.defaultColorTarget);gl.texParameteri(3553,10241,9728);gl.texParameteri(3553,10240,9728);gl.texParameteri(3553,10242,33071);gl.texParameteri(3553,10243,33071);gl.texImage2D(3553,0,6408,gl.canvas.width,gl.canvas.height,0,6408,5121,null);gl.framebufferTexture2D(36160,36064,3553,context.defaultColorTarget,0);gl.bindTexture(3553,null);var depthTarget=gl.createRenderbuffer();gl.bindRenderbuffer(36161,context.defaultDepthTarget);gl.renderbufferStorage(36161,33189,gl.canvas.width,gl.canvas.height);gl.framebufferRenderbuffer(36160,36096,36161,context.defaultDepthTarget);gl.bindRenderbuffer(36161,null);var vertices=[-1,-1,-1,1,1,-1,1,1];var vb=gl.createBuffer();gl.bindBuffer(34962,vb);gl.bufferData(34962,new Float32Array(vertices),35044);gl.bindBuffer(34962,null);context.blitVB=vb;var vsCode="attribute vec2 pos;"+"varying lowp vec2 tex;"+"void main() { tex = pos * 0.5 + vec2(0.5,0.5); gl_Position = vec4(pos, 0.0, 1.0); }";var vs=gl.createShader(35633);gl.shaderSource(vs,vsCode);gl.compileShader(vs);var fsCode="varying lowp vec2 tex;"+"uniform sampler2D sampler;"+"void main() { gl_FragColor = texture2D(sampler, tex); }";var fs=gl.createShader(35632);gl.shaderSource(fs,fsCode);gl.compileShader(fs);var blitProgram=gl.createProgram();gl.attachShader(blitProgram,vs);gl.attachShader(blitProgram,fs);gl.linkProgram(blitProgram);context.blitProgram=blitProgram;context.blitPosLoc=gl.getAttribLocation(blitProgram,"pos");gl.useProgram(blitProgram);gl.uniform1i(gl.getUniformLocation(blitProgram,"sampler"),0);gl.useProgram(null);context.defaultVao=undefined;if(gl.createVertexArray){context.defaultVao=gl.createVertexArray();gl.bindVertexArray(context.defaultVao);gl.enableVertexAttribArray(context.blitPosLoc);gl.bindVertexArray(null)}},resizeOffscreenFramebuffer:function(context){var gl=context.GLctx;if(context.defaultColorTarget){var prevTextureBinding=gl.getParameter(32873);gl.bindTexture(3553,context.defaultColorTarget);gl.texImage2D(3553,0,6408,gl.drawingBufferWidth,gl.drawingBufferHeight,0,6408,5121,null);gl.bindTexture(3553,prevTextureBinding)}if(context.defaultDepthTarget){var prevRenderBufferBinding=gl.getParameter(36007);gl.bindRenderbuffer(36161,context.defaultDepthTarget);gl.renderbufferStorage(36161,33189,gl.drawingBufferWidth,gl.drawingBufferHeight);gl.bindRenderbuffer(36161,prevRenderBufferBinding)}},blitOffscreenFramebuffer:function(context){var gl=context.GLctx;var prevScissorTest=gl.getParameter(3089);if(prevScissorTest)gl.disable(3089);var prevFbo=gl.getParameter(36006);if(gl.blitFramebuffer&&!context.defaultFboForbidBlitFramebuffer){gl.bindFramebuffer(36008,context.defaultFbo);gl.bindFramebuffer(36009,null);gl.blitFramebuffer(0,0,gl.canvas.width,gl.canvas.height,0,0,gl.canvas.width,gl.canvas.height,16384,9728)}else{gl.bindFramebuffer(36160,null);var prevProgram=gl.getParameter(35725);gl.useProgram(context.blitProgram);var prevVB=gl.getParameter(34964);gl.bindBuffer(34962,context.blitVB);var prevActiveTexture=gl.getParameter(34016);gl.activeTexture(33984);var prevTextureBinding=gl.getParameter(32873);gl.bindTexture(3553,context.defaultColorTarget);var prevBlend=gl.getParameter(3042);if(prevBlend)gl.disable(3042);var prevCullFace=gl.getParameter(2884);if(prevCullFace)gl.disable(2884);var prevDepthTest=gl.getParameter(2929);if(prevDepthTest)gl.disable(2929);var prevStencilTest=gl.getParameter(2960);if(prevStencilTest)gl.disable(2960);function draw(){gl.vertexAttribPointer(context.blitPosLoc,2,5126,false,0,0);gl.drawArrays(5,0,4)}if(context.defaultVao){var prevVAO=gl.getParameter(34229);gl.bindVertexArray(context.defaultVao);draw();gl.bindVertexArray(prevVAO)}else{var prevVertexAttribPointer={buffer:gl.getVertexAttrib(context.blitPosLoc,34975),size:gl.getVertexAttrib(context.blitPosLoc,34339),stride:gl.getVertexAttrib(context.blitPosLoc,34340),type:gl.getVertexAttrib(context.blitPosLoc,34341),normalized:gl.getVertexAttrib(context.blitPosLoc,34922),pointer:gl.getVertexAttribOffset(context.blitPosLoc,34373)};var maxVertexAttribs=gl.getParameter(34921);var prevVertexAttribEnables=[];for(var i=0;i=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};var __emscripten_webgl_power_preferences=["default","low-power","high-performance"];function _emscripten_webgl_do_create_context(target,attributes){var a=attributes>>2;var powerPreference=HEAP32[a+(24>>2)];var contextAttributes={"alpha":!!HEAP32[a+(0>>2)],"depth":!!HEAP32[a+(4>>2)],"stencil":!!HEAP32[a+(8>>2)],"antialias":!!HEAP32[a+(12>>2)],"premultipliedAlpha":!!HEAP32[a+(16>>2)],"preserveDrawingBuffer":!!HEAP32[a+(20>>2)],"powerPreference":__emscripten_webgl_power_preferences[powerPreference],"failIfMajorPerformanceCaveat":!!HEAP32[a+(28>>2)],majorVersion:HEAP32[a+(32>>2)],minorVersion:HEAP32[a+(36>>2)],enableExtensionsByDefault:HEAP32[a+(40>>2)],explicitSwapControl:HEAP32[a+(44>>2)],proxyContextToMainThread:HEAP32[a+(48>>2)],renderViaOffscreenBackBuffer:HEAP32[a+(52>>2)]};var canvas=findCanvasEventTarget(target);if(!canvas){return 0}if(contextAttributes.explicitSwapControl&&!contextAttributes.renderViaOffscreenBackBuffer){contextAttributes.renderViaOffscreenBackBuffer=true}var contextHandle=GL.createContext(canvas,contextAttributes);return contextHandle}var _emscripten_webgl_create_context=_emscripten_webgl_do_create_context;function _emscripten_webgl_destroy_context(contextHandle){if(GL.currentContext==contextHandle)GL.currentContext=0;GL.deleteContext(contextHandle)}function _emscripten_webgl_enable_extension(contextHandle,extension){var context=GL.getContext(contextHandle);var extString=UTF8ToString(extension);if(extString.startsWith("GL_"))extString=extString.substr(3);if(extString=="ANGLE_instanced_arrays")__webgl_enable_ANGLE_instanced_arrays(GLctx);if(extString=="OES_vertex_array_object")__webgl_enable_OES_vertex_array_object(GLctx);if(extString=="WEBGL_draw_buffers")__webgl_enable_WEBGL_draw_buffers(GLctx);if(extString=="WEBGL_draw_instanced_base_vertex_base_instance")__webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);if(extString=="WEBGL_multi_draw_instanced_base_vertex_base_instance")__webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(extString=="WEBGL_multi_draw")__webgl_enable_WEBGL_multi_draw(GLctx);var ext=context.GLctx.getExtension(extString);return!!ext}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _emscripten_webgl_get_supported_extensions(){return stringToNewUTF8(GLctx.getSupportedExtensions().join(" "))}function _emscripten_webgl_init_context_attributes(attributes){var a=attributes>>2;for(var i=0;i<56>>2;++i){HEAP32[a+i]=0}HEAP32[a+(0>>2)]=HEAP32[a+(4>>2)]=HEAP32[a+(12>>2)]=HEAP32[a+(16>>2)]=HEAP32[a+(32>>2)]=HEAP32[a+(40>>2)]=1}function _emscripten_webgl_make_context_current(contextHandle){var success=GL.makeContextCurrent(contextHandle);return success?0:-5}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAP32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBeginTransformFeedback(x0){GLctx["beginTransformFeedback"](x0)}function _glBindBuffer(target,buffer){if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindBufferBase(target,index,buffer){GLctx["bindBufferBase"](target,index,GL.buffers[buffer])}function _glBindBufferRange(target,index,buffer,offset,ptrsize){GLctx["bindBufferRange"](target,index,GL.buffers[buffer],offset,ptrsize)}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,framebuffer?GL.framebuffers[framebuffer]:GL.currentContext.defaultFbo)}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao])}function _glBlendColor(x0,x1,x2,x3){GLctx["blendColor"](x0,x1,x2,x3)}function _glBlendEquation(x0){GLctx["blendEquation"](x0)}function _glBlendFunc(x0,x1){GLctx["blendFunc"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx["blitFramebuffer"](x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}function _glBufferData(target,size,data,usage){if(GL.currentContext.version>=2){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}}function _glBufferSubData(target,offset,size,data){if(GL.currentContext.version>=2){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glCheckFramebufferStatus(x0){return GLctx["checkFramebufferStatus"](x0)}function _glClear(x0){GLctx["clear"](x0)}function _glClearBufferfv(buffer,drawbuffer,value){GLctx["clearBufferfv"](buffer,drawbuffer,HEAPF32,value>>2)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx["clearDepth"](x0)}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCompressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data){if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,imageSize,data)}else{GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,HEAPU8,data,imageSize)}return}GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,data?HEAPU8.subarray(data,data+imageSize):null)}function _glCompressedTexImage3D(target,level,internalFormat,width,height,depth,border,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexImage3D"](target,level,internalFormat,width,height,depth,border,imageSize,data)}else{GLctx["compressedTexImage3D"](target,level,internalFormat,width,height,depth,border,HEAPU8,data,imageSize)}}function _glCompressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}}function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx["copyBufferSubData"](x0,x1,x2,x3,x4)}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}function _glDeleteQueries(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx["deleteQuery"](query);GL.queries[id]=null}}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteSync(id){if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}function _glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}function _glDrawArraysInstanced(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}var tempFixedLengthArray=[];function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}function _glEndTransformFeedback(){GLctx["endTransformFeedback"]()}function _glFenceSync(condition,flags){var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0}function _glFinish(){GLctx["finish"]()}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFramebufferTextureLayer(target,attachment,texture,level,layer){GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenQueries(n,ids){__glGenObject(n,ids,"createQuery",GL.queries)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _glGetInteger64v(name_,p){emscriptenWebGLGet(name_,p,1)}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(GL.currentContext.version>=2)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}function _glGetSynciv(sync,pname,bufSize,length,values){if(bufSize<0){GL.recordError(1281);return}if(!values){GL.recordError(1281);return}var ret=GLctx.getSyncParameter(GL.syncs[sync],pname);if(ret!==null){HEAP32[values>>2]=ret;if(length)HEAP32[length>>2]=1}}function _glGetUniformBlockIndex(program,uniformBlockName){return GLctx["getUniformBlockIndex"](GL.programs[program],UTF8ToString(uniformBlockName))}function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>shift,pixels+bytes>>shift)}function _glReadPixels(x,y,width,height,format,type,pixels){if(GL.currentContext.version>=2){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx["renderbufferStorageMultisample"](x0,x1,x2,x3,x4)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexImage3D(target,level,internalFormat,width,height,depth,border,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,null)}}function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx["texStorage2D"](x0,x1,x2,x3,x4)}function _glTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}}function _glTransformFeedbackVaryings(program,count,varyings,bufferMode){program=GL.programs[program];var vars=[];for(var i=0;i>2]));GLctx["transformFeedbackVaryings"](program,vars,bufferMode)}function webglGetUniformLocation(location){var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}function _glUniform1f(location,v0){GLctx.uniform1f(webglGetUniformLocation(location),v0)}function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}var __miniTempWebGLIntBuffers=[];function _glUniform1iv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count);return}if(count<=288){var view=__miniTempWebGLIntBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)}function _glUniform1ui(location,v0){GLctx.uniform1ui(webglGetUniformLocation(location),v0)}function _glUniform1uiv(location,count,value){count&&GLctx.uniform1uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count)}function _glUniform2f(location,v0,v1){GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)}var miniTempWebGLFloatBuffers=[];function _glUniform2fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2);return}if(count<=144){var view=miniTempWebGLFloatBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)}function _glUniform2iv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2);return}if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)}function _glUniform3fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3);return}if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)}function _glUniform4f(location,v0,v1,v2,v3){GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)}function _glUniform4fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4);return}if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)}function _glUniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding){program=GL.programs[program];GLctx["uniformBlockBinding"](program,uniformBlockIndex,uniformBlockBinding)}function _glUniformMatrix3fv(location,count,transpose,value){if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9);return}if(count<=32){var view=miniTempWebGLFloatBuffers[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)}function _glUniformMatrix4fv(location,count,transpose,value){if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16);return}if(count<=18){var view=miniTempWebGLFloatBuffers[16*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<16*count;i+=16){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3];view[i+4]=heap[dst+4];view[i+5]=heap[dst+5];view[i+6]=heap[dst+6];view[i+7]=heap[dst+7];view[i+8]=heap[dst+8];view[i+9]=heap[dst+9];view[i+10]=heap[dst+10];view[i+11]=heap[dst+11];view[i+12]=heap[dst+12];view[i+13]=heap[dst+13];view[i+14]=heap[dst+14];view[i+15]=heap[dst+15]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)}function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _glVertexAttribDivisor(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx["vertexAttribI4ui"](x0,x1,x2,x3,x4)}function _glVertexAttribIPointer(index,size,type,stride,ptr){GLctx["vertexAttribIPointer"](index,size,type,stride,ptr)}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}var GodotRuntime={get_func:function(ptr){return wasmTable.get(ptr)},error:function(){err.apply(null,Array.from(arguments))},print:function(){out.apply(null,Array.from(arguments))},malloc:function(p_size){return _malloc(p_size)},free:function(p_ptr){_free(p_ptr)},getHeapValue:function(p_ptr,p_type){return getValue(p_ptr,p_type)},setHeapValue:function(p_ptr,p_value,p_type){setValue(p_ptr,p_value,p_type)},heapSub:function(p_heap,p_ptr,p_len){const bytes=p_heap.BYTES_PER_ELEMENT;return p_heap.subarray(p_ptr/bytes,p_ptr/bytes+p_len)},heapSlice:function(p_heap,p_ptr,p_len){const bytes=p_heap.BYTES_PER_ELEMENT;return p_heap.slice(p_ptr/bytes,p_ptr/bytes+p_len)},heapCopy:function(p_dst,p_src,p_ptr){const bytes=p_src.BYTES_PER_ELEMENT;return p_dst.set(p_src,p_ptr/bytes)},parseString:function(p_ptr){return UTF8ToString(p_ptr)},parseStringArray:function(p_ptr,p_size){const strings=[];const ptrs=GodotRuntime.heapSub(HEAP32,p_ptr,p_size);ptrs.forEach(function(ptr){strings.push(GodotRuntime.parseString(ptr))});return strings},strlen:function(p_str){return lengthBytesUTF8(p_str)},allocString:function(p_str){const length=GodotRuntime.strlen(p_str)+1;const c_str=GodotRuntime.malloc(length);stringToUTF8(p_str,c_str,length);return c_str},allocStringArray:function(p_strings){const size=p_strings.length;const c_ptr=GodotRuntime.malloc(size*4);for(let i=0;i>2)+i]=GodotRuntime.allocString(p_strings[i])}return c_ptr},freeStringArray:function(p_ptr,p_len){for(let i=0;i>2)+i])}GodotRuntime.free(p_ptr)},stringToHeap:function(p_str,p_ptr,p_len){return stringToUTF8Array(p_str,HEAP8,p_ptr,p_len)}};var GodotConfig={canvas:null,locale:"en",canvas_resize_policy:2,virtual_keyboard:false,persistent_drops:false,on_execute:null,on_exit:null,init_config:function(p_opts){GodotConfig.canvas_resize_policy=p_opts["canvasResizePolicy"];GodotConfig.canvas=p_opts["canvas"];GodotConfig.locale=p_opts["locale"]||GodotConfig.locale;GodotConfig.virtual_keyboard=p_opts["virtualKeyboard"];GodotConfig.persistent_drops=!!p_opts["persistentDrops"];GodotConfig.on_execute=p_opts["onExecute"];GodotConfig.on_exit=p_opts["onExit"];if(p_opts["focusCanvas"]){GodotConfig.canvas.focus()}},locate_file:function(file){return Module["locateFile"](file)},clear:function(){GodotConfig.canvas=null;GodotConfig.locale="en";GodotConfig.canvas_resize_policy=2;GodotConfig.virtual_keyboard=false;GodotConfig.persistent_drops=false;GodotConfig.on_execute=null;GodotConfig.on_exit=null}};var GodotFS={ENOENT:44,_idbfs:false,_syncing:false,_mount_points:[],is_persistent:function(){return GodotFS._idbfs?1:0},init:function(persistentPaths){GodotFS._idbfs=false;if(!Array.isArray(persistentPaths)){return Promise.reject(new Error("Persistent paths must be an array"))}if(!persistentPaths.length){return Promise.resolve()}GodotFS._mount_points=persistentPaths.slice();function createRecursive(dir){try{FS.stat(dir)}catch(e){if(e.errno!==GodotFS.ENOENT){GodotRuntime.error(e)}FS.mkdirTree(dir)}}GodotFS._mount_points.forEach(function(path){createRecursive(path);FS.mount(IDBFS,{},path)});return new Promise(function(resolve,reject){FS.syncfs(true,function(err){if(err){GodotFS._mount_points=[];GodotFS._idbfs=false;GodotRuntime.print(`IndexedDB not available: ${err.message}`)}else{GodotFS._idbfs=true}resolve(err)})})},deinit:function(){GodotFS._mount_points.forEach(function(path){try{FS.unmount(path)}catch(e){GodotRuntime.print("Already unmounted",e)}if(GodotFS._idbfs&&IDBFS.dbs[path]){IDBFS.dbs[path].close();delete IDBFS.dbs[path]}});GodotFS._mount_points=[];GodotFS._idbfs=false;GodotFS._syncing=false},sync:function(){if(GodotFS._syncing){GodotRuntime.error("Already syncing!");return Promise.resolve()}GodotFS._syncing=true;return new Promise(function(resolve,reject){FS.syncfs(false,function(error){if(error){GodotRuntime.error(`Failed to save IDB file system: ${error.message}`)}GodotFS._syncing=false;resolve(error)})})},copy_to_fs:function(path,buffer){const idx=path.lastIndexOf("/");let dir="/";if(idx>0){dir=path.slice(0,idx)}try{FS.stat(dir)}catch(e){if(e.errno!==GodotFS.ENOENT){GodotRuntime.error(e)}FS.mkdirTree(dir)}FS.writeFile(path,new Uint8Array(buffer))}};var GodotOS={request_quit:function(){},_async_cbs:[],_fs_sync_promise:null,atexit:function(p_promise_cb){GodotOS._async_cbs.push(p_promise_cb)},cleanup:function(exit_code){const cb=GodotConfig.on_exit;GodotFS.deinit();GodotConfig.clear();if(cb){cb(exit_code)}},finish_async:function(callback){GodotOS._fs_sync_promise.then(function(err){const promises=[];GodotOS._async_cbs.forEach(function(cb){promises.push(new Promise(cb))});return Promise.all(promises)}).then(function(){return GodotFS.sync()}).then(function(err){setTimeout(function(){callback()},0)})}};var GodotAudio={MAX_VOLUME_CHANNELS:8,GodotChannel:{CHANNEL_L:0,CHANNEL_R:1,CHANNEL_C:3,CHANNEL_LFE:4,CHANNEL_RL:5,CHANNEL_RR:6,CHANNEL_SL:7,CHANNEL_SR:8},WebChannel:{CHANNEL_L:0,CHANNEL_R:1,CHANNEL_SL:2,CHANNEL_SR:3,CHANNEL_C:4,CHANNEL_LFE:5},samples:null,Sample:class Sample{static getSample(id){if(!GodotAudio.samples.has(id)){throw new ReferenceError(`Could not find sample "${id}"`)}return GodotAudio.samples.get(id)}static getSampleOrNull(id){return GodotAudio.samples.get(id)??null}static create(params,options={}){const sample=new GodotAudio.Sample(params,options);GodotAudio.samples.set(params.id,sample);return sample}static delete(id){GodotAudio.samples.delete(id)}constructor(params,options={}){this.id=params.id;this._audioBuffer=null;this.numberOfChannels=(options.numberOfChannels??2);this.sampleRate=(options.sampleRate??44100);this.loopMode=(options.loopMode??"disabled");this.loopBegin=(options.loopBegin??0);this.loopEnd=(options.loopEnd??0);this.setAudioBuffer(params.audioBuffer)}getAudioBuffer(){return this._duplicateAudioBuffer()}setAudioBuffer(val){this._audioBuffer=val}clear(){this.setAudioBuffer(null);GodotAudio.Sample.delete(this.id)}_duplicateAudioBuffer(){if(this._audioBuffer==null){throw new Error("couldn't duplicate a null audioBuffer")}const channels=new Array(this._audioBuffer.numberOfChannels);for(let i=0;i{if(self.isPaused){return}switch(self.getSample().loopMode){case"disabled":{const id=this.id;self.stop();if(GodotAudio.sampleFinishedCallback!=null){const idCharPtr=GodotRuntime.allocString(id);GodotAudio.sampleFinishedCallback(idCharPtr);GodotRuntime.free(idCharPtr)}}break;case"forward":case"backward":self.restart();break;default:}};this._source.addEventListener("ended",this._onended)}},buses:null,busSolo:null,Bus:class Bus{static getCount(){return GodotAudio.buses.length}static setCount(val){const buses=GodotAudio.buses;if(val===buses.length){return}if(val=GodotAudio.buses.length){throw new ReferenceError(`invalid bus index "${index}"`)}return GodotAudio.buses[index]}static getBusOrNull(index){if(index<0||index>=GodotAudio.buses.length){return null}return GodotAudio.buses[index]}static move(fromIndex,toIndex){const movedBus=GodotAudio.Bus.getBus(fromIndex);const buses=GodotAudio.buses.filter((_,i)=>i!==fromIndex);buses.splice(toIndex-1,0,movedBus);GodotAudio.buses=buses}static addAt(index){const newBus=GodotAudio.Bus.create();if(index!==newBus.getId()){GodotAudio.Bus.move(newBus.getId(),index)}}static create(){const newBus=new GodotAudio.Bus;const isFirstBus=GodotAudio.buses.length===0;GodotAudio.buses.push(newBus);if(isFirstBus){newBus.setSend(null)}else{newBus.setSend(GodotAudio.Bus.getBus(0))}return newBus}constructor(){this._sampleNodes=new Set;this.isSolo=false;this._send=null;this._gainNode=GodotAudio.ctx.createGain();this._soloNode=GodotAudio.ctx.createGain();this._muteNode=GodotAudio.ctx.createGain();this._gainNode.connect(this._soloNode).connect(this._muteNode)}getId(){return GodotAudio.buses.indexOf(this)}getVolumeDb(){return GodotAudio.linear_to_db(this._gainNode.gain.value)}setVolumeDb(val){const linear=GodotAudio.db_to_linear(val);if(isFinite(linear)){this._gainNode.gain.value=linear}}getSend(){return this._send}setSend(val){this._send=val;if(val==null){if(this.getId()==0){this.getOutputNode().connect(GodotAudio.ctx.destination);return}throw new Error(`Cannot send to "${val}" without the bus being at index 0 (current index: ${this.getId()})`)}this.connect(val)}getInputNode(){return this._gainNode}getOutputNode(){return this._muteNode}mute(enable){this._muteNode.gain.value=enable?0:1}solo(enable){if(this.isSolo===enable){return}if(enable){if(GodotAudio.busSolo!=null&&GodotAudio.busSolo!==this){GodotAudio.busSolo._disableSolo()}this._enableSolo();return}this._disableSolo()}addSampleNode(sampleNode){this._sampleNodes.add(sampleNode);sampleNode.getOutputNode().connect(this.getInputNode())}removeSampleNode(sampleNode){this._sampleNodes.delete(sampleNode);sampleNode.getOutputNode().disconnect()}connect(bus){if(bus==null){throw new Error("cannot connect to null bus")}this.getOutputNode().disconnect();this.getOutputNode().connect(bus.getInputNode());return bus}clear(){GodotAudio.buses=GodotAudio.buses.filter(v=>v!==this)}_syncSampleNodes(){const sampleNodes=Array.from(this._sampleNodes);for(let i=0;iotherBus!==this);for(let i=0;iotherBus!==this);for(let i=0;iGodotAudio.Bus.getBus(busIndex));sampleNode.setVolumes(buses,volumes)},set_sample_bus_count:function(count){GodotAudio.Bus.setCount(count)},remove_sample_bus:function(index){const bus=GodotAudio.Bus.getBus(index);bus.clear()},add_sample_bus:function(atPos){GodotAudio.Bus.addAt(atPos)},move_sample_bus:function(busIndex,toPos){GodotAudio.Bus.move(busIndex,toPos)},set_sample_bus_send:function(busIndex,sendIndex){const bus=GodotAudio.Bus.getBus(busIndex);bus.setSend(GodotAudio.Bus.getBus(sendIndex))},set_sample_bus_volume_db:function(busIndex,volumeDb){const bus=GodotAudio.Bus.getBus(busIndex);bus.setVolumeDb(volumeDb)},set_sample_bus_solo:function(busIndex,enable){const bus=GodotAudio.Bus.getBus(busIndex);bus.solo(enable)},set_sample_bus_mute:function(busIndex,enable){const bus=GodotAudio.Bus.getBus(busIndex);bus.mute(enable)}};function _godot_audio_has_worklet(){return GodotAudio.ctx&&GodotAudio.ctx.audioWorklet?1:0}function _godot_audio_init(p_mix_rate,p_latency,p_state_change,p_latency_update){const statechange=GodotRuntime.get_func(p_state_change);const latencyupdate=GodotRuntime.get_func(p_latency_update);const mix_rate=GodotRuntime.getHeapValue(p_mix_rate,"i32");const channels=GodotAudio.init(mix_rate,p_latency,statechange,latencyupdate);GodotRuntime.setHeapValue(p_mix_rate,GodotAudio.ctx.sampleRate,"i32");return channels}function _godot_audio_input_start(){return GodotAudio.create_input(function(input){input.connect(GodotAudio.driver.get_node())})}function _godot_audio_input_stop(){if(GodotAudio.input){const tracks=GodotAudio.input["mediaStream"]["getTracks"]();for(let i=0;i=size){const high=size-wpos;wbuf.set(buffer.subarray(wpos,size));pending_samples-=high;wpos=0}if(pending_samples>0){wbuf.set(buffer.subarray(wpos,wpos+pending_samples),tot_sent-pending_samples)}port.postMessage({"cmd":"chunk","data":wbuf.subarray(0,tot_sent)});wpos+=pending_samples;pending_samples=0}this.receive=function(recv_buf){const buffer=GodotRuntime.heapSub(HEAPF32,p_in_buf,p_in_size);const from=rpos;let to_write=recv_buf.length;let high=0;if(rpos+to_write>=p_in_size){high=p_in_size-rpos;buffer.set(recv_buf.subarray(0,high),rpos);to_write-=high;rpos=0}if(to_write){buffer.set(recv_buf.subarray(high,to_write),rpos)}in_callback(from,recv_buf.length);rpos+=to_write};this.consumed=function(size,port){pending_samples+=size;send(port)}}GodotAudioWorklet.ring_buffer=new RingBuffer;GodotAudioWorklet.promise.then(function(){const node=GodotAudioWorklet.worklet;const buffer=GodotRuntime.heapSlice(HEAPF32,p_out_buf,p_out_size);node.connect(GodotAudio.ctx.destination);node.port.postMessage({"cmd":"start_nothreads","data":[buffer,p_in_size]});node.port.onmessage=function(event){if(!GodotAudioWorklet.worklet){return}if(event.data["cmd"]==="read"){const read=event.data["data"];GodotAudioWorklet.ring_buffer.consumed(read,GodotAudioWorklet.worklet.port)}else if(event.data["cmd"]==="input"){const buf=event.data["data"];if(buf.length>p_in_size){GodotRuntime.error("Input chunk is too big");return}GodotAudioWorklet.ring_buffer.receive(buf)}else{GodotRuntime.error(event.data)}}})},get_node:function(){return GodotAudioWorklet.worklet},close:function(){return new Promise(function(resolve,reject){if(GodotAudioWorklet.promise===null){return}const p=GodotAudioWorklet.promise;p.then(function(){GodotAudioWorklet.worklet.port.postMessage({"cmd":"stop","data":null});GodotAudioWorklet.worklet.disconnect();GodotAudioWorklet.worklet.port.onmessage=null;GodotAudioWorklet.worklet=null;GodotAudioWorklet.promise=null;resolve()}).catch(function(err){GodotRuntime.error(err)})})}};function _godot_audio_worklet_create(channels){try{GodotAudioWorklet.create(channels)}catch(e){GodotRuntime.error("Error starting AudioDriverWorklet",e);return 1}return 0}function _godot_audio_worklet_start_no_threads(p_out_buf,p_out_size,p_out_callback,p_in_buf,p_in_size,p_in_callback){const out_callback=GodotRuntime.get_func(p_out_callback);const in_callback=GodotRuntime.get_func(p_in_callback);GodotAudioWorklet.start_no_threads(p_out_buf,p_out_size,out_callback,p_in_buf,p_in_size,in_callback)}function _godot_js_config_canvas_id_get(p_ptr,p_ptr_max){GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`,p_ptr,p_ptr_max)}function _godot_js_config_locale_get(p_ptr,p_ptr_max){GodotRuntime.stringToHeap(GodotConfig.locale,p_ptr,p_ptr_max)}var GodotDisplayCursor={shape:"default",visible:true,cursors:{},set_style:function(style){GodotConfig.canvas.style.cursor=style},set_shape:function(shape){GodotDisplayCursor.shape=shape;let css=shape;if(shape in GodotDisplayCursor.cursors){const c=GodotDisplayCursor.cursors[shape];css=`url("${c.url}") ${c.x} ${c.y}, default`}if(GodotDisplayCursor.visible){GodotDisplayCursor.set_style(css)}},clear:function(){GodotDisplayCursor.set_style("");GodotDisplayCursor.shape="default";GodotDisplayCursor.visible=true;Object.keys(GodotDisplayCursor.cursors).forEach(function(key){URL.revokeObjectURL(GodotDisplayCursor.cursors[key]);delete GodotDisplayCursor.cursors[key]})},lockPointer:function(){const canvas=GodotConfig.canvas;if(canvas.requestPointerLock){canvas.requestPointerLock()}},releasePointer:function(){if(document.exitPointerLock){document.exitPointerLock()}},isPointerLocked:function(){return document.pointerLockElement===GodotConfig.canvas}};var GodotEventListeners={handlers:[],has:function(target,event,method,capture){return GodotEventListeners.handlers.findIndex(function(e){return e.target===target&&e.event===event&&e.method===method&&e.capture===capture})!==-1},add:function(target,event,method,capture){if(GodotEventListeners.has(target,event,method,capture)){return}function Handler(p_target,p_event,p_method,p_capture){this.target=p_target;this.event=p_event;this.method=p_method;this.capture=p_capture}GodotEventListeners.handlers.push(new Handler(target,event,method,capture));target.addEventListener(event,method,capture)},clear:function(){GodotEventListeners.handlers.forEach(function(h){h.target.removeEventListener(h.event,h.method,h.capture)});GodotEventListeners.handlers.length=0}};function _emscripten_webgl_do_get_current_context(){return GL.currentContext?GL.currentContext.handle:0}var _emscripten_webgl_get_current_context=_emscripten_webgl_do_get_current_context;var GodotDisplayScreen={desired_size:[0,0],hidpi:true,getPixelRatio:function(){return GodotDisplayScreen.hidpi?window.devicePixelRatio||1:1},isFullscreen:function(){const elem=document.fullscreenElement||document.mozFullscreenElement||document.webkitFullscreenElement||document.msFullscreenElement;if(elem){return elem===GodotConfig.canvas}return document.fullscreen||document.mozFullScreen||document.webkitIsFullscreen},hasFullscreen:function(){return document.fullscreenEnabled||document.mozFullScreenEnabled||document.webkitFullscreenEnabled},requestFullscreen:function(){if(!GodotDisplayScreen.hasFullscreen()){return 1}const canvas=GodotConfig.canvas;try{const promise=(canvas.requestFullscreen||canvas.msRequestFullscreen||canvas.mozRequestFullScreen||canvas.mozRequestFullscreen||canvas.webkitRequestFullscreen).call(canvas);if(promise){promise.catch(function(){})}}catch(e){return 1}return 0},exitFullscreen:function(){if(!GodotDisplayScreen.isFullscreen()){return 0}try{const promise=document.exitFullscreen();if(promise){promise.catch(function(){})}}catch(e){return 1}return 0},_updateGL:function(){const gl_context_handle=_emscripten_webgl_get_current_context();const gl=GL.getContext(gl_context_handle);if(gl){GL.resizeOffscreenFramebuffer(gl)}},updateSize:function(){const isFullscreen=GodotDisplayScreen.isFullscreen();const wantsFullWindow=GodotConfig.canvas_resize_policy===2;const noResize=GodotConfig.canvas_resize_policy===0;const dWidth=GodotDisplayScreen.desired_size[0];const dHeight=GodotDisplayScreen.desired_size[1];const canvas=GodotConfig.canvas;let width=dWidth;let height=dHeight;if(noResize){if(canvas.width!==width||canvas.height!==height){GodotDisplayScreen.desired_size=[canvas.width,canvas.height];GodotDisplayScreen._updateGL();return 1}return 0}const scale=GodotDisplayScreen.getPixelRatio();if(isFullscreen||wantsFullWindow){width=window.innerWidth*scale;height=window.innerHeight*scale}const csw=`${width/scale}px`;const csh=`${height/scale}px`;if(canvas.style.width!==csw||canvas.style.height!==csh||canvas.width!==width||canvas.height!==height){canvas.width=width;canvas.height=height;canvas.style.width=csw;canvas.style.height=csh;GodotDisplayScreen._updateGL();return 1}return 0}};var GodotDisplayVK={textinput:null,textarea:null,available:function(){return GodotConfig.virtual_keyboard&&"ontouchstart"in window},init:function(input_cb){function create(what){const elem=document.createElement(what);elem.style.display="none";elem.style.position="absolute";elem.style.zIndex="-1";elem.style.background="transparent";elem.style.padding="0px";elem.style.margin="0px";elem.style.overflow="hidden";elem.style.width="0px";elem.style.height="0px";elem.style.border="0px";elem.style.outline="none";elem.readonly=true;elem.disabled=true;GodotEventListeners.add(elem,"input",function(evt){const c_str=GodotRuntime.allocString(elem.value);input_cb(c_str,elem.selectionEnd);GodotRuntime.free(c_str)},false);GodotEventListeners.add(elem,"blur",function(evt){elem.style.display="none";elem.readonly=true;elem.disabled=true},false);GodotConfig.canvas.insertAdjacentElement("beforebegin",elem);return elem}GodotDisplayVK.textinput=create("input");GodotDisplayVK.textarea=create("textarea");GodotDisplayVK.updateSize()},show:function(text,type,start,end){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}if(GodotDisplayVK.textinput.style.display!==""||GodotDisplayVK.textarea.style.display!==""){GodotDisplayVK.hide()}GodotDisplayVK.updateSize();let elem=GodotDisplayVK.textinput;switch(type){case 0:elem.type="text";elem.inputmode="";break;case 1:elem=GodotDisplayVK.textarea;break;case 2:elem.type="text";elem.inputmode="numeric";break;case 3:elem.type="text";elem.inputmode="decimal";break;case 4:elem.type="tel";elem.inputmode="";break;case 5:elem.type="email";elem.inputmode="";break;case 6:elem.type="password";elem.inputmode="";break;case 7:elem.type="url";elem.inputmode="";break;default:elem.type="text";elem.inputmode="";break}elem.readonly=false;elem.disabled=false;elem.value=text;elem.style.display="block";elem.focus();elem.setSelectionRange(start,end)},hide:function(){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}[GodotDisplayVK.textinput,GodotDisplayVK.textarea].forEach(function(elem){elem.blur();elem.style.display="none";elem.value=""})},updateSize:function(){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}const rect=GodotConfig.canvas.getBoundingClientRect();function update(elem){elem.style.left=`${rect.left}px`;elem.style.top=`${rect.top}px`;elem.style.width=`${rect.width}px`;elem.style.height=`${rect.height}px`}update(GodotDisplayVK.textinput);update(GodotDisplayVK.textarea)},clear:function(){if(GodotDisplayVK.textinput){GodotDisplayVK.textinput.remove();GodotDisplayVK.textinput=null}if(GodotDisplayVK.textarea){GodotDisplayVK.textarea.remove();GodotDisplayVK.textarea=null}}};var GodotDisplay={window_icon:"",getDPI:function(){const dpi=Math.round(window.devicePixelRatio*96);return dpi>=96?dpi:96}};function _godot_js_display_alert(p_text){window.alert(GodotRuntime.parseString(p_text))}function _godot_js_display_canvas_focus(){GodotConfig.canvas.focus()}function _godot_js_display_canvas_is_focused(){return document.activeElement===GodotConfig.canvas}function _godot_js_display_clipboard_get(callback){const func=GodotRuntime.get_func(callback);try{navigator.clipboard.readText().then(function(result){const ptr=GodotRuntime.allocString(result);func(ptr);GodotRuntime.free(ptr)}).catch(function(e){})}catch(e){}}function _godot_js_display_clipboard_set(p_text){const text=GodotRuntime.parseString(p_text);if(!navigator.clipboard||!navigator.clipboard.writeText){return 1}navigator.clipboard.writeText(text).catch(function(e){GodotRuntime.error("Setting OS clipboard is only possible from an input callback for the Web platform. Exception:",e)});return 0}function _godot_js_display_cursor_is_hidden(){return!GodotDisplayCursor.visible}function _godot_js_display_cursor_is_locked(){return GodotDisplayCursor.isPointerLocked()?1:0}function _godot_js_display_cursor_lock_set(p_lock){if(p_lock){GodotDisplayCursor.lockPointer()}else{GodotDisplayCursor.releasePointer()}}function _godot_js_display_cursor_set_custom_shape(p_shape,p_ptr,p_len,p_hotspot_x,p_hotspot_y){const shape=GodotRuntime.parseString(p_shape);const old_shape=GodotDisplayCursor.cursors[shape];if(p_len>0){const png=new Blob([GodotRuntime.heapSlice(HEAPU8,p_ptr,p_len)],{type:"image/png"});const url=URL.createObjectURL(png);GodotDisplayCursor.cursors[shape]={url:url,x:p_hotspot_x,y:p_hotspot_y}}else{delete GodotDisplayCursor.cursors[shape]}if(shape===GodotDisplayCursor.shape){GodotDisplayCursor.set_shape(GodotDisplayCursor.shape)}if(old_shape){URL.revokeObjectURL(old_shape.url)}}function _godot_js_display_cursor_set_shape(p_string){GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string))}function _godot_js_display_cursor_set_visible(p_visible){const visible=p_visible!==0;if(visible===GodotDisplayCursor.visible){return}GodotDisplayCursor.visible=visible;if(visible){GodotDisplayCursor.set_shape(GodotDisplayCursor.shape)}else{GodotDisplayCursor.set_style("none")}}function _godot_js_display_desired_size_set(width,height){GodotDisplayScreen.desired_size=[width,height];GodotDisplayScreen.updateSize()}function _godot_js_display_fullscreen_cb(callback){const canvas=GodotConfig.canvas;const func=GodotRuntime.get_func(callback);function change_cb(evt){if(evt.target===canvas){func(GodotDisplayScreen.isFullscreen())}}GodotEventListeners.add(document,"fullscreenchange",change_cb,false);GodotEventListeners.add(document,"mozfullscreenchange",change_cb,false);GodotEventListeners.add(document,"webkitfullscreenchange",change_cb,false)}function _godot_js_display_fullscreen_exit(){return GodotDisplayScreen.exitFullscreen()}function _godot_js_display_fullscreen_request(){return GodotDisplayScreen.requestFullscreen()}function _godot_js_display_has_webgl(p_version){if(p_version!==1&&p_version!==2){return false}try{return!!document.createElement("canvas").getContext(p_version===2?"webgl2":"webgl")}catch(e){}return false}function _godot_js_display_is_swap_ok_cancel(){const win=["Windows","Win64","Win32","WinCE"];const plat=navigator.platform||"";if(win.indexOf(plat)!==-1){return 1}return 0}function _godot_js_display_notification_cb(callback,p_enter,p_exit,p_in,p_out){const canvas=GodotConfig.canvas;const func=GodotRuntime.get_func(callback);const notif=[p_enter,p_exit,p_in,p_out];["mouseover","mouseleave","focus","blur"].forEach(function(evt_name,idx){GodotEventListeners.add(canvas,evt_name,function(){func(notif[idx])},true)})}function _godot_js_display_pixel_ratio_get(){return GodotDisplayScreen.getPixelRatio()}function _godot_js_display_screen_dpi_get(){return GodotDisplay.getDPI()}function _godot_js_display_screen_size_get(width,height){const scale=GodotDisplayScreen.getPixelRatio();GodotRuntime.setHeapValue(width,window.screen.width*scale,"i32");GodotRuntime.setHeapValue(height,window.screen.height*scale,"i32")}function _godot_js_display_setup_canvas(p_width,p_height,p_fullscreen,p_hidpi){const canvas=GodotConfig.canvas;GodotEventListeners.add(canvas,"contextmenu",function(ev){ev.preventDefault()},false);GodotEventListeners.add(canvas,"webglcontextlost",function(ev){alert("WebGL context lost, please reload the page");ev.preventDefault()},false);GodotDisplayScreen.hidpi=!!p_hidpi;switch(GodotConfig.canvas_resize_policy){case 0:GodotDisplayScreen.desired_size=[canvas.width,canvas.height];break;case 1:GodotDisplayScreen.desired_size=[p_width,p_height];break;default:canvas.style.position="absolute";canvas.style.top=0;canvas.style.left=0;break}GodotDisplayScreen.updateSize();if(p_fullscreen){GodotDisplayScreen.requestFullscreen()}}function _godot_js_display_size_update(){const updated=GodotDisplayScreen.updateSize();if(updated){GodotDisplayVK.updateSize()}return updated}function _godot_js_display_touchscreen_is_available(){return"ontouchstart"in window}function _godot_js_display_tts_available(){return"speechSynthesis"in window}function _godot_js_display_vk_available(){return GodotDisplayVK.available()}function _godot_js_display_vk_cb(p_input_cb){const input_cb=GodotRuntime.get_func(p_input_cb);if(GodotDisplayVK.available()){GodotDisplayVK.init(input_cb)}}function _godot_js_display_vk_hide(){GodotDisplayVK.hide()}function _godot_js_display_vk_show(p_text,p_type,p_start,p_end){const text=GodotRuntime.parseString(p_text);const start=p_start>0?p_start:0;const end=p_end>0?p_end:start;GodotDisplayVK.show(text,p_type,start,end)}function _godot_js_display_window_blur_cb(callback){const func=GodotRuntime.get_func(callback);GodotEventListeners.add(window,"blur",function(){func()},false)}function _godot_js_display_window_icon_set(p_ptr,p_len){let link=document.getElementById("-gd-engine-icon");const old_icon=GodotDisplay.window_icon;if(p_ptr){if(link===null){link=document.createElement("link");link.rel="icon";link.id="-gd-engine-icon";document.head.appendChild(link)}const png=new Blob([GodotRuntime.heapSlice(HEAPU8,p_ptr,p_len)],{type:"image/png"});GodotDisplay.window_icon=URL.createObjectURL(png);link.href=GodotDisplay.window_icon}else{if(link){link.remove()}GodotDisplay.window_icon=null}if(old_icon){URL.revokeObjectURL(old_icon)}}function _godot_js_display_window_size_get(p_width,p_height){GodotRuntime.setHeapValue(p_width,GodotConfig.canvas.width,"i32");GodotRuntime.setHeapValue(p_height,GodotConfig.canvas.height,"i32")}function _godot_js_display_window_title_set(p_data){document.title=GodotRuntime.parseString(p_data)}function _godot_js_eval(p_js,p_use_global_ctx,p_union_ptr,p_byte_arr,p_byte_arr_write,p_callback){const js_code=GodotRuntime.parseString(p_js);let eval_ret=null;try{if(p_use_global_ctx){const global_eval=eval;eval_ret=global_eval(js_code)}else{eval_ret=eval(js_code)}}catch(e){GodotRuntime.error(e)}switch(typeof eval_ret){case"boolean":GodotRuntime.setHeapValue(p_union_ptr,eval_ret,"i32");return 1;case"number":GodotRuntime.setHeapValue(p_union_ptr,eval_ret,"double");return 3;case"string":GodotRuntime.setHeapValue(p_union_ptr,GodotRuntime.allocString(eval_ret),"*");return 4;case"object":if(eval_ret===null){break}if(ArrayBuffer.isView(eval_ret)&&!(eval_ret instanceof Uint8Array)){eval_ret=new Uint8Array(eval_ret.buffer)}else if(eval_ret instanceof ArrayBuffer){eval_ret=new Uint8Array(eval_ret)}if(eval_ret instanceof Uint8Array){const func=GodotRuntime.get_func(p_callback);const bytes_ptr=func(p_byte_arr,p_byte_arr_write,eval_ret.length);HEAPU8.set(eval_ret,bytes_ptr);return 29}break}return 0}var IDHandler={_last_id:0,_references:{},get:function(p_id){return IDHandler._references[p_id]},add:function(p_data){const id=++IDHandler._last_id;IDHandler._references[id]=p_data;return id},remove:function(p_id){delete IDHandler._references[p_id]}};var GodotFetch={onread:function(id,result){const obj=IDHandler.get(id);if(!obj){return}if(result.value){obj.chunks.push(result.value)}obj.reading=false;obj.done=result.done},onresponse:function(id,response){const obj=IDHandler.get(id);if(!obj){return}let chunked=false;response.headers.forEach(function(value,header){const v=value.toLowerCase().trim();const h=header.toLowerCase().trim();if(h==="transfer-encoding"&&v==="chunked"){chunked=true}});obj.status=response.status;obj.response=response;obj.reader=response.body.getReader();obj.chunked=chunked},onerror:function(id,err){GodotRuntime.error(err);const obj=IDHandler.get(id);if(!obj){return}obj.error=err},create:function(method,url,headers,body){const obj={request:null,response:null,reader:null,error:null,done:false,reading:false,status:0,chunks:[]};const id=IDHandler.add(obj);const init={method:method,headers:headers,body:body};obj.request=fetch(url,init);obj.request.then(GodotFetch.onresponse.bind(null,id)).catch(GodotFetch.onerror.bind(null,id));return id},free:function(id){const obj=IDHandler.get(id);if(!obj){return}IDHandler.remove(id);if(!obj.request){return}obj.request.then(function(response){response.abort()}).catch(function(e){})},read:function(id){const obj=IDHandler.get(id);if(!obj){return}if(obj.reader&&!obj.reading){if(obj.done){obj.reader=null;return}obj.reading=true;obj.reader.read().then(GodotFetch.onread.bind(null,id)).catch(GodotFetch.onerror.bind(null,id))}}};function _godot_js_fetch_create(p_method,p_url,p_headers,p_headers_size,p_body,p_body_size){const method=GodotRuntime.parseString(p_method);const url=GodotRuntime.parseString(p_url);const headers=GodotRuntime.parseStringArray(p_headers,p_headers_size);const body=p_body_size?GodotRuntime.heapSlice(HEAP8,p_body,p_body_size):null;return GodotFetch.create(method,url,headers.map(function(hv){const idx=hv.indexOf(":");if(idx<=0){return[]}return[hv.slice(0,idx).trim(),hv.slice(idx+1).trim()]}).filter(function(v){return v.length===2}),body)}function _godot_js_fetch_free(id){GodotFetch.free(id)}function _godot_js_fetch_http_status_get(p_id){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 0}return obj.status}function _godot_js_fetch_is_chunked(p_id){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return-1}return obj.chunked?1:0}function _godot_js_fetch_read_chunk(p_id,p_buf,p_buf_size){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 0}let to_read=p_buf_size;const chunks=obj.chunks;while(to_read&&chunks.length){const chunk=obj.chunks[0];if(chunk.length>to_read){GodotRuntime.heapCopy(HEAP8,chunk.slice(0,to_read),p_buf);chunks[0]=chunk.slice(to_read);to_read=0}else{GodotRuntime.heapCopy(HEAP8,chunk,p_buf);to_read-=chunk.length;chunks.pop()}}if(!chunks.length){GodotFetch.read(p_id)}return p_buf_size-to_read}function _godot_js_fetch_read_headers(p_id,p_parse_cb,p_ref){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 1}const cb=GodotRuntime.get_func(p_parse_cb);const arr=[];obj.response.headers.forEach(function(v,h){arr.push(`${h}:${v}`)});const c_ptr=GodotRuntime.allocStringArray(arr);cb(arr.length,c_ptr,p_ref);GodotRuntime.freeStringArray(c_ptr,arr.length);return 0}function _godot_js_fetch_state_get(p_id){const obj=IDHandler.get(p_id);if(!obj){return-1}if(obj.error){return-1}if(!obj.response){return 0}if(obj.reader){return 1}if(obj.done){return 2}return-1}var GodotInputGamepads={samples:[],get_pads:function(){try{const pads=navigator.getGamepads();if(pads){return pads}return[]}catch(e){return[]}},get_samples:function(){return GodotInputGamepads.samples},get_sample:function(index){const samples=GodotInputGamepads.samples;return index=0){os="Android"}else if(ua.indexOf("Linux")>=0){os="Linux"}else if(ua.indexOf("iPhone")>=0){os="iOS"}else if(ua.indexOf("Macintosh")>=0){os="MacOSX"}else if(ua.indexOf("Windows")>=0){os="Windows"}const id=pad.id;const exp1=/vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i;const exp2=/^([0-9a-f]+)-([0-9a-f]+)-/i;let vendor="";let product="";if(exp1.test(id)){const match=exp1.exec(id);vendor=match[1].padStart(4,"0");product=match[2].padStart(4,"0")}else if(exp2.test(id)){const match=exp2.exec(id);vendor=match[1].padStart(4,"0");product=match[2].padStart(4,"0")}if(!vendor||!product){return`${os}Unknown`}return os+vendor+product}};var GodotInputDragDrop={promises:[],pending_files:[],add_entry:function(entry){if(entry.isDirectory){GodotInputDragDrop.add_dir(entry)}else if(entry.isFile){GodotInputDragDrop.add_file(entry)}else{GodotRuntime.error("Unrecognized entry...",entry)}},add_dir:function(entry){GodotInputDragDrop.promises.push(new Promise(function(resolve,reject){const reader=entry.createReader();reader.readEntries(function(entries){for(let i=0;i{const path=elem["path"];GodotFS.copy_to_fs(DROP+path,elem["data"]);let idx=path.indexOf("/");if(idx===-1){drops.push(DROP+path)}else{const sub=path.substr(0,idx);idx=sub.indexOf("/");if(idx<0&&drops.indexOf(DROP+sub)===-1){drops.push(DROP+sub)}}files.push(DROP+path)});GodotInputDragDrop.promises=[];GodotInputDragDrop.pending_files=[];callback(drops);if(GodotConfig.persistent_drops){GodotOS.atexit(function(resolve,reject){GodotInputDragDrop.remove_drop(files,DROP);resolve()})}else{GodotInputDragDrop.remove_drop(files,DROP)}})},remove_drop:function(files,drop_path){const dirs=[drop_path.substr(0,drop_path.length-1)];files.forEach(function(file){FS.unlink(file);let dir=file.replace(drop_path,"");let idx=dir.lastIndexOf("/");while(idx>0){dir=dir.substr(0,idx);if(dirs.indexOf(drop_path+dir)===-1){dirs.push(drop_path+dir)}idx=dir.lastIndexOf("/")}});dirs.sort(function(a,b){const al=(a.match(/\//g)||[]).length;const bl=(b.match(/\//g)||[]).length;if(al>bl){return-1}else if(al=Number.MIN_SAFE_INTEGER&&heap_value<=Number.MAX_SAFE_INTEGER?Number(heap_value):heap_value}case 3:return Number(GodotRuntime.getHeapValue(val,"double"));case 4:return GodotRuntime.parseString(GodotRuntime.getHeapValue(val,"*"));case 24:return GodotJSWrapper.get_proxied_value(GodotRuntime.getHeapValue(val,"i64"));default:return undefined}},js2variant:function(p_val,p_exchange){if(p_val===undefined||p_val===null){return 0}const type=typeof p_val;if(type==="boolean"){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 1}else if(type==="number"){if(Number.isInteger(p_val)){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 2}GodotRuntime.setHeapValue(p_exchange,p_val,"double");return 3}else if(type==="bigint"){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 2}else if(type==="string"){const c_str=GodotRuntime.allocString(p_val);GodotRuntime.setHeapValue(p_exchange,c_str,"*");return 4}const id=GodotJSWrapper.get_proxied(p_val);GodotRuntime.setHeapValue(p_exchange,id,"i64");return 24}};function _godot_js_wrapper_create_cb(p_ref,p_func){const func=GodotRuntime.get_func(p_func);let id=0;const cb=function(){if(!GodotJSWrapper.get_proxied_value(id)){return undefined}GodotJSWrapper.cb_ret=null;const args=Array.from(arguments);const argsProxy=new GodotJSWrapper.MyProxy(args);func(p_ref,argsProxy.get_id(),args.length);argsProxy.unref();const ret=GodotJSWrapper.cb_ret;GodotJSWrapper.cb_ret=null;return ret};id=GodotJSWrapper.get_proxied(cb);return id}function _godot_js_wrapper_create_object(p_object,p_args,p_argc,p_convert_callback,p_exchange,p_lock,p_free_lock_callback){const name=GodotRuntime.parseString(p_object);if(typeof window[name]==="undefined"){return-1}const convert=GodotRuntime.get_func(p_convert_callback);const freeLock=GodotRuntime.get_func(p_free_lock_callback);const args=new Array(p_argc);for(let i=0;i{if(GodotWebXR.session&&GodotWebXR.space){const onFrame=function(time,frame){GodotWebXR.frame=frame;GodotWebXR.pose=frame.getViewerPose(GodotWebXR.space);callback(time);GodotWebXR.frame=null;GodotWebXR.pose=null};GodotWebXR.session.requestAnimationFrame(onFrame)}else{GodotWebXR.orig_requestAnimationFrame(callback)}},monkeyPatchRequestAnimationFrame:enable=>{if(GodotWebXR.orig_requestAnimationFrame===null){GodotWebXR.orig_requestAnimationFrame=Browser.requestAnimationFrame}Browser.requestAnimationFrame=enable?GodotWebXR.requestAnimationFrame:GodotWebXR.orig_requestAnimationFrame},pauseResumeMainLoop:()=>{Browser.mainLoop.pause();runtimeKeepalivePush();window.setTimeout(function(){runtimeKeepalivePop();Browser.mainLoop.resume()},0)},getLayer:()=>{const new_view_count=GodotWebXR.pose?GodotWebXR.pose.views.length:1;let layer=GodotWebXR.layer;if(layer&&GodotWebXR.view_count===new_view_count){return layer}if(!GodotWebXR.session||!GodotWebXR.gl_binding){return null}const gl=GodotWebXR.gl;layer=GodotWebXR.gl_binding.createProjectionLayer({textureType:new_view_count>1?"texture-array":"texture",colorFormat:gl.RGBA8,depthFormat:gl.DEPTH_COMPONENT24});GodotWebXR.session.updateRenderState({layers:[layer]});GodotWebXR.layer=layer;GodotWebXR.view_count=new_view_count;return layer},getSubImage:()=>{if(!GodotWebXR.pose){return null}const layer=GodotWebXR.getLayer();if(layer===null){return null}return GodotWebXR.gl_binding.getViewSubImage(layer,GodotWebXR.pose.views[0])},getTextureId:texture=>{if(texture.name!==undefined){return texture.name}const id=GL.getNewId(GL.textures);texture.name=id;GL.textures[id]=texture;return id},addInputSource:input_source=>{let name=-1;if(input_source.targetRayMode==="tracked-pointer"&&input_source.handedness==="left"){name=0}else if(input_source.targetRayMode==="tracked-pointer"&&input_source.handedness==="right"){name=1}else{for(let i=2;i<16;i++){if(!GodotWebXR.input_sources[i]){name=i;break}}}if(name>=0){GodotWebXR.input_sources[name]=input_source;input_source.name=name;if(input_source.targetRayMode==="screen"){let touch_index=-1;for(let i=0;i<5;i++){if(!GodotWebXR.touches[i]){touch_index=i;break}}if(touch_index>=0){GodotWebXR.touches[touch_index]=input_source;input_source.touch_index=touch_index}}}return name},removeInputSource:input_source=>{if(input_source.name!==undefined){const name=input_source.name;if(name>=0&&name<16){GodotWebXR.input_sources[name]=null}if(input_source.touch_index!==undefined){const touch_index=input_source.touch_index;if(touch_index>=0&&touch_index<5){GodotWebXR.touches[touch_index]=null}}return name}return-1},getInputSourceId:input_source=>{if(input_source!==undefined){return input_source.name}return-1},getTouchIndex:input_source=>{if(input_source.touch_index!==undefined){return input_source.touch_index}return-1}};function _godot_webxr_get_bounds_geometry(r_points){if(!GodotWebXR.space||!GodotWebXR.space.boundsGeometry){return 0}const point_count=GodotWebXR.space.boundsGeometry.length;if(point_count===0){return 0}const buf=GodotRuntime.malloc(point_count*3*4);for(let i=0;i=0){matrix=views[p_view].transform.matrix}else{matrix=GodotWebXR.pose.transform.matrix}for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_transform+i*4,matrix[i],"float")}return true}function _godot_webxr_get_velocity_texture(){const subimage=GodotWebXR.getSubImage();if(subimage===null){return 0}if(!subimage.motionVectorTexture){return 0}return GodotWebXR.getTextureId(subimage.motionVectorTexture)}function _godot_webxr_get_view_count(){if(!GodotWebXR.session||!GodotWebXR.pose){return 1}const view_count=GodotWebXR.pose.views.length;return view_count>0?view_count:1}function _godot_webxr_get_visibility_state(){if(!GodotWebXR.session||!GodotWebXR.session.visibilityState){return 0}return GodotRuntime.allocString(GodotWebXR.session.visibilityState)}function _godot_webxr_initialize(p_session_mode,p_required_features,p_optional_features,p_requested_reference_spaces,p_on_session_started,p_on_session_ended,p_on_session_failed,p_on_input_event,p_on_simple_event){GodotWebXR.monkeyPatchRequestAnimationFrame(true);const session_mode=GodotRuntime.parseString(p_session_mode);const required_features=GodotRuntime.parseString(p_required_features).split(",").map(s=>s.trim()).filter(s=>s!=="");const optional_features=GodotRuntime.parseString(p_optional_features).split(",").map(s=>s.trim()).filter(s=>s!=="");const requested_reference_space_types=GodotRuntime.parseString(p_requested_reference_spaces).split(",").map(s=>s.trim());const onstarted=GodotRuntime.get_func(p_on_session_started);const onended=GodotRuntime.get_func(p_on_session_ended);const onfailed=GodotRuntime.get_func(p_on_session_failed);const oninputevent=GodotRuntime.get_func(p_on_input_event);const onsimpleevent=GodotRuntime.get_func(p_on_simple_event);const session_init={};if(required_features.length>0){session_init["requiredFeatures"]=required_features}if(optional_features.length>0){session_init["optionalFeatures"]=optional_features}navigator.xr.requestSession(session_mode,session_init).then(function(session){GodotWebXR.session=session;session.addEventListener("end",function(evt){onended()});session.addEventListener("inputsourceschange",function(evt){evt.added.forEach(GodotWebXR.addInputSource);evt.removed.forEach(GodotWebXR.removeInputSource)});["selectstart","selectend","squeezestart","squeezeend"].forEach((input_event,index)=>{session.addEventListener(input_event,function(evt){GodotWebXR.frame=evt.frame;oninputevent(index,GodotWebXR.getInputSourceId(evt.inputSource));GodotWebXR.frame=null})});session.addEventListener("visibilitychange",function(evt){const c_str=GodotRuntime.allocString("visibility_state_changed");onsimpleevent(c_str);GodotRuntime.free(c_str)});GodotWebXR.onsimpleevent=onsimpleevent;const gl_context_handle=_emscripten_webgl_get_current_context();const gl=GL.getContext(gl_context_handle).GLctx;GodotWebXR.gl=gl;gl.makeXRCompatible().then(function(){GodotWebXR.gl_binding=new XRWebGLBinding(session,gl);GodotWebXR.getLayer();function onReferenceSpaceSuccess(reference_space,reference_space_type){GodotWebXR.space=reference_space;reference_space.onreset=function(evt){const c_str=GodotRuntime.allocString("reference_space_reset");onsimpleevent(c_str);GodotRuntime.free(c_str)};GodotWebXR.pauseResumeMainLoop();window.setTimeout(function(){const reference_space_c_str=GodotRuntime.allocString(reference_space_type);const enabled_features="enabledFeatures"in session?Array.from(session.enabledFeatures):[];const enabled_features_c_str=GodotRuntime.allocString(enabled_features.join(","));const environment_blend_mode="environmentBlendMode"in session?session.environmentBlendMode:"";const environment_blend_mode_c_str=GodotRuntime.allocString(environment_blend_mode);onstarted(reference_space_c_str,enabled_features_c_str,environment_blend_mode_c_str);GodotRuntime.free(reference_space_c_str);GodotRuntime.free(enabled_features_c_str);GodotRuntime.free(environment_blend_mode_c_str)},0)}function requestReferenceSpace(){const reference_space_type=requested_reference_space_types.shift();session.requestReferenceSpace(reference_space_type).then(refSpace=>{onReferenceSpaceSuccess(refSpace,reference_space_type)}).catch(()=>{if(requested_reference_space_types.length===0){const c_str=GodotRuntime.allocString("Unable to get any of the requested reference space types");onfailed(c_str);GodotRuntime.free(c_str)}else{requestReferenceSpace()}})}requestReferenceSpace()}).catch(function(error){const c_str=GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);onfailed(c_str);GodotRuntime.free(c_str)})}).catch(function(error){const c_str=GodotRuntime.allocString(`Unable to start session: ${error}`);onfailed(c_str);GodotRuntime.free(c_str)})}function _godot_webxr_is_session_supported(p_session_mode,p_callback){const session_mode=GodotRuntime.parseString(p_session_mode);const cb=GodotRuntime.get_func(p_callback);if(navigator.xr){navigator.xr.isSessionSupported(session_mode).then(function(supported){const c_str=GodotRuntime.allocString(session_mode);cb(c_str,supported?1:0);GodotRuntime.free(c_str)})}else{const c_str=GodotRuntime.allocString(session_mode);cb(c_str,0);GodotRuntime.free(c_str)}}function _godot_webxr_is_supported(){return!!navigator.xr}function _godot_webxr_uninitialize(){if(GodotWebXR.session){GodotWebXR.session.end().catch(e=>{})}GodotWebXR.session=null;GodotWebXR.gl_binding=null;GodotWebXR.layer=null;GodotWebXR.space=null;GodotWebXR.frame=null;GodotWebXR.pose=null;GodotWebXR.view_count=1;GodotWebXR.input_sources=new Array(16);GodotWebXR.touches=new Array(5);GodotWebXR.onsimpleevent=null;GodotWebXR.monkeyPatchRequestAnimationFrame(false);GodotWebXR.pauseResumeMainLoop()}function _godot_webxr_update_input_source(p_input_source_id,r_target_pose,r_target_ray_mode,r_touch_index,r_has_grip_pose,r_grip_pose,r_has_standard_mapping,r_button_count,r_buttons,r_axes_count,r_axes,r_has_hand_data,r_hand_joints,r_hand_radii){if(!GodotWebXR.session||!GodotWebXR.frame){return 0}if(p_input_source_id<0||p_input_source_id>=GodotWebXR.input_sources.length||!GodotWebXR.input_sources[p_input_source_id]){return false}const input_source=GodotWebXR.input_sources[p_input_source_id];const frame=GodotWebXR.frame;const space=GodotWebXR.space;const target_pose=frame.getPose(input_source.targetRaySpace,space);if(!target_pose){return false}const target_pose_matrix=target_pose.transform.matrix;for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_target_pose+i*4,target_pose_matrix[i],"float")}let target_ray_mode=0;switch(input_source.targetRayMode){case"gaze":target_ray_mode=1;break;case"tracked-pointer":target_ray_mode=2;break;case"screen":target_ray_mode=3;break;default:}GodotRuntime.setHeapValue(r_target_ray_mode,target_ray_mode,"i32");GodotRuntime.setHeapValue(r_touch_index,GodotWebXR.getTouchIndex(input_source),"i32");let has_grip_pose=false;if(input_source.gripSpace){const grip_pose=frame.getPose(input_source.gripSpace,space);if(grip_pose){const grip_pose_matrix=grip_pose.transform.matrix;for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_grip_pose+i*4,grip_pose_matrix[i],"float")}has_grip_pose=true}}GodotRuntime.setHeapValue(r_has_grip_pose,has_grip_pose?1:0,"i32");let has_standard_mapping=false;let button_count=0;let axes_count=0;if(input_source.gamepad){if(input_source.gamepad.mapping==="xr-standard"){has_standard_mapping=true}button_count=Math.min(input_source.gamepad.buttons.length,10);for(let i=0;i{const c_str=GodotRuntime.allocString("display_refresh_rate_changed");GodotWebXR.onsimpleevent(c_str);GodotRuntime.free(c_str)})}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm,loc){return _strftime(s,maxsize,format,tm)}function allocateUTF8OnStack(str){var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8Array(str,HEAP8,ret,size);return ret}function getCFunc(ident){var func=Module["_"+ident];return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;itype==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return function(){return ccall(ident,returnType,argTypes,arguments,opts)}}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();Module["requestFullscreen"]=function Module_requestFullscreen(lockPointer,resizeCanvas){Browser.requestFullscreen(lockPointer,resizeCanvas)};Module["requestAnimationFrame"]=function Module_requestAnimationFrame(func){Browser.requestAnimationFrame(func)};Module["setCanvasSize"]=function Module_setCanvasSize(width,height,noUpdates){Browser.setCanvasSize(width,height,noUpdates)};Module["pauseMainLoop"]=function Module_pauseMainLoop(){Browser.mainLoop.pause()};Module["resumeMainLoop"]=function Module_resumeMainLoop(){Browser.mainLoop.resume()};Module["getUserMedia"]=function Module_getUserMedia(){Browser.getUserMedia()};Module["createContext"]=function Module_createContext(canvas,useWebGL,setInModule,webGLContextAttributes){return Browser.createContext(canvas,useWebGL,setInModule,webGLContextAttributes)};var preloadedImages={};var preloadedAudios={};var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var __miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<288;++i){__miniTempWebGLIntBuffers[i]=__miniTempWebGLIntBuffersStorage.subarray(0,i+1)}var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i+1)}Module["request_quit"]=function(){GodotOS.request_quit()};Module["onExit"]=GodotOS.cleanup;GodotOS._fs_sync_promise=Promise.resolve();Module["initConfig"]=GodotConfig.init_config;Module["initFS"]=GodotFS.init;Module["copyToFS"]=GodotFS.copy_to_fs;GodotOS.atexit(function(resolve,reject){GodotDisplayCursor.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotEventListeners.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotDisplayVK.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotIME.clear();resolve()});GodotJSWrapper.proxies=new Map;var asmLibraryArg={"qf":___call_sighandler,"pf":___syscall__newselect,"of":___syscall_accept4,"nf":___syscall_bind,"mf":___syscall_chdir,"tb":___syscall_chmod,"lf":___syscall_connect,"kf":___syscall_faccessat,"jf":___syscall_fchmod,"U":___syscall_fcntl64,"wb":___syscall_ftruncate64,"hf":___syscall_getcwd,"gf":___syscall_getdents64,"ff":___syscall_getsockname,"ef":___syscall_getsockopt,"sb":___syscall_ioctl,"df":___syscall_listen,"cf":___syscall_lstat64,"bf":___syscall_mkdirat,"af":___syscall_mknodat,"$e":___syscall_newfstatat,"rb":___syscall_openat,"_e":___syscall_poll,"Ze":___syscall_readlinkat,"Ye":___syscall_recvfrom,"Xe":___syscall_renameat,"We":___syscall_rmdir,"Ve":___syscall_sendto,"qb":___syscall_socket,"Ue":___syscall_stat64,"Te":___syscall_statfs64,"Se":___syscall_symlink,"Re":___syscall_unlinkat,"Oe":__dlinit,"Ne":__dlopen_js,"Me":__dlsym_js,"Le":__emscripten_get_now_is_monotonic,"Ke":__gmtime_js,"Je":__localtime_js,"Ie":__tzset_js,"e":_abort,"He":_emscripten_cancel_main_loop,"Ja":_emscripten_date_now,"Ge":_emscripten_force_exit,"Fe":_emscripten_get_heap_max,"Ia":_emscripten_get_now,"Ee":_emscripten_memcpy_big,"De":_emscripten_resize_heap,"Ce":_emscripten_set_canvas_element_size,"Ha":_emscripten_set_main_loop,"nb":_emscripten_webgl_commit_frame,"Be":_emscripten_webgl_create_context,"Ae":_emscripten_webgl_destroy_context,"ze":_emscripten_webgl_enable_extension,"ye":_emscripten_webgl_get_supported_extensions,"xe":_emscripten_webgl_init_context_attributes,"we":_emscripten_webgl_make_context_current,"Qe":_environ_get,"Pe":_environ_sizes_get,"ua":_exit,"da":_fd_close,"pb":_fd_fdstat_get,"ob":_fd_read,"vb":_fd_seek,"Ka":_fd_write,"Ga":_getaddrinfo,"ve":_getnameinfo,"j":_glActiveTexture,"mb":_glAttachShader,"ha":_glBeginTransformFeedback,"b":_glBindBuffer,"w":_glBindBufferBase,"Fa":_glBindBufferRange,"d":_glBindFramebuffer,"ta":_glBindRenderbuffer,"c":_glBindTexture,"f":_glBindVertexArray,"ue":_glBlendColor,"J":_glBlendEquation,"la":_glBlendFunc,"C":_glBlendFuncSeparate,"ga":_glBlitFramebuffer,"h":_glBufferData,"P":_glBufferSubData,"O":_glCheckFramebufferStatus,"H":_glClear,"lb":_glClearBufferfv,"N":_glClearColor,"te":_glClearDepthf,"_":_glColorMask,"kb":_glCompileShader,"se":_glCompressedTexImage2D,"re":_glCompressedTexImage3D,"qe":_glCompressedTexSubImage3D,"pe":_glCopyBufferSubData,"oe":_glCreateProgram,"jb":_glCreateShader,"ka":_glCullFace,"o":_glDeleteBuffers,"v":_glDeleteFramebuffers,"fa":_glDeleteProgram,"ne":_glDeleteQueries,"na":_glDeleteRenderbuffers,"Z":_glDeleteShader,"ib":_glDeleteSync,"l":_glDeleteTextures,"M":_glDeleteVertexArrays,"W":_glDepthFunc,"z":_glDepthMask,"i":_glDisable,"q":_glDisableVertexAttribArray,"G":_glDrawArrays,"Y":_glDrawArraysInstanced,"ja":_glDrawBuffers,"T":_glDrawElements,"S":_glDrawElementsInstanced,"y":_glEnable,"g":_glEnableVertexAttribArray,"ea":_glEndTransformFeedback,"hb":_glFenceSync,"me":_glFinish,"Ea":_glFramebufferRenderbuffer,"u":_glFramebufferTexture2D,"aa":_glFramebufferTextureLayer,"gb":_glFrontFace,"m":_glGenBuffers,"A":_glGenFramebuffers,"le":_glGenQueries,"Da":_glGenRenderbuffers,"t":_glGenTextures,"L":_glGenVertexArrays,"ke":_glGenerateMipmap,"je":_glGetFloatv,"ie":_glGetInteger64v,"ma":_glGetIntegerv,"he":_glGetProgramInfoLog,"fb":_glGetProgramiv,"eb":_glGetShaderInfoLog,"sa":_glGetShaderiv,"$":_glGetString,"ge":_glGetSynciv,"fe":_glGetUniformBlockIndex,"Ca":_glGetUniformLocation,"ee":_glLinkProgram,"Ba":_glPixelStorei,"db":_glReadBuffer,"Aa":_glReadPixels,"de":_glRenderbufferStorage,"cb":_glRenderbufferStorageMultisample,"ra":_glScissor,"bb":_glShaderSource,"s":_glTexImage2D,"V":_glTexImage3D,"za":_glTexParameterf,"a":_glTexParameteri,"ya":_glTexStorage2D,"ab":_glTexSubImage3D,"ce":_glTransformFeedbackVaryings,"n":_glUniform1f,"E":_glUniform1i,"be":_glUniform1iv,"F":_glUniform1ui,"qa":_glUniform1uiv,"ca":_glUniform2f,"R":_glUniform2fv,"ia":_glUniform2iv,"r":_glUniform3fv,"K":_glUniform4f,"D":_glUniform4fv,"ae":_glUniformBlockBinding,"$a":_glUniformMatrix3fv,"I":_glUniformMatrix4fv,"p":_glUseProgram,"pa":_glVertexAttrib4f,"x":_glVertexAttribDivisor,"ba":_glVertexAttribI4ui,"Q":_glVertexAttribIPointer,"k":_glVertexAttribPointer,"B":_glViewport,"$d":_godot_audio_has_worklet,"_d":_godot_audio_init,"Zd":_godot_audio_input_start,"Yd":_godot_audio_input_stop,"Xd":_godot_audio_is_available,"Wd":_godot_audio_resume,"Vd":_godot_audio_sample_bus_add,"Ud":_godot_audio_sample_bus_move,"Td":_godot_audio_sample_bus_remove,"Sd":_godot_audio_sample_bus_set_count,"Rd":_godot_audio_sample_bus_set_mute,"Qd":_godot_audio_sample_bus_set_send,"Pd":_godot_audio_sample_bus_set_solo,"Od":_godot_audio_sample_bus_set_volume_db,"Nd":_godot_audio_sample_is_active,"Md":_godot_audio_sample_register_stream,"Ld":_godot_audio_sample_set_finished_callback,"Kd":_godot_audio_sample_set_pause,"Jd":_godot_audio_sample_set_volumes_linear,"Id":_godot_audio_sample_start,"Hd":_godot_audio_sample_stop,"Gd":_godot_audio_sample_stream_is_registered,"Fd":_godot_audio_sample_unregister_stream,"Ed":_godot_audio_sample_update_pitch_scale,"Dd":_godot_audio_worklet_create,"Cd":_godot_audio_worklet_start_no_threads,"Bd":_godot_js_config_canvas_id_get,"Ad":_godot_js_config_locale_get,"zd":_godot_js_display_alert,"yd":_godot_js_display_canvas_focus,"xd":_godot_js_display_canvas_is_focused,"wd":_godot_js_display_clipboard_get,"vd":_godot_js_display_clipboard_set,"ud":_godot_js_display_cursor_is_hidden,"td":_godot_js_display_cursor_is_locked,"xa":_godot_js_display_cursor_lock_set,"_a":_godot_js_display_cursor_set_custom_shape,"sd":_godot_js_display_cursor_set_shape,"wa":_godot_js_display_cursor_set_visible,"rd":_godot_js_display_desired_size_set,"qd":_godot_js_display_fullscreen_cb,"pd":_godot_js_display_fullscreen_exit,"od":_godot_js_display_fullscreen_request,"nd":_godot_js_display_has_webgl,"md":_godot_js_display_is_swap_ok_cancel,"ld":_godot_js_display_notification_cb,"kd":_godot_js_display_pixel_ratio_get,"jd":_godot_js_display_screen_dpi_get,"id":_godot_js_display_screen_size_get,"hd":_godot_js_display_setup_canvas,"gd":_godot_js_display_size_update,"fd":_godot_js_display_touchscreen_is_available,"ed":_godot_js_display_tts_available,"Za":_godot_js_display_vk_available,"dd":_godot_js_display_vk_cb,"cd":_godot_js_display_vk_hide,"bd":_godot_js_display_vk_show,"ad":_godot_js_display_window_blur_cb,"Ya":_godot_js_display_window_icon_set,"Xa":_godot_js_display_window_size_get,"$c":_godot_js_display_window_title_set,"_c":_godot_js_eval,"Zc":_godot_js_fetch_create,"Wa":_godot_js_fetch_free,"Yc":_godot_js_fetch_http_status_get,"Xc":_godot_js_fetch_is_chunked,"Wc":_godot_js_fetch_read_chunk,"Vc":_godot_js_fetch_read_headers,"va":_godot_js_fetch_state_get,"Uc":_godot_js_input_drop_files_cb,"Tc":_godot_js_input_gamepad_cb,"Sc":_godot_js_input_gamepad_sample,"Rc":_godot_js_input_gamepad_sample_count,"Qc":_godot_js_input_gamepad_sample_get,"Pc":_godot_js_input_key_cb,"Oc":_godot_js_input_mouse_button_cb,"Nc":_godot_js_input_mouse_move_cb,"Mc":_godot_js_input_mouse_wheel_cb,"Lc":_godot_js_input_paste_cb,"Kc":_godot_js_input_touch_cb,"Jc":_godot_js_input_vibrate_handheld,"Va":_godot_js_is_ime_focused,"Ic":_godot_js_os_download_buffer,"Hc":_godot_js_os_execute,"Ua":_godot_js_os_finish_async,"Gc":_godot_js_os_fs_is_persistent,"Fc":_godot_js_os_fs_sync,"Ec":_godot_js_os_has_feature,"Dc":_godot_js_os_hw_concurrency_get,"Cc":_godot_js_os_request_quit_cb,"Bc":_godot_js_os_shell_open,"Ac":_godot_js_pwa_cb,"zc":_godot_js_pwa_update,"Ta":_godot_js_rtc_datachannel_close,"yc":_godot_js_rtc_datachannel_connect,"xc":_godot_js_rtc_datachannel_destroy,"wc":_godot_js_rtc_datachannel_get_buffered_amount,"vc":_godot_js_rtc_datachannel_id_get,"uc":_godot_js_rtc_datachannel_is_negotiated,"tc":_godot_js_rtc_datachannel_is_ordered,"sc":_godot_js_rtc_datachannel_label_get,"rc":_godot_js_rtc_datachannel_max_packet_lifetime_get,"qc":_godot_js_rtc_datachannel_max_retransmits_get,"pc":_godot_js_rtc_datachannel_protocol_get,"oc":_godot_js_rtc_datachannel_ready_state_get,"nc":_godot_js_rtc_datachannel_send,"Sa":_godot_js_rtc_pc_close,"mc":_godot_js_rtc_pc_create,"lc":_godot_js_rtc_pc_datachannel_create,"Ra":_godot_js_rtc_pc_destroy,"kc":_godot_js_rtc_pc_ice_candidate_add,"jc":_godot_js_rtc_pc_local_description_set,"ic":_godot_js_rtc_pc_offer_create,"hc":_godot_js_rtc_pc_remote_description_set,"gc":_godot_js_set_ime_active,"fc":_godot_js_set_ime_cb,"ec":_godot_js_set_ime_position,"dc":_godot_js_tts_get_voices,"cc":_godot_js_tts_is_paused,"bc":_godot_js_tts_is_speaking,"ac":_godot_js_tts_pause,"$b":_godot_js_tts_resume,"_b":_godot_js_tts_speak,"Zb":_godot_js_tts_stop,"Yb":_godot_js_websocket_buffered_amount,"Xb":_godot_js_websocket_close,"Wb":_godot_js_websocket_create,"Qa":_godot_js_websocket_destroy,"Vb":_godot_js_websocket_send,"Ub":_godot_js_wrapper_create_cb,"Tb":_godot_js_wrapper_create_object,"Sb":_godot_js_wrapper_interface_get,"Rb":_godot_js_wrapper_object_call,"Qb":_godot_js_wrapper_object_get,"Pa":_godot_js_wrapper_object_getvar,"Pb":_godot_js_wrapper_object_set,"Ob":_godot_js_wrapper_object_set_cb_ret,"Nb":_godot_js_wrapper_object_setvar,"Mb":_godot_js_wrapper_object_unref,"Oa":_godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR,"X":_godot_webgl2_glFramebufferTextureMultiviewOVR,"oa":_godot_webgl2_glGetBufferSubData,"Lb":_godot_webxr_get_bounds_geometry,"Kb":_godot_webxr_get_color_texture,"Jb":_godot_webxr_get_depth_texture,"Ib":_godot_webxr_get_frame_rate,"Hb":_godot_webxr_get_projection_for_view,"Gb":_godot_webxr_get_render_target_size,"Fb":_godot_webxr_get_supported_frame_rates,"Na":_godot_webxr_get_transform_for_view,"Eb":_godot_webxr_get_velocity_texture,"Ma":_godot_webxr_get_view_count,"Db":_godot_webxr_get_visibility_state,"Cb":_godot_webxr_initialize,"Bb":_godot_webxr_is_session_supported,"Ab":_godot_webxr_is_supported,"zb":_godot_webxr_uninitialize,"yb":_godot_webxr_update_input_source,"xb":_godot_webxr_update_target_frame_rate,"La":_strftime,"ub":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["sf"]).apply(null,arguments)};var __Z14godot_web_mainiPPc=Module["__Z14godot_web_mainiPPc"]=function(){return(__Z14godot_web_mainiPPc=Module["__Z14godot_web_mainiPPc"]=Module["asm"]["uf"]).apply(null,arguments)};var _main=Module["_main"]=function(){return(_main=Module["_main"]=Module["asm"]["vf"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["wf"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["xf"]).apply(null,arguments)};var _fflush=Module["_fflush"]=function(){return(_fflush=Module["_fflush"]=Module["asm"]["yf"]).apply(null,arguments)};var _htonl=Module["_htonl"]=function(){return(_htonl=Module["_htonl"]=Module["asm"]["zf"]).apply(null,arguments)};var _htons=Module["_htons"]=function(){return(_htons=Module["_htons"]=Module["asm"]["Af"]).apply(null,arguments)};var _ntohs=Module["_ntohs"]=function(){return(_ntohs=Module["_ntohs"]=Module["asm"]["Bf"]).apply(null,arguments)};var __emwebxr_on_input_event=Module["__emwebxr_on_input_event"]=function(){return(__emwebxr_on_input_event=Module["__emwebxr_on_input_event"]=Module["asm"]["Cf"]).apply(null,arguments)};var __emwebxr_on_simple_event=Module["__emwebxr_on_simple_event"]=function(){return(__emwebxr_on_simple_event=Module["__emwebxr_on_simple_event"]=Module["asm"]["Df"]).apply(null,arguments)};var ___funcs_on_exit=Module["___funcs_on_exit"]=function(){return(___funcs_on_exit=Module["___funcs_on_exit"]=Module["asm"]["Ef"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["Ff"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["Gf"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["Hf"]).apply(null,arguments)};Module["callMain"]=callMain;Module["cwrap"]=cwrap;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args){var entryFunction=Module["_main"];args=args||[];args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv>>2;args.forEach(arg=>{HEAP32[argv_ptr++]=allocateUTF8OnStack(arg)});HEAP32[argv_ptr]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=false;if(Module["noInitialRun"])shouldRunNow=false;run(); - - - return Godot.ready -} -); -})(); -if (typeof exports === 'object' && typeof module === 'object') - module.exports = Godot; -else if (typeof define === 'function' && define['amd']) - define([], function() { return Godot; }); -else if (typeof exports === 'object') - exports["Godot"] = Godot; - -const Features = { - /** - * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for. - * - * @param {number=} [majorVersion=1] The major WebGL version to check for. - * @returns {boolean} If the given major version of WebGL is available. - * @function Engine.isWebGLAvailable - */ - isWebGLAvailable: function (majorVersion = 1) { - try { - return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]); - } catch (e) { /* Not available */ } - return false; - }, - - /** - * Check whether the Fetch API available and supports streaming responses. - * - * @returns {boolean} If the Fetch API is available and supports streaming responses. - * @function Engine.isFetchAvailable - */ - isFetchAvailable: function () { - return 'fetch' in window && 'Response' in window && 'body' in window.Response.prototype; - }, - - /** - * Check whether the engine is running in a Secure Context. - * - * @returns {boolean} If the engine is running in a Secure Context. - * @function Engine.isSecureContext - */ - isSecureContext: function () { - return window['isSecureContext'] === true; - }, - - /** - * Check whether the engine is cross origin isolated. - * This value is dependent on Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers sent by the server. - * - * @returns {boolean} If the engine is running in a Secure Context. - * @function Engine.isSecureContext - */ - isCrossOriginIsolated: function () { - return window['crossOriginIsolated'] === true; - }, - - /** - * Check whether SharedBufferArray is available. - * - * Most browsers require the page to be running in a secure context, and the - * the server to provide specific CORS headers for SharedArrayBuffer to be available. - * - * @returns {boolean} If SharedArrayBuffer is available. - * @function Engine.isSharedArrayBufferAvailable - */ - isSharedArrayBufferAvailable: function () { - return 'SharedArrayBuffer' in window; - }, - - /** - * Check whether the AudioContext supports AudioWorkletNodes. - * - * @returns {boolean} If AudioWorkletNode is available. - * @function Engine.isAudioWorkletAvailable - */ - isAudioWorkletAvailable: function () { - return 'AudioContext' in window && 'audioWorklet' in AudioContext.prototype; - }, - - /** - * Return an array of missing required features (as string). - * - * @returns {Array} A list of human-readable missing features. - * @function Engine.getMissingFeatures - * @param {{threads: (boolean|undefined)}} supportedFeatures - */ - getMissingFeatures: function (supportedFeatures = {}) { - const { - // Quotes are needed for the Closure compiler. - 'threads': supportsThreads = true, - } = supportedFeatures; - - const missing = []; - if (!Features.isWebGLAvailable(2)) { - missing.push('WebGL2 - Check web browser configuration and hardware support'); - } - if (!Features.isFetchAvailable()) { - missing.push('Fetch - Check web browser version'); - } - if (!Features.isSecureContext()) { - missing.push('Secure Context - Check web server configuration (use HTTPS)'); - } - - if (supportsThreads) { - if (!Features.isCrossOriginIsolated()) { - missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.'); - } - if (!Features.isSharedArrayBufferAvailable()) { - missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.'); - } - } - - // Audio is normally optional since we have a dummy fallback. - return missing; - }, -}; - -const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars - function getTrackedResponse(response, load_status) { - function onloadprogress(reader, controller) { - return reader.read().then(function (result) { - if (load_status.done) { - return Promise.resolve(); - } - if (result.value) { - controller.enqueue(result.value); - load_status.loaded += result.value.length; - } - if (!result.done) { - return onloadprogress(reader, controller); - } - load_status.done = true; - return Promise.resolve(); - }); - } - const reader = response.body.getReader(); - return new Response(new ReadableStream({ - start: function (controller) { - onloadprogress(reader, controller).then(function () { - controller.close(); - }); - }, - }), { headers: response.headers }); - } - - function loadFetch(file, tracker, fileSize, raw) { - tracker[file] = { - total: fileSize || 0, - loaded: 0, - done: false, - }; - return fetch(file).then(function (response) { - if (!response.ok) { - return Promise.reject(new Error(`Failed loading file '${file}'`)); - } - const tr = getTrackedResponse(response, tracker[file]); - if (raw) { - return Promise.resolve(tr); - } - return tr.arrayBuffer(); - }); - } - - function retry(func, attempts = 1) { - function onerror(err) { - if (attempts <= 1) { - return Promise.reject(err); - } - return new Promise(function (resolve, reject) { - setTimeout(function () { - retry(func, attempts - 1).then(resolve).catch(reject); - }, 1000); - }); - } - return func().catch(onerror); - } - - const DOWNLOAD_ATTEMPTS_MAX = 4; - const loadingFiles = {}; - const lastProgress = { loaded: 0, total: 0 }; - let progressFunc = null; - - const animateProgress = function () { - let loaded = 0; - let total = 0; - let totalIsValid = true; - let progressIsFinal = true; - - Object.keys(loadingFiles).forEach(function (file) { - const stat = loadingFiles[file]; - if (!stat.done) { - progressIsFinal = false; - } - if (!totalIsValid || stat.total === 0) { - totalIsValid = false; - total = 0; - } else { - total += stat.total; - } - loaded += stat.loaded; - }); - if (loaded !== lastProgress.loaded || total !== lastProgress.total) { - lastProgress.loaded = loaded; - lastProgress.total = total; - if (typeof progressFunc === 'function') { - progressFunc(loaded, total); - } - } - if (!progressIsFinal) { - requestAnimationFrame(animateProgress); - } - }; - - this.animateProgress = animateProgress; - - this.setProgressFunc = function (callback) { - progressFunc = callback; - }; - - this.loadPromise = function (file, fileSize, raw = false) { - return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX); - }; - - this.preloadedFiles = []; - this.preload = function (pathOrBuffer, destPath, fileSize) { - let buffer = null; - if (typeof pathOrBuffer === 'string') { - const me = this; - return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) { - me.preloadedFiles.push({ - path: destPath || pathOrBuffer, - buffer: buf, - }); - return Promise.resolve(); - }); - } else if (pathOrBuffer instanceof ArrayBuffer) { - buffer = new Uint8Array(pathOrBuffer); - } else if (ArrayBuffer.isView(pathOrBuffer)) { - buffer = new Uint8Array(pathOrBuffer.buffer); - } - if (buffer) { - this.preloadedFiles.push({ - path: destPath, - buffer: pathOrBuffer, - }); - return Promise.resolve(); - } - return Promise.reject(new Error('Invalid object for preloading')); - }; -}; - -/** - * An object used to configure the Engine instance based on godot export options, and to override those in custom HTML - * templates if needed. - * - * @header Engine configuration - * @summary The Engine configuration object. This is just a typedef, create it like a regular object, e.g.: - * - * ``const MyConfig = { executable: 'godot', unloadAfterInit: false }`` - * - * @typedef {Object} EngineConfig - */ -const EngineConfig = {}; // eslint-disable-line no-unused-vars - -/** - * @struct - * @constructor - * @ignore - */ -const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars - const cfg = /** @lends {InternalConfig.prototype} */ { - /** - * Whether to unload the engine automatically after the instance is initialized. - * - * @memberof EngineConfig - * @default - * @type {boolean} - */ - unloadAfterInit: true, - /** - * The HTML DOM Canvas object to use. - * - * By default, the first canvas element in the document will be used is none is specified. - * - * @memberof EngineConfig - * @default - * @type {?HTMLCanvasElement} - */ - canvas: null, - /** - * The name of the WASM file without the extension. (Set by Godot Editor export process). - * - * @memberof EngineConfig - * @default - * @type {string} - */ - executable: '', - /** - * An alternative name for the game pck to load. The executable name is used otherwise. - * - * @memberof EngineConfig - * @default - * @type {?string} - */ - mainPack: null, - /** - * Specify a language code to select the proper localization for the game. - * - * The browser locale will be used if none is specified. See complete list of - * :ref:`supported locales `. - * - * @memberof EngineConfig - * @type {?string} - * @default - */ - locale: null, - /** - * The canvas resize policy determines how the canvas should be resized by Godot. - * - * ``0`` means Godot won't do any resizing. This is useful if you want to control the canvas size from - * javascript code in your template. - * - * ``1`` means Godot will resize the canvas on start, and when changing window size via engine functions. - * - * ``2`` means Godot will adapt the canvas size to match the whole browser window. - * - * @memberof EngineConfig - * @type {number} - * @default - */ - canvasResizePolicy: 2, - /** - * The arguments to be passed as command line arguments on startup. - * - * See :ref:`command line tutorial `. - * - * **Note**: :js:meth:`startGame ` will always add the ``--main-pack`` argument. - * - * @memberof EngineConfig - * @type {Array} - * @default - */ - args: [], - /** - * When enabled, the game canvas will automatically grab the focus when the engine starts. - * - * @memberof EngineConfig - * @type {boolean} - * @default - */ - focusCanvas: true, - /** - * When enabled, this will turn on experimental virtual keyboard support on mobile. - * - * @memberof EngineConfig - * @type {boolean} - * @default - */ - experimentalVK: false, - /** - * The progressive web app service worker to install. - * @memberof EngineConfig - * @default - * @type {string} - */ - serviceWorker: '', - /** - * @ignore - * @type {Array.} - */ - persistentPaths: ['/userfs'], - /** - * @ignore - * @type {boolean} - */ - persistentDrops: false, - /** - * @ignore - * @type {Array.} - */ - gdextensionLibs: [], - /** - * @ignore - * @type {Array.} - */ - fileSizes: [], - /** - * A callback function for handling Godot's ``OS.execute`` calls. - * - * This is for example used in the Web Editor template to switch between project manager and editor, and for running the game. - * - * @callback EngineConfig.onExecute - * @param {string} path The path that Godot's wants executed. - * @param {Array.} args The arguments of the "command" to execute. - */ - /** - * @ignore - * @type {?function(string, Array.)} - */ - onExecute: null, - /** - * A callback function for being notified when the Godot instance quits. - * - * **Note**: This function will not be called if the engine crashes or become unresponsive. - * - * @callback EngineConfig.onExit - * @param {number} status_code The status code returned by Godot on exit. - */ - /** - * @ignore - * @type {?function(number)} - */ - onExit: null, - /** - * A callback function for displaying download progress. - * - * The function is called once per frame while downloading files, so the usage of ``requestAnimationFrame()`` - * is not necessary. - * - * If the callback function receives a total amount of bytes as 0, this means that it is impossible to calculate. - * Possible reasons include: - * - * - Files are delivered with server-side chunked compression - * - Files are delivered with server-side compression on Chromium - * - Not all file downloads have started yet (usually on servers without multi-threading) - * - * @callback EngineConfig.onProgress - * @param {number} current The current amount of downloaded bytes so far. - * @param {number} total The total amount of bytes to be downloaded. - */ - /** - * @ignore - * @type {?function(number, number)} - */ - onProgress: null, - /** - * A callback function for handling the standard output stream. This method should usually only be used in debug pages. - * - * By default, ``console.log()`` is used. - * - * @callback EngineConfig.onPrint - * @param {...*} [var_args] A variadic number of arguments to be printed. - */ - /** - * @ignore - * @type {?function(...*)} - */ - onPrint: function () { - console.log.apply(console, Array.from(arguments)); // eslint-disable-line no-console - }, - /** - * A callback function for handling the standard error stream. This method should usually only be used in debug pages. - * - * By default, ``console.error()`` is used. - * - * @callback EngineConfig.onPrintError - * @param {...*} [var_args] A variadic number of arguments to be printed as errors. - */ - /** - * @ignore - * @type {?function(...*)} - */ - onPrintError: function (var_args) { - console.error.apply(console, Array.from(arguments)); // eslint-disable-line no-console - }, - }; - - /** - * @ignore - * @struct - * @constructor - * @param {EngineConfig} opts - */ - function Config(opts) { - this.update(opts); - } - - Config.prototype = cfg; - - /** - * @ignore - * @param {EngineConfig} opts - */ - Config.prototype.update = function (opts) { - const config = opts || {}; - // NOTE: We must explicitly pass the default, accessing it via - // the key will fail due to closure compiler renames. - function parse(key, def) { - if (typeof (config[key]) === 'undefined') { - return def; - } - return config[key]; - } - // Module config - this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit); - this.onPrintError = parse('onPrintError', this.onPrintError); - this.onPrint = parse('onPrint', this.onPrint); - this.onProgress = parse('onProgress', this.onProgress); - - // Godot config - this.canvas = parse('canvas', this.canvas); - this.executable = parse('executable', this.executable); - this.mainPack = parse('mainPack', this.mainPack); - this.locale = parse('locale', this.locale); - this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy); - this.persistentPaths = parse('persistentPaths', this.persistentPaths); - this.persistentDrops = parse('persistentDrops', this.persistentDrops); - this.experimentalVK = parse('experimentalVK', this.experimentalVK); - this.focusCanvas = parse('focusCanvas', this.focusCanvas); - this.serviceWorker = parse('serviceWorker', this.serviceWorker); - this.gdextensionLibs = parse('gdextensionLibs', this.gdextensionLibs); - this.fileSizes = parse('fileSizes', this.fileSizes); - this.args = parse('args', this.args); - this.onExecute = parse('onExecute', this.onExecute); - this.onExit = parse('onExit', this.onExit); - }; - - /** - * @ignore - * @param {string} loadPath - * @param {Response} response - */ - Config.prototype.getModuleConfig = function (loadPath, response) { - let r = response; - const gdext = this.gdextensionLibs; - return { - 'print': this.onPrint, - 'printErr': this.onPrintError, - 'thisProgram': this.executable, - 'noExitRuntime': false, - 'dynamicLibraries': [`${loadPath}.side.wasm`].concat(this.gdextensionLibs), - 'instantiateWasm': function (imports, onSuccess) { - function done(result) { - onSuccess(result['instance'], result['module']); - } - if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') { - WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done); - } else { - r.arrayBuffer().then(function (buffer) { - WebAssembly.instantiate(buffer, imports).then(done); - }); - } - r = null; - return {}; - }, - 'locateFile': function (path) { - if (!path.startsWith('godot.')) { - return path; - } else if (path.endsWith('.worker.js')) { - return `${loadPath}.worker.js`; - } else if (path.endsWith('.audio.worklet.js')) { - return `${loadPath}.audio.worklet.js`; - } else if (path.endsWith('.js')) { - return `${loadPath}.js`; - } else if (path in gdext) { - return path; - } else if (path.endsWith('.side.wasm')) { - return `${loadPath}.side.wasm`; - } else if (path.endsWith('.wasm')) { - return `${loadPath}.wasm`; - } - return path; - }, - }; - }; - - /** - * @ignore - * @param {function()} cleanup - */ - Config.prototype.getGodotConfig = function (cleanup) { - // Try to find a canvas - if (!(this.canvas instanceof HTMLCanvasElement)) { - const nodes = document.getElementsByTagName('canvas'); - if (nodes.length && nodes[0] instanceof HTMLCanvasElement) { - const first = nodes[0]; - this.canvas = /** @type {!HTMLCanvasElement} */ (first); - } - if (!this.canvas) { - throw new Error('No canvas found in page'); - } - } - // Canvas can grab focus on click, or key events won't work. - if (this.canvas.tabIndex < 0) { - this.canvas.tabIndex = 0; - } - - // Browser locale, or custom one if defined. - let locale = this.locale; - if (!locale) { - locale = navigator.languages ? navigator.languages[0] : navigator.language; - locale = locale.split('.')[0]; - } - locale = locale.replace('-', '_'); - const onExit = this.onExit; - - // Godot configuration. - return { - 'canvas': this.canvas, - 'canvasResizePolicy': this.canvasResizePolicy, - 'locale': locale, - 'persistentDrops': this.persistentDrops, - 'virtualKeyboard': this.experimentalVK, - 'focusCanvas': this.focusCanvas, - 'onExecute': this.onExecute, - 'onExit': function (p_code) { - cleanup(); // We always need to call the cleanup callback to free memory. - if (typeof (onExit) === 'function') { - onExit(p_code); - } - }, - }; - }; - return new Config(initConfig); -}; - -/** - * Projects exported for the Web expose the :js:class:`Engine` class to the JavaScript environment, that allows - * fine control over the engine's start-up process. - * - * This API is built in an asynchronous manner and requires basic understanding - * of `Promises `__. - * - * @module Engine - * @header Web export JavaScript reference - */ -const Engine = (function () { - const preloader = new Preloader(); - - let loadPromise = null; - let loadPath = ''; - let initPromise = null; - - /** - * @classdesc The ``Engine`` class provides methods for loading and starting exported projects on the Web. For default export - * settings, this is already part of the exported HTML page. To understand practical use of the ``Engine`` class, - * see :ref:`Custom HTML page for Web export `. - * - * @description Create a new Engine instance with the given configuration. - * - * @global - * @constructor - * @param {EngineConfig} initConfig The initial config for this instance. - */ - function Engine(initConfig) { // eslint-disable-line no-shadow - this.config = new InternalConfig(initConfig); - this.rtenv = null; - } - - /** - * Load the engine from the specified base path. - * - * @param {string} basePath Base path of the engine to load. - * @param {number=} [size=0] The file size if known. - * @returns {Promise} A Promise that resolves once the engine is loaded. - * - * @function Engine.load - */ - Engine.load = function (basePath, size) { - if (loadPromise == null) { - loadPath = basePath; - loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true); - requestAnimationFrame(preloader.animateProgress); - } - return loadPromise; - }; - - /** - * Unload the engine to free memory. - * - * This method will be called automatically depending on the configuration. See :js:attr:`unloadAfterInit`. - * - * @function Engine.unload - */ - Engine.unload = function () { - loadPromise = null; - }; - - /** - * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution. - * @ignore - * @constructor - */ - function SafeEngine(initConfig) { - const proto = /** @lends Engine.prototype */ { - /** - * Initialize the engine instance. Optionally, pass the base path to the engine to load it, - * if it hasn't been loaded yet. See :js:meth:`Engine.load`. - * - * @param {string=} basePath Base path of the engine to load. - * @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized. - */ - init: function (basePath) { - if (initPromise) { - return initPromise; - } - if (loadPromise == null) { - if (!basePath) { - initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.')); - return initPromise; - } - Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]); - } - const me = this; - function doInit(promise) { - // Care! Promise chaining is bogus with old emscripten versions. - // This caused a regression with the Mono build (which uses an older emscripten version). - // Make sure to test that when refactoring. - return new Promise(function (resolve, reject) { - promise.then(function (response) { - const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] }); - Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) { - const paths = me.config.persistentPaths; - module['initFS'](paths).then(function (err) { - me.rtenv = module; - if (me.config.unloadAfterInit) { - Engine.unload(); - } - resolve(); - }); - }); - }); - }); - } - preloader.setProgressFunc(this.config.onProgress); - initPromise = doInit(loadPromise); - return initPromise; - }, - - /** - * Load a file so it is available in the instance's file system once it runs. Must be called **before** starting the - * instance. - * - * If not provided, the ``path`` is derived from the URL of the loaded file. - * - * @param {string|ArrayBuffer} file The file to preload. - * - * If a ``string`` the file will be loaded from that path. - * - * If an ``ArrayBuffer`` or a view on one, the buffer will used as the content of the file. - * - * @param {string=} path Path by which the file will be accessible. Required, if ``file`` is not a string. - * - * @returns {Promise} A Promise that resolves once the file is loaded. - */ - preloadFile: function (file, path) { - return preloader.preload(file, path, this.config.fileSizes[file]); - }, - - /** - * Start the engine instance using the given override configuration (if any). - * :js:meth:`startGame ` can be used in typical cases instead. - * - * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init `. - * The engine must be loaded beforehand. - * - * Fails if a canvas cannot be found on the page, or not specified in the configuration. - * - * @param {EngineConfig} override An optional configuration override. - * @return {Promise} Promise that resolves once the engine started. - */ - start: function (override) { - this.config.update(override); - const me = this; - return me.init().then(function () { - if (!me.rtenv) { - return Promise.reject(new Error('The engine must be initialized before it can be started')); - } - - let config = {}; - try { - config = me.config.getGodotConfig(function () { - me.rtenv = null; - }); - } catch (e) { - return Promise.reject(e); - } - // Godot configuration. - me.rtenv['initConfig'](config); - - // Preload GDExtension libraries. - if (me.config.gdextensionLibs.length > 0 && !me.rtenv['loadDynamicLibrary']) { - return Promise.reject(new Error('GDExtension libraries are not supported by this engine version. ' - + 'Enable "Extensions Support" for your export preset and/or build your custom template with "dlink_enabled=yes".')); - } - return new Promise(function (resolve, reject) { - for (const file of preloader.preloadedFiles) { - me.rtenv['copyToFS'](file.path, file.buffer); - } - preloader.preloadedFiles.length = 0; // Clear memory - me.rtenv['callMain'](me.config.args); - initPromise = null; - me.installServiceWorker(); - resolve(); - }); - }); - }, - - /** - * Start the game instance using the given configuration override (if any). - * - * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init `. - * - * This will load the engine if it is not loaded, and preload the main pck. - * - * This method expects the initial config (or the override) to have both the :js:attr:`executable` and :js:attr:`mainPack` - * properties set (normally done by the editor during export). - * - * @param {EngineConfig} override An optional configuration override. - * @return {Promise} Promise that resolves once the game started. - */ - startGame: function (override) { - this.config.update(override); - // Add main-pack argument. - const exe = this.config.executable; - const pack = this.config.mainPack || `${exe}.pck`; - this.config.args = ['--main-pack', pack].concat(this.config.args); - // Start and init with execName as loadPath if not inited. - const me = this; - return Promise.all([ - this.init(exe), - this.preloadFile(pack, pack), - ]).then(function () { - return me.start.apply(me); - }); - }, - - /** - * Create a file at the specified ``path`` with the passed as ``buffer`` in the instance's file system. - * - * @param {string} path The location where the file will be created. - * @param {ArrayBuffer} buffer The content of the file. - */ - copyToFS: function (path, buffer) { - if (this.rtenv == null) { - throw new Error('Engine must be inited before copying files'); - } - this.rtenv['copyToFS'](path, buffer); - }, - - /** - * Request that the current instance quit. - * - * This is akin the user pressing the close button in the window manager, and will - * have no effect if the engine has crashed, or is stuck in a loop. - * - */ - requestQuit: function () { - if (this.rtenv) { - this.rtenv['request_quit'](); - } - }, - - /** - * Install the progressive-web app service worker. - * @returns {Promise} The service worker registration promise. - */ - installServiceWorker: function () { - if (this.config.serviceWorker && 'serviceWorker' in navigator) { - return navigator.serviceWorker.register(this.config.serviceWorker); - } - return Promise.resolve(); - }, - }; - - Engine.prototype = proto; - // Closure compiler exported instance methods. - Engine.prototype['init'] = Engine.prototype.init; - Engine.prototype['preloadFile'] = Engine.prototype.preloadFile; - Engine.prototype['start'] = Engine.prototype.start; - Engine.prototype['startGame'] = Engine.prototype.startGame; - Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; - Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; - Engine.prototype['installServiceWorker'] = Engine.prototype.installServiceWorker; - // Also expose static methods as instance methods - Engine.prototype['load'] = Engine.load; - Engine.prototype['unload'] = Engine.unload; - return new Engine(initConfig); - } - - // Closure compiler exported static methods. - SafeEngine['load'] = Engine.load; - SafeEngine['unload'] = Engine.unload; - - // Feature-detection utilities. - SafeEngine['isWebGLAvailable'] = Features.isWebGLAvailable; - SafeEngine['isFetchAvailable'] = Features.isFetchAvailable; - SafeEngine['isSecureContext'] = Features.isSecureContext; - SafeEngine['isCrossOriginIsolated'] = Features.isCrossOriginIsolated; - SafeEngine['isSharedArrayBufferAvailable'] = Features.isSharedArrayBufferAvailable; - SafeEngine['isAudioWorkletAvailable'] = Features.isAudioWorkletAvailable; - SafeEngine['getMissingFeatures'] = Features.getMissingFeatures; - - return SafeEngine; -}()); -if (typeof window !== 'undefined') { - window['Engine'] = Engine; -} diff --git a/build/web/Test Project.pck b/build/web/Test Project.pck deleted file mode 100644 index 7965c68..0000000 Binary files a/build/web/Test Project.pck and /dev/null differ diff --git a/build/web/Test Project.png b/build/web/Test Project.png deleted file mode 100644 index 766b0b6..0000000 Binary files a/build/web/Test Project.png and /dev/null differ diff --git a/build/web/Test Project.wasm b/build/web/Test Project.wasm deleted file mode 100644 index 121549f..0000000 Binary files a/build/web/Test Project.wasm and /dev/null differ diff --git a/cyclops_settings.config b/cyclops_settings.config new file mode 100644 index 0000000..e69de29 diff --git a/export_presets.cfg b/export_presets.cfg index 5ea8789..8c1341b 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -9,7 +9,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/web/Test Project.html" +export_path="build/Test Project.html" encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false