diff --git a/TestScene.tscn b/TestScene.tscn
index 4c5850e..7174b91 100644
--- a/TestScene.tscn
+++ b/TestScene.tscn
@@ -19,7 +19,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0230917, 0, -0.0855939)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.951466, 0.283282, -0.120267, 0.221829, -0.360404, 0.906036, 0.213319, -0.888741, -0.405752, 0, 39.7095, 0)
-light_energy = 0.0
+light_energy = 0.774
shadow_enabled = true
shadow_opacity = 0.46
directional_shadow_pancake_size = 12.9
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/addons/cyclops_level_builder/art/cyclops1.png.import b/addons/cyclops_level_builder/art/cyclops1.png.import
new file mode 100644
index 0000000..881ae35
--- /dev/null
+++ b/addons/cyclops_level_builder/art/cyclops1.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://l4qj0lbj3ioa"
+path="res://.godot/imported/cyclops1.png-6f459321d21304ca30333893d06ffb09.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cyclops_level_builder/art/cyclops1.png"
+dest_files=["res://.godot/imported/cyclops1.png-6f459321d21304ca30333893d06ffb09.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/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/addons/cyclops_level_builder/art/cyclops2.png.import b/addons/cyclops_level_builder/art/cyclops2.png.import
new file mode 100644
index 0000000..8333681
--- /dev/null
+++ b/addons/cyclops_level_builder/art/cyclops2.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://oxrgrpeaamq3"
+path="res://.godot/imported/cyclops2.png-510e6418526608a41b6474466da5ef6e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cyclops_level_builder/art/cyclops2.png"
+dest_files=["res://.godot/imported/cyclops2.png-510e6418526608a41b6474466da5ef6e.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_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/addons/cyclops_level_builder/art/cyclops_16.png.import b/addons/cyclops_level_builder/art/cyclops_16.png.import
new file mode 100644
index 0000000..0c2777f
--- /dev/null
+++ b/addons/cyclops_level_builder/art/cyclops_16.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://df0a5uffpuqg3"
+path="res://.godot/imported/cyclops_16.png-f07e4f06ecdf5a5bd3bf65311c5a89e7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cyclops_level_builder/art/cyclops_16.png"
+dest_files=["res://.godot/imported/cyclops_16.png-f07e4f06ecdf5a5bd3bf65311c5a89e7.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_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 @@
+
+
+
+
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..9509dee
--- /dev/null
+++ b/addons/cyclops_level_builder/art/icons/arrow_down.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bor2x3t7fiqc2"
+path="res://.godot/imported/arrow_down.svg-2e1ff08c057ea7461c9327204e454db0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cyclops_level_builder/art/icons/arrow_down.svg"
+dest_files=["res://.godot/imported/arrow_down.svg-2e1ff08c057ea7461c9327204e454db0.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_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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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..fe69c4f
--- /dev/null
+++ b/addons/cyclops_level_builder/art/icons/create_cylinder.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0vye3ue3ayvf"
+path="res://.godot/imported/create_cylinder.svg-476a4bc6152ac152d747a20ef15c4e74.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/cyclops_level_builder/art/icons/create_cylinder.svg"
+dest_files=["res://.godot/imported/create_cylinder.svg-476a4bc6152ac152d747a20ef15c4e74.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_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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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..0a208fd
--- /dev/null
+++ b/addons/cyclops_level_builder/cyclops_level_builder.gd
@@ -0,0 +1,461 @@
+# 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..1dd5a76
--- /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://content/cardboard_material.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..fc4196a
--- /dev/null
+++ b/addons/cyclops_level_builder/materials/outline_material.tres
@@ -0,0 +1,8 @@
+[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://kw5ymmorwkvp"]
+
+[ext_resource type="Shader" uid="uid://cc5tovf48xmg1" path="res://addons/cyclops_level_builder/shaders/outline_shader.tres" id="1_i1fkq"]
+
+[resource]
+render_priority = 0
+shader = ExtResource("1_i1fkq")
+shader_parameter/ColorParameter = Color(0, 0, 0, 1)
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..66a387d
--- /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..f39feff
--- /dev/null
+++ b/addons/cyclops_level_builder/shaders/outline_shader.tres
@@ -0,0 +1,67 @@
+[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;
+
+
+}
+"
+graph_offset = Vector2(-10.4364, 103.709)
+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..ad3d116
--- /dev/null
+++ b/addons/cyclops_level_builder/shaders/tool_outline_shader.tres
@@ -0,0 +1,38 @@
+[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;
+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 += "" + name + ">"
+ 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/debug_draw_3d/LICENSE b/addons/debug_draw_3d/LICENSE
new file mode 100644
index 0000000..617a15b
--- /dev/null
+++ b/addons/debug_draw_3d/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 DmitriySalnikov
+
+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, andor 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/debug_draw_3d/README.md b/addons/debug_draw_3d/README.md
new file mode 100644
index 0000000..40fbfbd
--- /dev/null
+++ b/addons/debug_draw_3d/README.md
@@ -0,0 +1,162 @@
+
+
+# Debug drawing utility for Godot
+
+This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`.
+
+Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw)
+
+## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/)
+
+## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3)
+
+## Support me
+
+Your support adds motivation to develop my public projects.
+
+
+
+
+
+USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v
+
+## Features
+
+3D:
+
+* Arrow
+* Billboard opaque square
+* Box
+* Camera Frustum
+* Cylinder
+* Gizmo
+* Grid
+* Line
+* Line Path
+* Line with Arrow
+* Plane
+* Points
+* Position 3D (3 crossing axes)
+* Sphere
+
+2D:
+
+* **[Work in progress]**
+
+Overlay:
+
+* Text (with grouping and coloring)
+* FPS Graph
+* Custom Graphs
+
+Precompiled for:
+
+* Windows
+* Linux (built on Ubuntu 20.04)
+* macOS (10.14+)
+* Android (5.0+)
+* iOS
+* Web (Firefox not supported)
+
+This addon supports working with several World3D and different Viewports.
+There is also a no depth test mode and other settings that can be changed for each instance.
+
+This library supports double-precision builds, for more information, [see the documentation](https://dd3d.dmitriysalnikov.ru/docs/1.4.5/md_docs_2DoublePrecision.html).
+
+## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/)
+
+[](https://dd3d.dmitriysalnikov.ru/demo/)
+
+> [!WARNING]
+>
+> * Firefox most likely can't run this demo
+
+## Download
+
+To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page.
+
+For versions prior to `1.4.5`, just download one of the `source codes` in the assets. For newer versions, download `debug-draw-3d_[version].zip`.
+
+### Installation
+
+* Close editor
+* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist
+* Launch editor
+
+## Examples
+
+More examples can be found in the `examples_dd3d/` folder.
+
+Simple test:
+
+```gdscript
+func _process(delta: float) -> void:
+ var _time = Time.get_ticks_msec() / 1000.0
+ var box_pos = Vector3(0, sin(_time * 4), 0)
+ var line_begin = Vector3(-1, sin(_time * 4), 0)
+ var line_end = Vector3(1, cos(_time * 4), 0)
+
+ DebugDraw3D.draw_box(box_pos, Vector3(1, 2, 1), Color(0, 1, 0))
+ DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
+ DebugDraw2D.set_text("Time", _time)
+ DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
+ DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
+ DebugDraw2D.set_text("delta", delta)
+```
+
+
+
+An example of using scoped configs:
+
+```gdscript
+@tool
+extends Node3D
+
+func _ready():
+ # Set the base scoped_config.
+ # Each frame will be reset to these scoped values.
+ DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6)
+
+func _process(delta):
+ # Draw using the base scoped config.
+ DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE)
+ if true:
+ # Create a scoped config that will exist until exiting this if.
+ var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1)
+ # Draw with a thickness of 0
+ DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED)
+ # If necessary, the values inside this scope can be changed
+ # even before each call to draw_*.
+ _s.set_thickness(0.05)
+ DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET)
+```
+
+
+
+> [!TIP]
+>
+> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config!
+
+## API
+
+This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page.
+
+Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`).
+
+
+
+## Known issues and limitations
+
+The text in the keys and values of a text group cannot contain multi-line strings.
+
+The entire text overlay can only be placed in one corner, unlike `DataGraphs`.
+
+[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362).
+
+## More screenshots
+
+`DebugDrawDemoScene.tscn` in editor
+
+
+`DebugDrawDemoScene.tscn` in play mode
+
diff --git a/addons/debug_draw_3d/debug_draw_3d.gdextension b/addons/debug_draw_3d/debug_draw_3d.gdextension
new file mode 100644
index 0000000..9017632
--- /dev/null
+++ b/addons/debug_draw_3d/debug_draw_3d.gdextension
@@ -0,0 +1,153 @@
+[configuration]
+
+entry_symbol = "debug_draw_3d_library_init"
+compatibility_minimum = "4.1.4"
+reloadable = false
+
+[dependencies]
+
+; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", }
+; -------------------------------------
+; debug
+
+macos = { }
+windows.x86_64 = { }
+linux.x86_64 = { }
+
+; by default godot is using threads
+web.wasm32.nothreads = {}
+web.wasm32 = {}
+
+android.arm32 = { }
+android.arm64 = { }
+android.x86_32 = { }
+android.x86_64 = { }
+
+ios = {}
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release = { }
+windows.template_release.x86_64 = { }
+linux.template_release.x86_64 = { }
+
+web.template_release.wasm32.nothreads = { }
+web.template_release.wasm32 = { }
+
+android.template_release.arm32 = { }
+android.template_release.arm64 = { }
+android.template_release.x86_32 = { }
+android.template_release.x86_64 = { }
+
+ios.template_release = {}
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d = { }
+windows.template_release.x86_64.forced_dd3d = { }
+linux.template_release.x86_64.forced_dd3d = { }
+
+web.template_release.wasm32.nothreads.forced_dd3d = { }
+web.template_release.wasm32.forced_dd3d = { }
+
+ios.template_release.forced_dd3d = {}
+
+[libraries]
+
+; -------------------------------------
+; debug
+
+macos = "libs/libdd3d.macos.editor.universal.framework"
+windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll"
+linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so"
+
+web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm"
+web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm"
+
+android.arm32 = "libs/libdd3d.android.template_debug.arm32.so"
+android.arm64 = "libs/libdd3d.android.template_debug.arm64.so"
+android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so"
+android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so"
+
+ios = "libs/libdd3d.ios.template_debug.universal.dylib"
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release = "libs/libdd3d.macos.template_release.universal.framework"
+windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll"
+linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so"
+
+web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm"
+web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm"
+
+android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so"
+android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so"
+android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so"
+android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so"
+
+ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib"
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework"
+windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll"
+linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so"
+
+web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm"
+web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm"
+
+ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib"
+
+; -------------------------------------
+; DOUBLE PRECISION
+; -------------------------------------
+
+; -------------------------------------
+; debug
+
+macos.double = "libs/libdd3d.macos.editor.universal.double.framework"
+windows.x86_64.double = "libs/libdd3d.windows.editor.x86_64.double.dll"
+linux.x86_64.double = "libs/libdd3d.linux.editor.x86_64.double.so"
+
+web.wasm32.nothreads.double = "libs/libdd3d.web.template_debug.wasm32.double.wasm"
+web.wasm32.double = "libs/libdd3d.web.template_debug.wasm32.threads.double.wasm"
+
+android.arm32.double = "libs/libdd3d.android.template_debug.arm32.double.so"
+android.arm64.double = "libs/libdd3d.android.template_debug.arm64.double.so"
+android.x86_32.double = "libs/libdd3d.android.template_debug.x86_32.double.so"
+android.x86_64.double = "libs/libdd3d.android.template_debug.x86_64.double.so"
+
+ios.double = "libs/libdd3d.ios.template_debug.universal.dylib"
+
+; -------------------------------------
+; release no debug draw
+
+macos.template_release.double = "libs/libdd3d.macos.template_release.universal.double.framework"
+windows.template_release.x86_64.double = "libs/libdd3d.windows.template_release.x86_64.double.dll"
+linux.template_release.x86_64.double = "libs/libdd3d.linux.template_release.x86_64.double.so"
+
+web.template_release.wasm32.nothreads.double = "libs/libdd3d.web.template_release.wasm32.double.wasm"
+web.template_release.wasm32.double = "libs/libdd3d.web.template_release.wasm32.threads.double.wasm"
+
+android.template_release.arm32.double = "libs/libdd3d.android.template_release.arm32.double.so"
+android.template_release.arm64.double = "libs/libdd3d.android.template_release.arm64.double.so"
+android.template_release.x86_32.double = "libs/libdd3d.android.template_release.x86_32.double.so"
+android.template_release.x86_64.double = "libs/libdd3d.android.template_release.x86_64.double.so"
+
+ios.template_release.double = "libs/libdd3d.ios.template_release.universal.double.dylib"
+
+; -------------------------------------
+; release forced debug draw
+
+macos.template_release.forced_dd3d.double = "libs/libdd3d.macos.template_release.universal.enabled.double.framework"
+windows.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.windows.template_release.x86_64.enabled.double.dll"
+linux.template_release.x86_64.forced_dd3d.double = "libs/libdd3d.linux.template_release.x86_64.enabled.double.so"
+
+web.template_release.wasm32.nothreads.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.enabled.double.wasm"
+web.template_release.wasm32.forced_dd3d.double = "libs/libdd3d.web.template_release.wasm32.threads.enabled.double.wasm"
+
+ios.template_release.forced_dd3d.double = "libs/libdd3d.ios.template_release.universal.enabled.double.dylib"
diff --git a/addons/debug_draw_3d/libs/.gdignore b/addons/debug_draw_3d/libs/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so
new file mode 100644
index 0000000..d33ad3d
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so
new file mode 100644
index 0000000..5d36ab5
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so
new file mode 100644
index 0000000..a8a165f
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so
new file mode 100644
index 0000000..e17de52
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so
new file mode 100644
index 0000000..1422498
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so
new file mode 100644
index 0000000..fe76320
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so
new file mode 100644
index 0000000..f4870b9
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so
new file mode 100644
index 0000000..f71ef9d
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib
new file mode 100644
index 0000000..a009e34
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib
new file mode 100644
index 0000000..1011e20
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib b/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib
new file mode 100644
index 0000000..6ed3700
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so
new file mode 100644
index 0000000..f09ccad
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so
new file mode 100644
index 0000000..c044efa
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so
new file mode 100644
index 0000000..4016c35
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist
new file mode 100644
index 0000000..f478678
--- /dev/null
+++ b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.editor.universal.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.4.5
+ CFBundleShortVersionString
+ 1.4.5
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+
+
+
\ No newline at end of file
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib
new file mode 100644
index 0000000..8068462
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist
new file mode 100644
index 0000000..6728018
--- /dev/null
+++ b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.template_release.universal.enabled.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.4.5
+ CFBundleShortVersionString
+ 1.4.5
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+
+
+
\ No newline at end of file
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib
new file mode 100644
index 0000000..83d75e5
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist
new file mode 100644
index 0000000..bd05cdd
--- /dev/null
+++ b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ libdd3d.macos.template_release.universal.dylib
+ CFBundleName
+ Debug Draw 3D
+ CFBundleDisplayName
+ Debug Draw 3D
+ CFBundleIdentifier
+ ru.dmitriysalnikov.dd3d
+ NSHumanReadableCopyright
+ Copyright (c) Dmitriy Salnikov.
+ CFBundleVersion
+ 1.4.5
+ CFBundleShortVersionString
+ 1.4.5
+ CFBundlePackageType
+ FMWK
+ CSResourcesFileMapped
+
+ DTPlatformName
+ macosx
+ LSMinimumSystemVersion
+ 10.14
+
+
+
\ No newline at end of file
diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib
new file mode 100644
index 0000000..92aada2
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm
new file mode 100644
index 0000000..593d8d6
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm
new file mode 100644
index 0000000..2f7314e
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm
new file mode 100644
index 0000000..1f4011e
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm
new file mode 100644
index 0000000..66068a2
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm
new file mode 100644
index 0000000..575ca79
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm
new file mode 100644
index 0000000..dfbb1ce
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll b/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll
new file mode 100644
index 0000000..497a0a7
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll
new file mode 100644
index 0000000..63b3bb1
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll differ
diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll
new file mode 100644
index 0000000..c547b5a
Binary files /dev/null and b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll differ
diff --git a/ai/enemy.tres b/ai/enemy.tres
index 233bcea..8ed7aba 100644
--- a/ai/enemy.tres
+++ b/ai/enemy.tres
@@ -1,4 +1,4 @@
-[gd_resource type="BehaviorTree" load_steps=28 format=3 uid="uid://db7m2p0at5kmq"]
+[gd_resource type="BehaviorTree" load_steps=30 format=3 uid="uid://db7m2p0at5kmq"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_aewef"]
var/PlayerDistance/name = &"PlayerDistance"
@@ -11,6 +11,11 @@ var/LookingAngleToPlayer/type = 3
var/LookingAngleToPlayer/value = 0.0
var/LookingAngleToPlayer/hint = 0
var/LookingAngleToPlayer/hint_string = ""
+var/PlayerVisible/name = &"PlayerVisible"
+var/PlayerVisible/type = 1
+var/PlayerVisible/value = false
+var/PlayerVisible/hint = 0
+var/PlayerVisible/hint_string = ""
[sub_resource type="BBVariant" id="BBVariant_xy1ol"]
type = 3
@@ -24,14 +29,23 @@ value = SubResource("BBVariant_xy1ol")
[sub_resource type="BBVariant" id="BBVariant_b57jb"]
type = 3
-saved_value = 0.75
-resource_name = "0.75"
+saved_value = 0.28
+resource_name = "0.27999999999884"
[sub_resource type="BTCheckVar" id="BTCheckVar_75gjs"]
variable = &"LookingAngleToPlayer"
check_type = 3
value = SubResource("BBVariant_b57jb")
+[sub_resource type="BBVariant" id="BBVariant_nrjwm"]
+type = 1
+saved_value = true
+resource_name = "true"
+
+[sub_resource type="BTCheckVar" id="BTCheckVar_qmare"]
+variable = &"PlayerVisible"
+value = SubResource("BBVariant_nrjwm")
+
[sub_resource type="BBVariant" id="BBVariant_fnoth"]
type = 1
saved_value = true
@@ -75,9 +89,9 @@ children = [SubResource("BTProbability_3bm4s")]
[sub_resource type="BTAlwaysSucceed" id="BTAlwaysSucceed_5xj25"]
children = [SubResource("BTCooldown_3g17e")]
-[sub_resource type="BTSequence" id="BTSequence_wtx4r"]
+[sub_resource type="BTSequence" id="BTSequence_t4b71"]
custom_name = "Enemy Sees Player"
-children = [SubResource("BTCheckVar_7t8wr"), SubResource("BTCheckVar_75gjs"), SubResource("BTCallMethod_esnfv"), SubResource("BTCallMethod_rf34d"), SubResource("BTAlwaysSucceed_5xj25")]
+children = [SubResource("BTCheckVar_7t8wr"), SubResource("BTCheckVar_75gjs"), SubResource("BTCheckVar_qmare"), SubResource("BTCallMethod_esnfv"), SubResource("BTCallMethod_rf34d"), SubResource("BTAlwaysSucceed_5xj25")]
[sub_resource type="BBVariant" id="BBVariant_mxhbj"]
type = 1
@@ -119,7 +133,7 @@ custom_name = "Enemy doesn't see Player, Roaming"
children = [SubResource("BTCallMethod_ns46c"), SubResource("BTProbability_3yptl"), SubResource("BTWait_xq0e8")]
[sub_resource type="BTDynamicSelector" id="BTDynamicSelector_28yhc"]
-children = [SubResource("BTSequence_wtx4r"), SubResource("BTDynamicSequence_phkns")]
+children = [SubResource("BTSequence_t4b71"), SubResource("BTDynamicSequence_phkns")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_aewef")
diff --git a/audio/armorbreak/Armor Break 1.wav b/audio/armorbreak/Armor Break 1.wav
new file mode 100644
index 0000000..3c55ace
Binary files /dev/null and b/audio/armorbreak/Armor Break 1.wav differ
diff --git a/audio/armorbreak/Armor Break 1.wav.import b/audio/armorbreak/Armor Break 1.wav.import
new file mode 100644
index 0000000..cacef2b
--- /dev/null
+++ b/audio/armorbreak/Armor Break 1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dg4jmrpvifdrk"
+path="res://.godot/imported/Armor Break 1.wav-8a8213a5add173a1719940848abfca8d.sample"
+
+[deps]
+
+source_file="res://audio/armorbreak/Armor Break 1.wav"
+dest_files=["res://.godot/imported/Armor Break 1.wav-8a8213a5add173a1719940848abfca8d.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/armorbreak/Armor Break 2.wav b/audio/armorbreak/Armor Break 2.wav
new file mode 100644
index 0000000..dfccf26
Binary files /dev/null and b/audio/armorbreak/Armor Break 2.wav differ
diff --git a/audio/armorbreak/Armor Break 2.wav.import b/audio/armorbreak/Armor Break 2.wav.import
new file mode 100644
index 0000000..fcbcc29
--- /dev/null
+++ b/audio/armorbreak/Armor Break 2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://ds2s1qxaqboms"
+path="res://.godot/imported/Armor Break 2.wav-c82092066ffc61400b3ecb5dd21a691a.sample"
+
+[deps]
+
+source_file="res://audio/armorbreak/Armor Break 2.wav"
+dest_files=["res://.godot/imported/Armor Break 2.wav-c82092066ffc61400b3ecb5dd21a691a.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/armorbreak/Armor Break 3.wav b/audio/armorbreak/Armor Break 3.wav
new file mode 100644
index 0000000..68d134e
Binary files /dev/null and b/audio/armorbreak/Armor Break 3.wav differ
diff --git a/audio/armorbreak/Armor Break 3.wav.import b/audio/armorbreak/Armor Break 3.wav.import
new file mode 100644
index 0000000..db1579f
--- /dev/null
+++ b/audio/armorbreak/Armor Break 3.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dijtejkkki1cr"
+path="res://.godot/imported/Armor Break 3.wav-77fa5f2f3365c9c52e52cd5ac954d3ea.sample"
+
+[deps]
+
+source_file="res://audio/armorbreak/Armor Break 3.wav"
+dest_files=["res://.godot/imported/Armor Break 3.wav-77fa5f2f3365c9c52e52cd5ac954d3ea.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/armorbreak/armorbreak.tres b/audio/armorbreak/armorbreak.tres
new file mode 100644
index 0000000..dda23e5
--- /dev/null
+++ b/audio/armorbreak/armorbreak.tres
@@ -0,0 +1,10 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=3 format=3 uid="uid://dj5b4cn5ody1b"]
+
+[ext_resource type="AudioStream" uid="uid://dg4jmrpvifdrk" path="res://audio/armorbreak/Armor Break 1.wav" id="1_g764f"]
+[ext_resource type="AudioStream" uid="uid://dijtejkkki1cr" path="res://audio/armorbreak/Armor Break 3.wav" id="2_uylhy"]
+
+[resource]
+random_pitch = 1.25
+streams_count = 2
+stream_0/stream = ExtResource("1_g764f")
+stream_1/stream = ExtResource("2_uylhy")
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav
new file mode 100644
index 0000000..5be1586
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav.import
new file mode 100644
index 0000000..8c9616a
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_01.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://d0fwqrn3cvng4"
+path="res://.godot/imported/Footstep Carpet Running 1_01.wav-ed21fcefc97c6405f5c0d3d89a10c2c7.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_01.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_01.wav-ed21fcefc97c6405f5c0d3d89a10c2c7.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav
new file mode 100644
index 0000000..d63d358
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav.import
new file mode 100644
index 0000000..a88bea1
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_02.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://s05gn0oayh3b"
+path="res://.godot/imported/Footstep Carpet Running 1_02.wav-685f8bbbce1b272633f139b4553f248e.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_02.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_02.wav-685f8bbbce1b272633f139b4553f248e.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav
new file mode 100644
index 0000000..e719e68
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav.import
new file mode 100644
index 0000000..4ee2b88
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_03.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://jiquagkm6fcs"
+path="res://.godot/imported/Footstep Carpet Running 1_03.wav-679ec26b25983a747ca8755d03294a3a.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_03.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_03.wav-679ec26b25983a747ca8755d03294a3a.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav
new file mode 100644
index 0000000..42dbe55
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav.import
new file mode 100644
index 0000000..fc74268
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_04.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cqtt0jrgtqr1b"
+path="res://.godot/imported/Footstep Carpet Running 1_04.wav-3033b6c42eba166d2fc727dba814c5f9.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_04.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_04.wav-3033b6c42eba166d2fc727dba814c5f9.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav
new file mode 100644
index 0000000..9be3ae1
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav.import
new file mode 100644
index 0000000..f5c71c8
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_05.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cjxjm86u0hatp"
+path="res://.godot/imported/Footstep Carpet Running 1_05.wav-4665b67877e41f7789693ab15affacba.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_05.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_05.wav-4665b67877e41f7789693ab15affacba.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav
new file mode 100644
index 0000000..68846f8
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav.import
new file mode 100644
index 0000000..db7da01
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_06.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://tfle6uxjee7s"
+path="res://.godot/imported/Footstep Carpet Running 1_06.wav-ccfc18bc80382653581fc139f91e1c1e.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_06.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_06.wav-ccfc18bc80382653581fc139f91e1c1e.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav
new file mode 100644
index 0000000..f6bb000
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav.import
new file mode 100644
index 0000000..65a4e95
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_07.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cdhvcr4kkmgt1"
+path="res://.godot/imported/Footstep Carpet Running 1_07.wav-05c9c81d040bb204d20326d2131d37fc.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_07.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_07.wav-05c9c81d040bb204d20326d2131d37fc.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav
new file mode 100644
index 0000000..9a0a149
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav.import
new file mode 100644
index 0000000..c2a99a8
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_08.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://hlb1bh7fm74j"
+path="res://.godot/imported/Footstep Carpet Running 1_08.wav-f5ca7a3a01bd7fc7b05863bf06fda064.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_08.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_08.wav-f5ca7a3a01bd7fc7b05863bf06fda064.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav
new file mode 100644
index 0000000..327e569
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav.import
new file mode 100644
index 0000000..28457cd
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_09.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://3xrtbxrygism"
+path="res://.godot/imported/Footstep Carpet Running 1_09.wav-e756f691212cb87beaae716932dcfc40.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_09.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_09.wav-e756f691212cb87beaae716932dcfc40.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav b/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav
new file mode 100644
index 0000000..fe7298e
Binary files /dev/null and b/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav differ
diff --git a/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav.import b/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav.import
new file mode 100644
index 0000000..ec30d79
--- /dev/null
+++ b/audio/footsteps/sprint/Footstep Carpet Running 1_10.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cunqi1csfhr2j"
+path="res://.godot/imported/Footstep Carpet Running 1_10.wav-83495c0655798c1d7ccd911769eff214.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/sprint/Footstep Carpet Running 1_10.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Running 1_10.wav-83495c0655798c1d7ccd911769eff214.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/sprint/footsteps_running.tres b/audio/footsteps/sprint/footsteps_running.tres
new file mode 100644
index 0000000..c9e0fad
--- /dev/null
+++ b/audio/footsteps/sprint/footsteps_running.tres
@@ -0,0 +1,25 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=11 format=3 uid="uid://c0c1k0gmcd4yb"]
+
+[ext_resource type="AudioStream" uid="uid://d0fwqrn3cvng4" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_01.wav" id="1_hhyrx"]
+[ext_resource type="AudioStream" uid="uid://s05gn0oayh3b" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_02.wav" id="2_lyufr"]
+[ext_resource type="AudioStream" uid="uid://jiquagkm6fcs" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_03.wav" id="3_ipr8o"]
+[ext_resource type="AudioStream" uid="uid://cqtt0jrgtqr1b" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_04.wav" id="4_q2abj"]
+[ext_resource type="AudioStream" uid="uid://cjxjm86u0hatp" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_05.wav" id="5_antnx"]
+[ext_resource type="AudioStream" uid="uid://tfle6uxjee7s" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_06.wav" id="6_p1q50"]
+[ext_resource type="AudioStream" uid="uid://cdhvcr4kkmgt1" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_07.wav" id="7_ovid3"]
+[ext_resource type="AudioStream" uid="uid://hlb1bh7fm74j" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_08.wav" id="8_k5g5i"]
+[ext_resource type="AudioStream" uid="uid://3xrtbxrygism" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_09.wav" id="9_xu00p"]
+[ext_resource type="AudioStream" uid="uid://cunqi1csfhr2j" path="res://audio/footsteps/sprint/Footstep Carpet Running 1_10.wav" id="10_7yaga"]
+
+[resource]
+streams_count = 10
+stream_0/stream = ExtResource("1_hhyrx")
+stream_1/stream = ExtResource("2_lyufr")
+stream_2/stream = ExtResource("3_ipr8o")
+stream_3/stream = ExtResource("4_q2abj")
+stream_4/stream = ExtResource("5_antnx")
+stream_5/stream = ExtResource("6_p1q50")
+stream_6/stream = ExtResource("7_ovid3")
+stream_7/stream = ExtResource("8_k5g5i")
+stream_8/stream = ExtResource("9_xu00p")
+stream_9/stream = ExtResource("10_7yaga")
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav
new file mode 100644
index 0000000..9f8482d
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav.import
new file mode 100644
index 0000000..0bccd8d
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_01.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cd68armywc63l"
+path="res://.godot/imported/Footstep Carpet Walking 1_01.wav-9779e4c319c670f887155143b346ac11.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_01.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_01.wav-9779e4c319c670f887155143b346ac11.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav
new file mode 100644
index 0000000..6a501cc
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav.import
new file mode 100644
index 0000000..31258e5
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_02.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://iyidi7tjuthj"
+path="res://.godot/imported/Footstep Carpet Walking 1_02.wav-b424f3615145b03e112653e703ce8ab9.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_02.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_02.wav-b424f3615145b03e112653e703ce8ab9.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav
new file mode 100644
index 0000000..ef1bd98
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav.import
new file mode 100644
index 0000000..97ff684
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_03.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cnfix485ga7jx"
+path="res://.godot/imported/Footstep Carpet Walking 1_03.wav-1590901e4afed360cef80d624aa4a559.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_03.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_03.wav-1590901e4afed360cef80d624aa4a559.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav
new file mode 100644
index 0000000..9df81ad
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav.import
new file mode 100644
index 0000000..613ff1d
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_04.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c8moj6ehq6n3q"
+path="res://.godot/imported/Footstep Carpet Walking 1_04.wav-62439d5bea86f9f9cd64c57dd08ff0db.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_04.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_04.wav-62439d5bea86f9f9cd64c57dd08ff0db.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav
new file mode 100644
index 0000000..2a9c384
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav.import
new file mode 100644
index 0000000..39b0ced
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_05.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://m6emqit8sad5"
+path="res://.godot/imported/Footstep Carpet Walking 1_05.wav-fc3327a385c2223b954a48c4b1ace6a7.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_05.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_05.wav-fc3327a385c2223b954a48c4b1ace6a7.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav
new file mode 100644
index 0000000..4b46835
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav.import
new file mode 100644
index 0000000..73b0e06
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_06.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cf6fv448avrle"
+path="res://.godot/imported/Footstep Carpet Walking 1_06.wav-0dcbf37ec1b4f77e47bc1ff70a7a6602.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_06.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_06.wav-0dcbf37ec1b4f77e47bc1ff70a7a6602.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav
new file mode 100644
index 0000000..903a990
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav.import
new file mode 100644
index 0000000..d94feaa
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_07.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://7x8hiaygfn0m"
+path="res://.godot/imported/Footstep Carpet Walking 1_07.wav-6945fdb86cc795c6bce5ea6c7febe84e.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_07.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_07.wav-6945fdb86cc795c6bce5ea6c7febe84e.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav
new file mode 100644
index 0000000..26b208a
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav.import
new file mode 100644
index 0000000..e3353a1
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_08.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dbnw80vf2nwe3"
+path="res://.godot/imported/Footstep Carpet Walking 1_08.wav-9c7dd68f84719f2d8b4914fba8da09ff.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_08.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_08.wav-9c7dd68f84719f2d8b4914fba8da09ff.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav
new file mode 100644
index 0000000..ab49fd3
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav.import
new file mode 100644
index 0000000..2031c4f
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_09.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://3pdyxxirdtcv"
+path="res://.godot/imported/Footstep Carpet Walking 1_09.wav-6fe1d3a32ebb9c1412bbb965cac5bbac.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_09.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_09.wav-6fe1d3a32ebb9c1412bbb965cac5bbac.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav b/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav
new file mode 100644
index 0000000..708b2c9
Binary files /dev/null and b/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav differ
diff --git a/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav.import b/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav.import
new file mode 100644
index 0000000..7603f9c
--- /dev/null
+++ b/audio/footsteps/walk/Footstep Carpet Walking 1_10.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://b0n5dejvbv2ba"
+path="res://.godot/imported/Footstep Carpet Walking 1_10.wav-89f7c313636b2acf1f77fc56e51d946b.sample"
+
+[deps]
+
+source_file="res://audio/footsteps/walk/Footstep Carpet Walking 1_10.wav"
+dest_files=["res://.godot/imported/Footstep Carpet Walking 1_10.wav-89f7c313636b2acf1f77fc56e51d946b.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/footsteps/walk/footsteps_walking.tres b/audio/footsteps/walk/footsteps_walking.tres
new file mode 100644
index 0000000..5c046e9
--- /dev/null
+++ b/audio/footsteps/walk/footsteps_walking.tres
@@ -0,0 +1,25 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=11 format=3 uid="uid://dq8k15jwlxhw"]
+
+[ext_resource type="AudioStream" uid="uid://cd68armywc63l" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_01.wav" id="1_inv2y"]
+[ext_resource type="AudioStream" uid="uid://iyidi7tjuthj" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_02.wav" id="2_od45p"]
+[ext_resource type="AudioStream" uid="uid://cnfix485ga7jx" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_03.wav" id="3_m5pd6"]
+[ext_resource type="AudioStream" uid="uid://c8moj6ehq6n3q" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_04.wav" id="4_ar323"]
+[ext_resource type="AudioStream" uid="uid://m6emqit8sad5" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_05.wav" id="5_kr4xa"]
+[ext_resource type="AudioStream" uid="uid://cf6fv448avrle" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_06.wav" id="6_83yel"]
+[ext_resource type="AudioStream" uid="uid://7x8hiaygfn0m" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_07.wav" id="7_c7dr5"]
+[ext_resource type="AudioStream" uid="uid://dbnw80vf2nwe3" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_08.wav" id="8_jaxwg"]
+[ext_resource type="AudioStream" uid="uid://3pdyxxirdtcv" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_09.wav" id="9_lgd58"]
+[ext_resource type="AudioStream" uid="uid://b0n5dejvbv2ba" path="res://audio/footsteps/walk/Footstep Carpet Walking 1_10.wav" id="10_4p12k"]
+
+[resource]
+streams_count = 10
+stream_0/stream = ExtResource("1_inv2y")
+stream_1/stream = ExtResource("2_od45p")
+stream_2/stream = ExtResource("3_m5pd6")
+stream_3/stream = ExtResource("4_ar323")
+stream_4/stream = ExtResource("5_kr4xa")
+stream_5/stream = ExtResource("6_83yel")
+stream_6/stream = ExtResource("7_c7dr5")
+stream_7/stream = ExtResource("8_jaxwg")
+stream_8/stream = ExtResource("9_lgd58")
+stream_9/stream = ExtResource("10_4p12k")
diff --git a/audio/gun/Assault Rifle Shot 1.wav b/audio/gun/Assault Rifle Shot 1.wav
new file mode 100644
index 0000000..ff1c9f0
Binary files /dev/null and b/audio/gun/Assault Rifle Shot 1.wav differ
diff --git a/audio/gun/Assault Rifle Shot 1.wav.import b/audio/gun/Assault Rifle Shot 1.wav.import
new file mode 100644
index 0000000..5803a20
--- /dev/null
+++ b/audio/gun/Assault Rifle Shot 1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://xuxk1kkbsoi5"
+path="res://.godot/imported/Assault Rifle Shot 1.wav-7d452461ac069d92a9ca94e50a246d6f.sample"
+
+[deps]
+
+source_file="res://audio/gun/Assault Rifle Shot 1.wav"
+dest_files=["res://.godot/imported/Assault Rifle Shot 1.wav-7d452461ac069d92a9ca94e50a246d6f.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/gun/Assault Rifle Shot 2.wav b/audio/gun/Assault Rifle Shot 2.wav
new file mode 100644
index 0000000..9bdec4e
Binary files /dev/null and b/audio/gun/Assault Rifle Shot 2.wav differ
diff --git a/audio/gun/Assault Rifle Shot 2.wav.import b/audio/gun/Assault Rifle Shot 2.wav.import
new file mode 100644
index 0000000..de65435
--- /dev/null
+++ b/audio/gun/Assault Rifle Shot 2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://ch8cprpceoctk"
+path="res://.godot/imported/Assault Rifle Shot 2.wav-096cd598ecfaf5fe0a857e960e404d02.sample"
+
+[deps]
+
+source_file="res://audio/gun/Assault Rifle Shot 2.wav"
+dest_files=["res://.godot/imported/Assault Rifle Shot 2.wav-096cd598ecfaf5fe0a857e960e404d02.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/gun/Assault Rifle Shot 3.wav b/audio/gun/Assault Rifle Shot 3.wav
new file mode 100644
index 0000000..aa75ca6
Binary files /dev/null and b/audio/gun/Assault Rifle Shot 3.wav differ
diff --git a/audio/gun/Assault Rifle Shot 3.wav.import b/audio/gun/Assault Rifle Shot 3.wav.import
new file mode 100644
index 0000000..9051d14
--- /dev/null
+++ b/audio/gun/Assault Rifle Shot 3.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c7ny1uk2gfbu6"
+path="res://.godot/imported/Assault Rifle Shot 3.wav-b3af660c6e5158934e4bf20aef400bae.sample"
+
+[deps]
+
+source_file="res://audio/gun/Assault Rifle Shot 3.wav"
+dest_files=["res://.godot/imported/Assault Rifle Shot 3.wav-b3af660c6e5158934e4bf20aef400bae.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/gun/Assault Rifle Shot 4.wav b/audio/gun/Assault Rifle Shot 4.wav
new file mode 100644
index 0000000..565ecd6
Binary files /dev/null and b/audio/gun/Assault Rifle Shot 4.wav differ
diff --git a/audio/gun/Assault Rifle Shot 4.wav.import b/audio/gun/Assault Rifle Shot 4.wav.import
new file mode 100644
index 0000000..a6d2e59
--- /dev/null
+++ b/audio/gun/Assault Rifle Shot 4.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cqh4008f4ev3y"
+path="res://.godot/imported/Assault Rifle Shot 4.wav-a1eaf890933e06352120ea81424fa9cf.sample"
+
+[deps]
+
+source_file="res://audio/gun/Assault Rifle Shot 4.wav"
+dest_files=["res://.godot/imported/Assault Rifle Shot 4.wav-a1eaf890933e06352120ea81424fa9cf.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/gun/Assault Rifle Shot 6.wav b/audio/gun/Assault Rifle Shot 6.wav
new file mode 100644
index 0000000..b576b52
Binary files /dev/null and b/audio/gun/Assault Rifle Shot 6.wav differ
diff --git a/audio/gun/Assault Rifle Shot 6.wav.import b/audio/gun/Assault Rifle Shot 6.wav.import
new file mode 100644
index 0000000..f7c9277
--- /dev/null
+++ b/audio/gun/Assault Rifle Shot 6.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://r76ijywj4sib"
+path="res://.godot/imported/Assault Rifle Shot 6.wav-71af1c07f4e8f8daa30f7e202902af4a.sample"
+
+[deps]
+
+source_file="res://audio/gun/Assault Rifle Shot 6.wav"
+dest_files=["res://.godot/imported/Assault Rifle Shot 6.wav-71af1c07f4e8f8daa30f7e202902af4a.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/gun/assaultrifle.tres b/audio/gun/assaultrifle.tres
new file mode 100644
index 0000000..a70eca5
--- /dev/null
+++ b/audio/gun/assaultrifle.tres
@@ -0,0 +1,15 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=6 format=3 uid="uid://cn33abuyca8so"]
+
+[ext_resource type="AudioStream" uid="uid://xuxk1kkbsoi5" path="res://audio/gun/Assault Rifle Shot 1.wav" id="1_7sbe0"]
+[ext_resource type="AudioStream" uid="uid://ch8cprpceoctk" path="res://audio/gun/Assault Rifle Shot 2.wav" id="2_5tah1"]
+[ext_resource type="AudioStream" uid="uid://c7ny1uk2gfbu6" path="res://audio/gun/Assault Rifle Shot 3.wav" id="3_s56gj"]
+[ext_resource type="AudioStream" uid="uid://cqh4008f4ev3y" path="res://audio/gun/Assault Rifle Shot 4.wav" id="4_k6thi"]
+[ext_resource type="AudioStream" uid="uid://r76ijywj4sib" path="res://audio/gun/Assault Rifle Shot 6.wav" id="5_1a0de"]
+
+[resource]
+streams_count = 5
+stream_0/stream = ExtResource("1_7sbe0")
+stream_1/stream = ExtResource("2_5tah1")
+stream_2/stream = ExtResource("3_s56gj")
+stream_3/stream = ExtResource("4_k6thi")
+stream_4/stream = ExtResource("5_1a0de")
diff --git a/audio/playerhit/Punch Impact (Flesh) 1.wav b/audio/playerhit/Punch Impact (Flesh) 1.wav
new file mode 100644
index 0000000..6213cf5
Binary files /dev/null and b/audio/playerhit/Punch Impact (Flesh) 1.wav differ
diff --git a/audio/playerhit/Punch Impact (Flesh) 1.wav.import b/audio/playerhit/Punch Impact (Flesh) 1.wav.import
new file mode 100644
index 0000000..a065291
--- /dev/null
+++ b/audio/playerhit/Punch Impact (Flesh) 1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://f31466u47rhb"
+path="res://.godot/imported/Punch Impact (Flesh) 1.wav-3a580ea2db01709d767c3c194f4d7398.sample"
+
+[deps]
+
+source_file="res://audio/playerhit/Punch Impact (Flesh) 1.wav"
+dest_files=["res://.godot/imported/Punch Impact (Flesh) 1.wav-3a580ea2db01709d767c3c194f4d7398.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/playerhit/Punch Impact (Flesh) 2.wav b/audio/playerhit/Punch Impact (Flesh) 2.wav
new file mode 100644
index 0000000..bbc892e
Binary files /dev/null and b/audio/playerhit/Punch Impact (Flesh) 2.wav differ
diff --git a/audio/playerhit/Punch Impact (Flesh) 2.wav.import b/audio/playerhit/Punch Impact (Flesh) 2.wav.import
new file mode 100644
index 0000000..a7dca87
--- /dev/null
+++ b/audio/playerhit/Punch Impact (Flesh) 2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://djll8dguahmct"
+path="res://.godot/imported/Punch Impact (Flesh) 2.wav-eeec17337b4e30551c5ec1777755a8ab.sample"
+
+[deps]
+
+source_file="res://audio/playerhit/Punch Impact (Flesh) 2.wav"
+dest_files=["res://.godot/imported/Punch Impact (Flesh) 2.wav-eeec17337b4e30551c5ec1777755a8ab.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/playerhit/Punch Impact (Flesh) 3.wav b/audio/playerhit/Punch Impact (Flesh) 3.wav
new file mode 100644
index 0000000..486c15d
Binary files /dev/null and b/audio/playerhit/Punch Impact (Flesh) 3.wav differ
diff --git a/audio/playerhit/Punch Impact (Flesh) 3.wav.import b/audio/playerhit/Punch Impact (Flesh) 3.wav.import
new file mode 100644
index 0000000..cfb94a8
--- /dev/null
+++ b/audio/playerhit/Punch Impact (Flesh) 3.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://bt1ois31cbuh5"
+path="res://.godot/imported/Punch Impact (Flesh) 3.wav-09581eeb9fcabf47416a2560c287420b.sample"
+
+[deps]
+
+source_file="res://audio/playerhit/Punch Impact (Flesh) 3.wav"
+dest_files=["res://.godot/imported/Punch Impact (Flesh) 3.wav-09581eeb9fcabf47416a2560c287420b.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/playerhit/Punch Impact (Flesh) 4.wav b/audio/playerhit/Punch Impact (Flesh) 4.wav
new file mode 100644
index 0000000..1fae50d
Binary files /dev/null and b/audio/playerhit/Punch Impact (Flesh) 4.wav differ
diff --git a/audio/playerhit/Punch Impact (Flesh) 4.wav.import b/audio/playerhit/Punch Impact (Flesh) 4.wav.import
new file mode 100644
index 0000000..bab7f51
--- /dev/null
+++ b/audio/playerhit/Punch Impact (Flesh) 4.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://gv2ieeyk6d00"
+path="res://.godot/imported/Punch Impact (Flesh) 4.wav-9cc4a81454d1317a88313bafafc20ad1.sample"
+
+[deps]
+
+source_file="res://audio/playerhit/Punch Impact (Flesh) 4.wav"
+dest_files=["res://.godot/imported/Punch Impact (Flesh) 4.wav-9cc4a81454d1317a88313bafafc20ad1.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/playerhit/playerhit.tres b/audio/playerhit/playerhit.tres
new file mode 100644
index 0000000..4a70ef4
--- /dev/null
+++ b/audio/playerhit/playerhit.tres
@@ -0,0 +1,13 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=5 format=3 uid="uid://sisk81utsw72"]
+
+[ext_resource type="AudioStream" uid="uid://f31466u47rhb" path="res://audio/playerhit/Punch Impact (Flesh) 1.wav" id="1_83wiu"]
+[ext_resource type="AudioStream" uid="uid://djll8dguahmct" path="res://audio/playerhit/Punch Impact (Flesh) 2.wav" id="2_bvrlc"]
+[ext_resource type="AudioStream" uid="uid://bt1ois31cbuh5" path="res://audio/playerhit/Punch Impact (Flesh) 3.wav" id="3_o6fvc"]
+[ext_resource type="AudioStream" uid="uid://gv2ieeyk6d00" path="res://audio/playerhit/Punch Impact (Flesh) 4.wav" id="4_o6vqx"]
+
+[resource]
+streams_count = 4
+stream_0/stream = ExtResource("1_83wiu")
+stream_1/stream = ExtResource("2_bvrlc")
+stream_2/stream = ExtResource("3_o6fvc")
+stream_3/stream = ExtResource("4_o6vqx")
diff --git a/audio/startup/Armor On 2.wav b/audio/startup/Armor On 2.wav
new file mode 100644
index 0000000..fa451f2
Binary files /dev/null and b/audio/startup/Armor On 2.wav differ
diff --git a/audio/startup/Armor On 2.wav.import b/audio/startup/Armor On 2.wav.import
new file mode 100644
index 0000000..03c86c3
--- /dev/null
+++ b/audio/startup/Armor On 2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://d6805i7fj3at"
+path="res://.godot/imported/Armor On 2.wav-bd89da1d200fc8b5b74c1944b41e8009.sample"
+
+[deps]
+
+source_file="res://audio/startup/Armor On 2.wav"
+dest_files=["res://.godot/imported/Armor On 2.wav-bd89da1d200fc8b5b74c1944b41e8009.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Armor On.wav b/audio/startup/Armor On.wav
new file mode 100644
index 0000000..8640e82
Binary files /dev/null and b/audio/startup/Armor On.wav differ
diff --git a/audio/startup/Armor On.wav.import b/audio/startup/Armor On.wav.import
new file mode 100644
index 0000000..2d7d2c9
--- /dev/null
+++ b/audio/startup/Armor On.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://b3hqddf6m3hrh"
+path="res://.godot/imported/Armor On.wav-0f618bc6daf9daa10ec5f7acc39b1ee8.sample"
+
+[deps]
+
+source_file="res://audio/startup/Armor On.wav"
+dest_files=["res://.godot/imported/Armor On.wav-0f618bc6daf9daa10ec5f7acc39b1ee8.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Bullet In 2.wav b/audio/startup/Bullet In 2.wav
new file mode 100644
index 0000000..16ab912
Binary files /dev/null and b/audio/startup/Bullet In 2.wav differ
diff --git a/audio/startup/Bullet In 2.wav.import b/audio/startup/Bullet In 2.wav.import
new file mode 100644
index 0000000..c073cf6
--- /dev/null
+++ b/audio/startup/Bullet In 2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c2jmfoq3s4emc"
+path="res://.godot/imported/Bullet In 2.wav-71e0f195a3a62560490242573493f4d5.sample"
+
+[deps]
+
+source_file="res://audio/startup/Bullet In 2.wav"
+dest_files=["res://.godot/imported/Bullet In 2.wav-71e0f195a3a62560490242573493f4d5.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Bullet In 3.wav b/audio/startup/Bullet In 3.wav
new file mode 100644
index 0000000..fe2bfc2
Binary files /dev/null and b/audio/startup/Bullet In 3.wav differ
diff --git a/audio/startup/Bullet In 3.wav.import b/audio/startup/Bullet In 3.wav.import
new file mode 100644
index 0000000..3e9ae61
--- /dev/null
+++ b/audio/startup/Bullet In 3.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://3p5krok53q7m"
+path="res://.godot/imported/Bullet In 3.wav-5d0e604b14c951fd16b94b71c82c8e1a.sample"
+
+[deps]
+
+source_file="res://audio/startup/Bullet In 3.wav"
+dest_files=["res://.godot/imported/Bullet In 3.wav-5d0e604b14c951fd16b94b71c82c8e1a.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Bullet In 4.wav b/audio/startup/Bullet In 4.wav
new file mode 100644
index 0000000..bd31722
Binary files /dev/null and b/audio/startup/Bullet In 4.wav differ
diff --git a/audio/startup/Bullet In 4.wav.import b/audio/startup/Bullet In 4.wav.import
new file mode 100644
index 0000000..048f33e
--- /dev/null
+++ b/audio/startup/Bullet In 4.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c0gcxb4vtb8rr"
+path="res://.godot/imported/Bullet In 4.wav-a8999c76a283814fc9124351dff61c40.sample"
+
+[deps]
+
+source_file="res://audio/startup/Bullet In 4.wav"
+dest_files=["res://.godot/imported/Bullet In 4.wav-a8999c76a283814fc9124351dff61c40.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Bullet In 5.wav b/audio/startup/Bullet In 5.wav
new file mode 100644
index 0000000..d6ab16d
Binary files /dev/null and b/audio/startup/Bullet In 5.wav differ
diff --git a/audio/startup/Bullet In 5.wav.import b/audio/startup/Bullet In 5.wav.import
new file mode 100644
index 0000000..34bf638
--- /dev/null
+++ b/audio/startup/Bullet In 5.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dt64sagcd3tkh"
+path="res://.godot/imported/Bullet In 5.wav-b45efebb6539efd0671ac3ce60f4b9f2.sample"
+
+[deps]
+
+source_file="res://audio/startup/Bullet In 5.wav"
+dest_files=["res://.godot/imported/Bullet In 5.wav-b45efebb6539efd0671ac3ce60f4b9f2.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/Bullet In.wav b/audio/startup/Bullet In.wav
new file mode 100644
index 0000000..8b7a398
Binary files /dev/null and b/audio/startup/Bullet In.wav differ
diff --git a/audio/startup/Bullet In.wav.import b/audio/startup/Bullet In.wav.import
new file mode 100644
index 0000000..650116b
--- /dev/null
+++ b/audio/startup/Bullet In.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://g4p817n32px8"
+path="res://.godot/imported/Bullet In.wav-96c5ce8733ba2ea50ce20654d7fcd15b.sample"
+
+[deps]
+
+source_file="res://audio/startup/Bullet In.wav"
+dest_files=["res://.godot/imported/Bullet In.wav-96c5ce8733ba2ea50ce20654d7fcd15b.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/audio/startup/startup.tres b/audio/startup/startup.tres
new file mode 100644
index 0000000..29c1885
--- /dev/null
+++ b/audio/startup/startup.tres
@@ -0,0 +1,21 @@
+[gd_resource type="AudioStreamRandomizer" load_steps=8 format=3 uid="uid://ba5iu681ldn1r"]
+
+[ext_resource type="AudioStream" uid="uid://c2jmfoq3s4emc" path="res://audio/startup/Bullet In 2.wav" id="1_da377"]
+[ext_resource type="AudioStream" uid="uid://3p5krok53q7m" path="res://audio/startup/Bullet In 3.wav" id="2_iyajk"]
+[ext_resource type="AudioStream" uid="uid://c0gcxb4vtb8rr" path="res://audio/startup/Bullet In 4.wav" id="3_60xsq"]
+[ext_resource type="AudioStream" uid="uid://dt64sagcd3tkh" path="res://audio/startup/Bullet In 5.wav" id="4_cgpum"]
+[ext_resource type="AudioStream" uid="uid://g4p817n32px8" path="res://audio/startup/Bullet In.wav" id="5_syyuk"]
+[ext_resource type="AudioStream" uid="uid://b3hqddf6m3hrh" path="res://audio/startup/Armor On.wav" id="6_irqt3"]
+[ext_resource type="AudioStream" uid="uid://d6805i7fj3at" path="res://audio/startup/Armor On 2.wav" id="7_xhdyr"]
+
+[resource]
+streams_count = 7
+stream_0/stream = ExtResource("1_da377")
+stream_1/stream = ExtResource("2_iyajk")
+stream_2/stream = ExtResource("3_60xsq")
+stream_3/stream = ExtResource("4_cgpum")
+stream_4/stream = ExtResource("5_syyuk")
+stream_5/stream = ExtResource("6_irqt3")
+stream_5/weight = 2.5
+stream_6/stream = ExtResource("7_xhdyr")
+stream_6/weight = 2.5
diff --git a/content/IndieFlower-Regular.ttf b/content/IndieFlower-Regular.ttf
new file mode 100644
index 0000000..3774ef5
Binary files /dev/null and b/content/IndieFlower-Regular.ttf differ
diff --git a/content/IndieFlower-Regular.ttf.import b/content/IndieFlower-Regular.ttf.import
new file mode 100644
index 0000000..b309ea3
--- /dev/null
+++ b/content/IndieFlower-Regular.ttf.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://dxyqmtf7gmhfk"
+path="res://.godot/imported/IndieFlower-Regular.ttf-586b1270e2f35b3439bb6e47a2c0aac6.fontdata"
+
+[deps]
+
+source_file="res://content/IndieFlower-Regular.ttf"
+dest_files=["res://.godot/imported/IndieFlower-Regular.ttf-586b1270e2f35b3439bb6e47a2c0aac6.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/content/Logo2.png b/content/Logo2.png
new file mode 100644
index 0000000..1b5631f
Binary files /dev/null and b/content/Logo2.png differ
diff --git a/content/Logo2.png.import b/content/Logo2.png.import
new file mode 100644
index 0000000..08af657
--- /dev/null
+++ b/content/Logo2.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bctvdjk0xedjs"
+path.s3tc="res://.godot/imported/Logo2.png-3c67592a111ef088d049a5979075c0b5.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/Logo2.png"
+dest_files=["res://.godot/imported/Logo2.png-3c67592a111ef088d049a5979075c0b5.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/content/cardboard/Cardboard004/Cardboard004.png b/content/cardboard/Cardboard004/Cardboard004.png
new file mode 100644
index 0000000..fdbac36
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004.png.import b/content/cardboard/Cardboard004/Cardboard004.png.import
new file mode 100644
index 0000000..6865be7
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://p80iif1puoi"
+path="res://.godot/imported/Cardboard004.png-43c6537819902ee855179f3b130ed52d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004.png"
+dest_files=["res://.godot/imported/Cardboard004.png-43c6537819902ee855179f3b130ed52d.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/content/cardboard/Cardboard004/Cardboard004.tres b/content/cardboard/Cardboard004/Cardboard004.tres
new file mode 100644
index 0000000..8436eeb
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StandardMaterial3D" load_steps=5 format=3 uid="uid://dcaorx27eky1t"]
+
+[ext_resource type="Texture2D" uid="uid://du6dqldtyqxub" path="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png" id="1_vvorp"]
+[ext_resource type="Texture2D" uid="uid://w1qjoj4t60pe" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_Displacement.png" id="2_lscyd"]
+[ext_resource type="Texture2D" uid="uid://bwayhsly108wo" path="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png" id="3_j3rim"]
+[ext_resource type="Texture2D" uid="uid://bnomx0psm72hp" path="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png" id="4_266b4"]
+
+[resource]
+diffuse_mode = 3
+specular_mode = 1
+albedo_texture = ExtResource("1_vvorp")
+roughness_texture = ExtResource("4_266b4")
+normal_enabled = true
+normal_texture = ExtResource("3_j3rim")
+heightmap_enabled = true
+heightmap_scale = 6.455
+heightmap_texture = ExtResource("2_lscyd")
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG.mtlx b/content/cardboard/Cardboard004/Cardboard004_2K-PNG.mtlx
new file mode 100644
index 0000000..56ff7d7
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG.mtlx
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG.usdc b/content/cardboard/Cardboard004/Cardboard004_2K-PNG.usdc
new file mode 100644
index 0000000..6e3da84
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG.usdc differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png
new file mode 100644
index 0000000..2028690
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png.import b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png.import
new file mode 100644
index 0000000..a4cb9db
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://du6dqldtyqxub"
+path.s3tc="res://.godot/imported/Cardboard004_2K-PNG_Color.png-af8d8ca9335f4cc6fded2b865d2e2b3b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_Color.png"
+dest_files=["res://.godot/imported/Cardboard004_2K-PNG_Color.png-af8d8ca9335f4cc6fded2b865d2e2b3b.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/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png
new file mode 100644
index 0000000..201ef95
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png.import b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png.import
new file mode 100644
index 0000000..a15cc69
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bjfq5ifaymhph"
+path="res://.godot/imported/Cardboard004_2K-PNG_Displacement.png-11cbcbcc4e88e8e7ca1af4eab6339741.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_Displacement.png"
+dest_files=["res://.godot/imported/Cardboard004_2K-PNG_Displacement.png-11cbcbcc4e88e8e7ca1af4eab6339741.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/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png
new file mode 100644
index 0000000..6bea641
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png.import b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png.import
new file mode 100644
index 0000000..e454d2f
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bxbjpm2eqnalk"
+path="res://.godot/imported/Cardboard004_2K-PNG_NormalDX.png-e1acd2d5180374c2f6db4d8af21a56f8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalDX.png"
+dest_files=["res://.godot/imported/Cardboard004_2K-PNG_NormalDX.png-e1acd2d5180374c2f6db4d8af21a56f8.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/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png
new file mode 100644
index 0000000..77d32e7
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png.import b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png.import
new file mode 100644
index 0000000..c9ec046
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bwayhsly108wo"
+path.s3tc="res://.godot/imported/Cardboard004_2K-PNG_NormalGL.png-34a7a135803c38677965e11613ebf776.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png"
+dest_files=["res://.godot/imported/Cardboard004_2K-PNG_NormalGL.png-34a7a135803c38677965e11613ebf776.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=1
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=1
+roughness/src_normal="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_NormalGL.png"
+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/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png
new file mode 100644
index 0000000..f64a25b
Binary files /dev/null and b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png differ
diff --git a/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png.import b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png.import
new file mode 100644
index 0000000..5cec103
--- /dev/null
+++ b/content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bnomx0psm72hp"
+path.s3tc="res://.godot/imported/Cardboard004_2K-PNG_Roughness.png-c3c958b69638e7636773cd9e8d08394d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Cardboard004/Cardboard004_2K-PNG_Roughness.png"
+dest_files=["res://.godot/imported/Cardboard004_2K-PNG_Roughness.png-c3c958b69638e7636773cd9e8d08394d.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/content/cardboard/Paper/Paper.tres b/content/cardboard/Paper/Paper.tres
new file mode 100644
index 0000000..b906c5a
--- /dev/null
+++ b/content/cardboard/Paper/Paper.tres
@@ -0,0 +1,20 @@
+[gd_resource type="StandardMaterial3D" load_steps=4 format=3 uid="uid://ny6k7xqwvbut"]
+
+[ext_resource type="Texture2D" uid="uid://cvw2rvje403og" path="res://content/cardboard/Paper/Paper005_2K-PNG_Displacement.png" id="2_wjb5g"]
+[ext_resource type="Texture2D" uid="uid://djtg2ykgjylk8" path="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png" id="3_fl1ka"]
+[ext_resource type="Texture2D" uid="uid://ct83jsi7k2slj" path="res://content/cardboard/Paper/Paper005_2K-PNG_Roughness.png" id="4_0ih8e"]
+
+[resource]
+cull_mode = 2
+diffuse_mode = 3
+specular_mode = 2
+roughness_texture = ExtResource("4_0ih8e")
+normal_enabled = true
+normal_texture = ExtResource("3_fl1ka")
+heightmap_enabled = true
+heightmap_texture = ExtResource("2_wjb5g")
+billboard_mode = 3
+billboard_keep_scale = true
+particles_anim_h_frames = 1
+particles_anim_v_frames = 1
+particles_anim_loop = false
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_Color.png b/content/cardboard/Paper/Paper005_2K-PNG_Color.png
new file mode 100644
index 0000000..d01f715
Binary files /dev/null and b/content/cardboard/Paper/Paper005_2K-PNG_Color.png differ
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_Color.png.import b/content/cardboard/Paper/Paper005_2K-PNG_Color.png.import
new file mode 100644
index 0000000..c65d522
--- /dev/null
+++ b/content/cardboard/Paper/Paper005_2K-PNG_Color.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cruj1ph2x2n2a"
+path.s3tc="res://.godot/imported/Paper005_2K-PNG_Color.png-9db58dcf3755585e2f7404eefdfedee0.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Paper/Paper005_2K-PNG_Color.png"
+dest_files=["res://.godot/imported/Paper005_2K-PNG_Color.png-9db58dcf3755585e2f7404eefdfedee0.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/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png b/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png
new file mode 100644
index 0000000..422435b
Binary files /dev/null and b/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png differ
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png.import b/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png.import
new file mode 100644
index 0000000..37f33c8
--- /dev/null
+++ b/content/cardboard/Paper/Paper005_2K-PNG_Displacement.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvw2rvje403og"
+path.s3tc="res://.godot/imported/Paper005_2K-PNG_Displacement.png-bfbb733ddcd4562f7b63b61fd3368b67.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Paper/Paper005_2K-PNG_Displacement.png"
+dest_files=["res://.godot/imported/Paper005_2K-PNG_Displacement.png-bfbb733ddcd4562f7b63b61fd3368b67.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/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png b/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png
new file mode 100644
index 0000000..aa1e246
Binary files /dev/null and b/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png differ
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png.import b/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png.import
new file mode 100644
index 0000000..48b23a5
--- /dev/null
+++ b/content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dsgtmf3p7hldb"
+path="res://.godot/imported/Paper005_2K-PNG_NormalDX.png-eec5227a9ba47fe15a6cdbada2a258c5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://content/cardboard/Paper/Paper005_2K-PNG_NormalDX.png"
+dest_files=["res://.godot/imported/Paper005_2K-PNG_NormalDX.png-eec5227a9ba47fe15a6cdbada2a258c5.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/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png b/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png
new file mode 100644
index 0000000..2f06f22
Binary files /dev/null and b/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png differ
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png.import b/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png.import
new file mode 100644
index 0000000..18cd077
--- /dev/null
+++ b/content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djtg2ykgjylk8"
+path.s3tc="res://.godot/imported/Paper005_2K-PNG_NormalGL.png-8b69769626382100d06374dd4ef11956.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png"
+dest_files=["res://.godot/imported/Paper005_2K-PNG_NormalGL.png-8b69769626382100d06374dd4ef11956.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=1
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=1
+roughness/src_normal="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png"
+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/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png b/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png
new file mode 100644
index 0000000..49aac24
Binary files /dev/null and b/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png differ
diff --git a/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png.import b/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png.import
new file mode 100644
index 0000000..67d7f0d
--- /dev/null
+++ b/content/cardboard/Paper/Paper005_2K-PNG_Roughness.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ct83jsi7k2slj"
+path.s3tc="res://.godot/imported/Paper005_2K-PNG_Roughness.png-b114c1e1da6924452d929f3d1f4f959d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/cardboard/Paper/Paper005_2K-PNG_Roughness.png"
+dest_files=["res://.godot/imported/Paper005_2K-PNG_Roughness.png-b114c1e1da6924452d929f3d1f4f959d.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/content/cardboard_material.tres b/content/cardboard_material.tres
index c416821..4fd1bc8 100644
--- a/content/cardboard_material.tres
+++ b/content/cardboard_material.tres
@@ -11,12 +11,12 @@
cull_mode = 2
diffuse_mode = 3
specular_mode = 1
-albedo_color = Color(0.521141, 0.521141, 0.521141, 1)
albedo_texture = ExtResource("1_u5ab5")
roughness_texture = ExtResource("4_ptq0i")
normal_enabled = true
normal_scale = 2.89
normal_texture = ExtResource("3_24st2")
+heightmap_enabled = true
heightmap_scale = 0.697
heightmap_texture = ExtResource("2_p176y")
heightmap_flip_texture = true
@@ -24,5 +24,4 @@ refraction_scale = -0.25
detail_mask = SubResource("CompressedTexture2D_4ny28")
detail_albedo = SubResource("CompressedTexture2D_4ny28")
uv1_scale = Vector3(0.245, 0.245, 0.245)
-uv1_triplanar = true
uv1_world_triplanar = true
diff --git a/content/decals/Damage Imperfection seamless 4k-04-png.png b/content/decals/Damage Imperfection seamless 4k-04-png.png
new file mode 100644
index 0000000..45b39f5
Binary files /dev/null and b/content/decals/Damage Imperfection seamless 4k-04-png.png differ
diff --git a/content/decals/Damage Imperfection seamless 4k-04-png.png.import b/content/decals/Damage Imperfection seamless 4k-04-png.png.import
new file mode 100644
index 0000000..e9231de
--- /dev/null
+++ b/content/decals/Damage Imperfection seamless 4k-04-png.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cqbdilieomk50"
+path.s3tc="res://.godot/imported/Damage Imperfection seamless 4k-04-png.png-89056d2e1ea9077cacb64c6c1f9b34ff.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://content/decals/Damage Imperfection seamless 4k-04-png.png"
+dest_files=["res://.godot/imported/Damage Imperfection seamless 4k-04-png.png-89056d2e1ea9077cacb64c6c1f9b34ff.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/content/enemies/enemy.tscn b/content/enemies/enemy.tscn
index fad60a2..f1ad91b 100644
--- a/content/enemies/enemy.tscn
+++ b/content/enemies/enemy.tscn
@@ -1,16 +1,29 @@
-[gd_scene load_steps=24 format=3 uid="uid://b1l8fvlceolf8"]
+[gd_scene load_steps=31 format=3 uid="uid://b1l8fvlceolf8"]
[ext_resource type="Script" path="res://scripts/enemies/enemy.gd" id="1_n8jwt"]
[ext_resource type="PackedScene" uid="uid://o3oir3w6cs1y" path="res://content/enemies/enemies.fbx" id="1_nxu6l"]
[ext_resource type="Texture2D" uid="uid://csn8uoefcbua6" path="res://content/enemies/sprites/idle sprite.png" id="2_r5jpp"]
[ext_resource type="Animation" uid="uid://cguc5g4xb3ukw" path="res://content/enemies/animations/Vert_Fire.res" id="3_c2uq0"]
[ext_resource type="BehaviorTree" uid="uid://db7m2p0at5kmq" path="res://ai/enemy.tres" id="3_laok7"]
+[ext_resource type="Texture2D" uid="uid://dku7f5fijokpn" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_NormalGL.png" id="4_ift72"]
[ext_resource type="Animation" uid="uid://c4ko34bx2fw2a" path="res://content/enemies/animations/Vert_Idle.res" id="4_ldm0s"]
+[ext_resource type="AudioStream" uid="uid://cn33abuyca8so" path="res://audio/gun/assaultrifle.tres" id="4_t8l0v"]
[ext_resource type="Script" path="res://scripts/enemies/enemy_ai.gd" id="4_uu6te"]
[ext_resource type="Texture2D" uid="uid://buebtm6i0whgb" path="res://content/enemies/sprites/fire sprite.png" id="5_1c5tu"]
+[ext_resource type="Texture2D" uid="uid://cpmhd8ni4r5lu" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_Roughness.png" id="5_2stbb"]
[ext_resource type="Animation" uid="uid://bkg0my1x3pao4" path="res://content/enemies/animations/Vert_Move.res" id="5_saiyq"]
[ext_resource type="PackedScene" uid="uid://bnwuklgcmgyw8" path="res://content/muzzleflash/muzzleflash.tscn" id="10_dtmwf"]
[ext_resource type="Script" path="res://demo/agents/scripts/health.gd" id="11_o71in"]
+[ext_resource type="AudioStream" uid="uid://dj5b4cn5ody1b" path="res://audio/armorbreak/armorbreak.tres" id="13_ikanm"]
+[ext_resource type="AudioStream" uid="uid://dq8k15jwlxhw" path="res://audio/footsteps/walk/footsteps_walking.tres" id="13_ispa7"]
+[ext_resource type="Script" path="res://prefabs/movement_player.gd" id="14_m37f6"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3axg3"]
+transparency = 1
+albedo_texture = ExtResource("5_1c5tu")
+roughness_texture = ExtResource("5_2stbb")
+normal_enabled = true
+normal_texture = ExtResource("4_ift72")
[sub_resource type="AnimationLibrary" id="AnimationLibrary_jm6ft"]
_data = {
@@ -71,16 +84,17 @@ script = ExtResource("1_n8jwt")
transform = Transform3D(0.42, 0, 0, 0, 0.42, 0, 0, 0, 0.42, 0, 0, 0)
[node name="Vert" parent="enemies" index="0"]
-transform = Transform3D(97.8205, -0.0251948, -20.7636, 0.0257551, 100, -4.37103e-06, 20.7636, -0.00534343, 97.8206, 0, 0.000652266, 0)
+transform = Transform3D(99.7699, -0.0330577, 6.77809, 0.0331342, 100, -4.37112e-06, -6.77809, 0.00225023, 99.7699, 0, 0.000652266, 0)
+material_overlay = SubResource("StandardMaterial3D_3axg3")
-[node name="Sprite3D" type="Sprite3D" parent="enemies/Vert" index="0"]
-transform = Transform3D(0.00999984, -1.44888e-06, 5.6131e-05, 1.44904e-06, 0.01, -2.46636e-08, -5.6131e-05, 3.27968e-08, 0.00999984, -3.11379e-06, 0.0260873, 0.000118726)
-modulate = Color(1, 0.86, 0.86, 1)
-texture = ExtResource("2_r5jpp")
-
-[node name="Shoot Marker" type="Node3D" parent="enemies/Vert" index="1"]
+[node name="Shoot Marker" type="Node3D" parent="enemies/Vert" index="0"]
transform = Transform3D(0.0242074, -1.30064e-06, 0.00624512, 9.47821e-07, 0.025, 1.53265e-06, -0.00624511, -1.24728e-06, 0.0242074, -0.00403827, 0.0391857, 0.00234848)
+[node name="ShootSound" type="AudioStreamPlayer3D" parent="enemies/Vert/Shoot Marker"]
+process_mode = 3
+stream = ExtResource("4_t8l0v")
+bus = &"Enemies"
+
[node name="AnimationPlayer" parent="enemies" index="1"]
libraries = {
"": SubResource("AnimationLibrary_jm6ft")
@@ -92,12 +106,12 @@ tree_root = SubResource("AnimationNodeStateMachine_rdhva")
anim_player = NodePath("..")
parameters/Movement/blend_position = 0.00270998
-[node name="BTPlayer" type="BTPlayer" parent="." node_paths=PackedStringArray("character", "sprite", "agent", "animationTree", "Origin")]
+[node name="BTPlayer" type="BTPlayer" parent="." node_paths=PackedStringArray("character", "visual", "agent", "animationTree", "Origin")]
behavior_tree = ExtResource("3_laok7")
blackboard_plan = SubResource("BlackboardPlan_hf7lk")
script = ExtResource("4_uu6te")
character = NodePath("..")
-sprite = NodePath("../enemies/Vert/Sprite3D")
+visual = NodePath("../enemies/Vert")
idleTexture = ExtResource("2_r5jpp")
aggressiveTexture = ExtResource("5_1c5tu")
agent = NodePath("../NavigationAgent")
@@ -117,4 +131,15 @@ target_desired_distance = 0.5
script = ExtResource("11_o71in")
max_health = 1.0
+[node name="DeathSound" type="AudioStreamPlayer" parent="."]
+process_mode = 3
+stream = ExtResource("13_ikanm")
+bus = &"Player"
+
+[node name="Walking" type="AudioStreamPlayer3D" parent="." node_paths=PackedStringArray("targetElement")]
+stream = ExtResource("13_ispa7")
+bus = &"Enemies"
+script = ExtResource("14_m37f6")
+targetElement = NodePath("..")
+
[editable path="enemies"]
diff --git a/content/gemina2half.ttf b/content/gemina2half.ttf
new file mode 100644
index 0000000..350a94c
Binary files /dev/null and b/content/gemina2half.ttf differ
diff --git a/content/gemina2half.ttf.import b/content/gemina2half.ttf.import
new file mode 100644
index 0000000..5bf4923
--- /dev/null
+++ b/content/gemina2half.ttf.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://cynlx0ww4k85w"
+path="res://.godot/imported/gemina2half.ttf-ac090b7d31ee661db60aaa0bf0ea781a.fontdata"
+
+[deps]
+
+source_file="res://content/gemina2half.ttf"
+dest_files=["res://.godot/imported/gemina2half.ttf-ac090b7d31ee661db60aaa0bf0ea781a.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/content/levels/Level1.tscn b/content/levels/Level1.tscn
new file mode 100644
index 0000000..d90d698
--- /dev/null
+++ b/content/levels/Level1.tscn
@@ -0,0 +1,6877 @@
+[gd_scene load_steps=657 format=3 uid="uid://cim3u1dicqcvu"]
+
+[ext_resource type="PackedScene" uid="uid://ctu0cdqcefrwg" path="res://prefabs/player.tscn" id="2_iom4v"]
+[ext_resource type="Script" path="res://addons/cyclops_level_builder/nodes/cyclops_block.gd" id="2_vbywh"]
+[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/data_vector_byte.gd" id="3_f6xty"]
+[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/data_vector_int.gd" id="4_3vpm2"]
+[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/data_vector_float.gd" id="5_0ijij"]
+[ext_resource type="Script" path="res://addons/cyclops_level_builder/resources/mesh_vector_data.gd" id="6_8jlqm"]
+[ext_resource type="Material" uid="uid://dcaorx27eky1t" path="res://content/cardboard/Cardboard004/Cardboard004.tres" id="7_a6jsf"]
+[ext_resource type="Material" uid="uid://bjhdc2pxcui3" path="res://content/cardboard_material.tres" id="7_n23m2"]
+[ext_resource type="PackedScene" uid="uid://b1l8fvlceolf8" path="res://content/enemies/enemy.tscn" id="8_t5py6"]
+[ext_resource type="PackedScene" uid="uid://c2h3negcbnqi" path="res://prefabs/SecretTrigger.tscn" id="9_mmoff"]
+[ext_resource type="Script" path="res://scripts/level_secret_handler.gd" id="10_aof45"]
+[ext_resource type="Texture2D" uid="uid://bctvdjk0xedjs" path="res://content/Logo2.png" id="11_08oij"]
+[ext_resource type="Script" path="res://scripts/secret_label.gd" id="11_flx2f"]
+[ext_resource type="Texture2D" uid="uid://dku7f5fijokpn" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_NormalGL.png" id="12_6jxu0"]
+[ext_resource type="FontFile" uid="uid://dxyqmtf7gmhfk" path="res://content/IndieFlower-Regular.ttf" id="12_xvgha"]
+[ext_resource type="FontFile" uid="uid://cynlx0ww4k85w" path="res://content/gemina2half.ttf" id="13_hif5b"]
+[ext_resource type="Script" path="res://content/levels/exit_script.gd" id="13_nuwcy"]
+[ext_resource type="ArrayMesh" uid="uid://b08wpv25oghk2" path="res://content/props/Sofa.res" id="18_eun4c"]
+[ext_resource type="ArrayMesh" uid="uid://bdk8qkwkkeleu" path="res://content/props/Console.res" id="19_u0cos"]
+[ext_resource type="PackedScene" uid="uid://b7pmtf7c0rfke" path="res://content/props/TV.tscn" id="20_o2f1r"]
+[ext_resource type="ArrayMesh" uid="uid://d140dbn5ynbuw" path="res://content/props/Table.res" id="21_3g6q4"]
+[ext_resource type="ArrayMesh" uid="uid://bx21bwka0f0ub" path="res://content/props/Chair.res" id="22_r3ah8"]
+[ext_resource type="ArrayMesh" uid="uid://boiqcf8xxee0f" path="res://content/props/Paper_001.res" id="23_gn80l"]
+
+[sub_resource type="NavigationMesh" id="NavigationMesh_hdiwy"]
+vertices = PackedVector3Array(-20.2782, 7.25, -18.06, -20.2782, 7.25, -9.06002, -9.02817, 7.25, -9.06002, -9.02817, 7.25, -18.06, -6.52817, 4, -3.31002, -4.52817, 4, -2.31002, -3.77817, 4, -4.06002, -4.52817, 4, -0.560019, -7.27817, 4, -4.06002, -2.27817, 4, -5.56002, 5.22183, 4, -1.06002, 4.72183, 4, 0.689981, 6.72183, 4, 8.68998, -3.77817, 4, 1.18998, -6.27817, 4, 9.18998, 6.72183, 0.25, 13.19, 6.72183, 0.25, 10.69, -0.27817, 0.75, 10.69, -6.52817, 4, 13.19, 0.832941, 0.25, 13.19, 1.12183, 0.25, 10.69, 4.22183, 4, -4.31002, 4.97183, 4, -3.06002, 6.72183, 4, -6.56002, -5.77817, 3.75, 10.69, -16.5282, 4, -16.31, -16.5282, 4, -9.06002, -9.77817, 4, -9.06002, -9.77817, 4, -17.56, -18.2782, 4, -6.81002, 0.97183, 4, -6.31002, -17.2782, 4, -17.06, -20.2782, 4, -17.56, -18.2782, 4, -17.06, 2.97183, 4, 2.68998, -19.0282, 4, -16.31, -19.0282, 4, -7.56002, -20.2782, 4, -3.81002, -1.52817, 4, 3.18998, -5.77817, 4, 8.68998, 2.47183, 4, -5.81002, -0.27817, 4, 3.43998, -21.2782, 8.5, -7.06002, -21.2782, 8.5, -5.31002, -20.2782, 8.5, -4.81002, -9.77817, 8.25, -4.56002, -9.02817, 8.5, -5.06002, -9.02817, 8.25, -7.06002, -20.2782, 4.75, -0.0600185, -9.77817, 4.75, -0.0600185, -6.52817, 8.25, -7.06002, -6.52817, 8.25, -6.31002, -5.77817, 8.25, -6.81002, 6.97183, 8.25, -7.06002, 5.22183, 8.25, -7.06002, 5.97183, 8.25, -6.31002, -5.77817, 8.25, 7.43998, -6.52817, 8.25, 6.68998, -6.52817, 8.25, 13.69, 5.22183, 8.25, 7.43998, 6.97183, 8.25, 13.69, 5.97183, 8.25, 6.68998, 4.47183, 0.25, 0.689981, 4.72183, 0.25, 1.18998, 5.22183, 0.25, 0.689981, 6.72183, 0.25, -6.56002, 6.72183, 0.25, 0.689981, -6.77817, 0.25, -6.56002, -6.77817, 0.25, 0.689981, -1.27817, 4, -0.560019, -0.52817, 4, 0.189981, 0.97183, 4, 0.189981, 0.97183, 4, -3.06002, -0.27817, 4, -3.06002, -1.27817, 4, -2.31002, 1.97183, 4, -0.810019, 1.97183, 4, -2.06002, 5.22183, 0.25, 3.18998, 4.72183, 0.25, 2.68998, 4.47183, 0.25, 3.18998, -5.27817, 0.25, 3.18998, -5.27817, 0.25, 9.18998, -4.52817, 0.25, 8.43998, 0.47183, 0.5, 9.93998, -0.0281696, 0.25, 8.43998, 6.72183, 0.25, 3.18998)
+polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1), PackedInt32Array(6, 5, 4), PackedInt32Array(4, 5, 7), PackedInt32Array(4, 8, 6), PackedInt32Array(6, 8, 9), PackedInt32Array(12, 11, 10), PackedInt32Array(7, 13, 4), PackedInt32Array(4, 13, 14), PackedInt32Array(16, 15, 20), PackedInt32Array(20, 15, 19), PackedInt32Array(20, 19, 17), PackedInt32Array(17, 19, 18), PackedInt32Array(23, 22, 21), PackedInt32Array(24, 17, 18), PackedInt32Array(14, 24, 18), PackedInt32Array(26, 25, 27), PackedInt32Array(27, 25, 28), PackedInt32Array(30, 29, 23), PackedInt32Array(28, 25, 31), PackedInt32Array(33, 32, 31), PackedInt32Array(31, 32, 28), PackedInt32Array(12, 34, 11), PackedInt32Array(32, 33, 35), PackedInt32Array(35, 36, 32), PackedInt32Array(32, 36, 37), PackedInt32Array(29, 30, 9), PackedInt32Array(37, 36, 29), PackedInt32Array(37, 29, 8), PackedInt32Array(8, 29, 9), PackedInt32Array(14, 18, 4), PackedInt32Array(22, 23, 10), PackedInt32Array(10, 23, 12), PackedInt32Array(39, 14, 38), PackedInt32Array(38, 14, 13), PackedInt32Array(23, 21, 40), PackedInt32Array(39, 38, 41), PackedInt32Array(34, 12, 41), PackedInt32Array(41, 12, 39), PackedInt32Array(23, 40, 30), PackedInt32Array(43, 42, 44), PackedInt32Array(44, 42, 45), PackedInt32Array(45, 42, 46), PackedInt32Array(46, 42, 47), PackedInt32Array(49, 48, 45), PackedInt32Array(45, 48, 44), PackedInt32Array(52, 51, 50), PackedInt32Array(55, 54, 53), PackedInt32Array(56, 58, 57), PackedInt32Array(56, 59, 58), PackedInt32Array(58, 59, 60), PackedInt32Array(55, 53, 61), PackedInt32Array(61, 53, 60), PackedInt32Array(60, 59, 61), PackedInt32Array(63, 62, 64), PackedInt32Array(64, 62, 65), PackedInt32Array(65, 66, 64), PackedInt32Array(68, 67, 62), PackedInt32Array(62, 67, 65), PackedInt32Array(71, 70, 69), PackedInt32Array(74, 73, 72), PackedInt32Array(75, 71, 76), PackedInt32Array(76, 71, 72), PackedInt32Array(72, 71, 69), PackedInt32Array(72, 69, 74), PackedInt32Array(79, 78, 77), PackedInt32Array(82, 81, 80), PackedInt32Array(17, 83, 20), PackedInt32Array(20, 83, 16), PackedInt32Array(83, 84, 16), PackedInt32Array(16, 84, 77), PackedInt32Array(16, 77, 85), PackedInt32Array(82, 80, 84), PackedInt32Array(84, 80, 79), PackedInt32Array(84, 79, 77)]
+sample_partition_type = 2
+agent_height = 2.0
+agent_radius = 1.0
+
+[sub_resource type="Resource" id="Resource_mifc4"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0u8fc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_7vyb5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1c1ha"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_q7ins"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_5ndaw"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mub7j"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_s0vdq"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xlhy5"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_33av2"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6k3el"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 0, 0, 0, 10, 16, 0, 10, 16, 0, 0, 16, 0.942089, 0, 16, 0.942089, 10, 0, 0.942089, 10, 0, 0.942089, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_xkpl3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_wjtdm"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 4, 7, 7, 0, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 3, 1, 2, 1, 4, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+vertex_data = {
+"position": SubResource("Resource_6k3el"),
+"selected": SubResource("Resource_xkpl3")
+}
+edge_data = {
+"selected": SubResource("Resource_mifc4")
+}
+face_data = {
+"color": SubResource("Resource_0u8fc"),
+"material_index": SubResource("Resource_7vyb5"),
+"selected": SubResource("Resource_1c1ha"),
+"uv_transform": SubResource("Resource_q7ins"),
+"visible": SubResource("Resource_5ndaw")
+}
+face_vertex_data = {
+"color": SubResource("Resource_mub7j"),
+"face_index": SubResource("Resource_s0vdq"),
+"normal": SubResource("Resource_xlhy5"),
+"vertex_index": SubResource("Resource_33av2")
+}
+
+[sub_resource type="Resource" id="Resource_quxt2"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ieowe"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ixpyl"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_be3vd"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 1, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7v6gg"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_yo2sm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ick1c"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_eomg7"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kr2t0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_uu0oh"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_p2k22"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 0, 0, 0, 17.5868, 16, 0, 17.5868, 16, 0, 0, 16, 1, 0, 16, 1, 17.5868, 0, 1, 17.5868, 0, 1, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_1vsn0"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_k1hk4"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 4
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 4, 7, 7, 0, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 3, 1, 2, 1, 4, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+vertex_data = {
+"position": SubResource("Resource_p2k22"),
+"selected": SubResource("Resource_1vsn0")
+}
+edge_data = {
+"selected": SubResource("Resource_quxt2")
+}
+face_data = {
+"color": SubResource("Resource_ieowe"),
+"material_index": SubResource("Resource_ixpyl"),
+"selected": SubResource("Resource_be3vd"),
+"uv_transform": SubResource("Resource_7v6gg"),
+"visible": SubResource("Resource_yo2sm")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ick1c"),
+"face_index": SubResource("Resource_eomg7"),
+"normal": SubResource("Resource_kr2t0"),
+"vertex_index": SubResource("Resource_uu0oh")
+}
+
+[sub_resource type="Resource" id="Resource_obpq7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rmpqb"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_vrgwh"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_dksvm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 1, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_oygbn"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_3ledy"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4aumv"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_akkwa"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_f8svm"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-0.0101401, 0, 0.999949, -0.0101401, 0, 0.999949, -0.0101401, 0, 0.999949, -0.0101401, 0, 0.999949, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_7xqxf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_va0l8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-14.709, 8.0522, 0, -1.52818, 8.0522, 0.133661, -1.52818, -0.191, 0.133661, -14.709, -0.191, 0, 11.1177, 8.0522, 0, 11.1177, 8.0522, 0.133661, 11.1177, -0.191, 0.133661, 11.1177, -0.191, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_iq56k"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_w5ogc"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = 3
+active_face = 4
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_va0l8"),
+"selected": SubResource("Resource_iq56k")
+}
+edge_data = {
+"selected": SubResource("Resource_obpq7")
+}
+face_data = {
+"color": SubResource("Resource_rmpqb"),
+"material_index": SubResource("Resource_vrgwh"),
+"selected": SubResource("Resource_dksvm"),
+"uv_transform": SubResource("Resource_oygbn"),
+"visible": SubResource("Resource_3ledy")
+}
+face_vertex_data = {
+"color": SubResource("Resource_4aumv"),
+"face_index": SubResource("Resource_akkwa"),
+"normal": SubResource("Resource_f8svm"),
+"vertex_index": SubResource("Resource_7xqxf")
+}
+
+[sub_resource type="Resource" id="Resource_gr5xg"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kgh22"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_caqc3"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4u6pt"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_i7rom"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_f1ufq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6euki"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_jn12d"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pkbib"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -2.66971e-08, 0, 1, -2.66971e-08, 0, 1, -2.66971e-08, 0, 1, -2.66971e-08, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 2.61046e-08, 0, -1, 2.61046e-08, 0, -1, 2.61046e-08, 0, -1, 2.61046e-08, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_hg564"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_76hrr"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-4.92912, 2.71811, 0, -4.92912, 2.71811, 0.133661, -4.92912, 0, 0.133661, -4.92912, 0, 0, 5.1177, 2.71811, 2.62268e-07, 5.1177, 2.71811, 0.133661, 5.1177, 0, 0.133661, 5.1177, 0, 2.62268e-07)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_8us87"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_k4yf4"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_76hrr"),
+"selected": SubResource("Resource_8us87")
+}
+edge_data = {
+"selected": SubResource("Resource_gr5xg")
+}
+face_data = {
+"color": SubResource("Resource_kgh22"),
+"material_index": SubResource("Resource_caqc3"),
+"selected": SubResource("Resource_4u6pt"),
+"uv_transform": SubResource("Resource_i7rom"),
+"visible": SubResource("Resource_f1ufq")
+}
+face_vertex_data = {
+"color": SubResource("Resource_6euki"),
+"face_index": SubResource("Resource_jn12d"),
+"normal": SubResource("Resource_pkbib"),
+"vertex_index": SubResource("Resource_hg564")
+}
+
+[sub_resource type="Resource" id="Resource_76sm8"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_070jb"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wsuod"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_51lgx"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 1, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_yp362"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_j37qm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_afgx4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_rneh7"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_haqvc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1.30818e-08, 0, -1, -1.30818e-08, 0, -1, -1.30818e-08, 0, -1, -1.30818e-08, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_7rm5b"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_tbhyg"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-4.92912, 7.9968, 0, -4.92912, 7.9968, 0.133661, -4.92912, -0.180381, 0.133661, -4.92912, -0.180381, 0, 17.9712, 7.9968, -2.99577e-07, 17.9712, 7.9968, 0.133661, 17.9712, -0.180381, 0.133661, 17.9712, -0.180381, -2.99577e-07)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ssfhi"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_cbaxd"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 2
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_tbhyg"),
+"selected": SubResource("Resource_ssfhi")
+}
+edge_data = {
+"selected": SubResource("Resource_76sm8")
+}
+face_data = {
+"color": SubResource("Resource_070jb"),
+"material_index": SubResource("Resource_wsuod"),
+"selected": SubResource("Resource_51lgx"),
+"uv_transform": SubResource("Resource_yp362"),
+"visible": SubResource("Resource_j37qm")
+}
+face_vertex_data = {
+"color": SubResource("Resource_afgx4"),
+"face_index": SubResource("Resource_rneh7"),
+"normal": SubResource("Resource_haqvc"),
+"vertex_index": SubResource("Resource_7rm5b")
+}
+
+[sub_resource type="Resource" id="Resource_wyobj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xnj3j"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_x5woa"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c1021"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_02vlk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_m07ln"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5s3gw"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_jav1v"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jmsdi"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_i14ab"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ct3pt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-4.92912, -0.388208, 0.133661, -4.92912, -0.388208, 0, -4.92912, 2.71811, 0, -4.92912, 2.71811, 0.133661, 7.09605, 2.71811, 0, 7.09605, 2.71811, 0.133661, 7.09605, -0.388208, 0, 7.09605, -0.388208, 0.133661)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_6y72y"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ou5j3"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_ct3pt"),
+"selected": SubResource("Resource_6y72y")
+}
+edge_data = {
+"selected": SubResource("Resource_wyobj")
+}
+face_data = {
+"color": SubResource("Resource_xnj3j"),
+"material_index": SubResource("Resource_x5woa"),
+"selected": SubResource("Resource_c1021"),
+"uv_transform": SubResource("Resource_02vlk"),
+"visible": SubResource("Resource_m07ln")
+}
+face_vertex_data = {
+"color": SubResource("Resource_5s3gw"),
+"face_index": SubResource("Resource_jav1v"),
+"normal": SubResource("Resource_jmsdi"),
+"vertex_index": SubResource("Resource_i14ab")
+}
+
+[sub_resource type="Resource" id="Resource_mhof7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_b243c"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_x2gtd"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_f2mbt"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kc5qo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_7bupq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_frp1d"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_p1obx"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_u8m3k"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_nybmd"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_whhvy"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(8.59955, 2.71811, 0, 11.1177, 2.71811, 0, 11.1177, 2.71811, 0.133661, 8.59955, 2.71811, 0.133661, 11.1177, -0.081118, 0.133661, 11.1177, -0.081118, 0, 8.59955, -0.081118, 0.133661, 8.59955, -0.081118, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_3bowd"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5liv1"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 2, 1, 5, 5, 4, 6, 3, 4, 6, 7, 5, 0, 7, 7, 6)
+edge_face_indices = PackedInt32Array(0, 3, 0, 1, 0, 2, 0, 5, 1, 2, 1, 3, 1, 4, 2, 5, 2, 4, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+vertex_data = {
+"position": SubResource("Resource_whhvy"),
+"selected": SubResource("Resource_3bowd")
+}
+edge_data = {
+"selected": SubResource("Resource_mhof7")
+}
+face_data = {
+"color": SubResource("Resource_b243c"),
+"material_index": SubResource("Resource_x2gtd"),
+"selected": SubResource("Resource_f2mbt"),
+"uv_transform": SubResource("Resource_kc5qo"),
+"visible": SubResource("Resource_7bupq")
+}
+face_vertex_data = {
+"color": SubResource("Resource_frp1d"),
+"face_index": SubResource("Resource_p1obx"),
+"normal": SubResource("Resource_u8m3k"),
+"vertex_index": SubResource("Resource_nybmd")
+}
+
+[sub_resource type="Resource" id="Resource_tjcyq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hl031"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_txfud"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_foios"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0whfb"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_5fiac"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_qfgbx"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_gmxi8"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_or80m"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_uvbve"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_r6lcg"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(8.59955, 2.71811, 0.133661, 8.59955, 2.71811, 0, 8.59955, 2.27053, 0, 8.59955, 2.27053, 0.133661, 7.09605, 2.27053, 0, 7.09605, 2.71811, 0, 7.09605, 2.27053, 0.133661, 7.09605, 2.71811, 0.133661)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_d7ni7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xildq"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 2, 1, 5, 5, 4, 6, 3, 4, 6, 7, 5, 0, 7, 7, 6)
+edge_face_indices = PackedInt32Array(0, 3, 0, 1, 0, 2, 0, 5, 1, 2, 1, 3, 1, 4, 2, 5, 2, 4, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+vertex_data = {
+"position": SubResource("Resource_r6lcg"),
+"selected": SubResource("Resource_d7ni7")
+}
+edge_data = {
+"selected": SubResource("Resource_tjcyq")
+}
+face_data = {
+"color": SubResource("Resource_hl031"),
+"material_index": SubResource("Resource_txfud"),
+"selected": SubResource("Resource_foios"),
+"uv_transform": SubResource("Resource_0whfb"),
+"visible": SubResource("Resource_5fiac")
+}
+face_vertex_data = {
+"color": SubResource("Resource_qfgbx"),
+"face_index": SubResource("Resource_gmxi8"),
+"normal": SubResource("Resource_or80m"),
+"vertex_index": SubResource("Resource_uvbve")
+}
+
+[sub_resource type="Resource" id="Resource_o6deq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7vgvr"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_24umw"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rtn2h"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 1, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fdtr2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_rx2l6"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_yy5h0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ibcoi"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mvyk7"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_vx1we"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ec8ie"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-10.2519, 0.421415, 0, -10.2519, 0.421415, 12.4875, -10.2519, 0, 12.4875, -10.2519, 0, 0, 4.13556, 0.421415, 0, 4.13556, 0.421415, 12.4875, 4.13556, 0, 12.4875, 4.13556, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_nmkko"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8xs8b"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 4
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_ec8ie"),
+"selected": SubResource("Resource_nmkko")
+}
+edge_data = {
+"selected": SubResource("Resource_o6deq")
+}
+face_data = {
+"color": SubResource("Resource_7vgvr"),
+"material_index": SubResource("Resource_24umw"),
+"selected": SubResource("Resource_rtn2h"),
+"uv_transform": SubResource("Resource_fdtr2"),
+"visible": SubResource("Resource_rx2l6")
+}
+face_vertex_data = {
+"color": SubResource("Resource_yy5h0"),
+"face_index": SubResource("Resource_ibcoi"),
+"normal": SubResource("Resource_mvyk7"),
+"vertex_index": SubResource("Resource_vx1we")
+}
+
+[sub_resource type="Resource" id="Resource_qoy0p"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_j04ne"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_1xr4w"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ogw1g"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_krfvt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_wyvxi"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_vel7y"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_rxyyc"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7jy8c"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-0.997696, 0.0678362, 0, -0.997696, 0.0678362, 0, -0.997696, 0.0678362, 0, -0.997696, 0.0678362, 0, 0.997697, -0.0678292, 0, 0.997697, -0.0678292, 0, 0.997697, -0.0678292, 0, 0.997697, -0.0678292, 0, 0.476725, 0.879053, 0, 0.476725, 0.879053, 0, 0.476725, 0.879053, 0, 0.476725, 0.879053, 0, -0.213208, -0.393143, 0.894416, -0.213208, -0.393143, 0.894416, -0.213208, -0.393143, 0.894416, -0.213208, -0.393143, 0.894416, -0.476725, -0.879053, 0, -0.476725, -0.879053, 0, -0.476725, -0.879053, 0, -0.476725, -0.879053, 0, 0.213205, 0.393138, -0.894419, 0.213205, 0.393138, -0.894419, 0.213205, 0.393138, -0.894419, 0.213205, 0.393138, -0.894419)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_pfwv0"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_46wt8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.118064, 4.11666, 0.134848, 0.118064, 4.11666, 5.01342, 0.0889311, 3.68819, 4.81814, 0.0889311, 3.68819, -0.0604286, 6.91887, 0.428472, 0.134848, 6.91887, 0.428472, 5.01342, 6.88974, 0, 4.81814, 6.88974, 0, -0.0604286)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_gwniy"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fbjvs"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 0
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_46wt8"),
+"selected": SubResource("Resource_gwniy")
+}
+edge_data = {
+"selected": SubResource("Resource_qoy0p")
+}
+face_data = {
+"color": SubResource("Resource_j04ne"),
+"material_index": SubResource("Resource_1xr4w"),
+"selected": SubResource("Resource_ogw1g"),
+"uv_transform": SubResource("Resource_krfvt"),
+"visible": SubResource("Resource_wyvxi")
+}
+face_vertex_data = {
+"color": SubResource("Resource_vel7y"),
+"face_index": SubResource("Resource_rxyyc"),
+"normal": SubResource("Resource_7jy8c"),
+"vertex_index": SubResource("Resource_pfwv0")
+}
+
+[sub_resource type="Resource" id="Resource_hiap3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1je13"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_7vchf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xmh7x"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 1, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_x7up5"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_xc8xb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mloxv"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_0wxbi"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8efmy"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_1ix0d"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_vm8ru"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-0.506424, 3.59552, 0, -0.506424, 3.59552, 12.5238, -0.506424, -0.225952, 12.5238, -0.506424, -0.225952, 0, 1.09554, 3.59552, 0, 1.09554, 3.59552, 12.5238, 1.09554, -0.225952, 12.5238, 1.09554, -0.225952, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_84k3e"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_iplyh"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_vm8ru"),
+"selected": SubResource("Resource_84k3e")
+}
+edge_data = {
+"selected": SubResource("Resource_hiap3")
+}
+face_data = {
+"color": SubResource("Resource_1je13"),
+"material_index": SubResource("Resource_7vchf"),
+"selected": SubResource("Resource_xmh7x"),
+"uv_transform": SubResource("Resource_x7up5"),
+"visible": SubResource("Resource_xc8xb")
+}
+face_vertex_data = {
+"color": SubResource("Resource_mloxv"),
+"face_index": SubResource("Resource_0wxbi"),
+"normal": SubResource("Resource_8efmy"),
+"vertex_index": SubResource("Resource_1ix0d")
+}
+
+[sub_resource type="Resource" id="Resource_7v456"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5v5u8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_4u56c"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_l6d4n"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 1, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ddg1p"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_noh67"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_k10so"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_cgm8y"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ity16"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_k7up5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_s8ka8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 8.0878, 0, 0, 8.0878, 0.438861, 0, 0, 0.438861, 0, 0, 0, 15.6707, 8.0878, 0, 15.6707, 8.0878, 0.438861, 15.6707, 0, 0.438861, 15.6707, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_0a2np"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_u53xy"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 2
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_s8ka8"),
+"selected": SubResource("Resource_0a2np")
+}
+edge_data = {
+"selected": SubResource("Resource_7v456")
+}
+face_data = {
+"color": SubResource("Resource_5v5u8"),
+"material_index": SubResource("Resource_4u56c"),
+"selected": SubResource("Resource_l6d4n"),
+"uv_transform": SubResource("Resource_ddg1p"),
+"visible": SubResource("Resource_noh67")
+}
+face_vertex_data = {
+"color": SubResource("Resource_k10so"),
+"face_index": SubResource("Resource_cgm8y"),
+"normal": SubResource("Resource_ity16"),
+"vertex_index": SubResource("Resource_k7up5")
+}
+
+[sub_resource type="Resource" id="Resource_wbrev"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_12vv0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_xfykm"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ifrwm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 1)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5qf7w"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_ufdcy"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_d6llo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_8vmlv"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1mm7e"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_a0sfp"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fwdog"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(2.04906, 0.268241, 22.1489, 2.04906, 0, 22.1489, -0.324343, 0, 22.1489, -0.324343, 0.268241, 22.1489, -0.324343, 0, 0, -0.324343, 0.268241, 0, 2.04906, 0, 0, 2.04906, 0.268241, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_wc2jm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_qxrkd"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 5
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_fwdog"),
+"selected": SubResource("Resource_wc2jm")
+}
+edge_data = {
+"selected": SubResource("Resource_wbrev")
+}
+face_data = {
+"color": SubResource("Resource_12vv0"),
+"material_index": SubResource("Resource_xfykm"),
+"selected": SubResource("Resource_ifrwm"),
+"uv_transform": SubResource("Resource_5qf7w"),
+"visible": SubResource("Resource_ufdcy")
+}
+face_vertex_data = {
+"color": SubResource("Resource_d6llo"),
+"face_index": SubResource("Resource_8vmlv"),
+"normal": SubResource("Resource_1mm7e"),
+"vertex_index": SubResource("Resource_a0sfp")
+}
+
+[sub_resource type="Resource" id="Resource_v7d5n"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3o5im"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_h41bu"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_80fly"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_aji47"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_8epkk"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_g1j5u"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_3pf76"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jrp73"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_0iqah"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_eef3f"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(12.7723, 0, 22.1489, 12.7723, 0.268241, 22.1489, 15.6583, 0.268241, 22.1489, 15.6583, 0, 22.1489, 15.6583, 0.268241, 0, 15.6583, 0, 0, 12.7723, 0.268241, 0, 12.7723, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ooolb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rcdou"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_eef3f"),
+"selected": SubResource("Resource_ooolb")
+}
+edge_data = {
+"selected": SubResource("Resource_v7d5n")
+}
+face_data = {
+"color": SubResource("Resource_3o5im"),
+"material_index": SubResource("Resource_h41bu"),
+"selected": SubResource("Resource_80fly"),
+"uv_transform": SubResource("Resource_aji47"),
+"visible": SubResource("Resource_8epkk")
+}
+face_vertex_data = {
+"color": SubResource("Resource_g1j5u"),
+"face_index": SubResource("Resource_3pf76"),
+"normal": SubResource("Resource_jrp73"),
+"vertex_index": SubResource("Resource_0iqah")
+}
+
+[sub_resource type="Resource" id="Resource_66udn"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6334b"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_n63q1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bnkfu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c3627"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_jc6mu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_btk24"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_byuuh"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_corop"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_4xgmy"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c3tg5"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(12.7723, 0.268241, 22.1489, 12.7723, 0.268241, 14.3386, 12.7723, 0, 14.3386, 12.7723, 0, 22.1489, 1.04698, 0, 14.3386, 1.04698, 0, 22.1489, 1.04698, 0.268241, 14.3386, 1.04698, 0.268241, 22.1489)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_cldna"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_48olb"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_c3tg5"),
+"selected": SubResource("Resource_cldna")
+}
+edge_data = {
+"selected": SubResource("Resource_66udn")
+}
+face_data = {
+"color": SubResource("Resource_6334b"),
+"material_index": SubResource("Resource_n63q1"),
+"selected": SubResource("Resource_bnkfu"),
+"uv_transform": SubResource("Resource_c3627"),
+"visible": SubResource("Resource_jc6mu")
+}
+face_vertex_data = {
+"color": SubResource("Resource_btk24"),
+"face_index": SubResource("Resource_byuuh"),
+"normal": SubResource("Resource_corop"),
+"vertex_index": SubResource("Resource_4xgmy")
+}
+
+[sub_resource type="Resource" id="Resource_yhqxu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kg12h"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_43lwy"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mlfdo"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_sfgbl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_rss5j"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_76cov"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_4dqe8"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kdtek"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_yurou"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_tjgy0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1.04698, 0, 1.54587, 1.04698, 0, 0, 1.04698, 0.268241, 0, 1.04698, 0.268241, 1.54587, 12.7723, 0.268241, 0, 12.7723, 0, 0, 12.7723, 0.268241, 1.54587, 12.7723, 0, 1.54587)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_kkhvf"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_g38o5"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 2, 1, 5, 5, 4, 6, 3, 4, 6, 7, 5, 0, 7, 7, 6)
+edge_face_indices = PackedInt32Array(0, 3, 0, 1, 0, 2, 0, 5, 1, 2, 1, 3, 1, 4, 2, 5, 2, 4, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+vertex_data = {
+"position": SubResource("Resource_tjgy0"),
+"selected": SubResource("Resource_kkhvf")
+}
+edge_data = {
+"selected": SubResource("Resource_yhqxu")
+}
+face_data = {
+"color": SubResource("Resource_kg12h"),
+"material_index": SubResource("Resource_43lwy"),
+"selected": SubResource("Resource_mlfdo"),
+"uv_transform": SubResource("Resource_sfgbl"),
+"visible": SubResource("Resource_rss5j")
+}
+face_vertex_data = {
+"color": SubResource("Resource_76cov"),
+"face_index": SubResource("Resource_4dqe8"),
+"normal": SubResource("Resource_kdtek"),
+"vertex_index": SubResource("Resource_yurou")
+}
+
+[sub_resource type="Resource" id="Resource_3lx8i"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_unnts"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_s3gjn"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7almo"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 1)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_btr65"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_jjoge"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ub4gf"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_i636r"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0f4cr"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -0.796163, -0.605082, 0, -0.796163, -0.605082, 0, -0.796163, -0.605082, 0, -0.796163, -0.605082, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_aeitd"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_sqwi2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.386565, 4.19291, 22.4728, 0.386565, -0.409317, 22.4728, -0.5861, -0.409317, 22.4728, -0.5861, 4.19291, 22.4728, -0.5861, -0.409317, 8.17431, -0.5861, 4.19291, 2.11873, 0.386565, -0.409317, 8.17431, 0.386565, 4.19291, 2.11873)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_uxqk2"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_653r7"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 5
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_sqwi2"),
+"selected": SubResource("Resource_uxqk2")
+}
+edge_data = {
+"selected": SubResource("Resource_3lx8i")
+}
+face_data = {
+"color": SubResource("Resource_unnts"),
+"material_index": SubResource("Resource_s3gjn"),
+"selected": SubResource("Resource_7almo"),
+"uv_transform": SubResource("Resource_btr65"),
+"visible": SubResource("Resource_jjoge")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ub4gf"),
+"face_index": SubResource("Resource_i636r"),
+"normal": SubResource("Resource_0f4cr"),
+"vertex_index": SubResource("Resource_aeitd")
+}
+
+[sub_resource type="Resource" id="Resource_7ueb6"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_j768w"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_4fwpo"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_crj6y"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_cmpyy"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_suq3f"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3gkjc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_7ytsy"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_k3rsk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.980786, 0, 0.195085, -0.980786, 0, 0.195085, -0.980786, 0, 0.195085, -0.980786, 0, 0.195085, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877, 0.980785, 0, -0.195091, 0.980785, 0, -0.195091, 0.980785, 0, -0.195091, 0.980785, 0, -0.195091, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_3c05g"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_nwar3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-3.66678, 0, 0, -3.38767, 0, 1.40322, -2.63083, 0, 1.08972, -2.84759, 0, 0, -3.66678, 7.71482, 0, -3.38767, 7.71482, 1.40322, -2.63083, 7.71482, 1.08972, -2.84759, 7.71482, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_mtcj3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4gxdu"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_nwar3"),
+"selected": SubResource("Resource_mtcj3")
+}
+edge_data = {
+"selected": SubResource("Resource_7ueb6")
+}
+face_data = {
+"color": SubResource("Resource_j768w"),
+"material_index": SubResource("Resource_4fwpo"),
+"selected": SubResource("Resource_crj6y"),
+"uv_transform": SubResource("Resource_cmpyy"),
+"visible": SubResource("Resource_suq3f")
+}
+face_vertex_data = {
+"color": SubResource("Resource_3gkjc"),
+"face_index": SubResource("Resource_7ytsy"),
+"normal": SubResource("Resource_k3rsk"),
+"vertex_index": SubResource("Resource_3c05g")
+}
+
+[sub_resource type="Resource" id="Resource_wfrv4"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mu2bv"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_dpnes"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_23ylv"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_vkywv"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_m3sd6"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_isgdl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_uw6yc"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_d5qdu"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.831469, 0, 0.555571, -0.831469, 0, 0.555571, -0.831469, 0, 0.555571, -0.831469, 0, 0.555571, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107, 0.831471, 0, -0.555568, 0.831471, 0, -0.555568, 0.831471, 0, -0.555568, 0.831471, 0, -0.555568, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_r3xvp"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3dssj"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-3.38767, 0, 1.40322, -2.59281, 0, 2.59281, -2.01355, 0, 2.01355, -2.63083, 0, 1.08972, -3.38767, 7.71482, 1.40322, -2.59281, 7.71482, 2.59281, -2.01355, 7.71482, 2.01355, -2.63083, 7.71482, 1.08972)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_q71x8"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ckhg6"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_3dssj"),
+"selected": SubResource("Resource_q71x8")
+}
+edge_data = {
+"selected": SubResource("Resource_wfrv4")
+}
+face_data = {
+"color": SubResource("Resource_mu2bv"),
+"material_index": SubResource("Resource_dpnes"),
+"selected": SubResource("Resource_23ylv"),
+"uv_transform": SubResource("Resource_vkywv"),
+"visible": SubResource("Resource_m3sd6")
+}
+face_vertex_data = {
+"color": SubResource("Resource_isgdl"),
+"face_index": SubResource("Resource_uw6yc"),
+"normal": SubResource("Resource_d5qdu"),
+"vertex_index": SubResource("Resource_r3xvp")
+}
+
+[sub_resource type="Resource" id="Resource_myypu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_si8aa"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_8adn1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_lixbh"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_anv5s"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_j1h33"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3pcap"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_efene"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hm6t1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.555571, 0, 0.831469, -0.555571, 0, 0.831469, -0.555571, 0, 0.831469, -0.555571, 0, 0.831469, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269, 0.555568, 0, -0.831471, 0.555568, 0, -0.831471, 0.555568, 0, -0.831471, 0.555568, 0, -0.831471, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_exmal"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_awxw1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-2.59281, 0, 2.59281, -1.40322, 0, 3.38767, -1.08972, 0, 2.63083, -2.01355, 0, 2.01355, -2.59281, 7.71482, 2.59281, -1.40322, 7.71482, 3.38767, -1.08972, 7.71482, 2.63083, -2.01355, 7.71482, 2.01355)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_06srn"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7wq5j"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_awxw1"),
+"selected": SubResource("Resource_06srn")
+}
+edge_data = {
+"selected": SubResource("Resource_myypu")
+}
+face_data = {
+"color": SubResource("Resource_si8aa"),
+"material_index": SubResource("Resource_8adn1"),
+"selected": SubResource("Resource_lixbh"),
+"uv_transform": SubResource("Resource_anv5s"),
+"visible": SubResource("Resource_j1h33")
+}
+face_vertex_data = {
+"color": SubResource("Resource_3pcap"),
+"face_index": SubResource("Resource_efene"),
+"normal": SubResource("Resource_hm6t1"),
+"vertex_index": SubResource("Resource_exmal")
+}
+
+[sub_resource type="Resource" id="Resource_4d0u2"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_87d30"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wygsk"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_x0gbq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1sfni"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_ywpgn"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ut2eu"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wehts"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ecjsj"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.195085, 0, 0.980786, -0.195085, 0, 0.980786, -0.195085, 0, 0.980786, -0.195085, 0, 0.980786, 1, 0, -3.63811e-08, 1, 0, -3.63811e-08, 1, 0, -3.63811e-08, 1, 0, -3.63811e-08, 0.195091, 0, -0.980785, 0.195091, 0, -0.980785, 0.195091, 0, -0.980785, 0.195091, 0, -0.980785, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_rgxk0"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_04n01"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1.40322, 0, 3.38767, 1.49012e-07, 0, 3.66678, 1.19209e-07, 0, 2.84759, -1.08972, 0, 2.63083, -1.40322, 7.71482, 3.38767, 1.49012e-07, 7.71482, 3.66678, 1.19209e-07, 7.71482, 2.84759, -1.08972, 7.71482, 2.63083)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_unjtp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ir7n5"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_04n01"),
+"selected": SubResource("Resource_unjtp")
+}
+edge_data = {
+"selected": SubResource("Resource_4d0u2")
+}
+face_data = {
+"color": SubResource("Resource_87d30"),
+"material_index": SubResource("Resource_wygsk"),
+"selected": SubResource("Resource_x0gbq"),
+"uv_transform": SubResource("Resource_1sfni"),
+"visible": SubResource("Resource_ywpgn")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ut2eu"),
+"face_index": SubResource("Resource_wehts"),
+"normal": SubResource("Resource_ecjsj"),
+"vertex_index": SubResource("Resource_rgxk0")
+}
+
+[sub_resource type="Resource" id="Resource_7pj1s"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fa5l3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_n6o6m"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7ruxv"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7rip4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_44f2b"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_dyuim"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_bc6nl"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c1vfq"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.195085, 0, 0.980786, 0.195085, 0, 0.980786, 0.195085, 0, 0.980786, 0.195085, 0, 0.980786, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269, -0.195091, 0, -0.980785, -0.195091, 0, -0.980785, -0.195091, 0, -0.980785, -0.195091, 0, -0.980785, -1, 0, 3.63811e-08, -1, 0, 3.63811e-08, -1, 0, 3.63811e-08, -1, 0, 3.63811e-08)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_fg24o"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5auh6"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1.49012e-07, 0, 3.66678, 1.40322, 0, 3.38767, 1.08972, 0, 2.63083, 1.19209e-07, 0, 2.84759, 1.49012e-07, 7.71482, 3.66678, 1.40322, 7.71482, 3.38767, 1.08972, 7.71482, 2.63083, 1.19209e-07, 7.71482, 2.84759)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_cbg1s"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hdtyc"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_5auh6"),
+"selected": SubResource("Resource_cbg1s")
+}
+edge_data = {
+"selected": SubResource("Resource_7pj1s")
+}
+face_data = {
+"color": SubResource("Resource_fa5l3"),
+"material_index": SubResource("Resource_n6o6m"),
+"selected": SubResource("Resource_7ruxv"),
+"uv_transform": SubResource("Resource_7rip4"),
+"visible": SubResource("Resource_44f2b")
+}
+face_vertex_data = {
+"color": SubResource("Resource_dyuim"),
+"face_index": SubResource("Resource_bc6nl"),
+"normal": SubResource("Resource_c1vfq"),
+"vertex_index": SubResource("Resource_fg24o")
+}
+
+[sub_resource type="Resource" id="Resource_ypclj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_s4vr4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_jdu67"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_yf311"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_dh20e"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_mtb2q"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_elbtk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_57b62"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ionlo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.555571, 0, 0.831469, 0.555571, 0, 0.831469, 0.555571, 0, 0.831469, 0.555571, 0, 0.831469, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107, -0.555568, 0, -0.831471, -0.555568, 0, -0.831471, -0.555568, 0, -0.831471, -0.555568, 0, -0.831471, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_hv4ml"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_441m4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1.40322, 0, 3.38767, 2.59281, 0, 2.59281, 2.01355, 0, 2.01355, 1.08972, 0, 2.63083, 1.40322, 7.71482, 3.38767, 2.59281, 7.71482, 2.59281, 2.01355, 7.71482, 2.01355, 1.08972, 7.71482, 2.63083)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_e623m"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5hks2"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_441m4"),
+"selected": SubResource("Resource_e623m")
+}
+edge_data = {
+"selected": SubResource("Resource_ypclj")
+}
+face_data = {
+"color": SubResource("Resource_s4vr4"),
+"material_index": SubResource("Resource_jdu67"),
+"selected": SubResource("Resource_yf311"),
+"uv_transform": SubResource("Resource_dh20e"),
+"visible": SubResource("Resource_mtb2q")
+}
+face_vertex_data = {
+"color": SubResource("Resource_elbtk"),
+"face_index": SubResource("Resource_57b62"),
+"normal": SubResource("Resource_ionlo"),
+"vertex_index": SubResource("Resource_hv4ml")
+}
+
+[sub_resource type="Resource" id="Resource_d2tb6"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3pydo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_rd12p"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bi3cp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mydh1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_tsdnf"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jr2by"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_6cg2g"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6ofio"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.831469, 0, 0.555571, 0.831469, 0, 0.555571, 0.831469, 0, 0.555571, 0.831469, 0, 0.555571, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877, -0.831471, 0, -0.555568, -0.831471, 0, -0.555568, -0.831471, 0, -0.555568, -0.831471, 0, -0.555568, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_b8fl5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_t6ho8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(2.59281, 0, 2.59281, 3.38767, 0, 1.40322, 2.63083, 0, 1.08972, 2.01355, 0, 2.01355, 2.59281, 7.71482, 2.59281, 3.38767, 7.71482, 1.40322, 2.63083, 7.71482, 1.08972, 2.01355, 7.71482, 2.01355)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_06pbd"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_voyqv"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_t6ho8"),
+"selected": SubResource("Resource_06pbd")
+}
+edge_data = {
+"selected": SubResource("Resource_d2tb6")
+}
+face_data = {
+"color": SubResource("Resource_3pydo"),
+"material_index": SubResource("Resource_rd12p"),
+"selected": SubResource("Resource_bi3cp"),
+"uv_transform": SubResource("Resource_mydh1"),
+"visible": SubResource("Resource_tsdnf")
+}
+face_vertex_data = {
+"color": SubResource("Resource_jr2by"),
+"face_index": SubResource("Resource_6cg2g"),
+"normal": SubResource("Resource_6ofio"),
+"vertex_index": SubResource("Resource_b8fl5")
+}
+
+[sub_resource type="Resource" id="Resource_tmdg7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_gcknh"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_oc3vf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_axv8d"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_u6d11"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_k6cpo"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bogsk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_4ch5x"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_turs0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.980786, 0, 0.195085, 0.980786, 0, 0.195085, 0.980786, 0, 0.195085, 0.980786, 0, 0.195085, -1.45521e-07, 0, -1, -1.45521e-07, 0, -1, -1.45521e-07, 0, -1, -1.45521e-07, 0, -1, -0.980785, 0, -0.195091, -0.980785, 0, -0.195091, -0.980785, 0, -0.195091, -0.980785, 0, -0.195091, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_gmvgh"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mnjh1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(3.38767, 0, 1.40322, 3.66678, 0, -3.57628e-07, 2.84759, 0, -2.38419e-07, 2.63083, 0, 1.08972, 3.38767, 7.71482, 1.40322, 3.66678, 7.71482, -3.57628e-07, 2.84759, 7.71482, -2.38419e-07, 2.63083, 7.71482, 1.08972)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_fsx07"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ivve8"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_mnjh1"),
+"selected": SubResource("Resource_fsx07")
+}
+edge_data = {
+"selected": SubResource("Resource_tmdg7")
+}
+face_data = {
+"color": SubResource("Resource_gcknh"),
+"material_index": SubResource("Resource_oc3vf"),
+"selected": SubResource("Resource_axv8d"),
+"uv_transform": SubResource("Resource_u6d11"),
+"visible": SubResource("Resource_k6cpo")
+}
+face_vertex_data = {
+"color": SubResource("Resource_bogsk"),
+"face_index": SubResource("Resource_4ch5x"),
+"normal": SubResource("Resource_turs0"),
+"vertex_index": SubResource("Resource_gmvgh")
+}
+
+[sub_resource type="Resource" id="Resource_ijo62"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bwup1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_31faq"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kt7dn"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hdn1u"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_sijxt"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5n4ry"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ldssr"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pexf2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.980786, 0, -0.195085, 0.980786, 0, -0.195085, 0.980786, 0, -0.195085, 0.980786, 0, -0.195085, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877, -0.38269, 0, -0.923877, -0.980785, 0, 0.195091, -0.980785, 0, 0.195091, -0.980785, 0, 0.195091, -0.980785, 0, 0.195091, 1.45521e-07, 0, 1, 1.45521e-07, 0, 1, 1.45521e-07, 0, 1, 1.45521e-07, 0, 1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_0m5i2"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mnyut"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(3.66678, 0, -3.57628e-07, 3.38767, 0, -1.40322, 2.63083, 0, -1.08972, 2.84759, 0, -2.38419e-07, 3.66678, 7.71482, -3.57628e-07, 3.38767, 7.71482, -1.40322, 2.63083, 7.71482, -1.08972, 2.84759, 7.71482, -2.38419e-07)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_bwidy"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_heevy"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_mnyut"),
+"selected": SubResource("Resource_bwidy")
+}
+edge_data = {
+"selected": SubResource("Resource_ijo62")
+}
+face_data = {
+"color": SubResource("Resource_bwup1"),
+"material_index": SubResource("Resource_31faq"),
+"selected": SubResource("Resource_kt7dn"),
+"uv_transform": SubResource("Resource_hdn1u"),
+"visible": SubResource("Resource_sijxt")
+}
+face_vertex_data = {
+"color": SubResource("Resource_5n4ry"),
+"face_index": SubResource("Resource_ldssr"),
+"normal": SubResource("Resource_pexf2"),
+"vertex_index": SubResource("Resource_0m5i2")
+}
+
+[sub_resource type="Resource" id="Resource_1cyh1"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7blet"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_1hj4s"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c00ss"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_m5u7k"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_pi1hp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ytseo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_i3nhh"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_s2kwj"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.195085, 0, -0.980786, 0.195085, 0, -0.980786, 0.195085, 0, -0.980786, 0.195085, 0, -0.980786, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -0.195091, 0, 0.980785, -0.195091, 0, 0.980785, -0.195091, 0, 0.980785, -0.195091, 0, 0.980785, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269, 0.923877, 0, 0.38269)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_uq68v"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_f6xjt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1.40322, 0, -3.38767, -2.98023e-08, 0, -3.66678, -2.98023e-08, 0, -2.84759, 1.08972, 0, -2.63083, 1.40322, 7.71482, -3.38767, -2.98023e-08, 7.71482, -3.66678, -2.98023e-08, 7.71482, -2.84759, 1.08972, 7.71482, -2.63083)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_f02x5"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_q6nja"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_f6xjt"),
+"selected": SubResource("Resource_f02x5")
+}
+edge_data = {
+"selected": SubResource("Resource_1cyh1")
+}
+face_data = {
+"color": SubResource("Resource_7blet"),
+"material_index": SubResource("Resource_1hj4s"),
+"selected": SubResource("Resource_c00ss"),
+"uv_transform": SubResource("Resource_m5u7k"),
+"visible": SubResource("Resource_pi1hp")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ytseo"),
+"face_index": SubResource("Resource_i3nhh"),
+"normal": SubResource("Resource_s2kwj"),
+"vertex_index": SubResource("Resource_uq68v")
+}
+
+[sub_resource type="Resource" id="Resource_w3cj2"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_vftt3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wdhuu"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_tr4rq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pk0ot"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_qtidm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rnbj2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_3nes7"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ghqxw"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.195085, 0, -0.980786, -0.195085, 0, -0.980786, -0.195085, 0, -0.980786, -0.195085, 0, -0.980786, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269, -0.923877, 0, 0.38269, 0.195091, 0, 0.980785, 0.195091, 0, 0.980785, 0.195091, 0, 0.980785, 0.195091, 0, 0.980785, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_mv3tf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rscpl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-2.98023e-08, 0, -3.66678, -1.40322, 0, -3.38767, -1.08972, 0, -2.63083, -2.98023e-08, 0, -2.84759, -2.98023e-08, 7.71482, -3.66678, -1.40322, 7.71482, -3.38767, -1.08972, 7.71482, -2.63083, -2.98023e-08, 7.71482, -2.84759)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_oe8rs"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_67clf"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_rscpl"),
+"selected": SubResource("Resource_oe8rs")
+}
+edge_data = {
+"selected": SubResource("Resource_w3cj2")
+}
+face_data = {
+"color": SubResource("Resource_vftt3"),
+"material_index": SubResource("Resource_wdhuu"),
+"selected": SubResource("Resource_tr4rq"),
+"uv_transform": SubResource("Resource_pk0ot"),
+"visible": SubResource("Resource_qtidm")
+}
+face_vertex_data = {
+"color": SubResource("Resource_rnbj2"),
+"face_index": SubResource("Resource_3nes7"),
+"normal": SubResource("Resource_ghqxw"),
+"vertex_index": SubResource("Resource_mv3tf")
+}
+
+[sub_resource type="Resource" id="Resource_ypdgj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4f0x2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_xbpd3"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rbrpb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_a5dic"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_8ploo"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5sm1d"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_2b7kf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_qrimw"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.555571, 0, -0.831469, -0.555571, 0, -0.831469, -0.555571, 0, -0.831469, -0.555571, 0, -0.831469, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107, -0.707107, 0, 0.707107, 0.555568, 0, 0.831471, 0.555568, 0, 0.831471, 0.555568, 0, 0.831471, 0.555568, 0, 0.831471, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269, 0.923877, 0, -0.38269)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_vffc5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0uvpn"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1.40322, 0, -3.38767, -2.59281, 0, -2.59281, -2.01355, 0, -2.01355, -1.08972, 0, -2.63083, -1.40322, 7.71482, -3.38767, -2.59281, 7.71482, -2.59281, -2.01355, 7.71482, -2.01355, -1.08972, 7.71482, -2.63083)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_n8fto"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_sx3x1"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_0uvpn"),
+"selected": SubResource("Resource_n8fto")
+}
+edge_data = {
+"selected": SubResource("Resource_ypdgj")
+}
+face_data = {
+"color": SubResource("Resource_4f0x2"),
+"material_index": SubResource("Resource_xbpd3"),
+"selected": SubResource("Resource_rbrpb"),
+"uv_transform": SubResource("Resource_a5dic"),
+"visible": SubResource("Resource_8ploo")
+}
+face_vertex_data = {
+"color": SubResource("Resource_5sm1d"),
+"face_index": SubResource("Resource_2b7kf"),
+"normal": SubResource("Resource_qrimw"),
+"vertex_index": SubResource("Resource_vffc5")
+}
+
+[sub_resource type="Resource" id="Resource_mduwu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_nvyt4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wis5r"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_or4lg"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_btjm7"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_prko2"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_a0rea"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_jw15b"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bmvfp"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.831469, 0, -0.555571, -0.831469, 0, -0.555571, -0.831469, 0, -0.555571, -0.831469, 0, -0.555571, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877, -0.38269, 0, 0.923877, 0.831471, 0, 0.555568, 0.831471, 0, 0.555568, 0.831471, 0, 0.555568, 0.831471, 0, 0.555568, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107, 0.707107, 0, -0.707107)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_v6alm"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hnge8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-2.59281, 0, -2.59281, -3.38767, 0, -1.40322, -2.63083, 0, -1.08972, -2.01355, 0, -2.01355, -2.59281, 7.71482, -2.59281, -3.38767, 7.71482, -1.40322, -2.63083, 7.71482, -1.08972, -2.01355, 7.71482, -2.01355)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_6v0ne"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_oc134"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_hnge8"),
+"selected": SubResource("Resource_6v0ne")
+}
+edge_data = {
+"selected": SubResource("Resource_mduwu")
+}
+face_data = {
+"color": SubResource("Resource_nvyt4"),
+"material_index": SubResource("Resource_wis5r"),
+"selected": SubResource("Resource_or4lg"),
+"uv_transform": SubResource("Resource_btjm7"),
+"visible": SubResource("Resource_prko2")
+}
+face_vertex_data = {
+"color": SubResource("Resource_a0rea"),
+"face_index": SubResource("Resource_jw15b"),
+"normal": SubResource("Resource_bmvfp"),
+"vertex_index": SubResource("Resource_v6alm")
+}
+
+[sub_resource type="Resource" id="Resource_2w5sa"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1rfyd"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wt7yt"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7makj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_g6o8l"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_1fl85"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_s7jib"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_mwvy3"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jpyuc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -0.980786, 0, -0.195085, -0.980786, 0, -0.195085, -0.980786, 0, -0.195085, -0.980786, 0, -0.195085, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0.980785, 0, 0.195091, 0.980785, 0, 0.195091, 0.980785, 0, 0.195091, 0.980785, 0, 0.195091, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877, 0.38269, 0, -0.923877)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_w8fic"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_h8mfq"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-3.38767, 0, -1.40322, -3.66678, 0, 0, -2.84759, 0, 0, -2.63083, 0, -1.08972, -3.38767, 7.71482, -1.40322, -3.66678, 7.71482, 0, -2.84759, 7.71482, 0, -2.63083, 7.71482, -1.08972)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_gl75w"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jktse"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_h8mfq"),
+"selected": SubResource("Resource_gl75w")
+}
+edge_data = {
+"selected": SubResource("Resource_2w5sa")
+}
+face_data = {
+"color": SubResource("Resource_1rfyd"),
+"material_index": SubResource("Resource_wt7yt"),
+"selected": SubResource("Resource_7makj"),
+"uv_transform": SubResource("Resource_g6o8l"),
+"visible": SubResource("Resource_1fl85")
+}
+face_vertex_data = {
+"color": SubResource("Resource_s7jib"),
+"face_index": SubResource("Resource_mwvy3"),
+"normal": SubResource("Resource_jpyuc"),
+"vertex_index": SubResource("Resource_w8fic")
+}
+
+[sub_resource type="Resource" id="Resource_mvcno"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fkvsy"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_cdrvw"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xh72w"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_45jhx"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_fqaog"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ftxqt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_h7tcv"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xn63o"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.831469, 0, -0.555571, 0.831469, 0, -0.555571, 0.831469, 0, -0.555571, 0.831469, 0, -0.555571, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107, -0.707107, 0, -0.707107, -0.831471, 0, 0.555568, -0.831471, 0, 0.555568, -0.831471, 0, 0.555568, -0.831471, 0, 0.555568, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877, 0.38269, 0, 0.923877)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_i7bq4"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_63kd8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(3.38767, 0, -1.40322, 2.59281, 0, -2.59281, 2.01355, 0, -2.01355, 2.63083, 0, -1.08972, 3.38767, 7.71482, -1.40322, 2.59281, 7.71482, -2.59281, 2.01355, 7.71482, -2.01355, 2.63083, 7.71482, -1.08972)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_mpfue"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ejoew"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_63kd8"),
+"selected": SubResource("Resource_mpfue")
+}
+edge_data = {
+"selected": SubResource("Resource_mvcno")
+}
+face_data = {
+"color": SubResource("Resource_fkvsy"),
+"material_index": SubResource("Resource_cdrvw"),
+"selected": SubResource("Resource_xh72w"),
+"uv_transform": SubResource("Resource_45jhx"),
+"visible": SubResource("Resource_fqaog")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ftxqt"),
+"face_index": SubResource("Resource_h7tcv"),
+"normal": SubResource("Resource_xn63o"),
+"vertex_index": SubResource("Resource_i7bq4")
+}
+
+[sub_resource type="Resource" id="Resource_e5gjp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1s3wt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_bif5l"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_a8nmm"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fxncf"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.955007, 0.296582, -0.296582, 0.955007, -0.409209, -0.156138, 0.955007, 0.296582, -0.296582, 0.955007, -0.409209, -0.156138, 0.955007, 0, 0, 1, -0.409209, 0, 0.955007, 0, 0, 1, -0.156138, 0, 0.955007, 0, 0, 1, -0.409209, 0, 0.955007, 0, 0, 1, -0.409209, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_f0f1c"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6kjni"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_asja6"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8kau3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0.555571, 0, -0.831469, 0.555571, 0, -0.831469, 0.555571, 0, -0.831469, 0.555571, 0, -0.831469, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269, -0.923877, 0, -0.38269, -0.555568, 0, 0.831471, -0.555568, 0, 0.831471, -0.555568, 0, 0.831471, -0.555568, 0, 0.831471, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107, 0.707107, 0, 0.707107)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_vd4po"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_w28uo"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(2.59281, 1.63529, -2.59281, 1.40322, 1.63529, -3.38767, 1.08972, 1.63529, -2.63083, 2.01355, 1.63529, -2.01355, 2.59281, 7.71482, -2.59281, 1.40322, 7.71482, -3.38767, 1.08972, 7.71482, -2.63083, 2.01355, 7.71482, -2.01355)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_nsrbd"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3xtd7"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 0
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_w28uo"),
+"selected": SubResource("Resource_nsrbd")
+}
+edge_data = {
+"selected": SubResource("Resource_e5gjp")
+}
+face_data = {
+"color": SubResource("Resource_1s3wt"),
+"material_index": SubResource("Resource_bif5l"),
+"selected": SubResource("Resource_a8nmm"),
+"uv_transform": SubResource("Resource_fxncf"),
+"visible": SubResource("Resource_f0f1c")
+}
+face_vertex_data = {
+"color": SubResource("Resource_6kjni"),
+"face_index": SubResource("Resource_asja6"),
+"normal": SubResource("Resource_8kau3"),
+"vertex_index": SubResource("Resource_vd4po")
+}
+
+[sub_resource type="Resource" id="Resource_02twp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pncee"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ls7pq"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_liv2r"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_w0vxa"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_dfesi"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_say06"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_c6qni"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_33opt"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_qvg5c"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 0, 7, 6, 1)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_txtgm"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-14.1802, -0.409317, 8.2544, -14.1802, -0.409317, -11.1277, -14.1802, -0.173138, -11.1277, -14.1802, -0.173138, 8.2544, 0.386565, -0.173138, 8.2544, 0.386565, -0.173138, -11.1277, 0.386565, -0.409317, -11.1277, 0.386565, -0.409317, 8.2544)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_0tk17"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mt3jv"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 4, 7, 7, 0, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 3, 1, 2, 1, 4, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 0, 7, 6, 1)
+vertex_data = {
+"position": SubResource("Resource_txtgm"),
+"selected": SubResource("Resource_0tk17")
+}
+edge_data = {
+"selected": SubResource("Resource_02twp")
+}
+face_data = {
+"color": SubResource("Resource_pncee"),
+"material_index": SubResource("Resource_ls7pq"),
+"selected": SubResource("Resource_liv2r"),
+"uv_transform": SubResource("Resource_w0vxa"),
+"visible": SubResource("Resource_dfesi")
+}
+face_vertex_data = {
+"color": SubResource("Resource_say06"),
+"face_index": SubResource("Resource_c6qni"),
+"normal": SubResource("Resource_33opt"),
+"vertex_index": SubResource("Resource_qvg5c")
+}
+
+[sub_resource type="Resource" id="Resource_0wgq4"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pitvu"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ydg6y"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_a1crr"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bvdot"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_wq54x"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kg66g"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_taj7v"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jlvom"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ddaqv"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8w4vx"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 1.11611, 0, 0, 1.11611, 22.4934, 0, 0, 22.4934, 0, 0, 0, 0.288497, 1.11611, 0, 0.288497, 1.11611, 22.4934, 0.288497, 0, 22.4934, 0.288497, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_3gk2s"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_64xk3"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_8w4vx"),
+"selected": SubResource("Resource_3gk2s")
+}
+edge_data = {
+"selected": SubResource("Resource_0wgq4")
+}
+face_data = {
+"color": SubResource("Resource_pitvu"),
+"material_index": SubResource("Resource_ydg6y"),
+"selected": SubResource("Resource_a1crr"),
+"uv_transform": SubResource("Resource_bvdot"),
+"visible": SubResource("Resource_wq54x")
+}
+face_vertex_data = {
+"color": SubResource("Resource_kg66g"),
+"face_index": SubResource("Resource_taj7v"),
+"normal": SubResource("Resource_jlvom"),
+"vertex_index": SubResource("Resource_ddaqv")
+}
+
+[sub_resource type="Resource" id="Resource_crdq4"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_kawww"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_xmp8g"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_oro7o"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_wksri"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_byjql"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8j88p"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_c3mj1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_bed4r"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_nv082"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0rib3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 4.39206, -11.0531, 0, 4.39206, 8.40761, 0, 0, 8.40761, 0, 0, -11.0531, 0.735325, 4.39206, -11.0531, 0.735325, 4.39206, 8.40761, 0.735325, 0, 8.40761, 0.735325, 0, -11.0531)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_4sw3l"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hmbnf"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_0rib3"),
+"selected": SubResource("Resource_4sw3l")
+}
+edge_data = {
+"selected": SubResource("Resource_crdq4")
+}
+face_data = {
+"color": SubResource("Resource_kawww"),
+"material_index": SubResource("Resource_xmp8g"),
+"selected": SubResource("Resource_oro7o"),
+"uv_transform": SubResource("Resource_wksri"),
+"visible": SubResource("Resource_byjql")
+}
+face_vertex_data = {
+"color": SubResource("Resource_8j88p"),
+"face_index": SubResource("Resource_c3mj1"),
+"normal": SubResource("Resource_bed4r"),
+"vertex_index": SubResource("Resource_nv082")
+}
+
+[sub_resource type="Resource" id="Resource_1f65l"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_htfpk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_35mv1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_8pbtn"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jpbvq"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_rkyif"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_agxfu"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_6hyqk"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_o6sf8"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_x6urp"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_80j3r"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0.538043, 5.99567, 0, 0.538043, 17.0469, 0, 0, 17.0469, 0, 0, 5.99567, 13.1595, 0.538043, 5.99567, 13.1595, 0.538043, 17.0469, 13.1595, 0, 17.0469, 13.1595, 0, 5.99567)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_6kpys"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_nxnno"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_80j3r"),
+"selected": SubResource("Resource_6kpys")
+}
+edge_data = {
+"selected": SubResource("Resource_1f65l")
+}
+face_data = {
+"color": SubResource("Resource_htfpk"),
+"material_index": SubResource("Resource_35mv1"),
+"selected": SubResource("Resource_8pbtn"),
+"uv_transform": SubResource("Resource_jpbvq"),
+"visible": SubResource("Resource_rkyif")
+}
+face_vertex_data = {
+"color": SubResource("Resource_agxfu"),
+"face_index": SubResource("Resource_6hyqk"),
+"normal": SubResource("Resource_o6sf8"),
+"vertex_index": SubResource("Resource_x6urp")
+}
+
+[sub_resource type="Resource" id="Resource_4c7c7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4s0xk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_j0jcn"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_om7l3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 1)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_glimq"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_6cd4v"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_qhwii"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_k68ke"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1dls2"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_oj7v7"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_c2dgk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 2.57081, 0.990232, 0, 2.57081, 9.06656, 0, 0, 9.06656, 0, 0, 0.990232, 0.375159, 2.57081, 0.990232, 0.375159, 2.57081, 9.06656, 0.375159, 0, 9.06656, 0.375159, 0, 0.990232)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_hqcm3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_t3r81"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 5
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_c2dgk"),
+"selected": SubResource("Resource_hqcm3")
+}
+edge_data = {
+"selected": SubResource("Resource_4c7c7")
+}
+face_data = {
+"color": SubResource("Resource_4s0xk"),
+"material_index": SubResource("Resource_j0jcn"),
+"selected": SubResource("Resource_om7l3"),
+"uv_transform": SubResource("Resource_glimq"),
+"visible": SubResource("Resource_6cd4v")
+}
+face_vertex_data = {
+"color": SubResource("Resource_qhwii"),
+"face_index": SubResource("Resource_k68ke"),
+"normal": SubResource("Resource_1dls2"),
+"vertex_index": SubResource("Resource_oj7v7")
+}
+
+[sub_resource type="Resource" id="Resource_1l082"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_5nktd"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_gkyhr"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_lvw5u"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_0rmvl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_2n74b"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_2g5vb"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wsrej"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mmjrq"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_5t0a3"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_p57oj"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 2.8592, 0, 0, 2.8592, 0.342241, 0, 0, 0.342241, 0, 0, 0, 13.1089, 2.8592, 0, 13.1089, 2.8592, 0.342241, 13.1089, 0, 0.342241, 13.1089, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_5ndlq"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_81r8w"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_p57oj"),
+"selected": SubResource("Resource_5ndlq")
+}
+edge_data = {
+"selected": SubResource("Resource_1l082")
+}
+face_data = {
+"color": SubResource("Resource_5nktd"),
+"material_index": SubResource("Resource_gkyhr"),
+"selected": SubResource("Resource_lvw5u"),
+"uv_transform": SubResource("Resource_0rmvl"),
+"visible": SubResource("Resource_2n74b")
+}
+face_vertex_data = {
+"color": SubResource("Resource_2g5vb"),
+"face_index": SubResource("Resource_wsrej"),
+"normal": SubResource("Resource_mmjrq"),
+"vertex_index": SubResource("Resource_5t0a3")
+}
+
+[sub_resource type="Resource" id="Resource_6cu77"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_qt0k4"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_g4t48"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_l2ibv"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 1, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_isv7s"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_5sjcs"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_atvwg"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_vr8pf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_46rcc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0.801437, 0.598079, 0, 0.801437, 0.598079, 0, 0.801437, 0.598079, 0, 0.801437, 0.598079, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -0.801437, -0.598079, 0, -0.801437, -0.598079, 0, -0.801437, -0.598079, 0, -0.801437, -0.598079)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ingpq"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ivra6"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-12.683, 4.388, -5.88, -12.683, 4.388, -5.156, -12.683, 0, 0.724, -12.683, 0, 0, 0.385, 4.388, -5.88, 0.385, 4.388, -5.156, 0.385, 0, 0.724, 0.385, 0, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ftbc0"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jg86y"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 2
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 7, 6, 6, 5, 5, 4, 4, 7, 0, 4, 5, 1, 6, 2, 7, 3)
+edge_face_indices = PackedInt32Array(0, 2, 0, 3, 0, 4, 0, 5, 1, 4, 1, 3, 1, 2, 1, 5, 2, 5, 2, 3, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 7, 6, 5, 4, 1, 0, 4, 5, 2, 1, 5, 6, 3, 2, 6, 7, 0, 3, 7, 4)
+vertex_data = {
+"position": SubResource("Resource_ivra6"),
+"selected": SubResource("Resource_ftbc0")
+}
+edge_data = {
+"selected": SubResource("Resource_6cu77")
+}
+face_data = {
+"color": SubResource("Resource_qt0k4"),
+"material_index": SubResource("Resource_g4t48"),
+"selected": SubResource("Resource_l2ibv"),
+"uv_transform": SubResource("Resource_isv7s"),
+"visible": SubResource("Resource_5sjcs")
+}
+face_vertex_data = {
+"color": SubResource("Resource_atvwg"),
+"face_index": SubResource("Resource_vr8pf"),
+"normal": SubResource("Resource_46rcc"),
+"vertex_index": SubResource("Resource_ingpq")
+}
+
+[sub_resource type="Resource" id="Resource_hiyh5"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xoxhl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_bpmq7"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6golb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3ecgg"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_8bqje"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ae3hn"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ph87d"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_323ph"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_qtvtk"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mwxmk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.234361, 2.69813, 11.665, 0.234361, 2.20955, 11.665, 0, 2.20955, 11.665, 0, 2.69813, 11.665, 0, 2.20955, 0, 0, 2.69813, 0, 0.234361, 2.20955, 0, 0.234361, 2.69813, 0)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_nnid0"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_xrvgc"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_mwxmk"),
+"selected": SubResource("Resource_nnid0")
+}
+edge_data = {
+"selected": SubResource("Resource_hiyh5")
+}
+face_data = {
+"color": SubResource("Resource_xoxhl"),
+"material_index": SubResource("Resource_bpmq7"),
+"selected": SubResource("Resource_6golb"),
+"uv_transform": SubResource("Resource_3ecgg"),
+"visible": SubResource("Resource_8bqje")
+}
+face_vertex_data = {
+"color": SubResource("Resource_ae3hn"),
+"face_index": SubResource("Resource_ph87d"),
+"normal": SubResource("Resource_323ph"),
+"vertex_index": SubResource("Resource_qtvtk")
+}
+
+[sub_resource type="Resource" id="Resource_0l607"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_hdlxd"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_l7hnf"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_tuaf3"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 1, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1fqml"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_i0nro"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_uu05f"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_breh1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_w3eaf"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_1cw43"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_djsol"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.234361, 2.20998, 11.665, 0.234361, 0, 11.665, 0, 0, 11.665, 0, 2.20998, 11.665, 0, 0, 8.61629, 0, 2.20998, 8.61629, 0.234361, 0, 8.61629, 0.234361, 2.20998, 8.61629)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_ml3b6"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_w1001"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 3
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_djsol"),
+"selected": SubResource("Resource_ml3b6")
+}
+edge_data = {
+"selected": SubResource("Resource_0l607")
+}
+face_data = {
+"color": SubResource("Resource_hdlxd"),
+"material_index": SubResource("Resource_l7hnf"),
+"selected": SubResource("Resource_tuaf3"),
+"uv_transform": SubResource("Resource_1fqml"),
+"visible": SubResource("Resource_i0nro")
+}
+face_vertex_data = {
+"color": SubResource("Resource_uu05f"),
+"face_index": SubResource("Resource_breh1"),
+"normal": SubResource("Resource_w3eaf"),
+"vertex_index": SubResource("Resource_1cw43")
+}
+
+[sub_resource type="Resource" id="Resource_f5jwd"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_khlch"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_xbuww"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_4x56u"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_n01qm"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_73l26"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_475xs"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_ta6gi"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_17nfc"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_c08df"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_b1jmk"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.234361, 0, 0, 0.234361, 0.00358915, 0, 0, 0.00358915, 0, 0, 0, 0, 0, 0.00358915, 8.61629, 0, 0, 8.61629, 0.234361, 0.00358915, 8.61629, 0.234361, 0, 8.61629)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_asv7r"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_lj4m5"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_b1jmk"),
+"selected": SubResource("Resource_asv7r")
+}
+edge_data = {
+"selected": SubResource("Resource_f5jwd")
+}
+face_data = {
+"color": SubResource("Resource_khlch"),
+"material_index": SubResource("Resource_xbuww"),
+"selected": SubResource("Resource_4x56u"),
+"uv_transform": SubResource("Resource_n01qm"),
+"visible": SubResource("Resource_73l26")
+}
+face_vertex_data = {
+"color": SubResource("Resource_475xs"),
+"face_index": SubResource("Resource_ta6gi"),
+"normal": SubResource("Resource_17nfc"),
+"vertex_index": SubResource("Resource_c08df")
+}
+
+[sub_resource type="Resource" id="Resource_84l6i"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_gx6vl"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_78vj0"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_7l7rr"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 1, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_cr1wf"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_umyb5"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3wy3a"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_v374c"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_mkumw"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_i2116"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_6pje0"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0.234361, 0.00358915, 0, 0.234361, 2.22317, 0, 0, 2.22317, 0, 0, 0.00358915, 0, 0, 2.22317, 7.0589, 0, 0.00358915, 7.0589, 0.234361, 2.22317, 7.0589, 0.234361, 0.00358915, 7.0589)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_am1nj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_1vtnn"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = 2
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 3, 2, 4, 6, 4, 1, 6, 7, 0, 5, 7, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 4, 1, 3, 1, 2, 2, 4, 2, 5, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 5, 3, 2, 6, 4, 2, 1, 7, 0, 3, 5, 7, 5, 4, 6, 7, 6, 1, 0)
+vertex_data = {
+"position": SubResource("Resource_6pje0"),
+"selected": SubResource("Resource_am1nj")
+}
+edge_data = {
+"selected": SubResource("Resource_84l6i")
+}
+face_data = {
+"color": SubResource("Resource_gx6vl"),
+"material_index": SubResource("Resource_78vj0"),
+"selected": SubResource("Resource_7l7rr"),
+"uv_transform": SubResource("Resource_cr1wf"),
+"visible": SubResource("Resource_umyb5")
+}
+face_vertex_data = {
+"color": SubResource("Resource_3wy3a"),
+"face_index": SubResource("Resource_v374c"),
+"normal": SubResource("Resource_mkumw"),
+"vertex_index": SubResource("Resource_i2116")
+}
+
+[sub_resource type="Resource" id="Resource_ng7ch"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_50x21"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_vtvt8"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_83rx8"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_uihei"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_soy8t"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jmnfr"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_gmya5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_jrndp"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_bvgv4"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_11fa3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-13.7145, 4.19291, 2.11873, -13.7145, 4.19291, 0, -9.90203, 4.19291, 0, -9.90203, 4.19291, 2.11873, -9.90203, 2.45858, 4.38319, -9.90203, 2.45858, 0, -13.7145, 2.45858, 0, -13.7145, 2.45858, 4.38319)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_q1msk"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_negy1"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 4, 7, 7, 0, 6, 7)
+edge_face_indices = PackedInt32Array(0, 5, 0, 2, 0, 1, 0, 3, 1, 3, 1, 2, 1, 4, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 7, 4, 5, 6, 1, 0, 7)
+vertex_data = {
+"position": SubResource("Resource_11fa3"),
+"selected": SubResource("Resource_q1msk")
+}
+edge_data = {
+"selected": SubResource("Resource_ng7ch")
+}
+face_data = {
+"color": SubResource("Resource_50x21"),
+"material_index": SubResource("Resource_vtvt8"),
+"selected": SubResource("Resource_83rx8"),
+"uv_transform": SubResource("Resource_uihei"),
+"visible": SubResource("Resource_soy8t")
+}
+face_vertex_data = {
+"color": SubResource("Resource_jmnfr"),
+"face_index": SubResource("Resource_gmya5"),
+"normal": SubResource("Resource_jrndp"),
+"vertex_index": SubResource("Resource_bvgv4")
+}
+
+[sub_resource type="Resource" id="Resource_h60sc"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_aaf7h"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_k43w6"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_rm6v7"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_oagga"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_dvbqo"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_g8k4r"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_wnags"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_a0wdx"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, -9.5755e-08, -1.46676e-07, 1, -9.5755e-08, -1.46676e-07, 1, -9.5755e-08, -1.46676e-07, 1, -9.5755e-08, -1.46676e-07)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_qdtca"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 1, 0, 7, 6, 7, 4, 5)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_og5h1"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-5.90203, 2.45858, 4.38319, -5.90203, 2.45858, 0, -5.90203, 4.19291, 0, -5.90203, 4.19291, 2.11873, 0.386566, 4.19291, 2.11873, 0.386565, 4.19291, 0, 0.386565, 2.45858, 0, 0.386566, 2.45858, 4.38319)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_4r0bj"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_h38g1"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 4, 7, 7, 0, 7, 6)
+edge_face_indices = PackedInt32Array(0, 4, 0, 2, 0, 1, 0, 3, 1, 3, 1, 2, 1, 5, 2, 5, 2, 4, 3, 5, 3, 4, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 0, 3, 4, 7, 6, 1, 0, 7, 6, 7, 4, 5)
+vertex_data = {
+"position": SubResource("Resource_og5h1"),
+"selected": SubResource("Resource_4r0bj")
+}
+edge_data = {
+"selected": SubResource("Resource_h60sc")
+}
+face_data = {
+"color": SubResource("Resource_aaf7h"),
+"material_index": SubResource("Resource_k43w6"),
+"selected": SubResource("Resource_rm6v7"),
+"uv_transform": SubResource("Resource_oagga"),
+"visible": SubResource("Resource_dvbqo")
+}
+face_vertex_data = {
+"color": SubResource("Resource_g8k4r"),
+"face_index": SubResource("Resource_wnags"),
+"normal": SubResource("Resource_a0wdx"),
+"vertex_index": SubResource("Resource_qdtca")
+}
+
+[sub_resource type="Resource" id="Resource_ljoka"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_x7uin"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_5a2x5"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_iefyh"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3b2kh"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_ko6jb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_3n8yy"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_f3pg1"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fuki7"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 0.793903, 0.608044, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 4.56201e-07, 6.988e-07, 1, 4.56201e-07, 6.988e-07, 1, 4.56201e-07, 6.988e-07, 1, 4.56201e-07, 6.988e-07)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_aswf8"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 6, 7, 4, 5, 6, 1, 0, 7, 0, 3, 4, 7)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ls12f"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-5.90203, 2.45858, 4.38319, -9.90203, 2.45858, 4.38319, -9.90203, 4.19291, 2.11873, -5.90203, 4.19291, 2.11873, -5.90203, 4.19291, 1.88623, -9.90203, 4.19291, 1.88623, -9.90203, 2.45858, 1.88623, -5.90203, 2.45858, 1.88623)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_mjdkp"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_g8qt4"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 3, 2, 5, 5, 4, 6, 5, 1, 6, 6, 7, 7, 4, 0, 7)
+edge_face_indices = PackedInt32Array(0, 4, 0, 2, 0, 1, 0, 5, 1, 5, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 3, 2, 5, 6, 5, 2, 1, 6, 7, 4, 5, 6, 1, 0, 7, 0, 3, 4, 7)
+vertex_data = {
+"position": SubResource("Resource_ls12f"),
+"selected": SubResource("Resource_mjdkp")
+}
+edge_data = {
+"selected": SubResource("Resource_ljoka")
+}
+face_data = {
+"color": SubResource("Resource_x7uin"),
+"material_index": SubResource("Resource_5a2x5"),
+"selected": SubResource("Resource_iefyh"),
+"uv_transform": SubResource("Resource_3b2kh"),
+"visible": SubResource("Resource_ko6jb")
+}
+face_vertex_data = {
+"color": SubResource("Resource_3n8yy"),
+"face_index": SubResource("Resource_f3pg1"),
+"normal": SubResource("Resource_fuki7"),
+"vertex_index": SubResource("Resource_aswf8")
+}
+
+[sub_resource type="Resource" id="Resource_rdcdu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_ses2y"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_r4wnp"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 0, 0)
+name = &"material_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_fcqrk"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_pq68q"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
+name = &"uv_transform"
+category = ""
+data_type = 8
+stride = 6
+
+[sub_resource type="Resource" id="Resource_i0gbu"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(1, 1, 1, 1, 1, 1)
+name = &"visible"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_l7dbr"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
+name = &"color"
+category = ""
+data_type = 4
+stride = 4
+
+[sub_resource type="Resource" id="Resource_c53tk"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5)
+name = &"face_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_he5q3"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
+name = &"normal"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_5ldfb"]
+script = ExtResource("4_3vpm2")
+data = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+name = &"vertex_index"
+category = ""
+data_type = 1
+stride = 1
+
+[sub_resource type="Resource" id="Resource_dmfjn"]
+script = ExtResource("5_0ijij")
+data = PackedFloat32Array(-5.90203, 4.19291, 0.88623, -5.90203, 4.19291, 0, -5.90203, 2.45858, 0, -5.90203, 2.45858, 0.88623, -9.90203, 2.45858, 0, -9.90203, 4.19291, 0, -9.90203, 2.45858, 0.88623, -9.90203, 4.19291, 0.88623)
+name = &"position"
+category = ""
+data_type = 6
+stride = 3
+
+[sub_resource type="Resource" id="Resource_1hcmb"]
+script = ExtResource("3_f6xty")
+data = PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0)
+name = &"selected"
+category = ""
+data_type = 0
+stride = 1
+
+[sub_resource type="Resource" id="Resource_yfdm7"]
+script = ExtResource("6_8jlqm")
+num_vertices = 8
+num_edges = 12
+num_faces = 6
+num_face_vertices = 24
+active_vertex = -1
+active_edge = -1
+active_face = -1
+active_face_vertex = -1
+edge_vertex_indices = PackedInt32Array(0, 1, 1, 2, 2, 3, 3, 0, 4, 2, 1, 5, 5, 4, 6, 3, 4, 6, 7, 5, 0, 7, 7, 6)
+edge_face_indices = PackedInt32Array(0, 3, 0, 1, 0, 2, 0, 5, 1, 2, 1, 3, 1, 4, 2, 5, 2, 4, 3, 4, 3, 5, 4, 5)
+face_vertex_count = PackedInt32Array(4, 4, 4, 4, 4, 4)
+face_vertex_indices = PackedInt32Array(0, 1, 2, 3, 4, 2, 1, 5, 6, 3, 2, 4, 7, 5, 1, 0, 7, 6, 4, 5, 7, 0, 3, 6)
+vertex_data = {
+"position": SubResource("Resource_dmfjn"),
+"selected": SubResource("Resource_1hcmb")
+}
+edge_data = {
+"selected": SubResource("Resource_rdcdu")
+}
+face_data = {
+"color": SubResource("Resource_ses2y"),
+"material_index": SubResource("Resource_r4wnp"),
+"selected": SubResource("Resource_fcqrk"),
+"uv_transform": SubResource("Resource_pq68q"),
+"visible": SubResource("Resource_i0gbu")
+}
+face_vertex_data = {
+"color": SubResource("Resource_l7dbr"),
+"face_index": SubResource("Resource_c53tk"),
+"normal": SubResource("Resource_he5q3"),
+"vertex_index": SubResource("Resource_5ldfb")
+}
+
+[sub_resource type="CylinderShape3D" id="CylinderShape3D_3ew1m"]
+height = 3.47227
+radius = 3.23854
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n57cv"]
+transparency = 1
+albedo_texture = ExtResource("11_08oij")
+roughness_texture = ExtResource("12_6jxu0")
+normal_enabled = true
+normal_texture = ExtResource("12_6jxu0")
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_rmiiq"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_a1btl"]
+size = Vector3(4, 1.63977, 5)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_e5yxg"]
+cull_mode = 2
+albedo_color = Color(0, 0, 0, 1)
+emission_enabled = true
+emission = Color(1, 1, 1, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_8m1cn"]
+size = Vector3(0.76416, 1.90765, 2.28064)
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_14b7m"]
+lifetime_randomness = 0.14
+emission_shape = 3
+emission_box_extents = Vector3(7.21, 5.485, 13.86)
+angle_min = -178.5
+angle_max = 171.7
+direction = Vector3(0, 0, 0)
+spread = 42.36
+initial_velocity_max = 0.1
+angular_velocity_min = 10.15
+angular_velocity_max = 29.4
+gravity = Vector3(0, 0.015, 0)
+linear_accel_min = -2.23517e-06
+linear_accel_max = -2.23517e-06
+scale_min = 0.1
+scale_max = 0.2
+turbulence_noise_strength = 16.53
+collision_mode = 2
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_3l82k"]
+lifetime_randomness = 0.14
+emission_shape = 3
+emission_box_extents = Vector3(7.897, 2.435, 8.435)
+angle_min = -178.5
+angle_max = 171.7
+direction = Vector3(0, 0, 0)
+spread = 8.345
+initial_velocity_max = 0.1
+gravity = Vector3(0, 0.015, 0)
+linear_accel_min = -2.23517e-06
+linear_accel_max = -2.23517e-06
+scale_min = 0.1
+scale_max = 0.2
+turbulence_noise_strength = 16.53
+collision_mode = 2
+
+[node name="Level1" type="Node3D"]
+
+[node name="Player" parent="." instance=ExtResource("2_iom4v")]
+transform = Transform3D(0.72867, 0, 0.684865, 0, 1, 0, -0.684865, 0, 0.72867, -6.34549, 0, -5.72347)
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.764162, 0.632238, -0.127796, 0.397431, 0.617553, 0.678732, 0.508041, 0.467871, -0.723182, 0, 17.2518, 0)
+shadow_enabled = true
+directional_shadow_mode = 1
+directional_shadow_split_1 = 0.284
+directional_shadow_fade_start = 0.525
+directional_shadow_max_distance = 45.0
+directional_shadow_pancake_size = 15.0
+
+[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
+navigation_mesh = SubResource("NavigationMesh_hdiwy")
+
+[node name="Block_0" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, -1, -8)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_wjtdm")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_5" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 2.61711, -8)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_k1hk4")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_2" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.22675, -1.19209e-07, -7.90353)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_w5ogc")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_3" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -7.87954, -9.53674e-07, -3.03458)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_k4yf4")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_4" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 7.99486, -1.07288e-06, -3.16528)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_cbaxd")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_7" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_ou5j3")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_8" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_5liv1")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_9" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_xildq")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_1" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.73693, -0.479077, 2)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_8xs8b")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_10" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.54844, -0.499659, 9.62352)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_fbjvs")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_11" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.52828, 0.0214276, 1.98818)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_iplyh")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_6" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.80945, -0.176165, 14.2928)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_u53xy")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_14" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_qxrkd")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_15" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_rcdou")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_16" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_48olb")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_17" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_g38o5")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_12" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_653r7")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_18" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_4gxdu")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_19" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_ckhg6")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_20" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_7wq5j")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_21" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_ir7n5")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_22" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_hdtyc")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_23" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_5hks2")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_24" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_voyqv")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_25" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_ivve8")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_26" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_heevy")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_29" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_q6nja")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_30" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_67clf")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_31" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_sx3x1")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_32" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_oc134")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_33" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_jktse")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_27" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_ejoew")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_28" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_3xtd7")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_37" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.78706, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_mt3jv")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_13" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.02405, 7.98717, -7.87689)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_64xk3")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_34" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -22.2367, 3.61192, -8.11062)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_hmbnf")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_36" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.3769, 6.23453, -24.9331)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_nxnno")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_38" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.9298, 3.65562, -16.9701)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_t3r81")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_40" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.5013, 3.59214, -19.046)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_81r8w")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_41" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.858, 3.614, 0.112)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_jg86y")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_43" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_xrvgc")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_44" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_w1001")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_45" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_lj4m5")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_46" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_1vtnn")
+materials = Array[Material]([ExtResource("7_a6jsf")])
+
+[node name="Block_42" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_negy1")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_47" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_h38g1")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_48" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_g8qt4")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Block_49" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+script = ExtResource("2_vbywh")
+mesh_vector_data = SubResource("Resource_yfdm7")
+materials = Array[Material]([ExtResource("7_n23m2")])
+
+[node name="Node3D" type="Node3D" parent="."]
+transform = Transform3D(-0.127272, 0, 0.991868, 0, 1, 0, -0.991868, 0, -0.127272, -1.45819, -0.0530685, 5.7422)
+
+[node name="Enemy" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(0.886163, 0, -0.463373, 0, 1, 0, 0.463373, 0, 0.886163, 2.36963, 0, -1.44461)
+
+[node name="Enemy3" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(0.886162, 0, -0.463373, 0, 1, 0, 0.463373, 0, 0.886162, 11.7773, 7.85667, -3.75904)
+
+[node name="Enemy4" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792789, 0, -0.609495, 0, 1, 0, 0.609495, 0, -0.792789, 9.94873, 7.85667, 8.35039)
+
+[node name="Enemy5" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792789, 0, -0.609495, 0, 1, 0, 0.609495, 0, -0.792789, 4.42171, 7.85667, 8.19718)
+
+[node name="Enemy6" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792789, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792789, 3.68812, 3.62603, 7.24019)
+
+[node name="Enemy7" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792789, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792789, 11.2872, 3.62603, -1.26274)
+
+[node name="Enemy8" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 17.4707, 3.79078, -16.354)
+
+[node name="Enemy9" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 18.943, 3.79078, -16.2242)
+
+[node name="Enemy10" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 18.6843, 3.87412, -15.3698)
+
+[node name="Enemy11" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 19.4269, 3.87412, -12.0882)
+
+[node name="Enemy12" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 20.1413, 3.87412, -10.3197)
+
+[node name="Enemy13" parent="Node3D" instance=ExtResource("8_t5py6")]
+transform = Transform3D(0.999635, 0, -0.0269438, 0, 1, 0, 0.0269438, 0, 0.999635, 21.9169, 3.87412, -11.3213)
+
+[node name="Enemy2" parent="Node3D" instance=ExtResource("8_t5py6")]
+
+[node name="TubeSecret" parent="." node_paths=PackedStringArray("SecretHandler") instance=ExtResource("9_mmoff")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.403744, 4.27109, -1.69066)
+SecretHandler = NodePath("../SecretHandler")
+TriggerID = "TubeEasterEgg"
+
+[node name="CollisionShape3D" parent="TubeSecret" index="0"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.198989, 0.319885, -0.0117693)
+shape = SubResource("CylinderShape3D_3ew1m")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="TubeSecret"]
+transform = Transform3D(-2.19796, 0, 1.92152e-07, 0, 2.19796, 0, -1.92152e-07, 0, -2.19796, -0.0113032, -0.653244, 0.348745)
+material_override = SubResource("StandardMaterial3D_n57cv")
+mesh = SubResource("PlaneMesh_rmiiq")
+
+[node name="SpotLight3D" type="SpotLight3D" parent="TubeSecret"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0881028, 3.12017, 0.0140528)
+shadow_enabled = true
+spot_range = 6.478
+spot_attenuation = 1.7
+
+[node name="TitanfallSecret" parent="." node_paths=PackedStringArray("SecretHandler") instance=ExtResource("9_mmoff")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 11)
+SecretHandler = NodePath("../SecretHandler")
+TriggerID = "TitanfallEasterEgg"
+
+[node name="CollisionShape3D" parent="TitanfallSecret" index="0"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.5, 0.319885, 2)
+shape = SubResource("BoxShape3D_a1btl")
+
+[node name="SecretHandler" type="Node" parent="."]
+script = ExtResource("10_aof45")
+
+[node name="Label3D" type="Label3D" parent="SecretHandler"]
+transform = Transform3D(-2.7838e-08, -0.879175, -0.476499, -3.37006e-08, 0.476499, -0.879175, 1, -8.41615e-09, -4.28935e-08, -3.3394, 1.48691, 12.2744)
+shaded = true
+text = "Standby for
+Titanfall"
+font = ExtResource("13_hif5b")
+font_size = 62
+outline_size = 3
+
+[node name="Label3D" type="Label3D" parent="." node_paths=PackedStringArray("secretHandler")]
+transform = Transform3D(-1, 0, -8.9407e-08, 0, 1, 0, 8.9407e-08, 0, -1, -12.7439, 4.8765, -7.91977)
+alpha_scissor_threshold = 0.51
+texture_filter = 1
+text = "Secrets found:
+0/2"
+font = ExtResource("12_xvgha")
+font_size = 78
+outline_size = 0
+line_spacing = 8.445
+script = ExtResource("11_flx2f")
+secretHandler = NodePath("../SecretHandler")
+
+[node name="Label3D2" type="Label3D" parent="."]
+transform = Transform3D(1.33118e-07, 0, -1, 0, 1, 0, 1, 0, 1.33118e-07, -8.73379, 4.75402, -13.1583)
+alpha_scissor_threshold = 0.51
+texture_filter = 1
+text = "EXIT >"
+font = ExtResource("12_xvgha")
+font_size = 78
+outline_size = 0
+line_spacing = 8.445
+
+[node name="CSGBox3D" type="CSGBox3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.11451, 4.76498, -11.6401)
+material_override = SubResource("StandardMaterial3D_e5yxg")
+size = Vector3(1, 2.32227, 2.01953)
+
+[node name="Area3D" type="Area3D" parent="CSGBox3D"]
+script = ExtResource("13_nuwcy")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGBox3D/Area3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.11792, 0.126678, -0.0437622)
+shape = SubResource("BoxShape3D_8m1cn")
+
+[node name="Sofa" type="MeshInstance3D" parent="."]
+transform = Transform3D(0.866025, 0.5, 0, 2.18557e-08, -3.78552e-08, 1, 0.5, -0.866025, -4.37114e-08, 2.51683, -0.0579113, -5.13995)
+mesh = ExtResource("18_eun4c")
+
+[node name="Console" type="MeshInstance3D" parent="."]
+transform = Transform3D(-0.485899, -0.398015, -6.06429e-09, 1.87189e-08, -3.24221e-08, 0.628103, -0.398015, 0.485899, 3.69434e-08, 6.45319, 0.450982, -2.36383)
+mesh = ExtResource("19_u0cos")
+
+[node name="Props" parent="." instance=ExtResource("20_o2f1r")]
+transform = Transform3D(0.379356, 0, -0.379356, 0, 0.536491, 0, 0.379356, 0, 0.379356, 5.8947, 1.47981, -1.52369)
+
+[node name="SpotLight3D" type="SpotLight3D" parent="Props"]
+transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -0.0529249, 0, 4.50775e-10)
+shadow_enabled = true
+spot_range = 8.245
+spot_attenuation = 2.08
+spot_angle = 64.94
+
+[node name="Table" type="MeshInstance3D" parent="Props"]
+transform = Transform3D(1.61424, -0.931983, -5.96046e-08, 5.67324e-08, -5.96597e-08, 1.86396, -0.931983, -1.61424, 5.96046e-08, -4.99424, -2.97375, -1.15214)
+mesh = ExtResource("21_3g6q4")
+
+[node name="Chair" type="MeshInstance3D" parent="Props"]
+transform = Transform3D(1.11156, -0.00104791, -3.55447e-08, 2.08551e-08, -1.30766e-08, 1.11156, -0.00104785, -1.11156, 3.55447e-08, -8.0301, -1.5956, 14.6172)
+mesh = ExtResource("22_r3ah8")
+
+[node name="OmniLight3D" type="OmniLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.41333, 1.31711, 5.22692)
+light_energy = 0.342
+omni_range = 5.096
+omni_attenuation = 6.57
+
+[node name="SpotLight3D2" type="SpotLight3D" parent="."]
+transform = Transform3D(0.960378, 0.00345009, -0.278678, 0.0247549, 0.994915, 0.0976273, 0.277598, -0.100658, 0.95541, 1.98901, 2.71689, 13.4963)
+shadow_enabled = true
+spot_range = 29.68
+spot_attenuation = 2.08
+spot_angle = 23.92
+
+[node name="GPUParticles3D" type="GPUParticles3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.09567, 0)
+amount = 200
+lifetime = 30.07
+speed_scale = 2.5
+explosiveness = 0.04
+randomness = 0.31
+visibility_aabb = AABB(-8.27, -7.28, -8.505, 16.915, 11.64, 26.17)
+process_material = SubResource("ParticleProcessMaterial_14b7m")
+draw_pass_1 = ExtResource("23_gn80l")
+
+[node name="GPUParticles3D2" type="GPUParticles3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.6456, 6.09567, -9.72798)
+amount = 150
+lifetime = 30.07
+speed_scale = 2.5
+explosiveness = 0.04
+randomness = 0.31
+visibility_aabb = AABB(-7.31, -5.25, -10.41, 16.37, 8, 20.32)
+process_material = SubResource("ParticleProcessMaterial_3l82k")
+draw_pass_1 = ExtResource("23_gn80l")
+
+[node name="SpotLight3D" type="SpotLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -16.492, 6.491, -6.3759)
+shadow_enabled = true
+spot_range = 6.875
+spot_attenuation = 2.18
+spot_angle = 79.3
+
+[connection signal="body_entered" from="CSGBox3D/Area3D" to="CSGBox3D/Area3D" method="_on_body_entered"]
+
+[editable path="TubeSecret"]
+[editable path="TitanfallSecret"]
+[editable path="Props"]
diff --git a/content/levels/Level1Content.tscn b/content/levels/Level1Content.tscn
new file mode 100644
index 0000000..9957ff9
--- /dev/null
+++ b/content/levels/Level1Content.tscn
@@ -0,0 +1,2360 @@
+[gd_scene load_steps=172 format=4 uid="uid://daryahp5bg8cw"]
+
+[ext_resource type="PackedScene" uid="uid://ctu0cdqcefrwg" path="res://prefabs/player.tscn" id="1_2ewlq"]
+[ext_resource type="Material" uid="uid://bjhdc2pxcui3" path="res://content/cardboard_material.tres" id="3_j4tkg"]
+[ext_resource type="Material" uid="uid://dcaorx27eky1t" path="res://content/cardboard/Cardboard004/Cardboard004.tres" id="4_sgv3l"]
+[ext_resource type="PackedScene" uid="uid://b1l8fvlceolf8" path="res://content/enemies/enemy.tscn" id="5_7de8f"]
+[ext_resource type="PackedScene" uid="uid://c2h3negcbnqi" path="res://prefabs/SecretTrigger.tscn" id="7_d6qhp"]
+[ext_resource type="Texture2D" uid="uid://bctvdjk0xedjs" path="res://content/Logo2.png" id="9_dpylm"]
+[ext_resource type="Texture2D" uid="uid://dku7f5fijokpn" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_NormalGL.png" id="10_q7sve"]
+[ext_resource type="Script" path="res://scripts/level_secret_handler.gd" id="11_keyvq"]
+[ext_resource type="FontFile" uid="uid://cynlx0ww4k85w" path="res://content/gemina2half.ttf" id="12_50ppg"]
+[ext_resource type="FontFile" uid="uid://dxyqmtf7gmhfk" path="res://content/IndieFlower-Regular.ttf" id="13_lmvoj"]
+[ext_resource type="Script" path="res://scripts/secret_label.gd" id="14_gubu6"]
+[ext_resource type="Script" path="res://content/levels/exit_script.gd" id="15_skkge"]
+[ext_resource type="ArrayMesh" uid="uid://b08wpv25oghk2" path="res://content/props/Sofa.res" id="16_gg841"]
+[ext_resource type="ArrayMesh" uid="uid://bdk8qkwkkeleu" path="res://content/props/Console.res" id="17_7mfba"]
+[ext_resource type="PackedScene" uid="uid://b7pmtf7c0rfke" path="res://content/props/TV.tscn" id="18_t7vaq"]
+[ext_resource type="ArrayMesh" uid="uid://d140dbn5ynbuw" path="res://content/props/Table.res" id="20_3uy1u"]
+[ext_resource type="ArrayMesh" uid="uid://bx21bwka0f0ub" path="res://content/props/Chair.res" id="21_fb4hp"]
+[ext_resource type="ArrayMesh" uid="uid://boiqcf8xxee0f" path="res://content/props/Paper_001.res" id="22_ad2hv"]
+
+[sub_resource type="NavigationMesh" id="NavigationMesh_hdiwy"]
+vertices = PackedVector3Array(-20.2782, 7.25, -18.06, -20.2782, 7.25, -9.06002, -9.02817, 7.25, -9.06002, -9.02817, 7.25, -18.06, -6.52817, 4, -3.31002, -4.52817, 4, -2.31002, -3.77817, 4, -4.06002, -4.52817, 4, -0.560019, -7.27817, 4, -4.06002, -2.27817, 4, -5.56002, 5.22183, 4, -1.06002, 4.72183, 4, 0.689981, 6.72183, 4, 8.68998, -3.77817, 4, 1.18998, -6.27817, 4, 9.18998, 6.72183, 0.25, 13.19, 6.72183, 0.25, 10.69, -0.27817, 0.75, 10.69, -6.52817, 4, 13.19, 0.832941, 0.25, 13.19, 1.12183, 0.25, 10.69, 4.22183, 4, -4.31002, 4.97183, 4, -3.06002, 6.72183, 4, -6.56002, -5.77817, 3.75, 10.69, -16.5282, 4, -16.31, -16.5282, 4, -9.06002, -9.77817, 4, -9.06002, -9.77817, 4, -17.56, -18.2782, 4, -6.81002, 0.97183, 4, -6.31002, -17.2782, 4, -17.06, -20.2782, 4, -17.56, -18.2782, 4, -17.06, 2.97183, 4, 2.68998, -19.0282, 4, -16.31, -19.0282, 4, -7.56002, -20.2782, 4, -3.81002, -1.52817, 4, 3.18998, -5.77817, 4, 8.68998, 2.47183, 4, -5.81002, -0.27817, 4, 3.43998, -21.2782, 8.5, -7.06002, -21.2782, 8.5, -5.31002, -20.2782, 8.5, -4.81002, -9.77817, 8.25, -4.56002, -9.02817, 8.5, -5.06002, -9.02817, 8.25, -7.06002, -20.2782, 4.75, -0.0600185, -9.77817, 4.75, -0.0600185, -6.52817, 8.25, -7.06002, -6.52817, 8.25, -6.31002, -5.77817, 8.25, -6.81002, 6.97183, 8.25, -7.06002, 5.22183, 8.25, -7.06002, 5.97183, 8.25, -6.31002, -5.77817, 8.25, 7.43998, -6.52817, 8.25, 6.68998, -6.52817, 8.25, 13.69, 5.22183, 8.25, 7.43998, 6.97183, 8.25, 13.69, 5.97183, 8.25, 6.68998, 4.47183, 0.25, 0.689981, 4.72183, 0.25, 1.18998, 5.22183, 0.25, 0.689981, 6.72183, 0.25, -6.56002, 6.72183, 0.25, 0.689981, -6.77817, 0.25, -6.56002, -6.77817, 0.25, 0.689981, -1.27817, 4, -0.560019, -0.52817, 4, 0.189981, 0.97183, 4, 0.189981, 0.97183, 4, -3.06002, -0.27817, 4, -3.06002, -1.27817, 4, -2.31002, 1.97183, 4, -0.810019, 1.97183, 4, -2.06002, 5.22183, 0.25, 3.18998, 4.72183, 0.25, 2.68998, 4.47183, 0.25, 3.18998, -5.27817, 0.25, 3.18998, -5.27817, 0.25, 9.18998, -4.52817, 0.25, 8.43998, 0.47183, 0.5, 9.93998, -0.0281696, 0.25, 8.43998, 6.72183, 0.25, 3.18998)
+polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1), PackedInt32Array(6, 5, 4), PackedInt32Array(4, 5, 7), PackedInt32Array(4, 8, 6), PackedInt32Array(6, 8, 9), PackedInt32Array(12, 11, 10), PackedInt32Array(7, 13, 4), PackedInt32Array(4, 13, 14), PackedInt32Array(16, 15, 20), PackedInt32Array(20, 15, 19), PackedInt32Array(20, 19, 17), PackedInt32Array(17, 19, 18), PackedInt32Array(23, 22, 21), PackedInt32Array(24, 17, 18), PackedInt32Array(14, 24, 18), PackedInt32Array(26, 25, 27), PackedInt32Array(27, 25, 28), PackedInt32Array(30, 29, 23), PackedInt32Array(28, 25, 31), PackedInt32Array(33, 32, 31), PackedInt32Array(31, 32, 28), PackedInt32Array(12, 34, 11), PackedInt32Array(32, 33, 35), PackedInt32Array(35, 36, 32), PackedInt32Array(32, 36, 37), PackedInt32Array(29, 30, 9), PackedInt32Array(37, 36, 29), PackedInt32Array(37, 29, 8), PackedInt32Array(8, 29, 9), PackedInt32Array(14, 18, 4), PackedInt32Array(22, 23, 10), PackedInt32Array(10, 23, 12), PackedInt32Array(39, 14, 38), PackedInt32Array(38, 14, 13), PackedInt32Array(23, 21, 40), PackedInt32Array(39, 38, 41), PackedInt32Array(34, 12, 41), PackedInt32Array(41, 12, 39), PackedInt32Array(23, 40, 30), PackedInt32Array(43, 42, 44), PackedInt32Array(44, 42, 45), PackedInt32Array(45, 42, 46), PackedInt32Array(46, 42, 47), PackedInt32Array(49, 48, 45), PackedInt32Array(45, 48, 44), PackedInt32Array(52, 51, 50), PackedInt32Array(55, 54, 53), PackedInt32Array(56, 58, 57), PackedInt32Array(56, 59, 58), PackedInt32Array(58, 59, 60), PackedInt32Array(55, 53, 61), PackedInt32Array(61, 53, 60), PackedInt32Array(60, 59, 61), PackedInt32Array(63, 62, 64), PackedInt32Array(64, 62, 65), PackedInt32Array(65, 66, 64), PackedInt32Array(68, 67, 62), PackedInt32Array(62, 67, 65), PackedInt32Array(71, 70, 69), PackedInt32Array(74, 73, 72), PackedInt32Array(75, 71, 76), PackedInt32Array(76, 71, 72), PackedInt32Array(72, 71, 69), PackedInt32Array(72, 69, 74), PackedInt32Array(79, 78, 77), PackedInt32Array(82, 81, 80), PackedInt32Array(17, 83, 20), PackedInt32Array(20, 83, 16), PackedInt32Array(83, 84, 16), PackedInt32Array(16, 84, 77), PackedInt32Array(16, 77, 85), PackedInt32Array(82, 80, 84), PackedInt32Array(84, 80, 79), PackedInt32Array(84, 79, 77)]
+sample_partition_type = 2
+agent_height = 2.0
+agent_radius = 1.0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_us1rf"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 16, 0.942089, 10),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAAAAAAAAAAAAAAAAAAAAAAAAAACBBAACAQQAAAAAAACBBAAAAAAAAAAAAAAAAAACAQQAAAAAAACBBAACAQQAAAAAAAAAAAACAQb8scT8AAAAAAACAQQAAAAAAAAAAAACAQQAAAAAAACBBAACAQb8scT8AAAAAAACAQQAAAAAAACBBAACAQb8scT8AACBBAAAAAL8scT8AACBBAACAQb8scT8AACBBAACAQQAAAAAAACBBAAAAAL8scT8AACBBAACAQQAAAAAAACBBAAAAAAAAAAAAACBBAAAAAAAAAAAAAAAAAACAQQAAAAAAAAAAAACAQb8scT8AAAAAAAAAAAAAAAAAAAAAAACAQb8scT8AAAAAAAAAAL8scT8AAAAAAAAAAL8scT8AACBBAAAAAL8scT8AAAAAAACAQb8scT8AAAAAAAAAAL8scT8AACBBAACAQb8scT8AAAAAAACAQb8scT8AACBBAAAAAL8scT8AACBBAAAAAAAAAAAAACBBAAAAAAAAAAAAAAAAAAAAAL8scT8AACBBAAAAAAAAAAAAAAAAAAAAAL8scT8AAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_b0dem"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 16, 0.942089, 10),
+"attribute_data": PackedByteArray("/////wAAAAAAAAAAiEVKPSHD5z7/////AAAAAAAAIMGIRUo91bx/Pf////8AAIDBAAAgwc4PCz/VvH89/////wAAgMEAAAAAzg8LPyHD5z7/////AAAAAL8scb+IRUo9NARwP/////8AAAAAAAAAAIhFSj3tmmY//////wAAIMEAAAAABE+3Pu2aZj//////AAAgwb8scb8ET7c+NARwP/////8AAAAAvyxxv4hFSj0r2RM//////wAAgMG/LHG/zg8LPyvZEz//////AACAwQAAAADODws/cUIdP/////8AAAAAAAAAAIhFSj1xQh0//////wAAAAAAAAAAiEVKPQw6PT//////AACAwQAAAADODws/DDo9P/////8AAIDBvyxxv84PCz9To0Y//////wAAAAC/LHG/iEVKPVOjRj//////AAAAAAAAIMGoW3M/1bx/Pf////8AAAAAAAAAAH9YJD/VvH89/////wAAgMEAAAAAf1gkP9PRLz//////AACAwQAAIMGoW3M/09EvP/////8AACDBvyxxv39YJD+0Mlk//////wAAIMEAAAAAf1gkP27JTz//////AAAAAAAAAACoW3M/bslPP/////8AAAAAvyxxv6hbcz+0Mlk/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAAAAAAAAAAAAAAAAAAAAAAAAAACBBAACAQQAAAAAAACBBAACAQQAAAAAAAAAAAACAQb8scT8AAAAAAACAQQAAAAAAAAAAAACAQQAAAAAAACBBAACAQb8scT8AACBBAAAAAL8scT8AACBBAACAQb8scT8AACBBAACAQQAAAAAAACBBAAAAAAAAAAAAACBBAAAAAAAAAAAAAAAAAACAQQAAAAAAAAAAAACAQb8scT8AAAAAAAAAAL8scT8AAAAAAAAAAL8scT8AACBBAAAAAL8scT8AAAAAAACAQb8scT8AAAAAAACAQb8scT8AACBBAAAAAL8scT8AACBBAAAAAAAAAAAAACBBAAAAAAAAAAAAAAAAAAAAAL8scT8AAAAA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z////9//////////3//////////f/////////9///////9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8//////wAA/7//////AAD/v/////8AAP+//////wAA/7//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_us1rf")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_ks5ww"]
+points = PackedVector3Array(0, 0, 0, 0, 0, 10, 16, 0, 10, 16, 0, 0, 16, 0.942089, 0, 16, 0.942089, 10, 0, 0.942089, 10, 0, 0.942089, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_qvslh"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 16, 1, 17.5868),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAAAAAAAAAAAAAAAAAAAAAAADEsYxBAACAQQAAAADEsYxBAAAAAAAAAAAAAAAAAACAQQAAAADEsYxBAACAQQAAAAAAAAAAAACAQQAAgD8AAAAAAACAQQAAAAAAAAAAAACAQQAAAADEsYxBAACAQQAAgD8AAAAAAACAQQAAAADEsYxBAACAQQAAgD/EsYxBAAAAAAAAgD/EsYxBAACAQQAAgD/EsYxBAACAQQAAAADEsYxBAAAAAAAAgD/EsYxBAACAQQAAAADEsYxBAAAAAAAAAADEsYxBAAAAAAAAAAAAAAAAAACAQQAAAAAAAAAAAACAQQAAgD8AAAAAAAAAAAAAAAAAAAAAAACAQQAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD/EsYxBAAAAAAAAgD8AAAAAAACAQQAAgD8AAAAAAAAAAAAAgD/EsYxBAACAQQAAgD8AAAAAAACAQQAAgD/EsYxBAAAAAAAAgD/EsYxBAAAAAAAAAADEsYxBAAAAAAAAAAAAAAAAAAAAAAAAgD/EsYxBAAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_xye82"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 16, 1, 17.5868),
+"attribute_data": PackedByteArray("/////wAAAAAAAAAAW1UxPcPUWT3/////AAAAAMSxjMFd1fM+w9RZPf////8AAIDBxLGMwV3V8z6aeQk//////wAAgMEAAAAAW1UxPZp5CT//////AAAAAAAAgL9bVTE9+HEsP/////8AAAAAAAAAAFtVMT0ztCQ//////8SxjMEAAAAAXdXzPjO0JD//////xLGMwQAAgL9d1fM++HEsP/////8AAAAAAACAv1oVED/f/DA//////wAAgMEAAIC/qup0P9/8MD//////AACAwQAAAACq6nQ/pLo4P/////8AAAAAAAAAAFoVED+kujg//////wAAAAAAAAAAW1UxPe6kaj//////AACAwQAAAABM1d8+7qRqP/////8AAIDBAACAv0zV3z6zYnI//////wAAAAAAAIC/W1UxPbNicj//////AAAAAMSxjMFaFRA/w9RZPf////8AAAAAAAAAAFoVED9GwhU//////wAAgMEAAAAAqup0P0bCFT//////AACAwcSxjMGq6nQ/w9RZPf/////EsYzBAACAv1tVMT1Wak8//////8SxjMEAAAAAW1UxPZGsRz//////AAAAAAAAAABd1fM+kaxHP/////8AAAAAAACAv13V8z5Wak8/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAAAAAAAAAAAAAAAAAAAAAAADEsYxBAACAQQAAAADEsYxBAACAQQAAAAAAAAAAAACAQQAAgD8AAAAAAACAQQAAAAAAAAAAAACAQQAAAADEsYxBAACAQQAAgD/EsYxBAAAAAAAAgD/EsYxBAACAQQAAgD/EsYxBAACAQQAAAADEsYxBAAAAAAAAAADEsYxBAAAAAAAAAAAAAAAAAACAQQAAAAAAAAAAAACAQQAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD/EsYxBAAAAAAAAgD8AAAAAAACAQQAAgD8AAAAAAACAQQAAgD/EsYxBAAAAAAAAgD/EsYxBAAAAAAAAAADEsYxBAAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z////9//////////3//////////f/////////9///////9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8//////wAA/7//////AAD/v/////8AAP+//////wAA/7//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_qvslh")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_573tf"]
+points = PackedVector3Array(0, 0, 0, 0, 0, 17.5868, 16, 0, 17.5868, 16, 0, 0, 16, 1, 0, 16, 1, 17.5868, 0, 1, 17.5868, 0, 1, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_d6lnr"]
+_surfaces = [{
+"aabb": AABB(-14.709, -0.191, 0, 25.8267, 8.2432, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("EFhrwdDVAEEAAAAAZ5vDv9DVAEFu3gg+Z5vDv4GVQ75u3gg+EFhrwdDVAEEAAAAAZ5vDv4GVQ75u3gg+EFhrwYGVQ74AAAAAGeIxQYGVQ74AAAAAGeIxQYGVQ75u3gg+GeIxQdDVAEFu3gg+GeIxQYGVQ74AAAAAGeIxQdDVAEFu3gg+GeIxQdDVAEEAAAAAZ5vDv9DVAEFu3gg+EFhrwdDVAEEAAAAAGeIxQdDVAEEAAAAAZ5vDv9DVAEFu3gg+GeIxQdDVAEEAAAAAGeIxQdDVAEFu3gg+Z5vDv4GVQ75u3gg+Z5vDv9DVAEFu3gg+GeIxQdDVAEFu3gg+Z5vDv4GVQ75u3gg+GeIxQdDVAEFu3gg+GeIxQYGVQ75u3gg+EFhrwYGVQ74AAAAAZ5vDv4GVQ75u3gg+GeIxQYGVQ75u3gg+EFhrwYGVQ74AAAAAGeIxQYGVQ75u3gg+GeIxQYGVQ74AAAAAEFhrwdDVAEEAAAAAEFhrwYGVQ74AAAAAGeIxQYGVQ74AAAAAEFhrwdDVAEEAAAAAGeIxQYGVQ74AAAAAGeIxQdDVAEEAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_27lps"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-14.709, -0.191, 0, 25.8267, 8.2432, 0.133661),
+"attribute_data": PackedByteArray("/////xBYa0HQ1QDBN7+RPXvtNT//////Z5vDP9DVAMFKZ94+e+01P/////9nm8M/gZVDPkpn3j41VG4//////xBYa0GBlUM+N7+RPTVUbj//////AAAAAIGVQz4ayG0/S16NPf////9u3gi+gZVDPrrWbD9LXo09/////27eCL7Q1QDButZsPwsllD7/////AAAAANDVAMEayG0/CyWUPv////9nm8M/bt4Ivtpk3j5Ar5Q9/////xBYa0EAAAAAN7+RPUtejT3/////GeIxwQAAAADuZkg/S16NPf////8Z4jHBbt4Ivu5mSD9Ar5Q9/////2ebwz+BlUM+dKMTPzdUbj//////Z5vDP9DVAMF0oxM/e+01P/////8Z4jHB0NUAwfTXbD977TU//////xniMcGBlUM+9NdsPzdUbj//////EFhrQQAAAADuZkg/67VXPv////9nm8M/bt4IvtDY1j5mXls+/////xniMcFu3gi+N7+RPWZeWz7/////GeIxwQAAAAA3v5E967VXPv////8QWGtB0NUAwTe/kT3nlRI//////xBYa0GBlUM+N7+RPVdetD7/////GeIxwYGVQz7tZkg/V160Pv////8Z4jHB0NUAwe1mSD/nlRI/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("EFhrwdDVAEEAAAAAZ5vDv9DVAEFu3gg+Z5vDv4GVQ75u3gg+EFhrwYGVQ74AAAAAGeIxQYGVQ74AAAAAGeIxQYGVQ75u3gg+GeIxQdDVAEFu3gg+GeIxQdDVAEEAAAAAZ5vDv9DVAEFu3gg+EFhrwdDVAEEAAAAAGeIxQdDVAEEAAAAAGeIxQdDVAEFu3gg+Z5vDv4GVQ75u3gg+Z5vDv9DVAEFu3gg+GeIxQdDVAEFu3gg+GeIxQYGVQ75u3gg+EFhrwYGVQ74AAAAAZ5vDv4GVQ75u3gg+GeIxQYGVQ75u3gg+GeIxQYGVQ74AAAAAEFhrwdDVAEEAAAAAEFhrwYGVQ74AAAAAGeIxQYGVQ74AAAAAGeIxQdDVAEEAAAAAtn7/fwAAWz+2fv9/AABbP7Z+/38AAFs/tn7/fwAAWz////9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_d6lnr")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_2oeue"]
+points = PackedVector3Array(-14.709, 8.0522, 0, -1.52818, 8.0522, 0.133661, -1.52818, -0.191, 0.133661, -14.709, -0.191, 0, 11.1177, 8.0522, 0, 11.1177, 8.0522, 0.133661, 11.1177, -0.191, 0.133661, 11.1177, -0.191, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_20twc"]
+_surfaces = [{
+"aabb": AABB(-4.92912, 0, 0, 10.0468, 2.71811, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("WrudwIT1LUAAAAAAWrudwIT1LUBu3gg+WrudwAAAAABu3gg+WrudwIT1LUAAAAAAWrudwAAAAABu3gg+WrudwAAAAAAAAAAAM8SjQAAAAADXzYw0M8SjQAAAAABu3gg+M8SjQIT1LUBu3gg+M8SjQAAAAADXzYw0M8SjQIT1LUBu3gg+M8SjQIT1LUDXzYw0WrudwIT1LUBu3gg+WrudwIT1LUAAAAAAM8SjQIT1LUDXzYw0WrudwIT1LUBu3gg+M8SjQIT1LUDXzYw0M8SjQIT1LUBu3gg+WrudwAAAAABu3gg+WrudwIT1LUBu3gg+M8SjQIT1LUBu3gg+WrudwAAAAABu3gg+M8SjQIT1LUBu3gg+M8SjQAAAAABu3gg+WrudwAAAAAAAAAAAWrudwAAAAABu3gg+M8SjQAAAAABu3gg+WrudwAAAAAAAAAAAM8SjQAAAAABu3gg+M8SjQAAAAADXzYw0WrudwIT1LUAAAAAAWrudwAAAAAAAAAAAM8SjQAAAAADXzYw0WrudwIT1LUAAAAAAM8SjQAAAAADXzYw0M8SjQIT1LUDXzYw0")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_5laxv"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-4.92912, 0, 0, 10.0468, 2.71811, 0.133661),
+"attribute_data": PackedByteArray("/////wAAAACE9S3A6+JtP6i9lT3/////bt4IvoT1LcACems/qL2VPf////9u3gi+AAAAAAJ6az/Mtoo+/////wAAAAAAAAAA6+JtP8y2ij7/////182MtAAAAADr4m0/tbcSP/////9u3gi+AAAAAAJ6az+1txI//////27eCL6E9S3AAnprP2ZbRT//////182MtIT1LcDr4m0/ZltFP/////9au51Abt4Ivp/okD1+qak9/////1q7nUAAAAAAn+iQPai9lT3/////M8SjwNfNjLTaP0c/qL2VPf////8zxKPAbt4Ivto/Rz98qak9/////1q7nUAAAAAAn+iQPZaQ2j7/////WrudQIT1LcCf6JA9aJJqPv////8zxKPAhPUtwNo/Rz9okmo+/////zPEo8AAAAAA2j9HP5aQ2j7/////WrudQAAAAACf6JA9MDUVP/////9au51Abt4Ivp/okD21txI//////zPEo8Bu3gi+2j9HP7W3Ej//////M8SjwNfNjLTaP0c/LzUVP/////9au51AhPUtwJ/okD1MSG0//////1q7nUAAAAAAn+iQPZmkOj//////M8SjwAAAAADaP0c/maQ6P/////8zxKPAhPUtwNo/Rz9MSG0/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("WrudwIT1LUAAAAAAWrudwIT1LUBu3gg+WrudwAAAAABu3gg+WrudwAAAAAAAAAAAM8SjQAAAAADXzYw0M8SjQAAAAABu3gg+M8SjQIT1LUBu3gg+M8SjQIT1LUDXzYw0WrudwIT1LUBu3gg+WrudwIT1LUAAAAAAM8SjQIT1LUDXzYw0M8SjQIT1LUBu3gg+WrudwAAAAABu3gg+WrudwIT1LUBu3gg+M8SjQIT1LUBu3gg+M8SjQAAAAABu3gg+WrudwAAAAAAAAAAAWrudwAAAAABu3gg+M8SjQAAAAABu3gg+M8SjQAAAAADXzYw0WrudwIT1LUAAAAAAWrudwAAAAAAAAAAAM8SjQAAAAADXzYw0M8SjQIT1LUDXzYw0AAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_20twc")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_1b16g"]
+points = PackedVector3Array(-4.92912, 2.71811, 0, -4.92912, 2.71811, 0.133661, -4.92912, 0, 0.133661, -4.92912, 0, 0, 5.1177, 2.71811, 2.62268e-07, 5.1177, 2.71811, 0.133661, 5.1177, 0, 0.133661, 5.1177, 0, 2.62268e-07)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_rob7q"]
+_surfaces = [{
+"aabb": AABB(-4.92912, -0.180381, -2.99577e-07, 22.9003, 8.17718, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("WrudwMnl/0AAAAAAWrudwMnl/0Bu3gg+WrudwMy1OL5u3gg+WrudwMnl/0AAAAAAWrudwMy1OL5u3gg+WrudwMy1OL4AAAAABcWPQcy1OL6N1aC0BcWPQcy1OL5u3gg+BcWPQcnl/0Bu3gg+BcWPQcy1OL6N1aC0BcWPQcnl/0Bu3gg+BcWPQcnl/0CN1aC0WrudwMnl/0Bu3gg+WrudwMnl/0AAAAAABcWPQcnl/0CN1aC0WrudwMnl/0Bu3gg+BcWPQcnl/0CN1aC0BcWPQcnl/0Bu3gg+WrudwMy1OL5u3gg+WrudwMnl/0Bu3gg+BcWPQcnl/0Bu3gg+WrudwMy1OL5u3gg+BcWPQcnl/0Bu3gg+BcWPQcy1OL5u3gg+WrudwMy1OL4AAAAAWrudwMy1OL5u3gg+BcWPQcy1OL5u3gg+WrudwMy1OL4AAAAABcWPQcy1OL5u3gg+BcWPQcy1OL6N1aC0WrudwMnl/0AAAAAAWrudwMy1OL4AAAAABcWPQcy1OL6N1aC0WrudwMnl/0AAAAAABcWPQcy1OL6N1aC0BcWPQcnl/0CN1aC0")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_jro6u"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-4.92912, -0.180381, -2.99577e-07, 22.9003, 8.17718, 0.133661),
+"attribute_data": PackedByteArray("/////wAAAADJ5f/AS8ptP+U4hj3/////bt4Ivsnl/8A1umw/5TiGPf////9u3gi+zLU4PjW6bD/zX5k+/////wAAAADMtTg+S8ptP/NfmT7/////jdWgNMy1OD5Lym0/HccQP/////9u3gi+zLU4PjW6bD8dxxA//////27eCL7J5f/ANbpsP/uvTD//////jdWgNMnl/8BLym0/+69MP/////9au51Abt4IvqqtkT1rDo49/////1q7nUAAAAAAqq2RPeU4hj3/////BcWPwY3VoDTKTkg/5TiGPf////8FxY/Bbt4IvspOSD9sDo49/////1q7nUDMtTg+qq2RPcdx3j7/////WrudQMnl/8CqrZE9G0BNPv////8FxY/ByeX/wMpOSD8bQE0+/////wXFj8HMtTg+yk5IP8dx3j7/////WrudQAAAAACqrZE9zsERP/////9au51Abt4IvqqtkT0dxxA//////wXFj8Fu3gi+yk5IPx3HED//////BcWPwY3VoDTKTkg/zsERP/////9au51AyeX/wKqtkT3lOG8//////1q7nUDMtTg+qq2RPQdQMz//////BcWPwcy1OD7KTkg/B1AzP/////8FxY/ByeX/wMpOSD/lOG8/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("WrudwMnl/0AAAAAAWrudwMnl/0Bu3gg+WrudwMy1OL5u3gg+WrudwMy1OL4AAAAABcWPQcy1OL6N1aC0BcWPQcy1OL5u3gg+BcWPQcnl/0Bu3gg+BcWPQcnl/0CN1aC0WrudwMnl/0Bu3gg+WrudwMnl/0AAAAAABcWPQcnl/0CN1aC0BcWPQcnl/0Bu3gg+WrudwMy1OL5u3gg+WrudwMnl/0Bu3gg+BcWPQcnl/0Bu3gg+BcWPQcy1OL5u3gg+WrudwMy1OL4AAAAAWrudwMy1OL5u3gg+BcWPQcy1OL5u3gg+BcWPQcy1OL6N1aC0WrudwMnl/0AAAAAAWrudwMy1OL4AAAAABcWPQcy1OL6N1aC0BcWPQcnl/0CN1aC0AAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/PwAA//8AAP+/AAD//wAA/78AAP//AAD/vwAA//8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_rob7q")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_5t0bo"]
+points = PackedVector3Array(-4.92912, 7.9968, 0, -4.92912, 7.9968, 0.133661, -4.92912, -0.180381, 0.133661, -4.92912, -0.180381, 0, 17.9712, 7.9968, -2.99577e-07, 17.9712, 7.9968, 0.133661, 17.9712, -0.180381, 0.133661, 17.9712, -0.180381, -2.99577e-07)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_41q7m"]
+_surfaces = [{
+"aabb": AABB(-4.92912, -0.388208, 0, 12.0252, 3.10632, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("WrudwDPDxr5u3gg+WrudwDPDxr4AAAAAWrudwIT1LUAAAAAAWrudwDPDxr5u3gg+WrudwIT1LUAAAAAAWrudwIT1LUBu3gg+1xLjQIT1LUAAAAAA1xLjQIT1LUBu3gg+WrudwIT1LUBu3gg+1xLjQIT1LUAAAAAAWrudwIT1LUBu3gg+WrudwIT1LUAAAAAA1xLjQDPDxr4AAAAA1xLjQIT1LUAAAAAAWrudwIT1LUAAAAAA1xLjQDPDxr4AAAAAWrudwIT1LUAAAAAAWrudwDPDxr4AAAAA1xLjQDPDxr5u3gg+WrudwDPDxr5u3gg+WrudwIT1LUBu3gg+1xLjQDPDxr5u3gg+WrudwIT1LUBu3gg+1xLjQIT1LUBu3gg+1xLjQDPDxr5u3gg+1xLjQIT1LUBu3gg+1xLjQIT1LUAAAAAA1xLjQDPDxr5u3gg+1xLjQIT1LUAAAAAA1xLjQDPDxr4AAAAA1xLjQDPDxr5u3gg+1xLjQDPDxr4AAAAAWrudwDPDxr4AAAAA1xLjQDPDxr5u3gg+WrudwDPDxr4AAAAAWrudwDPDxr5u3gg+")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_uf1y7"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-4.92912, -0.388208, 0, 12.0252, 3.10632, 0.133661),
+"attribute_data": PackedByteArray("/////27eCL4zw8Y+utttPxn3mD3/////AAAAADPDxj6D12s/GfeYPf////8AAAAAhPUtwIPXaz+HBok+/////27eCL6E9S3AutttP4cGiT7/////1xLjwAAAAAApIpE9rfepPf/////XEuPAbt4IvikikT0Z95g9/////1q7nUBu3gi++I5HPxn3mD3/////WrudQAAAAAD4jkc/rfepPf/////XEuPAM8PGPikikT05wtk+/////9cS48CE9S3AKSKRPfDybT7/////WrudQIT1LcD4jkc/8PJtPv////9au51AM8PGPviORz85wtk+/////9cS48Azw8Y+KSKRPeMeEz//////WrudQDPDxj74jkc/4x4TP/////9au51AhPUtwPiORz9Dg0Q//////9cS48CE9S3AKSKRPUODRD//////bt4IvjPDxj6D12s/4x4TP/////9u3gi+hPUtwIPXaz9Dg0Q//////wAAAACE9S3AutttP0ODRD//////AAAAADPDxj66220/4x4TP//////XEuPAbt4IvikikT0c4Ww//////9cS48AAAAAAKSKRPQnBaj//////WrudQAAAAAD4jkc/CcFqP/////9au51Abt4IvviORz8c4Ww/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("WrudwDPDxr5u3gg+WrudwDPDxr4AAAAAWrudwIT1LUAAAAAAWrudwIT1LUBu3gg+1xLjQIT1LUAAAAAA1xLjQIT1LUBu3gg+WrudwIT1LUBu3gg+WrudwIT1LUAAAAAA1xLjQDPDxr4AAAAA1xLjQIT1LUAAAAAAWrudwIT1LUAAAAAAWrudwDPDxr4AAAAA1xLjQDPDxr5u3gg+WrudwDPDxr5u3gg+WrudwIT1LUBu3gg+1xLjQIT1LUBu3gg+1xLjQDPDxr5u3gg+1xLjQIT1LUBu3gg+1xLjQIT1LUAAAAAA1xLjQDPDxr4AAAAA1xLjQDPDxr5u3gg+1xLjQDPDxr4AAAAAWrudwDPDxr4AAAAAWrudwDPDxr5u3gg+AAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD/f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z////9//////////3//////////f/////////9///////9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_41q7m")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_vkn26"]
+points = PackedVector3Array(-4.92912, -0.388208, 0.133661, -4.92912, -0.388208, 0, -4.92912, 2.71811, 0, -4.92912, 2.71811, 0.133661, 7.09605, 2.71811, 0, 7.09605, 2.71811, 0.133661, 7.09605, -0.388208, 0, 7.09605, -0.388208, 0.133661)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_uk663"]
+_surfaces = [{
+"aabb": AABB(8.59955, -0.081118, 0, 2.51815, 2.79923, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("wpcJQYT1LUAAAAAAGeIxQYT1LUAAAAAAGeIxQYT1LUBu3gg+wpcJQYT1LUAAAAAAGeIxQYT1LUBu3gg+wpcJQYT1LUBu3gg+GeIxQTIhpr1u3gg+GeIxQYT1LUBu3gg+GeIxQYT1LUAAAAAAGeIxQTIhpr1u3gg+GeIxQYT1LUAAAAAAGeIxQTIhpr0AAAAAwpcJQTIhpr1u3gg+wpcJQYT1LUBu3gg+GeIxQYT1LUBu3gg+wpcJQTIhpr1u3gg+GeIxQYT1LUBu3gg+GeIxQTIhpr1u3gg+wpcJQTIhpr0AAAAAGeIxQTIhpr0AAAAAGeIxQYT1LUAAAAAAwpcJQTIhpr0AAAAAGeIxQYT1LUAAAAAAwpcJQYT1LUAAAAAAwpcJQTIhpr0AAAAAwpcJQTIhpr1u3gg+GeIxQTIhpr1u3gg+wpcJQTIhpr0AAAAAGeIxQTIhpr1u3gg+GeIxQTIhpr0AAAAAwpcJQTIhpr0AAAAAwpcJQYT1LUAAAAAAwpcJQYT1LUBu3gg+wpcJQTIhpr0AAAAAwpcJQYT1LUBu3gg+wpcJQTIhpr1u3gg+")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_prpwy"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(8.59955, -0.081118, 0, 2.51815, 2.79923, 0.133661),
+"attribute_data": PackedByteArray("/////8KXCcEAAAAAvbgQP2KXND//////GeIxwQAAAAAY3nQ/Ypc0P/////8Z4jHBbt4IvhjedD95OTs//////8KXCcFu3gi+vbgQP3k5Oz//////bt4IvjIhpj1yHjI9KkRePf////9u3gi+hPUtwN3p9D4qRF49/////wAAAACE9S3A3en0PscypD3/////AAAAADIhpj1yHjI9xzKkPf/////ClwnBMiGmPXIeMj15O0E+/////8KXCcGE9S3A3en0Pnk7QT7/////GeIxwYT1LcDd6fQ+hUYtP/////8Z4jHBMiGmPXIeMj2FRi0//////8KXCcEyIaY9GN50PypEXj3/////GeIxwTIhpj29uBA/KkRePf////8Z4jHBhPUtwL24ED/dzhg//////8KXCcGE9S3AGN50P93OGD//////wpcJwQAAAAByHjI9vRtyP//////ClwnBbt4IvnIeMj2oeWs//////xniMcFu3gi+hY7ePqh5az//////GeIxwQAAAACFjt4+vRtyP/////8AAAAAMiGmPXIeMj0MD0k//////wAAAACE9S3A3en0PgwPST//////bt4IvoT1LcDd6fQ+IbFPP/////9u3gi+MiGmPXIeMj0hsU8/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("wpcJQYT1LUAAAAAAGeIxQYT1LUAAAAAAGeIxQYT1LUBu3gg+wpcJQYT1LUBu3gg+GeIxQTIhpr1u3gg+GeIxQYT1LUBu3gg+GeIxQYT1LUAAAAAAGeIxQTIhpr0AAAAAwpcJQTIhpr1u3gg+wpcJQYT1LUBu3gg+GeIxQYT1LUBu3gg+GeIxQTIhpr1u3gg+wpcJQTIhpr0AAAAAGeIxQTIhpr0AAAAAGeIxQYT1LUAAAAAAwpcJQYT1LUAAAAAAwpcJQTIhpr0AAAAAwpcJQTIhpr1u3gg+GeIxQTIhpr1u3gg+GeIxQTIhpr0AAAAAwpcJQTIhpr0AAAAAwpcJQYT1LUAAAAAAwpcJQYT1LUBu3gg+wpcJQTIhpr1u3gg+/3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/7////9//////////3//////////f/////////9///////9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8//////wAA/7//////AAD/v/////8AAP+//////wAA/7//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/PwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_uk663")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_5ftl2"]
+points = PackedVector3Array(8.59955, 2.71811, 0, 11.1177, 2.71811, 0, 11.1177, 2.71811, 0.133661, 8.59955, 2.71811, 0.133661, 11.1177, -0.081118, 0.133661, 11.1177, -0.081118, 0, 8.59955, -0.081118, 0.133661, 8.59955, -0.081118, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_fdypv"]
+_surfaces = [{
+"aabb": AABB(7.09605, 2.27053, 0, 1.5035, 0.44758, 0.133661),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("wpcJQYT1LUBu3gg+wpcJQYT1LUAAAAAAwpcJQV1QEUAAAAAAwpcJQYT1LUBu3gg+wpcJQV1QEUAAAAAAwpcJQV1QEUBu3gg+1xLjQF1QEUAAAAAAwpcJQV1QEUAAAAAAwpcJQYT1LUAAAAAA1xLjQF1QEUAAAAAAwpcJQYT1LUAAAAAA1xLjQIT1LUAAAAAA1xLjQF1QEUBu3gg+wpcJQV1QEUBu3gg+wpcJQV1QEUAAAAAA1xLjQF1QEUBu3gg+wpcJQV1QEUAAAAAA1xLjQF1QEUAAAAAA1xLjQIT1LUBu3gg+1xLjQIT1LUAAAAAAwpcJQYT1LUAAAAAA1xLjQIT1LUBu3gg+wpcJQYT1LUAAAAAAwpcJQYT1LUBu3gg+1xLjQIT1LUBu3gg+1xLjQF1QEUBu3gg+1xLjQF1QEUAAAAAA1xLjQIT1LUBu3gg+1xLjQF1QEUAAAAAA1xLjQIT1LUAAAAAA1xLjQIT1LUBu3gg+wpcJQYT1LUBu3gg+wpcJQV1QEUBu3gg+1xLjQIT1LUBu3gg+wpcJQV1QEUBu3gg+1xLjQF1QEUBu3gg+")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_ns850"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(7.09605, 2.27053, 0, 1.5035, 0.44758, 0.133661),
+"attribute_data": PackedByteArray("/////27eCL6E9S3AW85uP48ugj3/////AAAAAIT1LcBOhV8/jy6CPf////8AAAAAXVARwE6FXz9DboE+/////27eCL5dUBHAW85uP0NugT7/////1xLjwF1QEcAdjYk9jy6CPf/////ClwnBXVARwAciPT+PLoI9/////8KXCcGE9S3AByI9P0NugT7/////1xLjwIT1LcAdjYk9Q26BPv/////XEuPAbt4Ivh2NiT2KhcI+/////8KXCcFu3gi+ByI9P4qFwj7/////wpcJwQAAAAAHIj0/W3TfPv/////XEuPAAAAAAB2NiT1bdN8+/////9cS48Bu3gi+HY2JPTq9Hj//////1xLjwAAAAAAdjYk90kUQP//////ClwnBAAAAAAciPT/SRRA//////8KXCcFu3gi+ByI9Pzq9Hj//////bt4IvoT1LcBOhV8/ioXCPv////9u3gi+XVARwE6FXz8VtBE//////wAAAABdUBHAW85uPxW0ET//////AAAAAIT1LcBbzm4/ioXCPv/////XEuPAhPUtwB2NiT3eSD8//////8KXCcGE9S3AByI9P95IPz//////wpcJwV1QEcAHIj0/LbpvP//////XEuPAXVARwB2NiT0tum8/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("wpcJQYT1LUBu3gg+wpcJQYT1LUAAAAAAwpcJQV1QEUAAAAAAwpcJQV1QEUBu3gg+1xLjQF1QEUAAAAAAwpcJQV1QEUAAAAAAwpcJQYT1LUAAAAAA1xLjQIT1LUAAAAAA1xLjQF1QEUBu3gg+wpcJQV1QEUBu3gg+wpcJQV1QEUAAAAAA1xLjQF1QEUAAAAAA1xLjQIT1LUBu3gg+1xLjQIT1LUAAAAAAwpcJQYT1LUAAAAAAwpcJQYT1LUBu3gg+1xLjQIT1LUBu3gg+1xLjQF1QEUBu3gg+1xLjQF1QEUAAAAAA1xLjQIT1LUAAAAAA1xLjQIT1LUBu3gg+wpcJQYT1LUBu3gg+wpcJQV1QEUBu3gg+1xLjQF1QEUBu3gg+////f/////////9//////////3//////////f///////////AAD/v/////8AAP+//////wAA/7//////AAD/v/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/78AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_fdypv")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_esyo4"]
+points = PackedVector3Array(8.59955, 2.71811, 0.133661, 8.59955, 2.71811, 0, 8.59955, 2.27053, 0, 8.59955, 2.27053, 0.133661, 7.09605, 2.27053, 0, 7.09605, 2.71811, 0, 7.09605, 2.27053, 0.133661, 7.09605, 2.71811, 0.133661)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_e5nte"]
+_surfaces = [{
+"aabb": AABB(-10.2519, 0, 0, 14.3875, 0.421415, 12.4875),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("yAckwbXD1z4AAAAAyAckwbXD1z7NzEdByAckwQAAAADNzEdByAckwbXD1z4AAAAAyAckwQAAAADNzEdByAckwQAAAAAAAAAAglaEQAAAAAAAAAAAglaEQAAAAADNzEdBglaEQLXD1z7NzEdBglaEQAAAAAAAAAAAglaEQLXD1z7NzEdBglaEQLXD1z4AAAAAyAckwbXD1z7NzEdByAckwbXD1z4AAAAAglaEQLXD1z4AAAAAyAckwbXD1z7NzEdBglaEQLXD1z4AAAAAglaEQLXD1z7NzEdByAckwQAAAADNzEdByAckwbXD1z7NzEdBglaEQLXD1z7NzEdByAckwQAAAADNzEdBglaEQLXD1z7NzEdBglaEQAAAAADNzEdByAckwQAAAAAAAAAAyAckwQAAAADNzEdBglaEQAAAAADNzEdByAckwQAAAAAAAAAAglaEQAAAAADNzEdBglaEQAAAAAAAAAAAyAckwbXD1z4AAAAAyAckwQAAAAAAAAAAglaEQAAAAAAAAAAAyAckwbXD1z4AAAAAglaEQAAAAAAAAAAAglaEQLXD1z4AAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_q6emc"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-10.2519, 0, 0, 14.3875, 0.421415, 12.4875),
+"attribute_data": PackedByteArray("/////wAAAAC1w9e+qr0SP6qKPT//////zcxHwbXD175VtnQ/qoo9P//////NzEfBAAAAAFW2dD/vz0E//////wAAAAAAAAAAqr0SP+/PQT//////AAAAAAAAAACqmjQ9OCZtP//////NzEfBAAAAAKyE2j44Jm0//////83MR8G1w9e+rITaPn5rcT//////AAAAALXD176qmjQ9fmtxP//////IByRBzcxHwaqaND2cIA0//////8gHJEEAAAAAqpo0PTRIaT3/////glaEwAAAAACqVPg+NEhpPf////+CVoTAzcxHwapU+D6cIA0//////8gHJEEAAAAAqpo0PeeOLj//////yAckQbXD176qmjQ9okkqP/////+CVoTAtcPXvqpU+D6iSSo//////4JWhMAAAAAAqlT4PueOLj//////yAckQQAAAABVtnQ/NEhpPf/////IByRBzcxHwaq9Ej80SGk9/////4JWhMDNzEfBqr0SP6RhID//////glaEwAAAAABVtnQ/pGEgP//////IByRBtcPXvqqaND0y/U8//////8gHJEEAAAAAqpo0Pe23Sz//////glaEwAAAAACqVPg+7bdLP/////+CVoTAtcPXvqpU+D4y/U8/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("yAckwbXD1z4AAAAAyAckwbXD1z7NzEdByAckwQAAAADNzEdByAckwQAAAAAAAAAAglaEQAAAAAAAAAAAglaEQAAAAADNzEdBglaEQLXD1z7NzEdBglaEQLXD1z4AAAAAyAckwbXD1z7NzEdByAckwbXD1z4AAAAAglaEQLXD1z4AAAAAglaEQLXD1z7NzEdByAckwQAAAADNzEdByAckwbXD1z7NzEdBglaEQLXD1z7NzEdBglaEQAAAAADNzEdByAckwQAAAAAAAAAAyAckwQAAAADNzEdBglaEQAAAAADNzEdBglaEQAAAAAAAAAAAyAckwbXD1z4AAAAAyAckwQAAAAAAAAAAglaEQAAAAAAAAAAAglaEQLXD1z4AAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_e5nte")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_1lv78"]
+points = PackedVector3Array(-10.2519, 0.421415, 0, -10.2519, 0.421415, 12.4875, -10.2519, 0, 12.4875, -10.2519, 0, 0, 4.13556, 0.421415, 0, 4.13556, 0.421415, 12.4875, 4.13556, 0, 12.4875, 4.13556, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_0ls0f"]
+_surfaces = [{
+"aabb": AABB(0.0889311, 0, -0.0604286, 6.82994, 4.11666, 5.07385),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("isvxPa67g0CYFQo+isvxPa67g0DwbaBAgiG2PU4LbEA0LppAisvxPa67g0CYFQo+giG2PU4LbEA0LppAgiG2PU4LbED7g3e9wHjcQAAAAAD7g3e9wHjcQAAAAAA0LppAYmfdQK9g2z7wbaBAwHjcQAAAAAD7g3e9YmfdQK9g2z7wbaBAYmfdQK9g2z6YFQo+isvxPa67g0DwbaBAisvxPa67g0CYFQo+YmfdQK9g2z6YFQo+isvxPa67g0DwbaBAYmfdQK9g2z6YFQo+YmfdQK9g2z7wbaBAgiG2PU4LbEA0LppAisvxPa67g0DwbaBAYmfdQK9g2z7wbaBAgiG2PU4LbEA0LppAYmfdQK9g2z7wbaBAwHjcQAAAAAA0LppAgiG2PU4LbED7g3e9giG2PU4LbEA0LppAwHjcQAAAAAA0LppAgiG2PU4LbED7g3e9wHjcQAAAAAA0LppAwHjcQAAAAAD7g3e9isvxPa67g0CYFQo+giG2PU4LbED7g3e9wHjcQAAAAAD7g3e9isvxPa67g0CYFQo+wHjcQAAAAAD7g3e9YmfdQK9g2z6YFQo+")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_mq426"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0.0889311, 0, -0.0604286, 6.82994, 4.11666, 5.07385),
+"attribute_data": PackedByteArray("/////5gVCr6uu4PA6oVqPe/lSD//////8G2gwK67g8CAcsA+7+VIP/////80LprATgtswNvquT6cDFE///////uDdz1OC2zAAUk2PZwMUT//////+4N3PQAAAAB6csA+GBFzP/////80LprAAAAAACWGaj0YEXM///////BtoMCvYNu+AUk2PWvqaj//////mBUKvq9g277d6rk+a+pqP/////+Ky/G98G2gwHCbdD8sTaU+/////4rL8b2YFQq+qDojPyxNpT7/////YmfdwJgVCr6oOiM/bCZlP/////9iZ93A8G2gwHCbdD9sJmU//////4Ihtr1OC2zAub4XP4TuTj3/////isvxva67g8Bu/Bo/gGevPf////9iZ93Ar2DbvoAkaj2CZ689/////4Ihtr1OC2zAub4XP4TuTj3/////YmfdwK9g276AJGo9gmevPf/////AeNzAAAAAAAFJNj2G7k49/////4Ihtr37g3c9AUk2PR8ILz//////giG2vTQumsABSTY9LE2lPv/////AeNzANC6awIhxDD8sTaU+/////8B43MD7g3c9iHEMPx8ILz//////isvxva67g8ABSTY9FCNjPv////+CIba9TgtswD4kaj0DKz8+/////8B43MAAAAAAVvwaPwMrPz7/////isvxva67g8ABSTY9FCNjPv/////AeNzAAAAAAFb8Gj8DKz8+/////2Jn3cCvYNu+nr4XPxMjYz4="),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADwAQABEAEgATABQAEgAUABUAFgAXABgAGQAaABsA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 28,
+"vertex_data": PackedByteArray("isvxPa67g0CYFQo+isvxPa67g0DwbaBAgiG2PU4LbEA0LppAgiG2PU4LbED7g3e9wHjcQAAAAAD7g3e9wHjcQAAAAAA0LppAYmfdQK9g2z7wbaBAYmfdQK9g2z6YFQo+isvxPa67g0DwbaBAisvxPa67g0CYFQo+YmfdQK9g2z6YFQo+YmfdQK9g2z7wbaBAgiG2PU4LbEA0LppAisvxPa67g0DwbaBAYmfdQK9g2z7wbaBAgiG2PU4LbEA0LppAYmfdQK9g2z7wbaBAwHjcQAAAAAA0LppAgiG2PU4LbED7g3e9giG2PU4LbEA0LppAwHjcQAAAAAA0LppAwHjcQAAAAAD7g3e9isvxPa67g0CYFQo+giG2PU4LbED7g3e9wHjcQAAAAAD7g3e9isvxPa67g0CYFQo+wHjcQAAAAAD7g3e9YmfdQK9g2z6YFQo+JggliP//AAAmCCWI//8AACYIJYj//wAAJggliP//AADZ99l3/////9n32Xf/////2ffZd//////Z99l3/////wGt/dIBLYDWAa390gEtgNYBrf3SAS2A1gGt/dIBLYDW0G13XgAArjPQbXdeAACuM9Btd14AAK4z0G13XgAArjPQbXdeAACuM9Btd14AAK4z/VIBLQEtfin9UgEtAS1+Kf1SAS0BLX4p/VIBLQEtfil33s/tAABQzHfez+0AAFDMd97P7QAAUMx33s/tAABQzHfez+0AAFDMd97P7QAAUMw=")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_0ls0f")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_8pnv3"]
+points = PackedVector3Array(0.118064, 4.11666, 0.134848, 0.118064, 4.11666, 5.01342, 0.0889311, 3.68819, 4.81814, 0.0889311, 3.68819, -0.0604286, 6.91887, 0.428472, 0.134848, 6.91887, 0.428472, 5.01342, 6.88974, 0, 4.81814, 6.88974, 0, -0.0604286)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_i16hd"]
+_surfaces = [{
+"aabb": AABB(-0.506424, -0.225952, 0, 1.60196, 3.82147, 12.5238),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AaUBvwAdZkAAAAAAAaUBvwAdZkB8YUhBAaUBv/ZfZ758YUhBAaUBvwAdZkAAAAAAAaUBv/ZfZ758YUhBAaUBv/ZfZ74AAAAAqDqMP/ZfZ74AAAAAqDqMP/ZfZ758YUhBqDqMPwAdZkB8YUhBqDqMP/ZfZ74AAAAAqDqMPwAdZkB8YUhBqDqMPwAdZkAAAAAAAaUBvwAdZkB8YUhBAaUBvwAdZkAAAAAAqDqMPwAdZkAAAAAAAaUBvwAdZkB8YUhBqDqMPwAdZkAAAAAAqDqMPwAdZkB8YUhBAaUBv/ZfZ758YUhBAaUBvwAdZkB8YUhBqDqMPwAdZkB8YUhBAaUBv/ZfZ758YUhBqDqMPwAdZkB8YUhBqDqMP/ZfZ758YUhBAaUBv/ZfZ74AAAAAAaUBv/ZfZ758YUhBqDqMP/ZfZ758YUhBAaUBv/ZfZ74AAAAAqDqMP/ZfZ758YUhBqDqMP/ZfZ74AAAAAAaUBvwAdZkAAAAAAAaUBv/ZfZ74AAAAAqDqMP/ZfZ74AAAAAAaUBvwAdZkAAAAAAqDqMP/ZfZ74AAAAAqDqMPwAdZkAAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_ym55x"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-0.506424, -0.225952, 0, 1.60196, 3.82147, 12.5238),
+"attribute_data": PackedByteArray("/////wAAAAAAHWbA+wmGPe7XdT3/////fGFIwQAdZsC6TTg/7td1Pf////98YUjB9l9nPrpNOD8nAHk+/////wAAAAD2X2c++wmGPScAeT7/////AAAAAPZfZz77CYY9D/a5Pv////98YUjB9l9nPrpNOD8P9rk+/////3xhSMEAHWbAuk04P5LdCz//////AAAAAAAdZsD7CYY9kt0LP/////8BpQE/fGFIwfsJhj2QmCo//////wGlAT8AAAAAuk04P5CYKj//////qDqMvwAAAAC6TTg/CUA+P/////+oOoy/fGFIwfsJhj0JQD4//////wGlAT/2X2c++wmGPQf7XD//////AaUBPwAdZsCkwoc+B/tcP/////+oOoy/AB1mwKTChz6AonA//////6g6jL/2X2c++wmGPYCicD//////AaUBPwAAAAA40Fk/7td1Pf////8BpQE/fGFIwTjQWT90BCk//////6g6jL98YUjBwD5vP3QEKT//////qDqMvwAAAADAPm8/7td1Pf////8BpQE/AB1mwKHHyj4H+1w//////wGlAT/2X2c+44MYPwf7XD//////qDqMv/ZfZz7jgxg/gKJwP/////+oOoy/AB1mwKHHyj6AonA/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AaUBvwAdZkAAAAAAAaUBvwAdZkB8YUhBAaUBv/ZfZ758YUhBAaUBv/ZfZ74AAAAAqDqMP/ZfZ74AAAAAqDqMP/ZfZ758YUhBqDqMPwAdZkB8YUhBqDqMPwAdZkAAAAAAAaUBvwAdZkB8YUhBAaUBvwAdZkAAAAAAqDqMPwAdZkAAAAAAqDqMPwAdZkB8YUhBAaUBv/ZfZ758YUhBAaUBvwAdZkB8YUhBqDqMPwAdZkB8YUhBqDqMP/ZfZ758YUhBAaUBv/ZfZ74AAAAAAaUBv/ZfZ758YUhBqDqMP/ZfZ758YUhBqDqMP/ZfZ74AAAAAAaUBvwAdZkAAAAAAAaUBv/ZfZ74AAAAAqDqMP/ZfZ74AAAAAqDqMPwAdZkAAAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_i16hd")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_qusty"]
+points = PackedVector3Array(-0.506424, 3.59552, 0, -0.506424, 3.59552, 12.5238, -0.506424, -0.225952, 12.5238, -0.506424, -0.225952, 0, 1.09554, 3.59552, 0, 1.09554, 3.59552, 12.5238, 1.09554, -0.225952, 12.5238, 1.09554, -0.225952, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_2pfvo"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 15.6707, 8.0878, 0.438861),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAAKFnAUEAAAAAAAAAAKFnAUFksuA+AAAAAAAAAABksuA+AAAAAKFnAUEAAAAAAAAAAAAAAABksuA+AAAAAAAAAAAAAAAAMLt6QQAAAAAAAAAAMLt6QQAAAABksuA+MLt6QaFnAUFksuA+MLt6QQAAAAAAAAAAMLt6QaFnAUFksuA+MLt6QaFnAUEAAAAAAAAAAKFnAUFksuA+AAAAAKFnAUEAAAAAMLt6QaFnAUEAAAAAAAAAAKFnAUFksuA+MLt6QaFnAUEAAAAAMLt6QaFnAUFksuA+AAAAAAAAAABksuA+AAAAAKFnAUFksuA+MLt6QaFnAUFksuA+AAAAAAAAAABksuA+MLt6QaFnAUFksuA+MLt6QQAAAABksuA+AAAAAAAAAAAAAAAAAAAAAAAAAABksuA+MLt6QQAAAABksuA+AAAAAAAAAAAAAAAAMLt6QQAAAABksuA+MLt6QQAAAAAAAAAAAAAAAKFnAUEAAAAAAAAAAAAAAAAAAAAAMLt6QQAAAAAAAAAAAAAAAKFnAUEAAAAAMLt6QQAAAAAAAAAAMLt6QaFnAUEAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_wg2ug"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 15.6707, 8.0878, 0.438861),
+"attribute_data": PackedByteArray("/////wAAAAChZwHBqBJuPznsWD3/////ZLLgvqFnAcFnDWk/OexYPf////9ksuC+AAAAAGcNaT9tD6c+/////wAAAAAAAAAAqBJuP20Ppz7/////AAAAAAAAAACoEm4/w44NP/////9ksuC+AAAAAGcNaT/Djg0//////2Sy4L6hZwHBZw1pP7aHUz//////AAAAAKFnAcGoEm4/todTP/////8AAAAAZLLgvrVqjz0Q1oo9/////wAAAAAAAAAAtWqPPTnsWD3/////MLt6wQAAAAC5MkU/OexYPf////8wu3rBZLLgvrkyRT8Q1oo9/////wAAAAAAAAAAtWqPPXji5D7/////AAAAAKFnAcG1ao89JeExPv////8wu3rBoWcBwbkyRT8l4TE+/////zC7esEAAAAAuTJFP3ji5D7/////AAAAAAAAAAC1ao89wloRP/////8AAAAAZLLgvrVqjz3Djg0//////zC7esFksuC+uTJFP8OODT//////MLt6wQAAAAC5MkU/wloRP/////8AAAAAoWcBwbVqjz08cXI//////wAAAAAAAAAAtWqPPUl4LD//////MLt6wQAAAAC5MkU/SXgsP/////8wu3rBoWcBwbkyRT88cXI/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAAKFnAUEAAAAAAAAAAKFnAUFksuA+AAAAAAAAAABksuA+AAAAAAAAAAAAAAAAMLt6QQAAAAAAAAAAMLt6QQAAAABksuA+MLt6QaFnAUFksuA+MLt6QaFnAUEAAAAAAAAAAKFnAUFksuA+AAAAAKFnAUEAAAAAMLt6QaFnAUEAAAAAMLt6QaFnAUFksuA+AAAAAAAAAABksuA+AAAAAKFnAUFksuA+MLt6QaFnAUFksuA+MLt6QQAAAABksuA+AAAAAAAAAAAAAAAAAAAAAAAAAABksuA+MLt6QQAAAABksuA+MLt6QQAAAAAAAAAAAAAAAKFnAUEAAAAAAAAAAAAAAAAAAAAAMLt6QQAAAAAAAAAAMLt6QaFnAUEAAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_2pfvo")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_hk1my"]
+points = PackedVector3Array(0, 8.0878, 0, 0, 8.0878, 0.438861, 0, 0, 0.438861, 0, 0, 0, 15.6707, 8.0878, 0, 15.6707, 8.0878, 0.438861, 15.6707, 0, 0.438861, 15.6707, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_efxjw"]
+_surfaces = [{
+"aabb": AABB(-0.324343, 0, 0, 2.3734, 0.268241, 22.1489),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("zSMDQOJWiT7yMLFBzSMDQAAAAADyMLFBSRCmvgAAAADyMLFBzSMDQOJWiT7yMLFBSRCmvgAAAADyMLFBSRCmvuJWiT7yMLFBSRCmvgAAAAAAAAAASRCmvuJWiT4AAAAASRCmvuJWiT7yMLFBSRCmvgAAAAAAAAAASRCmvuJWiT7yMLFBSRCmvgAAAADyMLFBzSMDQAAAAAAAAAAASRCmvgAAAAAAAAAASRCmvgAAAADyMLFBzSMDQAAAAAAAAAAASRCmvgAAAADyMLFBzSMDQAAAAADyMLFBzSMDQOJWiT4AAAAAzSMDQOJWiT7yMLFBSRCmvuJWiT7yMLFBzSMDQOJWiT4AAAAASRCmvuJWiT7yMLFBSRCmvuJWiT4AAAAAzSMDQOJWiT4AAAAASRCmvuJWiT4AAAAASRCmvgAAAAAAAAAAzSMDQOJWiT4AAAAASRCmvgAAAAAAAAAAzSMDQAAAAAAAAAAAzSMDQOJWiT4AAAAAzSMDQAAAAAAAAAAAzSMDQAAAAADyMLFBzSMDQOJWiT4AAAAAzSMDQAAAAADyMLFBzSMDQOJWiT7yMLFB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_xag18"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-0.324343, 0, 0, 2.3734, 0.268241, 22.1489),
+"attribute_data": PackedByteArray("/////80jA8DiVom+q6qqPdSHaz//////zSMDwAAAAACrqqo9Mw1pP/////9JEKY+AAAAABPGMD4zDWk//////0kQpj7iVom+E8YwPtSHaz//////AAAAAAAAAACrqqo9Zpa3Pf////8AAAAA4laJvquqqj1iwaM9//////IwscHiVom+q6pqP2LBoz3/////8jCxwQAAAACrqmo/Zpa3Pf/////NIwPAAAAAAKuqqj2vpKs+/////0kQpj4AAAAAq6qqPZWMfz7/////SRCmPvIwscGrqmo/lYx/Pv/////NIwPA8jCxwauqaj+vpKs+/////80jA8AAAAAAq6qqPV+F/T7/////zSMDwPIwscGrqmo/X4X9Pv////9JEKY+8jCxwauqaj/isRQ//////0kQpj4AAAAAq6qqPeKxFD//////zSMDwOJWib5fuK0+Mw1pP/////9JEKY+4laJvr1w2z4zDWk//////0kQpj4AAAAAvXDbPtSHaz//////zSMDwAAAAABfuK0+1IdrP/////8AAAAA4laJvquqqj3bHEA//////wAAAAAAAAAAq6qqPTqiPT//////8jCxwQAAAACrqmo/OqI9P//////yMLHB4laJvquqaj/bHEA/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("zSMDQOJWiT7yMLFBzSMDQAAAAADyMLFBSRCmvgAAAADyMLFBSRCmvuJWiT7yMLFBSRCmvgAAAAAAAAAASRCmvuJWiT4AAAAASRCmvuJWiT7yMLFBSRCmvgAAAADyMLFBzSMDQAAAAAAAAAAASRCmvgAAAAAAAAAASRCmvgAAAADyMLFBzSMDQAAAAADyMLFBzSMDQOJWiT4AAAAAzSMDQOJWiT7yMLFBSRCmvuJWiT7yMLFBSRCmvuJWiT4AAAAAzSMDQOJWiT4AAAAASRCmvuJWiT4AAAAASRCmvgAAAAAAAAAAzSMDQAAAAAAAAAAAzSMDQOJWiT4AAAAAzSMDQAAAAAAAAAAAzSMDQAAAAADyMLFBzSMDQOJWiT7yMLFB/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_efxjw")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_fnhql"]
+points = PackedVector3Array(2.04906, 0.268241, 22.1489, 2.04906, 0, 22.1489, -0.324343, 0, 22.1489, -0.324343, 0.268241, 22.1489, -0.324343, 0, 0, -0.324343, 0.268241, 0, 2.04906, 0, 0, 2.04906, 0.268241, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_2c3nt"]
+_surfaces = [{
+"aabb": AABB(12.7723, 0, 0, 2.886, 0.268241, 22.1489),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("V1tMQQAAAADyMLFBV1tMQeJWiT7yMLFBZoh6QeJWiT7yMLFBV1tMQQAAAADyMLFBZoh6QeJWiT7yMLFBZoh6QQAAAADyMLFBZoh6QeJWiT4AAAAAZoh6QQAAAAAAAAAAZoh6QQAAAADyMLFBZoh6QeJWiT4AAAAAZoh6QQAAAADyMLFBZoh6QeJWiT7yMLFBV1tMQeJWiT4AAAAAZoh6QeJWiT4AAAAAZoh6QeJWiT7yMLFBV1tMQeJWiT4AAAAAZoh6QeJWiT7yMLFBV1tMQeJWiT7yMLFBV1tMQQAAAAAAAAAAV1tMQQAAAADyMLFBZoh6QQAAAADyMLFBV1tMQQAAAAAAAAAAZoh6QQAAAADyMLFBZoh6QQAAAAAAAAAAV1tMQQAAAAAAAAAAZoh6QQAAAAAAAAAAZoh6QeJWiT4AAAAAV1tMQQAAAAAAAAAAZoh6QeJWiT4AAAAAV1tMQeJWiT4AAAAAV1tMQQAAAAAAAAAAV1tMQeJWiT4AAAAAV1tMQeJWiT7yMLFBV1tMQQAAAAAAAAAAV1tMQeJWiT7yMLFBV1tMQQAAAADyMLFB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_nns2b"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(12.7723, 0, 0, 2.886, 0.268241, 22.1489),
+"attribute_data": PackedByteArray("/////1dbTMEAAAAAq6qqPdhCbD//////V1tMweJWib6rqqo93d5pP/////9miHrB4laJvsyFRD7d3mk//////2aIesEAAAAAzIVEPthCbD//////AAAAAOJWib6rqqo9DwmxPf////8AAAAAAAAAAKuqqj096Z09//////IwscEAAAAAq6pqPz3pnT3/////8jCxweJWib6rqmo/DwmxPf////9XW0zBAAAAAKuqqj1np64+/////2aIesEAAAAAq6qqPcRtdj7/////Zoh6wfIwscGrqmo/xG12Pv////9XW0zB8jCxwauqaj9np64+/////1dbTMEAAAAAq6qqPQSc/T7/////V1tMwfIwscGrqmo/BJz9Pv////9miHrB8jCxwauqaj9Fhhg//////2aIesEAAAAAq6qqPUWGGD//////V1tMwQAAAAA7mLc+3d5pP/////9miHrBAAAAAHcw7z7d3mk//////2aIesHiVom+dzDvPthCbD//////V1tMweJWib47mLc+2EJsP/////8AAAAAAAAAAKuqqj2OZEI//////wAAAADiVom+q6qqPZMAQD//////8jCxweJWib6rqmo/kwBAP//////yMLHBAAAAAKuqaj+OZEI/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("V1tMQQAAAADyMLFBV1tMQeJWiT7yMLFBZoh6QeJWiT7yMLFBZoh6QQAAAADyMLFBZoh6QeJWiT4AAAAAZoh6QQAAAAAAAAAAZoh6QQAAAADyMLFBZoh6QeJWiT7yMLFBV1tMQeJWiT4AAAAAZoh6QeJWiT4AAAAAZoh6QeJWiT7yMLFBV1tMQeJWiT7yMLFBV1tMQQAAAAAAAAAAV1tMQQAAAADyMLFBZoh6QQAAAADyMLFBZoh6QQAAAAAAAAAAV1tMQQAAAAAAAAAAZoh6QQAAAAAAAAAAZoh6QeJWiT4AAAAAV1tMQeJWiT4AAAAAV1tMQQAAAAAAAAAAV1tMQeJWiT4AAAAAV1tMQeJWiT7yMLFBV1tMQQAAAADyMLFB/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z////9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//////AAD/v/////8AAP+//////wAA/7//////AAD/vwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_2c3nt")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_u5ric"]
+points = PackedVector3Array(12.7723, 0, 22.1489, 12.7723, 0.268241, 22.1489, 15.6583, 0.268241, 22.1489, 15.6583, 0, 22.1489, 15.6583, 0.268241, 0, 15.6583, 0, 0, 12.7723, 0.268241, 0, 12.7723, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_l1txu"]
+_surfaces = [{
+"aabb": AABB(1.04698, 0, 14.3386, 11.7253, 0.268241, 7.8103),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("V1tMQeJWiT7yMLFBV1tMQeJWiT7oamVBV1tMQQAAAADoamVBV1tMQeJWiT7yMLFBV1tMQQAAAADoamVBV1tMQQAAAADyMLFBcQOGPwAAAADoamVBcQOGPwAAAADyMLFBV1tMQQAAAADyMLFBcQOGPwAAAADoamVBV1tMQQAAAADyMLFBV1tMQQAAAADoamVBcQOGP+JWiT7oamVBcQOGPwAAAADoamVBV1tMQQAAAADoamVBcQOGP+JWiT7oamVBV1tMQQAAAADoamVBV1tMQeJWiT7oamVBcQOGP+JWiT7yMLFBV1tMQeJWiT7yMLFBV1tMQQAAAADyMLFBcQOGP+JWiT7yMLFBV1tMQQAAAADyMLFBcQOGPwAAAADyMLFBcQOGP+JWiT7yMLFBcQOGPwAAAADyMLFBcQOGPwAAAADoamVBcQOGP+JWiT7yMLFBcQOGPwAAAADoamVBcQOGP+JWiT7oamVBcQOGP+JWiT7yMLFBcQOGP+JWiT7oamVBV1tMQeJWiT7oamVBcQOGP+JWiT7yMLFBV1tMQeJWiT7oamVBV1tMQeJWiT7yMLFB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_rspiw"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(1.04698, 0, 14.3386, 11.7253, 0.268241, 7.8103),
+"attribute_data": PackedByteArray("//////IwscHiVom+VD9GPe2Aaz//////6GplweJWib4W2b0+7YBrP//////oamXBAAAAABbZvT7SUW8///////IwscEAAAAAVD9GPdJRbz//////cQOGv+hqZcFUP0Y9JZT/Pv////9xA4a/8jCxwVQ/Rj1ucYU9/////1dbTMHyMLHBiksIP25xhT3/////V1tMwehqZcGKSwg/JZT/Pv////9xA4a/4laJvlQ/Rj1S9yQ//////3EDhr8AAAAAVD9GPW4mIT//////V1tMwQAAAACKSwg/biYhP/////9XW0zB4laJvopLCD9S9yQ//////3EDhr/iVom+VD9GPa5TRj//////V1tMweJWib6KSwg/rlNGP/////9XW0zBAAAAAIpLCD+SJEo//////3EDhr8AAAAAVD9GPZIkSj//////8jCxweJWib51EyE/OKlcP//////yMLHBAAAAAHUTIT9U2Fg//////+hqZcEAAAAACpxzP1TYWD//////6GplweJWib4KnHM/OKlcP/////9xA4a/8jCxwQqccz9ucYU9/////3EDhr/oamXBdRMhP25xhT3/////V1tMwehqZcF1EyE/+Hs3P/////9XW0zB8jCxwQqccz/4ezc/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("V1tMQeJWiT7yMLFBV1tMQeJWiT7oamVBV1tMQQAAAADoamVBV1tMQQAAAADyMLFBcQOGPwAAAADoamVBcQOGPwAAAADyMLFBV1tMQQAAAADyMLFBV1tMQQAAAADoamVBcQOGP+JWiT7oamVBcQOGPwAAAADoamVBV1tMQQAAAADoamVBV1tMQeJWiT7oamVBcQOGP+JWiT7yMLFBV1tMQeJWiT7yMLFBV1tMQQAAAADyMLFBcQOGPwAAAADyMLFBcQOGP+JWiT7yMLFBcQOGPwAAAADyMLFBcQOGPwAAAADoamVBcQOGP+JWiT7oamVBcQOGP+JWiT7yMLFBcQOGP+JWiT7oamVBV1tMQeJWiT7oamVBV1tMQeJWiT7yMLFB////f/////////9//////////3//////////f///////fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_l1txu")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_3s54x"]
+points = PackedVector3Array(12.7723, 0.268241, 22.1489, 12.7723, 0.268241, 14.3386, 12.7723, 0, 14.3386, 12.7723, 0, 22.1489, 1.04698, 0, 14.3386, 1.04698, 0, 22.1489, 1.04698, 0.268241, 14.3386, 1.04698, 0.268241, 22.1489)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_1mojb"]
+_surfaces = [{
+"aabb": AABB(1.04698, 0, 0, 11.7253, 0.268241, 1.54587),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("cQOGPwAAAAAR38U/cQOGPwAAAAAAAAAAcQOGP+JWiT4AAAAAcQOGPwAAAAAR38U/cQOGP+JWiT4AAAAAcQOGP+JWiT4R38U/V1tMQeJWiT4AAAAAcQOGP+JWiT4AAAAAcQOGPwAAAAAAAAAAV1tMQeJWiT4AAAAAcQOGPwAAAAAAAAAAV1tMQQAAAAAAAAAAV1tMQeJWiT4R38U/cQOGP+JWiT4R38U/cQOGP+JWiT4AAAAAV1tMQeJWiT4R38U/cQOGP+JWiT4AAAAAV1tMQeJWiT4AAAAAV1tMQQAAAAAR38U/V1tMQQAAAAAAAAAAcQOGPwAAAAAAAAAAV1tMQQAAAAAR38U/cQOGPwAAAAAAAAAAcQOGPwAAAAAR38U/V1tMQQAAAAAR38U/V1tMQeJWiT4R38U/V1tMQeJWiT4AAAAAV1tMQQAAAAAR38U/V1tMQeJWiT4AAAAAV1tMQQAAAAAAAAAAV1tMQQAAAAAR38U/cQOGPwAAAAAR38U/cQOGP+JWiT4R38U/V1tMQQAAAAAR38U/cQOGP+JWiT4R38U/V1tMQeJWiT4R38U/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_tgata"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(1.04698, 0, 0, 11.7253, 0.268241, 1.54587),
+"attribute_data": PackedByteArray("/////xHfxb8AAAAAq6qqPbhjaD//////AAAAAAAAAABL1kU+uGNoP/////8AAAAA4laJvkvWRT4JyWw//////xHfxb/iVom+q6qqPQnJbD//////V1tMweJWib6rqqo9wLeZPf////9xA4a/4laJvquqaj/At5k9/////3EDhr8AAAAAq6pqP0fivD3/////V1tMwQAAAACrqqo9R+K8Pf////9XW0zBEd/Fv6uqqj3iKHg+/////3EDhr8R38W/q6pqP+IoeD7/////cQOGvwAAAACrqmo/0L6uPv////9XW0zBAAAAAKuqqj3Qvq4+/////1dbTMER38W/q6qqPYciFz//////V1tMwQAAAACrqqo9sJr7Pv////9xA4a/AAAAAKuqaj+wmvs+/////3EDhr8R38W/q6pqP4ciFz//////Ed/FvwAAAAB7QLg+CclsP/////8R38W/4laJvntAuD64Y2g//////wAAAADiVom+9YDwPrhjaD//////AAAAAAAAAAD1gPA+CclsP/////9XW0zBAAAAAKuqqj13kD0//////3EDhr8AAAAAq6pqP3eQPT//////cQOGv+JWib6rqmo/yPVBP/////9XW0zB4laJvquqqj3I9UE/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("cQOGPwAAAAAR38U/cQOGPwAAAAAAAAAAcQOGP+JWiT4AAAAAcQOGP+JWiT4R38U/V1tMQeJWiT4AAAAAcQOGP+JWiT4AAAAAcQOGPwAAAAAAAAAAV1tMQQAAAAAAAAAAV1tMQeJWiT4R38U/cQOGP+JWiT4R38U/cQOGP+JWiT4AAAAAV1tMQeJWiT4AAAAAV1tMQQAAAAAR38U/V1tMQQAAAAAAAAAAcQOGPwAAAAAAAAAAcQOGPwAAAAAR38U/V1tMQQAAAAAR38U/V1tMQeJWiT4R38U/V1tMQeJWiT4AAAAAV1tMQQAAAAAAAAAAV1tMQQAAAAAR38U/cQOGPwAAAAAR38U/cQOGP+JWiT4R38U/V1tMQeJWiT4R38U/AAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD/////AAD/v/////8AAP+//////wAA/7//////AAD/v/9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z////9//////////3//////////f/////////9///////9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_1mojb")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_gr45o"]
+points = PackedVector3Array(1.04698, 0, 1.54587, 1.04698, 0, 0, 1.04698, 0.268241, 0, 1.04698, 0.268241, 1.54587, 12.7723, 0.268241, 0, 12.7723, 0, 0, 12.7723, 0.268241, 1.54587, 12.7723, 0, 1.54587)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_dwpel"]
+_surfaces = [{
+"aabb": AABB(-0.5861, -0.409317, 2.11873, 0.972665, 4.60223, 20.3541),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("2evFPlIshkBLyLNB2evFPv+R0b5LyLNBpgoWv/+R0b5LyLNB2evFPlIshkBLyLNBpgoWv/+R0b5LyLNBpgoWv1IshkBLyLNBpgoWv/+R0b75yQJBpgoWv1IshkBGmQdApgoWv1IshkBLyLNBpgoWv/+R0b75yQJBpgoWv1IshkBLyLNBpgoWv/+R0b5LyLNB2evFPv+R0b75yQJBpgoWv/+R0b75yQJBpgoWv/+R0b5LyLNB2evFPv+R0b75yQJBpgoWv/+R0b5LyLNB2evFPv+R0b5LyLNB2evFPlIshkBGmQdA2evFPlIshkBLyLNBpgoWv1IshkBLyLNB2evFPlIshkBGmQdApgoWv1IshkBLyLNBpgoWv1IshkBGmQdA2evFPlIshkBGmQdApgoWv1IshkBGmQdApgoWv/+R0b75yQJB2evFPlIshkBGmQdApgoWv/+R0b75yQJB2evFPv+R0b75yQJB2evFPlIshkBGmQdA2evFPv+R0b75yQJB2evFPv+R0b5LyLNB2evFPlIshkBGmQdA2evFPv+R0b5LyLNB2evFPlIshkBLyLNB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_ha825"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-0.5861, -0.409317, 2.11873, 0.972665, 4.60223, 20.3541),
+"attribute_data": PackedByteArray("/////9nrxb5SLIbAF/FjP3n+Ej//////2evFvv+R0T4X8WM/8PA9P/////+mChY//5HRPh5SbD/w8D0//////6YKFj9SLIbAHlJsP3n+Ej//////+ckCwf+R0T48Zos+wcN3Pv////9GmQfAUiyGwJhFjD3K85c9/////0vIs8FSLIbAsd9AP8rzlz3/////S8izwf+R0T6x30A/wcN3Pv/////Z68W++ckCwZhFjD2HAW0//////6YKFj/5yQLBmEWMPeLtYz//////pgoWP0vIs8FGtQw/4u1jP//////Z68W+S8izwUa1DD+HAW0//////9nrxb5GmQfAmEWMPcXbxz7/////2evFvkvIs8Gx30A/xdvHPv////+mChY/S8izwbHfQD8OA9o+/////6YKFj9GmQfAmEWMPQ4D2j7/////2evFvkaZB8BMd24/yvOXPf////+mChY/RpkHwBfxYz/K85c9/////6YKFj/5yQLBF/FjP2BJ2D7/////2evFvvnJAsFMd24/YEnYPv////9GmQfAUiyGwLHfQD95/hI///////nJAsH/kdE+RrUMP/DwPT//////S8izwf+R0T6YRYw98PA9P/////9LyLPBUiyGwJhFjD15/hI/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("2evFPlIshkBLyLNB2evFPv+R0b5LyLNBpgoWv/+R0b5LyLNBpgoWv1IshkBLyLNBpgoWv/+R0b75yQJBpgoWv1IshkBGmQdApgoWv1IshkBLyLNBpgoWv/+R0b5LyLNB2evFPv+R0b75yQJBpgoWv/+R0b75yQJBpgoWv/+R0b5LyLNB2evFPv+R0b5LyLNB2evFPlIshkBGmQdA2evFPlIshkBLyLNBpgoWv1IshkBLyLNBpgoWv1IshkBGmQdA2evFPlIshkBGmQdApgoWv1IshkBGmQdApgoWv/+R0b75yQJB2evFPv+R0b75yQJB2evFPlIshkBGmQdA2evFPv+R0b75yQJB2evFPv+R0b5LyLNB2evFPlIshkBLyLNB/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/79FtwAAAAD/P0W3AAAAAP8/RbcAAAAA/z9FtwAAAAD/P////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_dwpel")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_nxwni"]
+points = PackedVector3Array(0.386565, 4.19291, 22.4728, 0.386565, -0.409317, 22.4728, -0.5861, -0.409317, 22.4728, -0.5861, 4.19291, 22.4728, -0.5861, -0.409317, 8.17431, -0.5861, 4.19291, 2.11873, 0.386565, -0.409317, 8.17431, 0.386565, 4.19291, 2.11873)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_shhbd"]
+_surfaces = [{
+"aabb": AABB(-3.66678, 0, 0, 1.03595, 7.71482, 1.40322),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("hqxqwAAAAAAAAAAAls9YwAAAAAC3nLM/hV8owAAAAADye4s/hqxqwAAAAAAAAAAAhV8owAAAAADye4s/6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhV8owM7f9kDye4s/ls9YwM7f9kC3nLM/6j42wM7f9kAAAAAAls9YwM7f9kC3nLM/hqxqwM7f9kAAAAAAls9YwAAAAAC3nLM/hqxqwAAAAAAAAAAAhqxqwM7f9kAAAAAAls9YwAAAAAC3nLM/hqxqwM7f9kAAAAAAls9YwM7f9kC3nLM/hV8owAAAAADye4s/ls9YwAAAAAC3nLM/ls9YwM7f9kC3nLM/hV8owAAAAADye4s/ls9YwM7f9kC3nLM/hV8owM7f9kDye4s/6j42wAAAAAAAAAAAhV8owAAAAADye4s/hV8owM7f9kDye4s/6j42wAAAAAAAAAAAhV8owM7f9kDye4s/6j42wM7f9kAAAAAAhqxqwAAAAAAAAAAA6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhqxqwAAAAAAAAAAA6j42wM7f9kAAAAAAhqxqwM7f9kAAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_66lia"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-3.66678, 0, 0, 1.03595, 7.71482, 1.40322),
+"attribute_data": PackedByteArray("/////4asakAAAAAAXUFdP2+gmD3/////ls9YQLecs79dQV0/BdZZPv////+FXyhA8nuLv/r6bj+xBko+/////+o+NkAAAAAA7/puPz8+uD3/////6j42QAAAAADv+m4/rhb4Pv////+FXyhA8nuLv/r6bj/lIsE+/////5bPWEC3nLO/XUFdPzs7uT7/////hqxqQAAAAABdQV0/If7/Pv////+3nLO/AAAAADkoiD0H1lk+/////wAAAAAAAAAAOSiIPW+gmD3/////AAAAAM7f9sBONzs/b6CYPf////+3nLO/zt/2wE43Oz8H1lk+/////4VfKEAAAAAAOSiIPYW/4T7/////ls9YQAAAAAA5KIg9Ozu5Pv////+Wz1hAzt/2wE43Oz87O7k+/////4VfKEDO3/bATjc7P4W/4T7/////AAAAAAAAAAA5KIg9w4EyP//////ye4u/AAAAADkoiD3eBxc///////J7i7/O3/bATjc7P94HFz//////AAAAAM7f9sBONzs/w4EyP/////+GrGpAAAAAADkoiD3z62w//////+o+NkAAAAAAOSiIPd6pWD//////6j42QM7f9sBONzs/3qlYP/////+GrGpAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("hqxqwAAAAAAAAAAAls9YwAAAAAC3nLM/hV8owAAAAADye4s/6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhV8owM7f9kDye4s/ls9YwM7f9kC3nLM/hqxqwM7f9kAAAAAAls9YwAAAAAC3nLM/hqxqwAAAAAAAAAAAhqxqwM7f9kAAAAAAls9YwM7f9kC3nLM/hV8owAAAAADye4s/ls9YwAAAAAC3nLM/ls9YwM7f9kC3nLM/hV8owM7f9kDye4s/6j42wAAAAAAAAAAAhV8owAAAAADye4s/hV8owM7f9kDye4s/6j42wM7f9kAAAAAAhqxqwAAAAAAAAAAA6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhqxqwM7f9kAAAAAA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vzwV/38AAJ4KPBX/fwAAngo8Ff9/AACeCjwV/38AAJ4KfaX/f30l/z99pf9/fSX/P32l/399Jf8/faX/f30l/z///zyVAABg9f//PJUAAGD1//88lQAAYPX//zyVAABg9f////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_shhbd")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_kebix"]
+points = PackedVector3Array(-3.66678, 0, 0, -3.38767, 0, 1.40322, -2.63083, 0, 1.08972, -2.84759, 0, 0, -3.66678, 7.71482, 0, -3.38767, 7.71482, 1.40322, -2.63083, 7.71482, 1.08972, -2.84759, 7.71482, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_hq7sm"]
+_surfaces = [{
+"aabb": AABB(-3.38767, 0, 1.08972, 1.37412, 7.71482, 1.50309),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("ls9YwAAAAAC3nLM/mfAlwAAAAACZ8CVAAd4AwAAAAAAB3gBAls9YwAAAAAC3nLM/Ad4AwAAAAAAB3gBAhV8owAAAAADye4s/hV8owM7f9kDye4s/Ad4AwM7f9kAB3gBAmfAlwM7f9kCZ8CVAhV8owM7f9kDye4s/mfAlwM7f9kCZ8CVAls9YwM7f9kC3nLM/mfAlwAAAAACZ8CVAls9YwAAAAAC3nLM/ls9YwM7f9kC3nLM/mfAlwAAAAACZ8CVAls9YwM7f9kC3nLM/mfAlwM7f9kCZ8CVAAd4AwAAAAAAB3gBAmfAlwAAAAACZ8CVAmfAlwM7f9kCZ8CVAAd4AwAAAAAAB3gBAmfAlwM7f9kCZ8CVAAd4AwM7f9kAB3gBAhV8owAAAAADye4s/Ad4AwAAAAAAB3gBAAd4AwM7f9kAB3gBAhV8owAAAAADye4s/Ad4AwM7f9kAB3gBAhV8owM7f9kDye4s/ls9YwAAAAAC3nLM/hV8owAAAAADye4s/hV8owM7f9kDye4s/ls9YwAAAAAC3nLM/hV8owM7f9kDye4s/ls9YwM7f9kC3nLM/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_hykr2"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-3.38767, 0, 1.08972, 1.37412, 7.71482, 1.50309),
+"attribute_data": PackedByteArray("/////5bPWEC3nLO/WUFdP16gmD3/////mfAlQJnwJcBZQV0/6tVZPv////8B3gBAAd4AwPT6bj/kBko+/////4VfKEDye4u/+vpuPzk+uD3/////hV8oQPJ7i7/6+m4/jBb4Pv////8B3gBAAd4AwPT6bj+oIsE+/////5nwJUCZ8CXAWUFdPyQ7uT7/////ls9YQLecs79ZQV0/Af7/Pv////+Z8CXAAAAAADcoiD3q1Vk+/////7ecs78AAAAANyiIPV6gmD3/////t5yzv87f9sBKNzs/XqCYPf////+Z8CXAzt/2wEo3Oz/q1Vk+/////wHeAEAAAAAANyiIPWC/4T7/////mfAlQAAAAAA3KIg9JDu5Pv////+Z8CVAzt/2wEo3Oz8kO7k+/////wHeAEDO3/bASjc7P2C/4T7/////8nuLvwAAAAA3KIg9uYEyP/////8B3gDAAAAAADcoiD3IBxc//////wHeAMDO3/bASjc7P8gHFz//////8nuLv87f9sBKNzs/uYEyP/////+Wz1hAAAAAADcoiD3062w//////4VfKEAAAAAANyiIPdGpWD//////hV8oQM7f9sBKNzs/0alYP/////+Wz1hAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("ls9YwAAAAAC3nLM/mfAlwAAAAACZ8CVAAd4AwAAAAAAB3gBAhV8owAAAAADye4s/hV8owM7f9kDye4s/Ad4AwM7f9kAB3gBAmfAlwM7f9kCZ8CVAls9YwM7f9kC3nLM/mfAlwAAAAACZ8CVAls9YwAAAAAC3nLM/ls9YwM7f9kC3nLM/mfAlwM7f9kCZ8CVAAd4AwAAAAAAB3gBAmfAlwAAAAACZ8CVAmfAlwM7f9kCZ8CVAAd4AwM7f9kAB3gBAhV8owAAAAADye4s/Ad4AwAAAAAAB3gBAAd4AwM7f9kAB3gBAhV8owM7f9kDye4s/ls9YwAAAAAC3nLM/hV8owAAAAADye4s/hV8owM7f9kDye4s/ls9YwM7f9kC3nLM//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v0Qz/38AAKIZRDP/fwAAohlEM/9/AACiGUQz/38AAKIZ/7//f/8//z//v/9//z//P/+//3//P/8//7//f/8//z///0SzAABc5v//RLMAAFzm//9EswAAXOb//0SzAABc5gAAgdp9Jf+/AACB2n0l/78AAIHafSX/vwAAgdp9Jf+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_hq7sm")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_pa4gv"]
+points = PackedVector3Array(-3.38767, 0, 1.40322, -2.59281, 0, 2.59281, -2.01355, 0, 2.01355, -2.63083, 0, 1.08972, -3.38767, 7.71482, 1.40322, -2.59281, 7.71482, 2.59281, -2.01355, 7.71482, 2.01355, -2.63083, 7.71482, 1.08972)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_g3sfn"]
+_surfaces = [{
+"aabb": AABB(-2.59281, 0, 2.01355, 1.50309, 7.71482, 1.37412),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("mfAlwAAAAACZ8CVAt5yzvwAAAACWz1hA8nuLvwAAAACFXyhAmfAlwAAAAACZ8CVA8nuLvwAAAACFXyhAAd4AwAAAAAAB3gBAAd4AwM7f9kAB3gBA8nuLv87f9kCFXyhAt5yzv87f9kCWz1hAAd4AwM7f9kAB3gBAt5yzv87f9kCWz1hAmfAlwM7f9kCZ8CVAt5yzvwAAAACWz1hAmfAlwAAAAACZ8CVAmfAlwM7f9kCZ8CVAt5yzvwAAAACWz1hAmfAlwM7f9kCZ8CVAt5yzv87f9kCWz1hA8nuLvwAAAACFXyhAt5yzvwAAAACWz1hAt5yzv87f9kCWz1hA8nuLvwAAAACFXyhAt5yzv87f9kCWz1hA8nuLv87f9kCFXyhAAd4AwAAAAAAB3gBA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhAAd4AwAAAAAAB3gBA8nuLv87f9kCFXyhAAd4AwM7f9kAB3gBAmfAlwAAAAACZ8CVAAd4AwAAAAAAB3gBAAd4AwM7f9kAB3gBAmfAlwAAAAACZ8CVAAd4AwM7f9kAB3gBAmfAlwM7f9kCZ8CVA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_sylhf"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-2.59281, 0, 2.01355, 1.50309, 7.71482, 1.37412),
+"attribute_data": PackedByteArray("/////5nwJUCZ8CXAWUFdP16gmD3/////t5yzP5bPWMBZQV0/6tVZPv/////ye4s/hV8owPr6bj/9Bko+/////wHeAEAB3gDA9PpuP2w+uD3/////Ad4AQAHeAMD0+m4/fhb4Pv/////ye4s/hV8owPr6bj+aIsE+/////7ecsz+Wz1jAWUFdPyQ7uT7/////mfAlQJnwJcBZQV0/Af7/Pv////+3nLM/AAAAADcoiD3q1Vk+/////5nwJUAAAAAANyiIPV6gmD3/////mfAlQM7f9sBKNzs/XqCYPf////+3nLM/zt/2wEo3Oz/q1Vk+/////4VfKMAAAAAANyiIPWq/4T7/////ls9YwAAAAAA3KIg9JDu5Pv////+Wz1jAzt/2wEo3Oz8kO7k+/////4VfKMDO3/bASjc7P2q/4T7/////Ad4AQAAAAAA3KIg9voEyP//////ye4s/AAAAADcoiD3MBxc///////J7iz/O3/bASjc7P8wHFz//////Ad4AQM7f9sBKNzs/voEyP/////+Z8CVAAAAAADcoiD3062w//////wHeAEAAAAAANyiIPdepWD//////Ad4AQM7f9sBKNzs/16lYP/////+Z8CVAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("mfAlwAAAAACZ8CVAt5yzvwAAAACWz1hA8nuLvwAAAACFXyhAAd4AwAAAAAAB3gBAAd4AwM7f9kAB3gBA8nuLv87f9kCFXyhAt5yzv87f9kCWz1hAmfAlwM7f9kCZ8CVAt5yzvwAAAACWz1hAmfAlwAAAAACZ8CVAmfAlwM7f9kCZ8CVAt5yzv87f9kCWz1hA8nuLvwAAAACFXyhAt5yzvwAAAACWz1hAt5yzv87f9kCWz1hA8nuLv87f9kCFXyhAAd4AwAAAAAAB3gBA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhAAd4AwM7f9kAB3gBAmfAlwAAAAACZ8CVAAd4AwAAAAAAB3gBAAd4AwM7f9kAB3gBAmfAlwM7f9kCZ8CVA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v7pM/38AAF0mukz/fwAAXSa6TP9/AABdJrpM/38AAF0mgdr/f///QO2B2v9///9A7YHa/3///0Dtgdr/f///QO3//7rMAACh2f//uswAAKHZ//+6zAAAodn//7rMAACh2QAA/7//P/+/AAD/v/8//78AAP+//z//vwAA/7//P/+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_g3sfn")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_6flwn"]
+points = PackedVector3Array(-2.59281, 0, 2.59281, -1.40322, 0, 3.38767, -1.08972, 0, 2.63083, -2.01355, 0, 2.01355, -2.59281, 7.71482, 2.59281, -1.40322, 7.71482, 3.38767, -1.08972, 7.71482, 2.63083, -2.01355, 7.71482, 2.01355)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_tqq0o"]
+_surfaces = [{
+"aabb": AABB(-1.40322, 0, 2.63083, 1.40322, 7.71482, 1.03595),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("t5yzvwAAAACWz1hAGwAgNAAAAACGrGpA1///MwAAAADqPjZAt5yzvwAAAACWz1hA1///MwAAAADqPjZA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhA1///M87f9kDqPjZAGwAgNM7f9kCGrGpA8nuLv87f9kCFXyhAGwAgNM7f9kCGrGpAt5yzv87f9kCWz1hAGwAgNAAAAACGrGpAt5yzvwAAAACWz1hAt5yzv87f9kCWz1hAGwAgNAAAAACGrGpAt5yzv87f9kCWz1hAGwAgNM7f9kCGrGpA1///MwAAAADqPjZAGwAgNAAAAACGrGpAGwAgNM7f9kCGrGpA1///MwAAAADqPjZAGwAgNM7f9kCGrGpA1///M87f9kDqPjZA8nuLvwAAAACFXyhA1///MwAAAADqPjZA1///M87f9kDqPjZA8nuLvwAAAACFXyhA1///M87f9kDqPjZA8nuLv87f9kCFXyhAt5yzvwAAAACWz1hA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhAt5yzvwAAAACWz1hA8nuLv87f9kCFXyhAt5yzv87f9kCWz1hA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_bgpj6"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-1.40322, 0, 2.63083, 1.40322, 7.71482, 1.03595),
+"attribute_data": PackedByteArray("/////7ecsz+Wz1jAXUFdP2+gmD3/////GwAgtIasasBdQV0/B9ZZPv/////X//+z6j42wO/6bj8fB0o+//////J7iz+FXyjA+vpuPxc/uD3/////8nuLP4VfKMD6+m4/dxb4Pv/////X//+z6j42wO/6bj+vIsE+/////xsAILSGrGrAXUFdPzs7uT7/////t5yzP5bPWMBdQV0/I/7/Pv////8bACC0AAAAADkoiD0H1lk+/////7ecsz8AAAAAOSiIPW+gmD3/////t5yzP87f9sBONzs/b6CYPf////8bACC0zt/2wE43Oz8H1lk+/////+o+NsAAAAAAOSiIPWS/4T7/////hqxqwAAAAAA5KIg9Ozu5Pv////+GrGrAzt/2wE43Oz87O7k+/////+o+NsDO3/bATjc7P2S/4T7/////8nuLPwAAAAA5KIg9soEyP//////X//+zAAAAADkoiD3OBxc//////9f//7PO3/bATjc7P84HFz//////8nuLP87f9sBONzs/soEyP/////+Wz1jAAAAAADkoiD3z62w//////4VfKMAAAAAAOSiIPc+pWD//////hV8owM7f9sBONzs/z6lYP/////+Wz1jAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("t5yzvwAAAACWz1hAGwAgNAAAAACGrGpA1///MwAAAADqPjZA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhA1///M87f9kDqPjZAGwAgNM7f9kCGrGpAt5yzv87f9kCWz1hAGwAgNAAAAACGrGpAt5yzvwAAAACWz1hAt5yzv87f9kCWz1hAGwAgNM7f9kCGrGpA1///MwAAAADqPjZAGwAgNAAAAACGrGpAGwAgNM7f9kCGrGpA1///M87f9kDqPjZA8nuLvwAAAACFXyhA1///MwAAAADqPjZA1///M87f9kDqPjZA8nuLv87f9kCFXyhAt5yzvwAAAACWz1hA8nuLvwAAAACFXyhA8nuLv87f9kCFXyhAt5yzv87f9kCWz1hA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v8Nq/38AAGE1w2r/fwAAYTXDav9/AABhNcNq/38AAGE1////f/////////9//////////3//////////f////////8LqAACdyv//wuoAAJ3K///C6gAAncr//8LqAACdygAAfaX//74SAAB9pf//vhIAAH2l//++EgAAfaX//74S")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_tqq0o")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_fd1jt"]
+points = PackedVector3Array(-1.40322, 0, 3.38767, 1.49012e-07, 0, 3.66678, 1.19209e-07, 0, 2.84759, -1.08972, 0, 2.63083, -1.40322, 7.71482, 3.38767, 1.49012e-07, 7.71482, 3.66678, 1.19209e-07, 7.71482, 2.84759, -1.08972, 7.71482, 2.63083)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_4fy0e"]
+_surfaces = [{
+"aabb": AABB(1.19209e-07, 0, 2.63083, 1.40322, 7.71482, 1.03595),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("GwAgNAAAAACGrGpAt5yzPwAAAACWz1hA8nuLPwAAAACFXyhAGwAgNAAAAACGrGpA8nuLPwAAAACFXyhA1///MwAAAADqPjZA1///M87f9kDqPjZA8nuLP87f9kCFXyhAt5yzP87f9kCWz1hA1///M87f9kDqPjZAt5yzP87f9kCWz1hAGwAgNM7f9kCGrGpAt5yzPwAAAACWz1hAGwAgNAAAAACGrGpAGwAgNM7f9kCGrGpAt5yzPwAAAACWz1hAGwAgNM7f9kCGrGpAt5yzP87f9kCWz1hA8nuLPwAAAACFXyhAt5yzPwAAAACWz1hAt5yzP87f9kCWz1hA8nuLPwAAAACFXyhAt5yzP87f9kCWz1hA8nuLP87f9kCFXyhA1///MwAAAADqPjZA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhA1///MwAAAADqPjZA8nuLP87f9kCFXyhA1///M87f9kDqPjZAGwAgNAAAAACGrGpA1///MwAAAADqPjZA1///M87f9kDqPjZAGwAgNAAAAACGrGpA1///M87f9kDqPjZAGwAgNM7f9kCGrGpA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_nm5sf"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(1.19209e-07, 0, 2.63083, 1.40322, 7.71482, 1.03595),
+"attribute_data": PackedByteArray("/////xsAILSGrGrAXUFdP2+gmD3/////t5yzv5bPWMBdQV0/BNZZPv/////ye4u/hV8owPr6bj+xBko+/////9f//7PqPjbA7/puPz8+uD3/////1///s+o+NsDv+m4/rBb4Pv/////ye4u/hV8owPr6bj/kIsE+/////7ecs7+Wz1jAXUFdPzo7uT7/////GwAgtIasasBdQV0/If7/Pv////+3nLO/AAAAADkoiD0F1lk+/////xsAILQAAAAAOSiIPW+gmD3/////GwAgtM7f9sBONzs/b6CYPf////+3nLO/zt/2wE43Oz8F1lk+/////4VfKMAAAAAAOSiIPYO/4T7/////ls9YwAAAAAA5KIg9Oju5Pv////+Wz1jAzt/2wE43Oz86O7k+/////4VfKMDO3/bATjc7P4O/4T7/////1///swAAAAA5KIg9w4EyP//////ye4u/AAAAADkoiD3eBxc///////J7i7/O3/bATjc7P94HFz//////1///s87f9sBONzs/w4EyP/////+GrGrAAAAAADkoiD3y62w//////+o+NsAAAAAAOSiIPd6pWD//////6j42wM7f9sBONzs/3qlYP/////+GrGrAzt/2wE43Oz/y62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("GwAgNAAAAACGrGpAt5yzPwAAAACWz1hA8nuLPwAAAACFXyhA1///MwAAAADqPjZA1///M87f9kDqPjZA8nuLP87f9kCFXyhAt5yzP87f9kCWz1hAGwAgNM7f9kCGrGpAt5yzPwAAAACWz1hAGwAgNAAAAACGrGpAGwAgNM7f9kCGrGpAt5yzP87f9kCWz1hA8nuLPwAAAACFXyhAt5yzPwAAAACWz1hAt5yzP87f9kCWz1hA8nuLP87f9kCFXyhA1///MwAAAADqPjZA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhA1///M87f9kDqPjZAGwAgNAAAAACGrGpA1///MwAAAADqPjZA1///M87f9kDqPjZAGwAgNM7f9kCGrGpA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vzuV/388Ff8/O5X/fzwV/z87lf9/PBX/PzuV/388Ff8///99pQAAQO3//32lAABA7f//faUAAEDt//99pQAAQO0AAMLqPBX/vwAAwuo8Ff+/AADC6jwV/78AAMLqPBX/vwAA/38AAAAAAAD/fwAAAAAAAP9/AAAAAAAA/38AAAAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_4fy0e")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_8o5fx"]
+points = PackedVector3Array(1.49012e-07, 0, 3.66678, 1.40322, 0, 3.38767, 1.08972, 0, 2.63083, 1.19209e-07, 0, 2.84759, 1.49012e-07, 7.71482, 3.66678, 1.40322, 7.71482, 3.38767, 1.08972, 7.71482, 2.63083, 1.19209e-07, 7.71482, 2.84759)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_k3w60"]
+_surfaces = [{
+"aabb": AABB(1.08972, 0, 2.01355, 1.50309, 7.71482, 1.37412),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("t5yzPwAAAACWz1hAmfAlQAAAAACZ8CVAAd4AQAAAAAAB3gBAt5yzPwAAAACWz1hAAd4AQAAAAAAB3gBA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhAAd4AQM7f9kAB3gBAmfAlQM7f9kCZ8CVA8nuLP87f9kCFXyhAmfAlQM7f9kCZ8CVAt5yzP87f9kCWz1hAmfAlQAAAAACZ8CVAt5yzPwAAAACWz1hAt5yzP87f9kCWz1hAmfAlQAAAAACZ8CVAt5yzP87f9kCWz1hAmfAlQM7f9kCZ8CVAAd4AQAAAAAAB3gBAmfAlQAAAAACZ8CVAmfAlQM7f9kCZ8CVAAd4AQAAAAAAB3gBAmfAlQM7f9kCZ8CVAAd4AQM7f9kAB3gBA8nuLPwAAAACFXyhAAd4AQAAAAAAB3gBAAd4AQM7f9kAB3gBA8nuLPwAAAACFXyhAAd4AQM7f9kAB3gBA8nuLP87f9kCFXyhAt5yzPwAAAACWz1hA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhAt5yzPwAAAACWz1hA8nuLP87f9kCFXyhAt5yzP87f9kCWz1hA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_lk6hb"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(1.08972, 0, 2.01355, 1.50309, 7.71482, 1.37412),
+"attribute_data": PackedByteArray("/////7ecs7+Wz1jAWUFdP16gmD3/////mfAlwJnwJcBZQV0/6tVZPv////8B3gDAAd4AwPT6bj/kBko+//////J7i7+FXyjA+vpuPzk+uD3/////8nuLv4VfKMD6+m4/jBb4Pv////8B3gDAAd4AwPT6bj+oIsE+/////5nwJcCZ8CXAWUFdPyQ7uT7/////t5yzv5bPWMBZQV0/Af7/Pv////+Z8CXAAAAAADcoiD3q1Vk+/////7ecs78AAAAANyiIPV6gmD3/////t5yzv87f9sBKNzs/XqCYPf////+Z8CXAzt/2wEo3Oz/q1Vk+/////wHeAMAAAAAANyiIPWC/4T7/////mfAlwAAAAAA3KIg9JDu5Pv////+Z8CXAzt/2wEo3Oz8kO7k+/////wHeAMDO3/bASjc7P2C/4T7/////8nuLvwAAAAA3KIg9uYEyP/////8B3gDAAAAAADcoiD3IBxc//////wHeAMDO3/bASjc7P8gHFz//////8nuLv87f9sBKNzs/uYEyP/////+Wz1jAAAAAADcoiD3062w//////4VfKMAAAAAANyiIPdGpWD//////hV8owM7f9sBKNzs/0alYP/////+Wz1jAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("t5yzPwAAAACWz1hAmfAlQAAAAACZ8CVAAd4AQAAAAAAB3gBA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhAAd4AQM7f9kAB3gBAmfAlQM7f9kCZ8CVAt5yzP87f9kCWz1hAmfAlQAAAAACZ8CVAt5yzPwAAAACWz1hAt5yzP87f9kCWz1hAmfAlQM7f9kCZ8CVAAd4AQAAAAAAB3gBAmfAlQAAAAACZ8CVAmfAlQM7f9kCZ8CVAAd4AQM7f9kAB3gBA8nuLPwAAAACFXyhAAd4AQAAAAAAB3gBAAd4AQM7f9kAB3gBA8nuLP87f9kCFXyhAt5yzPwAAAACWz1hA8nuLPwAAAACFXyhA8nuLP87f9kCFXyhAt5yzP87f9kCWz1hA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v0Sz/39EM/8/RLP/f0Qz/z9Es/9/RDP/P0Sz/39EM/8/////vwAA/9////+/AAD/3////78AAP/f////vwAA/98AALrMRDP/vwAAusxEM/+/AAC6zEQz/78AALrMRDP/v30l/38AAL4SfSX/fwAAvhJ9Jf9/AAC+En0l/38AAL4S")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_k3w60")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_hwigh"]
+points = PackedVector3Array(1.40322, 0, 3.38767, 2.59281, 0, 2.59281, 2.01355, 0, 2.01355, 1.08972, 0, 2.63083, 1.40322, 7.71482, 3.38767, 2.59281, 7.71482, 2.59281, 2.01355, 7.71482, 2.01355, 1.08972, 7.71482, 2.63083)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_2crio"]
+_surfaces = [{
+"aabb": AABB(2.01355, 0, 1.08972, 1.37412, 7.71482, 1.50309),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("mfAlQAAAAACZ8CVAls9YQAAAAAC3nLM/hV8oQAAAAADye4s/mfAlQAAAAACZ8CVAhV8oQAAAAADye4s/Ad4AQAAAAAAB3gBAAd4AQM7f9kAB3gBAhV8oQM7f9kDye4s/ls9YQM7f9kC3nLM/Ad4AQM7f9kAB3gBAls9YQM7f9kC3nLM/mfAlQM7f9kCZ8CVAls9YQAAAAAC3nLM/mfAlQAAAAACZ8CVAmfAlQM7f9kCZ8CVAls9YQAAAAAC3nLM/mfAlQM7f9kCZ8CVAls9YQM7f9kC3nLM/hV8oQAAAAADye4s/ls9YQAAAAAC3nLM/ls9YQM7f9kC3nLM/hV8oQAAAAADye4s/ls9YQM7f9kC3nLM/hV8oQM7f9kDye4s/Ad4AQAAAAAAB3gBAhV8oQAAAAADye4s/hV8oQM7f9kDye4s/Ad4AQAAAAAAB3gBAhV8oQM7f9kDye4s/Ad4AQM7f9kAB3gBAmfAlQAAAAACZ8CVAAd4AQAAAAAAB3gBAAd4AQM7f9kAB3gBAmfAlQAAAAACZ8CVAAd4AQM7f9kAB3gBAmfAlQM7f9kCZ8CVA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_g8jdo"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(2.01355, 0, 1.08972, 1.37412, 7.71482, 1.50309),
+"attribute_data": PackedByteArray("/////5nwJcCZ8CXAWUFdP16gmD3/////ls9YwLecs79ZQV0/6tVZPv////+FXyjA8nuLv/r6bj/9Bko+/////wHeAMAB3gDA9PpuP2w+uD3/////Ad4AwAHeAMD0+m4/fhb4Pv////+FXyjA8nuLv/r6bj+aIsE+/////5bPWMC3nLO/WUFdPyQ7uT7/////mfAlwJnwJcBZQV0/Af7/Pv////+3nLO/AAAAADcoiD3q1Vk+/////5nwJcAAAAAANyiIPV6gmD3/////mfAlwM7f9sBKNzs/XqCYPf////+3nLO/zt/2wEo3Oz/q1Vk+/////4VfKMAAAAAANyiIPWq/4T7/////ls9YwAAAAAA3KIg9JDu5Pv////+Wz1jAzt/2wEo3Oz8kO7k+/////4VfKMDO3/bASjc7P2q/4T7/////Ad4AwAAAAAA3KIg9voEyP//////ye4u/AAAAADcoiD3MBxc///////J7i7/O3/bASjc7P8wHFz//////Ad4AwM7f9sBKNzs/voEyP/////+Z8CXAAAAAADcoiD3062w//////wHeAMAAAAAANyiIPdepWD//////Ad4AwM7f9sBKNzs/16lYP/////+Z8CXAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("mfAlQAAAAACZ8CVAls9YQAAAAAC3nLM/hV8oQAAAAADye4s/Ad4AQAAAAAAB3gBAAd4AQM7f9kAB3gBAhV8oQM7f9kDye4s/ls9YQM7f9kC3nLM/mfAlQM7f9kCZ8CVAls9YQAAAAAC3nLM/mfAlQAAAAACZ8CVAmfAlQM7f9kCZ8CVAls9YQM7f9kC3nLM/hV8oQAAAAADye4s/ls9YQAAAAAC3nLM/ls9YQM7f9kC3nLM/hV8oQM7f9kDye4s/Ad4AQAAAAAAB3gBAhV8oQAAAAADye4s/hV8oQM7f9kDye4s/Ad4AQM7f9kAB3gBAmfAlQAAAAACZ8CVAAd4AQAAAAAAB3gBAAd4AQM7f9kAB3gBAmfAlQM7f9kCZ8CVA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v7rM/3///1zmusz/f///XOa6zP9///9c5rrM/3///1zm//+B2gAAvtL//4HaAAC+0v//gdoAAL7S//+B2gAAvtIAAESz//+iGQAARLP//6IZAABEs///ohkAAESz//+iGf8//38AAP8f/z//fwAA/x//P/9/AAD/H/8//38AAP8f")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_2crio")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_rbvqr"]
+points = PackedVector3Array(2.59281, 0, 2.59281, 3.38767, 0, 1.40322, 2.63083, 0, 1.08972, 2.01355, 0, 2.01355, 2.59281, 7.71482, 2.59281, 3.38767, 7.71482, 1.40322, 2.63083, 7.71482, 1.08972, 2.01355, 7.71482, 2.01355)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_d7fjw"]
+_surfaces = [{
+"aabb": AABB(2.63083, 0, -3.57628e-07, 1.03595, 7.71482, 1.40322),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("ls9YQAAAAAC3nLM/hqxqQAAAAAAFAMC06j42QAAAAAAPAIC0ls9YQAAAAAC3nLM/6j42QAAAAAAPAIC0hV8oQAAAAADye4s/hV8oQM7f9kDye4s/6j42QM7f9kAPAIC0hqxqQM7f9kAFAMC0hV8oQM7f9kDye4s/hqxqQM7f9kAFAMC0ls9YQM7f9kC3nLM/hqxqQAAAAAAFAMC0ls9YQAAAAAC3nLM/ls9YQM7f9kC3nLM/hqxqQAAAAAAFAMC0ls9YQM7f9kC3nLM/hqxqQM7f9kAFAMC06j42QAAAAAAPAIC0hqxqQAAAAAAFAMC0hqxqQM7f9kAFAMC06j42QAAAAAAPAIC0hqxqQM7f9kAFAMC06j42QM7f9kAPAIC0hV8oQAAAAADye4s/6j42QAAAAAAPAIC06j42QM7f9kAPAIC0hV8oQAAAAADye4s/6j42QM7f9kAPAIC0hV8oQM7f9kDye4s/ls9YQAAAAAC3nLM/hV8oQAAAAADye4s/hV8oQM7f9kDye4s/ls9YQAAAAAC3nLM/hV8oQM7f9kDye4s/ls9YQM7f9kC3nLM/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_httos"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(2.63083, 0, -3.57628e-07, 1.03595, 7.71482, 1.40322),
+"attribute_data": PackedByteArray("/////5bPWMC3nLO/XUFdP26gmD3/////hqxqwAUAwDRdQV0/B9ZZPv/////qPjbADwCANO/6bj8fB0o+/////4VfKMDye4u/+vpuPxc/uD3/////hV8owPJ7i7/6+m4/eRb4Pv/////qPjbADwCANO/6bj+vIsE+/////4asasAFAMA0XUFdPzs7uT7/////ls9YwLecs79dQV0/I/7/Pv////8FAMA0AAAAADkoiD0H1lk+/////7ecs78AAAAAOSiIPW6gmD3/////t5yzv87f9sBONzs/bqCYPf////8FAMA0zt/2wE43Oz8H1lk+/////+o+NsAAAAAAOSiIPWO/4T7/////hqxqwAAAAAA5KIg9Ozu5Pv////+GrGrAzt/2wE43Oz87O7k+/////+o+NsDO3/bATjc7P2O/4T7/////8nuLvwAAAAA5KIg9soEyP/////8PAIA0AAAAADkoiD3OBxc//////w8AgDTO3/bATjc7P84HFz//////8nuLv87f9sBONzs/soEyP/////+Wz1jAAAAAADkoiD3z62w//////4VfKMAAAAAAOSiIPc6pWD//////hV8owM7f9sBONzs/zqlYP/////+Wz1jAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("ls9YQAAAAAC3nLM/hqxqQAAAAAAFAMC06j42QAAAAAAPAIC0hV8oQAAAAADye4s/hV8oQM7f9kDye4s/6j42QM7f9kAPAIC0hqxqQM7f9kAFAMC0ls9YQM7f9kC3nLM/hqxqQAAAAAAFAMC0ls9YQAAAAAC3nLM/ls9YQM7f9kC3nLM/hqxqQM7f9kAFAMC06j42QAAAAAAPAIC0hqxqQAAAAAAFAMC0hqxqQM7f9kAFAMC06j42QM7f9kAPAIC0hV8oQAAAAADye4s/6j42QAAAAAAPAIC06j42QM7f9kAPAIC0hV8oQM7f9kDye4s/ls9YQAAAAAC3nLM/hV8oQAAAAADye4s/hV8oQM7f9kDye4s/ls9YQM7f9kC3nLM//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v8Lq/3///2D1wur/f///YPXC6v9///9g9cLq/3///2D1AAD+/wAA/78AAP7/AAD/vwAA/v8AAP+/AAD+/wAA/78AADyV//+eCgAAPJX//54KAAA8lf//ngoAADyV//+eCoFa/38AAEAtgVr/fwAAQC2BWv9/AABALYFa/38AAEAt")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_d7fjw")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_no1sb"]
+points = PackedVector3Array(3.38767, 0, 1.40322, 3.66678, 0, -3.57628e-07, 2.84759, 0, -2.38419e-07, 2.63083, 0, 1.08972, 3.38767, 7.71482, 1.40322, 3.66678, 7.71482, -3.57628e-07, 2.84759, 7.71482, -2.38419e-07, 2.63083, 7.71482, 1.08972)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_r2ew6"]
+_surfaces = [{
+"aabb": AABB(2.63083, 0, -1.40322, 1.03595, 7.71482, 1.40322),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("hqxqQAAAAAAFAMC0ls9YQAAAAAC3nLO/hV8oQAAAAADye4u/hqxqQAAAAAAFAMC0hV8oQAAAAADye4u/6j42QAAAAAAPAIC06j42QM7f9kAPAIC0hV8oQM7f9kDye4u/ls9YQM7f9kC3nLO/6j42QM7f9kAPAIC0ls9YQM7f9kC3nLO/hqxqQM7f9kAFAMC0ls9YQAAAAAC3nLO/hqxqQAAAAAAFAMC0hqxqQM7f9kAFAMC0ls9YQAAAAAC3nLO/hqxqQM7f9kAFAMC0ls9YQM7f9kC3nLO/hV8oQAAAAADye4u/ls9YQAAAAAC3nLO/ls9YQM7f9kC3nLO/hV8oQAAAAADye4u/ls9YQM7f9kC3nLO/hV8oQM7f9kDye4u/6j42QAAAAAAPAIC0hV8oQAAAAADye4u/hV8oQM7f9kDye4u/6j42QAAAAAAPAIC0hV8oQM7f9kDye4u/6j42QM7f9kAPAIC0hqxqQAAAAAAFAMC06j42QAAAAAAPAIC06j42QM7f9kAPAIC0hqxqQAAAAAAFAMC06j42QM7f9kAPAIC0hqxqQM7f9kAFAMC0")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_xudhy"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(2.63083, 0, -1.40322, 1.03595, 7.71482, 1.40322),
+"attribute_data": PackedByteArray("/////4asasAFAMA0XUFdP2+gmD3/////ls9YwLecsz9dQV0/BNZZPv////+FXyjA8nuLP/r6bj+vBko+/////+o+NsAPAIA07/puPz4+uD3/////6j42wA8AgDTv+m4/rBb4Pv////+FXyjA8nuLP/r6bj/jIsE+/////5bPWMC3nLM/XUFdPzk7uT7/////hqxqwAUAwDRdQV0/H/7/Pv////+3nLM/AAAAADkoiD0E1lk+/////wUAwDQAAAAAOSiIPW+gmD3/////BQDANM7f9sBONzs/b6CYPf////+3nLM/zt/2wE43Oz8E1lk+/////4VfKMAAAAAAOSiIPYO/4T7/////ls9YwAAAAAA5KIg9OTu5Pv////+Wz1jAzt/2wE43Oz85O7k+/////4VfKMDO3/bATjc7P4O/4T7/////DwCANAAAAAA5KIg9wYEyP//////ye4s/AAAAADkoiD3dBxc///////J7iz/O3/bATjc7P90HFz//////DwCANM7f9sBONzs/wYEyP/////+GrGrAAAAAADkoiD3y62w//////+o+NsAAAAAAOSiIPd6pWD//////6j42wM7f9sBONzs/3qlYP/////+GrGrAzt/2wE43Oz/y62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("hqxqQAAAAAAFAMC0ls9YQAAAAAC3nLO/hV8oQAAAAADye4u/6j42QAAAAAAPAIC06j42QM7f9kAPAIC0hV8oQM7f9kDye4u/ls9YQM7f9kC3nLO/hqxqQM7f9kAFAMC0ls9YQAAAAAC3nLO/hqxqQAAAAAAFAMC0hqxqQM7f9kAFAMC0ls9YQM7f9kC3nLO/hV8oQAAAAADye4u/ls9YQAAAAAC3nLO/ls9YQM7f9kC3nLO/hV8oQM7f9kDye4u/6j42QAAAAAAPAIC0hV8oQAAAAADye4u/hV8oQM7f9kDye4u/6j42QM7f9kAPAIC0hqxqQAAAAAAFAMC06j42QAAAAAAPAIC06j42QM7f9kAPAIC0hqxqQM7f9kAFAMC0/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v///O5UAAGD1//87lQAAYPX//zuVAABg9f//O5UAAGD1AACB2n0l/78AAIHafSX/vwAAgdp9Jf+/AACB2n0l/788Ff9/AACeCjwV/38AAJ4KPBX/fwAAngo8Ff9/AACeCv9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_r2ew6")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_aiw4m"]
+points = PackedVector3Array(3.66678, 0, -3.57628e-07, 3.38767, 0, -1.40322, 2.63083, 0, -1.08972, 2.84759, 0, -2.38419e-07, 3.66678, 7.71482, -3.57628e-07, 3.38767, 7.71482, -1.40322, 2.63083, 7.71482, -1.08972, 2.84759, 7.71482, -2.38419e-07)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_hhjc6"]
+_surfaces = [{
+"aabb": AABB(-2.98023e-08, 0, -3.66678, 1.40322, 7.71482, 1.03595),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("t5yzPwAAAACWz1jA8///sgAAAACGrGrA8///sgAAAADqPjbAt5yzPwAAAACWz1jA8///sgAAAADqPjbA8nuLPwAAAACFXyjA8nuLP87f9kCFXyjA8///ss7f9kDqPjbA8///ss7f9kCGrGrA8nuLP87f9kCFXyjA8///ss7f9kCGrGrAt5yzP87f9kCWz1jA8///sgAAAACGrGrAt5yzPwAAAACWz1jAt5yzP87f9kCWz1jA8///sgAAAACGrGrAt5yzP87f9kCWz1jA8///ss7f9kCGrGrA8///sgAAAADqPjbA8///sgAAAACGrGrA8///ss7f9kCGrGrA8///sgAAAADqPjbA8///ss7f9kCGrGrA8///ss7f9kDqPjbA8nuLPwAAAACFXyjA8///sgAAAADqPjbA8///ss7f9kDqPjbA8nuLPwAAAACFXyjA8///ss7f9kDqPjbA8nuLP87f9kCFXyjAt5yzPwAAAACWz1jA8nuLPwAAAACFXyjA8nuLP87f9kCFXyjAt5yzPwAAAACWz1jA8nuLP87f9kCFXyjAt5yzP87f9kCWz1jA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_fos3l"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-2.98023e-08, 0, -3.66678, 1.40322, 7.71482, 1.03595),
+"attribute_data": PackedByteArray("/////7ecs7+Wz1hAXUFdP2+gmD3/////8///MoasakBdQV0/BdZZPv/////z//8y6j42QO/6bj8dB0o+//////J7i7+FXyhA+vpuPxc/uD3/////8nuLv4VfKED6+m4/dxb4Pv/////z//8y6j42QO/6bj+vIsE+//////P//zKGrGpAXUFdPzs7uT7/////t5yzv5bPWEBdQV0/If7/Pv/////z//8yAAAAADkoiD0H1lk+/////7ecs78AAAAAOSiIPW+gmD3/////t5yzv87f9sBONzs/b6CYPf/////z//8yzt/2wE43Oz8H1lk+/////+o+NkAAAAAAOSiIPWS/4T7/////hqxqQAAAAAA5KIg9Ozu5Pv////+GrGpAzt/2wE43Oz87O7k+/////+o+NkDO3/bATjc7P2S/4T7/////8nuLvwAAAAA5KIg9soEyP//////z//8yAAAAADkoiD3OBxc///////P//zLO3/bATjc7P84HFz//////8nuLv87f9sBONzs/soEyP/////+Wz1hAAAAAADkoiD3z62w//////4VfKEAAAAAAOSiIPc+pWD//////hV8oQM7f9sBONzs/z6lYP/////+Wz1hAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("t5yzPwAAAACWz1jA8///sgAAAACGrGrA8///sgAAAADqPjbA8nuLPwAAAACFXyjA8nuLP87f9kCFXyjA8///ss7f9kDqPjbA8///ss7f9kCGrGrAt5yzP87f9kCWz1jA8///sgAAAACGrGrAt5yzPwAAAACWz1jAt5yzP87f9kCWz1jA8///ss7f9kCGrGrA8///sgAAAADqPjbA8///sgAAAACGrGrA8///ss7f9kCGrGrA8///ss7f9kDqPjbA8nuLPwAAAACFXyjA8///sgAAAADqPjbA8///ss7f9kDqPjbA8nuLP87f9kCFXyjAt5yzPwAAAACWz1jA8nuLPwAAAACFXyjA8nuLP87f9kCFXyjAt5yzP87f9kCWz1jA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v///wuoAAJ3K///C6gAAncr//8LqAACdyv//wuoAAJ3KAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AADCav9/AABhNcJq/38AAGE1wmr/fwAAYTXCav9/AABhNYHa/3///0Dtgdr/f///QO2B2v9///9A7YHa/3///0Dt")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_hhjc6")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_6ci4b"]
+points = PackedVector3Array(1.40322, 0, -3.38767, -2.98023e-08, 0, -3.66678, -2.98023e-08, 0, -2.84759, 1.08972, 0, -2.63083, 1.40322, 7.71482, -3.38767, -2.98023e-08, 7.71482, -3.66678, -2.98023e-08, 7.71482, -2.84759, 1.08972, 7.71482, -2.63083)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_2ql3f"]
+_surfaces = [{
+"aabb": AABB(-1.40322, 0, -3.66678, 1.40322, 7.71482, 1.03595),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("8///sgAAAACGrGrAt5yzvwAAAACWz1jA8nuLvwAAAACFXyjA8///sgAAAACGrGrA8nuLvwAAAACFXyjA8///sgAAAADqPjbA8///ss7f9kDqPjbA8nuLv87f9kCFXyjAt5yzv87f9kCWz1jA8///ss7f9kDqPjbAt5yzv87f9kCWz1jA8///ss7f9kCGrGrAt5yzvwAAAACWz1jA8///sgAAAACGrGrA8///ss7f9kCGrGrAt5yzvwAAAACWz1jA8///ss7f9kCGrGrAt5yzv87f9kCWz1jA8nuLvwAAAACFXyjAt5yzvwAAAACWz1jAt5yzv87f9kCWz1jA8nuLvwAAAACFXyjAt5yzv87f9kCWz1jA8nuLv87f9kCFXyjA8///sgAAAADqPjbA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjA8///sgAAAADqPjbA8nuLv87f9kCFXyjA8///ss7f9kDqPjbA8///sgAAAACGrGrA8///sgAAAADqPjbA8///ss7f9kDqPjbA8///sgAAAACGrGrA8///ss7f9kDqPjbA8///ss7f9kCGrGrA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_r2ato"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-1.40322, 0, -3.66678, 1.40322, 7.71482, 1.03595),
+"attribute_data": PackedByteArray("//////P//zKGrGpAXUFdP2+gmD3/////t5yzP5bPWEBdQV0/BdZZPv/////ye4s/hV8oQPr6bj+xBko+//////P//zLqPjZA7/puPz8+uD3/////8///Muo+NkDv+m4/rhb4Pv/////ye4s/hV8oQPr6bj/lIsE+/////7ecsz+Wz1hAXUFdPzs7uT7/////8///MoasakBdQV0/If7/Pv////+3nLM/AAAAADkoiD0H1lk+//////P//zIAAAAAOSiIPW+gmD3/////8///Ms7f9sBONzs/b6CYPf////+3nLM/zt/2wE43Oz8H1lk+/////4VfKEAAAAAAOSiIPYW/4T7/////ls9YQAAAAAA5KIg9Ozu5Pv////+Wz1hAzt/2wE43Oz87O7k+/////4VfKEDO3/bATjc7P4W/4T7/////8///MgAAAAA5KIg9w4EyP//////ye4s/AAAAADkoiD3eBxc///////J7iz/O3/bATjc7P94HFz//////8///Ms7f9sBONzs/w4EyP/////+GrGpAAAAAADkoiD3z62w//////+o+NkAAAAAAOSiIPd6pWD//////6j42QM7f9sBONzs/3qlYP/////+GrGpAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("8///sgAAAACGrGrAt5yzvwAAAACWz1jA8nuLvwAAAACFXyjA8///sgAAAADqPjbA8///ss7f9kDqPjbA8nuLv87f9kCFXyjAt5yzv87f9kCWz1jA8///ss7f9kCGrGrAt5yzvwAAAACWz1jA8///sgAAAACGrGrA8///ss7f9kCGrGrAt5yzv87f9kCWz1jA8nuLvwAAAACFXyjAt5yzvwAAAACWz1jAt5yzv87f9kCWz1jA8nuLv87f9kCFXyjA8///sgAAAADqPjbA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjA8///ss7f9kDqPjbA8///sgAAAACGrGrA8///sgAAAADqPjbA8///ss7f9kDqPjbA8///ss7f9kCGrGrA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAAwuo8Ff+/AADC6jwV/78AAMLqPBX/vwAAwuo8Ff+/fSX/fwAAvhJ9Jf9/AAC+En0l/38AAL4SfSX/fwAAvhI8lf9/PBX/PzyV/388Ff8/PJX/fzwV/z88lf9/PBX/P////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_2ql3f")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_uhnxj"]
+points = PackedVector3Array(-2.98023e-08, 0, -3.66678, -1.40322, 0, -3.38767, -1.08972, 0, -2.63083, -2.98023e-08, 0, -2.84759, -2.98023e-08, 7.71482, -3.66678, -1.40322, 7.71482, -3.38767, -1.08972, 7.71482, -2.63083, -2.98023e-08, 7.71482, -2.84759)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_qcvii"]
+_surfaces = [{
+"aabb": AABB(-2.59281, 0, -3.38767, 1.50309, 7.71482, 1.37412),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("t5yzvwAAAACWz1jAmfAlwAAAAACZ8CXAAd4AwAAAAAAB3gDAt5yzvwAAAACWz1jAAd4AwAAAAAAB3gDA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjAAd4AwM7f9kAB3gDAmfAlwM7f9kCZ8CXA8nuLv87f9kCFXyjAmfAlwM7f9kCZ8CXAt5yzv87f9kCWz1jAmfAlwAAAAACZ8CXAt5yzvwAAAACWz1jAt5yzv87f9kCWz1jAmfAlwAAAAACZ8CXAt5yzv87f9kCWz1jAmfAlwM7f9kCZ8CXAAd4AwAAAAAAB3gDAmfAlwAAAAACZ8CXAmfAlwM7f9kCZ8CXAAd4AwAAAAAAB3gDAmfAlwM7f9kCZ8CXAAd4AwM7f9kAB3gDA8nuLvwAAAACFXyjAAd4AwAAAAAAB3gDAAd4AwM7f9kAB3gDA8nuLvwAAAACFXyjAAd4AwM7f9kAB3gDA8nuLv87f9kCFXyjAt5yzvwAAAACWz1jA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjAt5yzvwAAAACWz1jA8nuLv87f9kCFXyjAt5yzv87f9kCWz1jA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_7m1w5"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-2.59281, 0, -3.38767, 1.50309, 7.71482, 1.37412),
+"attribute_data": PackedByteArray("/////7ecsz+Wz1hAWUFdP16gmD3/////mfAlQJnwJUBZQV0/6tVZPv////8B3gBAAd4AQPT6bj/kBko+//////J7iz+FXyhA+vpuPzk+uD3/////8nuLP4VfKED6+m4/jBb4Pv////8B3gBAAd4AQPT6bj+oIsE+/////5nwJUCZ8CVAWUFdPyQ7uT7/////t5yzP5bPWEBZQV0/Af7/Pv////+Z8CVAAAAAADcoiD3q1Vk+/////7ecsz8AAAAANyiIPV6gmD3/////t5yzP87f9sBKNzs/XqCYPf////+Z8CVAzt/2wEo3Oz/q1Vk+/////wHeAEAAAAAANyiIPWC/4T7/////mfAlQAAAAAA3KIg9JDu5Pv////+Z8CVAzt/2wEo3Oz8kO7k+/////wHeAEDO3/bASjc7P2C/4T7/////8nuLPwAAAAA3KIg9uYEyP/////8B3gBAAAAAADcoiD3IBxc//////wHeAEDO3/bASjc7P8gHFz//////8nuLP87f9sBKNzs/uYEyP/////+Wz1hAAAAAADcoiD3062w//////4VfKEAAAAAANyiIPdGpWD//////hV8oQM7f9sBKNzs/0alYP/////+Wz1hAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("t5yzvwAAAACWz1jAmfAlwAAAAACZ8CXAAd4AwAAAAAAB3gDA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjAAd4AwM7f9kAB3gDAmfAlwM7f9kCZ8CXAt5yzv87f9kCWz1jAmfAlwAAAAACZ8CXAt5yzvwAAAACWz1jAt5yzv87f9kCWz1jAmfAlwM7f9kCZ8CXAAd4AwAAAAAAB3gDAmfAlwAAAAACZ8CXAmfAlwM7f9kCZ8CXAAd4AwM7f9kAB3gDA8nuLvwAAAACFXyjAAd4AwAAAAAAB3gDAAd4AwM7f9kAB3gDA8nuLv87f9kCFXyjAt5yzvwAAAACWz1jA8nuLvwAAAACFXyjA8nuLv87f9kCFXyjAt5yzv87f9kCWz1jA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAAusxEM/+/AAC6zEQz/78AALrMRDP/vwAAusxEM/+//z//fwAA/x//P/9/AAD/H/8//38AAP8f/z//fwAA/x9Es/9/RDP/P0Sz/39EM/8/RLP/f0Qz/z9Es/9/RDP/P///faUAAEDt//99pQAAQO3//32lAABA7f//faUAAEDt")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_qcvii")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_6nufb"]
+points = PackedVector3Array(-1.40322, 0, -3.38767, -2.59281, 0, -2.59281, -2.01355, 0, -2.01355, -1.08972, 0, -2.63083, -1.40322, 7.71482, -3.38767, -2.59281, 7.71482, -2.59281, -2.01355, 7.71482, -2.01355, -1.08972, 7.71482, -2.63083)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_mnuiy"]
+_surfaces = [{
+"aabb": AABB(-3.38767, 0, -2.59281, 1.37412, 7.71482, 1.50309),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("mfAlwAAAAACZ8CXAls9YwAAAAAC3nLO/hV8owAAAAADye4u/mfAlwAAAAACZ8CXAhV8owAAAAADye4u/Ad4AwAAAAAAB3gDAAd4AwM7f9kAB3gDAhV8owM7f9kDye4u/ls9YwM7f9kC3nLO/Ad4AwM7f9kAB3gDAls9YwM7f9kC3nLO/mfAlwM7f9kCZ8CXAls9YwAAAAAC3nLO/mfAlwAAAAACZ8CXAmfAlwM7f9kCZ8CXAls9YwAAAAAC3nLO/mfAlwM7f9kCZ8CXAls9YwM7f9kC3nLO/hV8owAAAAADye4u/ls9YwAAAAAC3nLO/ls9YwM7f9kC3nLO/hV8owAAAAADye4u/ls9YwM7f9kC3nLO/hV8owM7f9kDye4u/Ad4AwAAAAAAB3gDAhV8owAAAAADye4u/hV8owM7f9kDye4u/Ad4AwAAAAAAB3gDAhV8owM7f9kDye4u/Ad4AwM7f9kAB3gDAmfAlwAAAAACZ8CXAAd4AwAAAAAAB3gDAAd4AwM7f9kAB3gDAmfAlwAAAAACZ8CXAAd4AwM7f9kAB3gDAmfAlwM7f9kCZ8CXA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_725yh"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-3.38767, 0, -2.59281, 1.37412, 7.71482, 1.50309),
+"attribute_data": PackedByteArray("/////5nwJUCZ8CVAWUFdP16gmD3/////ls9YQLecsz9ZQV0/6tVZPv////+FXyhA8nuLP/r6bj/9Bko+/////wHeAEAB3gBA9PpuP2w+uD3/////Ad4AQAHeAED0+m4/fhb4Pv////+FXyhA8nuLP/r6bj+aIsE+/////5bPWEC3nLM/WUFdPyQ7uT7/////mfAlQJnwJUBZQV0/Af7/Pv////+3nLM/AAAAADcoiD3q1Vk+/////5nwJUAAAAAANyiIPV6gmD3/////mfAlQM7f9sBKNzs/XqCYPf////+3nLM/zt/2wEo3Oz/q1Vk+/////4VfKEAAAAAANyiIPWq/4T7/////ls9YQAAAAAA3KIg9JDu5Pv////+Wz1hAzt/2wEo3Oz8kO7k+/////4VfKEDO3/bASjc7P2q/4T7/////Ad4AQAAAAAA3KIg9voEyP//////ye4s/AAAAADcoiD3MBxc///////J7iz/O3/bASjc7P8wHFz//////Ad4AQM7f9sBKNzs/voEyP/////+Z8CVAAAAAADcoiD3062w//////wHeAEAAAAAANyiIPdepWD//////Ad4AQM7f9sBKNzs/16lYP/////+Z8CVAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("mfAlwAAAAACZ8CXAls9YwAAAAAC3nLO/hV8owAAAAADye4u/Ad4AwAAAAAAB3gDAAd4AwM7f9kAB3gDAhV8owM7f9kDye4u/ls9YwM7f9kC3nLO/mfAlwM7f9kCZ8CXAls9YwAAAAAC3nLO/mfAlwAAAAACZ8CXAmfAlwM7f9kCZ8CXAls9YwM7f9kC3nLO/hV8owAAAAADye4u/ls9YwAAAAAC3nLO/ls9YwM7f9kC3nLO/hV8owM7f9kDye4u/Ad4AwAAAAAAB3gDAhV8owAAAAADye4u/hV8owM7f9kDye4u/Ad4AwM7f9kAB3gDAmfAlwAAAAACZ8CXAAd4AwAAAAAAB3gDAAd4AwM7f9kAB3gDAmfAlwM7f9kCZ8CXA/38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAARLP//6IZAABEs///ohkAAESz//+iGQAARLP//6IZgVr/fwAAQC2BWv9/AABALYFa/38AAEAtgVr/fwAAQC26zP9///9c5rrM/3///1zmusz/f///XOa6zP9///9c5v///78AAP/f////vwAA/9////+/AAD/3////78AAP/f")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_mnuiy")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_op77g"]
+points = PackedVector3Array(-2.59281, 0, -2.59281, -3.38767, 0, -1.40322, -2.63083, 0, -1.08972, -2.01355, 0, -2.01355, -2.59281, 7.71482, -2.59281, -3.38767, 7.71482, -1.40322, -2.63083, 7.71482, -1.08972, -2.01355, 7.71482, -2.01355)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_v7rld"]
+_surfaces = [{
+"aabb": AABB(-3.66678, 0, -1.40322, 1.03595, 7.71482, 1.40322),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("ls9YwAAAAAC3nLO/hqxqwAAAAAAAAAAA6j42wAAAAAAAAAAAls9YwAAAAAC3nLO/6j42wAAAAAAAAAAAhV8owAAAAADye4u/hV8owM7f9kDye4u/6j42wM7f9kAAAAAAhqxqwM7f9kAAAAAAhV8owM7f9kDye4u/hqxqwM7f9kAAAAAAls9YwM7f9kC3nLO/hqxqwAAAAAAAAAAAls9YwAAAAAC3nLO/ls9YwM7f9kC3nLO/hqxqwAAAAAAAAAAAls9YwM7f9kC3nLO/hqxqwM7f9kAAAAAA6j42wAAAAAAAAAAAhqxqwAAAAAAAAAAAhqxqwM7f9kAAAAAA6j42wAAAAAAAAAAAhqxqwM7f9kAAAAAA6j42wM7f9kAAAAAAhV8owAAAAADye4u/6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhV8owAAAAADye4u/6j42wM7f9kAAAAAAhV8owM7f9kDye4u/ls9YwAAAAAC3nLO/hV8owAAAAADye4u/hV8owM7f9kDye4u/ls9YwAAAAAC3nLO/hV8owM7f9kDye4u/ls9YwM7f9kC3nLO/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_wfq43"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-3.66678, 0, -1.40322, 1.03595, 7.71482, 1.40322),
+"attribute_data": PackedByteArray("/////5bPWEC3nLM/XUFdP2+gmD3/////hqxqQAAAAABdQV0/BdZZPv/////qPjZAAAAAAO/6bj8dB0o+/////4VfKEDye4s/+vpuPxc/uD3/////hV8oQPJ7iz/6+m4/dxb4Pv/////qPjZAAAAAAO/6bj+vIsE+/////4asakAAAAAAXUFdPzs7uT7/////ls9YQLecsz9dQV0/If7/Pv////8AAAAAAAAAADkoiD0H1lk+/////7ecsz8AAAAAOSiIPW+gmD3/////t5yzP87f9sBONzs/b6CYPf////8AAAAAzt/2wE43Oz8H1lk+/////+o+NkAAAAAAOSiIPWS/4T7/////hqxqQAAAAAA5KIg9Ozu5Pv////+GrGpAzt/2wE43Oz87O7k+/////+o+NkDO3/bATjc7P2S/4T7/////8nuLPwAAAAA5KIg9soEyP/////8AAAAAAAAAADkoiD3OBxc//////wAAAADO3/bATjc7P84HFz//////8nuLP87f9sBONzs/soEyP/////+Wz1hAAAAAADkoiD3z62w//////4VfKEAAAAAAOSiIPc+pWD//////hV8oQM7f9sBONzs/z6lYP/////+Wz1hAzt/2wE43Oz/z62w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("ls9YwAAAAAC3nLO/hqxqwAAAAAAAAAAA6j42wAAAAAAAAAAAhV8owAAAAADye4u/hV8owM7f9kDye4u/6j42wM7f9kAAAAAAhqxqwM7f9kAAAAAAls9YwM7f9kC3nLO/hqxqwAAAAAAAAAAAls9YwAAAAAC3nLO/ls9YwM7f9kC3nLO/hqxqwM7f9kAAAAAA6j42wAAAAAAAAAAAhqxqwAAAAAAAAAAAhqxqwM7f9kAAAAAA6j42wM7f9kAAAAAAhV8owAAAAADye4u/6j42wAAAAAAAAAAA6j42wM7f9kAAAAAAhV8owM7f9kDye4u/ls9YwAAAAAC3nLO/hV8owAAAAADye4u/hV8owM7f9kDye4u/ls9YwM7f9kC3nLO//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAAO5X//54KAAA7lf//ngoAADuV//+eCgAAO5X//54K/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z/C6v9///9g9cLq/3///2D1wur/f///YPXC6v9///9g9f//gdoAAL7S//+B2gAAvtL//4HaAAC+0v//gdoAAL7S")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_v7rld")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_80hi8"]
+points = PackedVector3Array(-3.38767, 0, -1.40322, -3.66678, 0, 0, -2.84759, 0, 0, -2.63083, 0, -1.08972, -3.38767, 7.71482, -1.40322, -3.66678, 7.71482, 0, -2.84759, 7.71482, 0, -2.63083, 7.71482, -1.08972)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_cj1it"]
+_surfaces = [{
+"aabb": AABB(2.01355, 0, -2.59281, 1.37412, 7.71482, 1.50309),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("ls9YQAAAAAC3nLO/mfAlQAAAAACZ8CXAAd4AQAAAAAAB3gDAls9YQAAAAAC3nLO/Ad4AQAAAAAAB3gDAhV8oQAAAAADye4u/hV8oQM7f9kDye4u/Ad4AQM7f9kAB3gDAmfAlQM7f9kCZ8CXAhV8oQM7f9kDye4u/mfAlQM7f9kCZ8CXAls9YQM7f9kC3nLO/mfAlQAAAAACZ8CXAls9YQAAAAAC3nLO/ls9YQM7f9kC3nLO/mfAlQAAAAACZ8CXAls9YQM7f9kC3nLO/mfAlQM7f9kCZ8CXAAd4AQAAAAAAB3gDAmfAlQAAAAACZ8CXAmfAlQM7f9kCZ8CXAAd4AQAAAAAAB3gDAmfAlQM7f9kCZ8CXAAd4AQM7f9kAB3gDAhV8oQAAAAADye4u/Ad4AQAAAAAAB3gDAAd4AQM7f9kAB3gDAhV8oQAAAAADye4u/Ad4AQM7f9kAB3gDAhV8oQM7f9kDye4u/ls9YQAAAAAC3nLO/hV8oQAAAAADye4u/hV8oQM7f9kDye4u/ls9YQAAAAAC3nLO/hV8oQM7f9kDye4u/ls9YQM7f9kC3nLO/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_1ttv3"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(2.01355, 0, -2.59281, 1.37412, 7.71482, 1.50309),
+"attribute_data": PackedByteArray("/////5bPWMC3nLM/WUFdP16gmD3/////mfAlwJnwJUBZQV0/6tVZPv////8B3gDAAd4AQPT6bj/kBko+/////4VfKMDye4s/+vpuPzk+uD3/////hV8owPJ7iz/6+m4/jBb4Pv////8B3gDAAd4AQPT6bj+oIsE+/////5nwJcCZ8CVAWUFdPyQ7uT7/////ls9YwLecsz9ZQV0/Af7/Pv////+Z8CVAAAAAADcoiD3q1Vk+/////7ecsz8AAAAANyiIPV6gmD3/////t5yzP87f9sBKNzs/XqCYPf////+Z8CVAzt/2wEo3Oz/q1Vk+/////wHeAMAAAAAANyiIPWC/4T7/////mfAlwAAAAAA3KIg9JDu5Pv////+Z8CXAzt/2wEo3Oz8kO7k+/////wHeAMDO3/bASjc7P2C/4T7/////8nuLPwAAAAA3KIg9uYEyP/////8B3gBAAAAAADcoiD3IBxc//////wHeAEDO3/bASjc7P8gHFz//////8nuLP87f9sBKNzs/uYEyP/////+Wz1jAAAAAADcoiD3062w//////4VfKMAAAAAANyiIPdGpWD//////hV8owM7f9sBKNzs/0alYP/////+Wz1jAzt/2wEo3Oz/062w/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("ls9YQAAAAAC3nLO/mfAlQAAAAACZ8CXAAd4AQAAAAAAB3gDAhV8oQAAAAADye4u/hV8oQM7f9kDye4u/Ad4AQM7f9kAB3gDAmfAlQM7f9kCZ8CXAls9YQM7f9kC3nLO/mfAlQAAAAACZ8CXAls9YQAAAAAC3nLO/ls9YQM7f9kC3nLO/mfAlQM7f9kCZ8CXAAd4AQAAAAAAB3gDAmfAlQAAAAACZ8CXAmfAlQM7f9kCZ8CXAAd4AQM7f9kAB3gDAhV8oQAAAAADye4u/Ad4AQAAAAAAB3gDAAd4AQM7f9kAB3gDAhV8oQM7f9kDye4u/ls9YQAAAAAC3nLO/hV8oQAAAAADye4u/hV8oQM7f9kDye4u/ls9YQM7f9kC3nLO//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v///RLMAAFzm//9EswAAXOb//0SzAABc5v//RLMAAFzmAAD/v/8//78AAP+//z//vwAA/7//P/+/AAD/v/8//79EM/9/AACiGUQz/38AAKIZRDP/fwAAohlEM/9/AACiGX2l/399Jf8/faX/f30l/z99pf9/fSX/P32l/399Jf8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_cj1it")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_8mb13"]
+points = PackedVector3Array(3.38767, 0, -1.40322, 2.59281, 0, -2.59281, 2.01355, 0, -2.01355, 2.63083, 0, -1.08972, 3.38767, 7.71482, -1.40322, 2.59281, 7.71482, -2.59281, 2.01355, 7.71482, -2.01355, 2.63083, 7.71482, -1.08972)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_y7a5v"]
+_surfaces = [{
+"aabb": AABB(1.08972, 1.63529, -3.38767, 1.50309, 6.07953, 1.37412),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("mfAlQC9R0T+Z8CXAt5yzPy9R0T+Wz1jA8nuLPy9R0T+FXyjAmfAlQC9R0T+Z8CXA8nuLPy9R0T+FXyjAAd4AQC9R0T8B3gDAAd4AQM7f9kAB3gDA8nuLP87f9kCFXyjAt5yzP87f9kCWz1jAAd4AQM7f9kAB3gDAt5yzP87f9kCWz1jAmfAlQM7f9kCZ8CXAt5yzPy9R0T+Wz1jAmfAlQC9R0T+Z8CXAmfAlQM7f9kCZ8CXAt5yzPy9R0T+Wz1jAmfAlQM7f9kCZ8CXAt5yzP87f9kCWz1jA8nuLPy9R0T+FXyjAt5yzPy9R0T+Wz1jAt5yzP87f9kCWz1jA8nuLPy9R0T+FXyjAt5yzP87f9kCWz1jA8nuLP87f9kCFXyjAAd4AQC9R0T8B3gDA8nuLPy9R0T+FXyjA8nuLP87f9kCFXyjAAd4AQC9R0T8B3gDA8nuLP87f9kCFXyjAAd4AQM7f9kAB3gDAmfAlQC9R0T+Z8CXAAd4AQC9R0T8B3gDAAd4AQM7f9kAB3gDAmfAlQC9R0T+Z8CXAAd4AQM7f9kAB3gDAmfAlQM7f9kCZ8CXA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_t4xdn"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(1.08972, 1.63529, -3.38767, 1.50309, 6.07953, 1.37412),
+"attribute_data": PackedByteArray("/////7vgacBAiMY/vTVZPxYopj7/////zUEwwJ9tKkC9NVk/Wyf3Pv/////hug7A1R4CQKFKbz8vG+4+/////7vgacBAiMY/vTVZPxYopj7/////4boOwNUeAkChSm8/LxvuPv////9pejvAjraVP5pKbz9ONK8+/////2l6O8COtpU/mkpvP0X4QT//////4boOwNUeAkChSm8/1IQiP//////NQTDAn20qQL01WT+//h0//////2l6O8COtpU/mkpvP0X4QT//////zUEwwJ9tKkC9NVk/v/4dP/////+74GnAQIjGP701WT9ifkY//////97o378vUdG/66qFPVkn9z7/////wKk4wC9R0b/rqoU9FiimPv/////AqTjAzt/2wAPLNz8WKKY+/////97o37/O3/bAA8s3P1kn9z7/////AM4WQC9R0b/rqoU9FS81P/////8mEEVAL1HRv+uqhT2+/h0//////yYQRUDO3/bAA8s3P77+HT//////AM4WQM7f9sADyzc/FS81P/////8qQhXAL1HRv+uqhT3lo0I+/////02Wub8vUdG/66qFPUasiT3/////TZa5v87f9sAFyzc/RqyJPf////8qQhXAzt/2wAXLNz/lo0I+/////8CpOMAvUdG/66qFPXfKbj//////KkIVwC9R0b/rqoU9J5pXP/////8qQhXAzt/2wAPLNz8nmlc//////8CpOMDO3/bAA8s3P3fKbj8="),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAGAAZABoAGAAaABsA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 28,
+"vertex_data": PackedByteArray("mfAlQC9R0T+Z8CXAt5yzPy9R0T+Wz1jA8nuLPy9R0T+FXyjAmfAlQC9R0T+Z8CXA8nuLPy9R0T+FXyjAAd4AQC9R0T8B3gDAAd4AQM7f9kAB3gDA8nuLP87f9kCFXyjAt5yzP87f9kCWz1jAAd4AQM7f9kAB3gDAt5yzP87f9kCWz1jAmfAlQM7f9kCZ8CXAt5yzPy9R0T+Wz1jAmfAlQC9R0T+Z8CXAmfAlQM7f9kCZ8CXAt5yzP87f9kCWz1jA8nuLPy9R0T+FXyjAt5yzPy9R0T+Wz1jAt5yzP87f9kCWz1jA8nuLP87f9kCFXyjAAd4AQC9R0T8B3gDA8nuLPy9R0T+FXyjA8nuLP87f9kCFXyjAAd4AQM7f9kAB3gDAmfAlQC9R0T+Z8CXAAd4AQC9R0T8B3gDAAd4AQM7f9kAB3gDAmfAlQM7f9kCZ8CXA/38AAFQe/z//fwAAVB7/P/9/AABUHv8//38AAFQe/z//fwAAVB7/P/9/AABUHv8//3///1Qe/7//f///VB7/v/9///9UHv+//3///1Qe/7//f///VB7/v/9///9UHv+///+6zAAAodn//7rMAACh2f//uswAAKHZ//+6zAAAodkAAH2l//++EgAAfaX//74SAAB9pf//vhIAAH2l//++ErpM/38AAF0mukz/fwAAXSa6TP9/AABdJrpM/38AAF0m/7//f/8//z//v/9//z//P/+//3//P/8//7//f/8//z8=")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_y7a5v")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_klscc"]
+points = PackedVector3Array(2.59281, 1.63529, -2.59281, 1.40322, 1.63529, -3.38767, 1.08972, 1.63529, -2.63083, 2.01355, 1.63529, -2.01355, 2.59281, 7.71482, -2.59281, 1.40322, 7.71482, -3.38767, 1.08972, 7.71482, -2.63083, 2.01355, 7.71482, -2.01355)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_q5qj6"]
+_surfaces = [{
+"aabb": AABB(-14.1802, -0.409317, -11.1277, 14.5668, 0.236179, 19.3821),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("GeJiwf+R0b4GEgRBGeJiwf+R0b4PCzLBGeJiwRZLMb4PCzLBGeJiwf+R0b4GEgRBGeJiwRZLMb4PCzLBGeJiwRZLMb4GEgRB2evFPhZLMb4GEgRBGeJiwRZLMb4GEgRBGeJiwRZLMb4PCzLB2evFPhZLMb4GEgRBGeJiwRZLMb4PCzLB2evFPhZLMb4PCzLB2evFPv+R0b4PCzLB2evFPhZLMb4PCzLBGeJiwRZLMb4PCzLB2evFPv+R0b4PCzLBGeJiwRZLMb4PCzLBGeJiwf+R0b4PCzLBGeJiwf+R0b4GEgRBGeJiwRZLMb4GEgRB2evFPhZLMb4GEgRBGeJiwf+R0b4GEgRB2evFPhZLMb4GEgRB2evFPv+R0b4GEgRB2evFPv+R0b4PCzLB2evFPv+R0b4GEgRB2evFPhZLMb4GEgRB2evFPv+R0b4PCzLB2evFPhZLMb4GEgRB2evFPhZLMb4PCzLBGeJiwf+R0b4GEgRB2evFPv+R0b4GEgRB2evFPv+R0b4PCzLBGeJiwf+R0b4GEgRB2evFPv+R0b4PCzLBGeJiwf+R0b4PCzLB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_ep5bw"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-14.1802, -0.409317, -11.1277, 14.5668, 0.236179, 19.3821),
+"attribute_data": PackedByteArray("/////wYSBMH/kdE+q18+PUD1gD3/////DwsyQf+R0T7F4QI/QPWAPf////8PCzJBFksxPsXhAj8NrJA9/////wYSBMEWSzE+q18+PQ2skD3/////2evFvgYSBMGrXz49BHkrP/////8Z4mJBBhIEwatfPj1HS0k+/////xniYkEPCzJBxeECP0dLST7/////2evFvg8LMkHF4QI/BHkrP//////Z68W+/5HRPqtfPj1X4W8//////9nrxb4WSzE+q18+PX3qbT//////GeJiQRZLMT6JpMo+feptP/////8Z4mJB/5HRPomkyj5X4W8//////xniYkH/kdE+uq0aP2GFUz//////GeJiQRZLMT66rRo/h45RP//////Z68W+FksxPgUadD+HjlE//////9nrxb7/kdE+BRp0P2GFUz//////DwsyQf+R0T6rXz49VLZLP/////8GEgTB/5HRPsXhAj9Utks//////wYSBMEWSzE+xeECPy6tTT//////DwsyQRZLMT6rXz49Lq1NP/////8Z4mJBBhIEwQUadD9A9YA9/////9nrxb4GEgTBuq0aP0D1gD3/////2evFvg8LMkG6rRo/OFExP/////8Z4mJBDwsyQQUadD84UTE/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("GeJiwf+R0b4GEgRBGeJiwf+R0b4PCzLBGeJiwRZLMb4PCzLBGeJiwRZLMb4GEgRB2evFPhZLMb4GEgRBGeJiwRZLMb4GEgRBGeJiwRZLMb4PCzLB2evFPhZLMb4PCzLB2evFPv+R0b4PCzLB2evFPhZLMb4PCzLBGeJiwRZLMb4PCzLBGeJiwf+R0b4PCzLBGeJiwf+R0b4GEgRBGeJiwRZLMb4GEgRB2evFPhZLMb4GEgRB2evFPv+R0b4GEgRB2evFPv+R0b4PCzLB2evFPv+R0b4GEgRB2evFPhZLMb4GEgRB2evFPhZLMb4PCzLBGeJiwf+R0b4GEgRB2evFPv+R0b4GEgRB2evFPv+R0b4PCzLBGeJiwf+R0b4PCzLBAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD/f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z////9//////////3//////////f/////////9///////9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_q5qj6")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_bydka"]
+points = PackedVector3Array(-14.1802, -0.409317, 8.2544, -14.1802, -0.409317, -11.1277, -14.1802, -0.173138, -11.1277, -14.1802, -0.173138, 8.2544, 0.386565, -0.173138, 8.2544, 0.386565, -0.173138, -11.1277, 0.386565, -0.409317, -11.1277, 0.386565, -0.409317, 8.2544)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_1txin"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 0.288497, 1.11611, 22.4934),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAALHcjj8AAAAAAAAAALHcjj988rNBAAAAAAAAAAB88rNBAAAAALHcjj8AAAAAAAAAAAAAAAB88rNBAAAAAAAAAAAAAAAA4bWTPgAAAAAAAAAA4bWTPgAAAAB88rNB4bWTPrHcjj988rNB4bWTPgAAAAAAAAAA4bWTPrHcjj988rNB4bWTPrHcjj8AAAAAAAAAALHcjj988rNBAAAAALHcjj8AAAAA4bWTPrHcjj8AAAAAAAAAALHcjj988rNB4bWTPrHcjj8AAAAA4bWTPrHcjj988rNBAAAAAAAAAAB88rNBAAAAALHcjj988rNB4bWTPrHcjj988rNBAAAAAAAAAAB88rNB4bWTPrHcjj988rNB4bWTPgAAAAB88rNBAAAAAAAAAAAAAAAAAAAAAAAAAAB88rNB4bWTPgAAAAB88rNBAAAAAAAAAAAAAAAA4bWTPgAAAAB88rNB4bWTPgAAAAAAAAAAAAAAALHcjj8AAAAAAAAAAAAAAAAAAAAA4bWTPgAAAAAAAAAAAAAAALHcjj8AAAAA4bWTPgAAAAAAAAAA4bWTPrHcjj8AAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_6igbr"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 0.288497, 1.11611, 22.4934),
+"attribute_data": PackedByteArray("/////wAAAACx3I6/rKqqPXwCtD3/////fPKzwbHcjr+sqmo/fAK0Pf////988rPBAAAAAKyqaj8tqgY+/////wAAAAAAAAAArKqqPS2qBj7/////AAAAAAAAAACsqqo9VVadPv////988rPBAAAAAKyqaj9VVp0+/////3zys8Gx3I6/rKpqP8yqsz7/////AAAAALHcjr+sqqo9zKqzPv////8AAAAAfPKzwayqqj0F1gY//////wAAAAAAAAAArKpqPwXWBj//////4bWTvgAAAACsqmo/1LgJP//////htZO+fPKzwayqqj3UuAk//////wAAAAAAAAAArKqqPeOcZj//////AAAAALHcjr+9Wf8945xmP//////htZO+sdyOv71Z/z2yf2k//////+G1k74AAAAArKqqPbJ/aT//////AAAAAAAAAACsqqo9dLk2P/////8AAAAAfPKzwayqaj90uTY//////+G1k7588rPBrKpqP0OcOT//////4bWTvgAAAACsqqo9Q5w5P/////8AAAAAsdyOv8UrlT7jnGY//////wAAAAAAAAAAiVeqPuOcZj//////4bWTvgAAAACJV6o+sn9pP//////htZO+sdyOv8UrlT6yf2k/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAALHcjj8AAAAAAAAAALHcjj988rNBAAAAAAAAAAB88rNBAAAAAAAAAAAAAAAA4bWTPgAAAAAAAAAA4bWTPgAAAAB88rNB4bWTPrHcjj988rNB4bWTPrHcjj8AAAAAAAAAALHcjj988rNBAAAAALHcjj8AAAAA4bWTPrHcjj8AAAAA4bWTPrHcjj988rNBAAAAAAAAAAB88rNBAAAAALHcjj988rNB4bWTPrHcjj988rNB4bWTPgAAAAB88rNBAAAAAAAAAAAAAAAAAAAAAAAAAAB88rNB4bWTPgAAAAB88rNB4bWTPgAAAAAAAAAAAAAAALHcjj8AAAAAAAAAAAAAAAAAAAAA4bWTPgAAAAAAAAAA4bWTPrHcjj8AAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_1txin")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_7spni"]
+points = PackedVector3Array(0, 1.11611, 0, 0, 1.11611, 22.4934, 0, 0, 22.4934, 0, 0, 0, 0.288497, 1.11611, 0, 0.288497, 1.11611, 22.4934, 0.288497, 0, 22.4934, 0.288497, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_g8dwp"]
+_surfaces = [{
+"aabb": AABB(0, 0, -11.0531, 0.735325, 4.39206, 19.4607),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAAMGLjEB/2TDBAAAAAMGLjECShQZBAAAAAAAAAACShQZBAAAAAMGLjEB/2TDBAAAAAAAAAACShQZBAAAAAAAAAAB/2TDBQj48PwAAAAB/2TDBQj48PwAAAACShQZBQj48P8GLjECShQZBQj48PwAAAAB/2TDBQj48P8GLjECShQZBQj48P8GLjEB/2TDBAAAAAMGLjECShQZBAAAAAMGLjEB/2TDBQj48P8GLjEB/2TDBAAAAAMGLjECShQZBQj48P8GLjEB/2TDBQj48P8GLjECShQZBAAAAAAAAAACShQZBAAAAAMGLjECShQZBQj48P8GLjECShQZBAAAAAAAAAACShQZBQj48P8GLjECShQZBQj48PwAAAACShQZBAAAAAAAAAAB/2TDBAAAAAAAAAACShQZBQj48PwAAAACShQZBAAAAAAAAAAB/2TDBQj48PwAAAACShQZBQj48PwAAAAB/2TDBAAAAAMGLjEB/2TDBAAAAAAAAAAB/2TDBQj48PwAAAAB/2TDBAAAAAMGLjEB/2TDBQj48PwAAAAB/2TDBQj48P8GLjEB/2TDB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_iins8"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, -11.0531, 0.735325, 4.39206, 19.4607),
+"attribute_data": PackedByteArray("/////3/ZMEHBi4zA+3COPddWmj3/////koUGwcGLjMBZ20M/11aaPf////+ShQbBAAAAAFnbQz8lVXs+/////3/ZMEEAAAAA+3COPSVVez7/////f9kwQQAAAAD7cI49/dXKPv////+ShQbBAAAAAFnbQz/91co+/////5KFBsHBi4zAWdtDP231ED//////f9kwQcGLjMD7cI49bfUQP/////8AAAAAkoUGwftwjj0jizc//////wAAAAB/2TBBWdtDPyOLNz//////Qj48v3/ZMEFZ20M/StU+P/////9CPjy/koUGwftwjj1K1T4//////wAAAAAAAAAAl3dnP9dWmj3/////AAAAAMGLjMCXd2c/JVV7Pv////9CPjy/wYuMwOIxbj8lVXs+/////0I+PL8AAAAA4jFuP9dWmj3/////AAAAAH/ZMEH7cI49/2plP/////8AAAAAkoUGwVnbQz//amU//////0I+PL+ShQbBWdtDPya1bD//////Qj48v3/ZMEH7cI49JrVsP/////8AAAAAwYuMwJd3Zz/91co+/////wAAAAAAAAAAl3dnP231ED//////Qj48vwAAAADiMW4/bfUQP/////9CPjy/wYuMwOIxbj/91co+"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAAMGLjEB/2TDBAAAAAMGLjECShQZBAAAAAAAAAACShQZBAAAAAAAAAAB/2TDBQj48PwAAAAB/2TDBQj48PwAAAACShQZBQj48P8GLjECShQZBQj48P8GLjEB/2TDBAAAAAMGLjECShQZBAAAAAMGLjEB/2TDBQj48P8GLjEB/2TDBQj48P8GLjECShQZBAAAAAAAAAACShQZBAAAAAMGLjECShQZBQj48P8GLjECShQZBQj48PwAAAACShQZBAAAAAAAAAAB/2TDBAAAAAAAAAACShQZBQj48PwAAAACShQZBQj48PwAAAAB/2TDBAAAAAMGLjEB/2TDBAAAAAAAAAAB/2TDBQj48PwAAAAB/2TDBQj48P8GLjEB/2TDBAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_g8dwp")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_ddg1r"]
+points = PackedVector3Array(0, 4.39206, -11.0531, 0, 4.39206, 8.40761, 0, 0, 8.40761, 0, 0, -11.0531, 0.735325, 4.39206, -11.0531, 0.735325, 4.39206, 8.40761, 0.735325, 0, 8.40761, 0.735325, 0, -11.0531)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_kexc8"]
+_surfaces = [{
+"aabb": AABB(0, 0, 5.99567, 13.1595, 0.538043, 11.0512),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAADC9CT+H3L9AAAAAADC9CT8NYIhBAAAAAAAAAAANYIhBAAAAADC9CT+H3L9AAAAAAAAAAAANYIhBAAAAAAAAAACH3L9AUI1SQQAAAACH3L9AUI1SQQAAAAANYIhBUI1SQTC9CT8NYIhBUI1SQQAAAACH3L9AUI1SQTC9CT8NYIhBUI1SQTC9CT+H3L9AAAAAADC9CT8NYIhBAAAAADC9CT+H3L9AUI1SQTC9CT+H3L9AAAAAADC9CT8NYIhBUI1SQTC9CT+H3L9AUI1SQTC9CT8NYIhBAAAAAAAAAAANYIhBAAAAADC9CT8NYIhBUI1SQTC9CT8NYIhBAAAAAAAAAAANYIhBUI1SQTC9CT8NYIhBUI1SQQAAAAANYIhBAAAAAAAAAACH3L9AAAAAAAAAAAANYIhBUI1SQQAAAAANYIhBAAAAAAAAAACH3L9AUI1SQQAAAAANYIhBUI1SQQAAAACH3L9AAAAAADC9CT+H3L9AAAAAAAAAAACH3L9AUI1SQQAAAACH3L9AAAAAADC9CT+H3L9AUI1SQQAAAACH3L9AUI1SQTC9CT+H3L9A")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_osqfm"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 5.99567, 13.1595, 0.538043, 11.0512),
+"attribute_data": PackedByteArray("/////4fcv8AwvQm/1ZUUP/zTPD//////DWCIwTC9Cb8DknQ//NM8P/////8NYIjBAAAAAAOSdD9UxEI//////4fcv8AAAAAA1ZUUP1TEQj//////h9y/wAAAAADM3zY9NIlrP/////8NYIjBAAAAAFTU1j40iWs//////w1giMEwvQm/VNTWPot5cT//////h9y/wDC9Cb/M3zY9i3lxP/////8AAAAADWCIwczfNj3CgQg//////wAAAACH3L/AzN82PV9naD3/////UI1SwYfcv8C4c/s+X2doPf////9QjVLBDWCIwbhz+z7CgQg//////wAAAAAAAAAAzN82PQV/Kz//////AAAAADC9Cb/M3zY9ro4lP/////9QjVLBML0Jv7hz+z6ujiU//////1CNUsEAAAAAuHP7PgV/Kz//////AAAAAIfcv8ADknQ/X2doPf////8AAAAADWCIwdWVFD9fZ2g9/////1CNUsENYIjB1ZUUPxHHHz//////UI1SwYfcv8ADknQ/EccfP/////8AAAAAML0Jv8zfNj1IfE4//////wAAAAAAAAAAzN82PfGLSD//////UI1SwQAAAAC4c/s+8YtIP/////9QjVLBML0Jv7hz+z5IfE4/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAADC9CT+H3L9AAAAAADC9CT8NYIhBAAAAAAAAAAANYIhBAAAAAAAAAACH3L9AUI1SQQAAAACH3L9AUI1SQQAAAAANYIhBUI1SQTC9CT8NYIhBUI1SQTC9CT+H3L9AAAAAADC9CT8NYIhBAAAAADC9CT+H3L9AUI1SQTC9CT+H3L9AUI1SQTC9CT8NYIhBAAAAAAAAAAANYIhBAAAAADC9CT8NYIhBUI1SQTC9CT8NYIhBUI1SQQAAAAANYIhBAAAAAAAAAACH3L9AAAAAAAAAAAANYIhBUI1SQQAAAAANYIhBUI1SQQAAAACH3L9AAAAAADC9CT+H3L9AAAAAAAAAAACH3L9AUI1SQQAAAACH3L9AUI1SQTC9CT+H3L9AAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_kexc8")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_3uau3"]
+points = PackedVector3Array(0, 0.538043, 5.99567, 0, 0.538043, 17.0469, 0, 0, 17.0469, 0, 0, 5.99567, 13.1595, 0.538043, 5.99567, 13.1595, 0.538043, 17.0469, 13.1595, 0, 17.0469, 13.1595, 0, 5.99567)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_m4bco"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0.990232, 0.375159, 2.57081, 8.07633),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAACeIJEDYf30/AAAAACeIJEChEBFBAAAAAAAAAAChEBFBAAAAACeIJEDYf30/AAAAAAAAAAChEBFBAAAAAAAAAADYf30/1xTAPgAAAADYf30/1xTAPgAAAAChEBFB1xTAPieIJEChEBFB1xTAPgAAAADYf30/1xTAPieIJEChEBFB1xTAPieIJEDYf30/AAAAACeIJEChEBFBAAAAACeIJEDYf30/1xTAPieIJEDYf30/AAAAACeIJEChEBFB1xTAPieIJEDYf30/1xTAPieIJEChEBFBAAAAAAAAAAChEBFBAAAAACeIJEChEBFB1xTAPieIJEChEBFBAAAAAAAAAAChEBFB1xTAPieIJEChEBFB1xTAPgAAAAChEBFBAAAAAAAAAADYf30/AAAAAAAAAAChEBFB1xTAPgAAAAChEBFBAAAAAAAAAADYf30/1xTAPgAAAAChEBFB1xTAPgAAAADYf30/AAAAACeIJEDYf30/AAAAAAAAAADYf30/1xTAPgAAAADYf30/AAAAACeIJEDYf30/1xTAPgAAAADYf30/1xTAPieIJEDYf30/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_p3b6g"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0.990232, 0.375159, 2.57081, 8.07633),
+"attribute_data": PackedByteArray("/////9h/fb8niCTAfpaNPa3lhT3/////oRARwSeIJMDtrkI/reWFPf////+hEBHBAAAAAO2uQj8ZB4w+/////9h/fb8AAAAAfpaNPRkHjD7/////2H99vwAAAAB+lo098fnOPv////+hEBHBAAAAAO2uQj/x+c4+/////6EQEcEniCTA7a5CP8/DHD//////2H99vyeIJMB+lo09z8McP/////8AAAAAoRARwX6WjT06PT4//////wAAAADYf32/7a5CPzo9Pj//////1xTAvth/fb/trkI/jANGP//////XFMC+oRARwX6WjT2MA0Y//////wAAAAAAAAAAfpaNPfd8Zz//////AAAAACeIJMAOEpQ+93xnP//////XFMC+J4gkwA4SlD5JQ28//////9cUwL4AAAAAfpaNPUlDbz//////AAAAANh/fb+MFGY/reWFPf////8AAAAAoRARwYwUZj/OGzg//////9cUwL6hEBHBME1uP84bOD//////1xTAvth/fb8wTW4/reWFPf////8AAAAAJ4gkwE7d2j73fGc//////wAAAAAAAAAA3sQlP/d8Zz//////1xTAvgAAAADexCU/SUNvP//////XFMC+J4gkwE7d2j5JQ28/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAACeIJEDYf30/AAAAACeIJEChEBFBAAAAAAAAAAChEBFBAAAAAAAAAADYf30/1xTAPgAAAADYf30/1xTAPgAAAAChEBFB1xTAPieIJEChEBFB1xTAPieIJEDYf30/AAAAACeIJEChEBFBAAAAACeIJEDYf30/1xTAPieIJEDYf30/1xTAPieIJEChEBFBAAAAAAAAAAChEBFBAAAAACeIJEChEBFB1xTAPieIJEChEBFB1xTAPgAAAAChEBFBAAAAAAAAAADYf30/AAAAAAAAAAChEBFB1xTAPgAAAAChEBFB1xTAPgAAAADYf30/AAAAACeIJEDYf30/AAAAAAAAAADYf30/1xTAPgAAAADYf30/1xTAPieIJEDYf30/AAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_m4bco")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_1cxuh"]
+points = PackedVector3Array(0, 2.57081, 0.990232, 0, 2.57081, 9.06656, 0, 0, 9.06656, 0, 0, 0.990232, 0.375159, 2.57081, 0.990232, 0.375159, 2.57081, 9.06656, 0.375159, 0, 9.06656, 0.375159, 0, 0.990232)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_ksk5k"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 13.1089, 2.8592, 0.342241),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("AAAAACL9NkAAAAAAAAAAACL9NkA2Oq8+AAAAAAAAAAA2Oq8+AAAAACL9NkAAAAAAAAAAAAAAAAA2Oq8+AAAAAAAAAAAAAAAADr5RQQAAAAAAAAAADr5RQQAAAAA2Oq8+Dr5RQSL9NkA2Oq8+Dr5RQQAAAAAAAAAADr5RQSL9NkA2Oq8+Dr5RQSL9NkAAAAAAAAAAACL9NkA2Oq8+AAAAACL9NkAAAAAADr5RQSL9NkAAAAAAAAAAACL9NkA2Oq8+Dr5RQSL9NkAAAAAADr5RQSL9NkA2Oq8+AAAAAAAAAAA2Oq8+AAAAACL9NkA2Oq8+Dr5RQSL9NkA2Oq8+AAAAAAAAAAA2Oq8+Dr5RQSL9NkA2Oq8+Dr5RQQAAAAA2Oq8+AAAAAAAAAAAAAAAAAAAAAAAAAAA2Oq8+Dr5RQQAAAAA2Oq8+AAAAAAAAAAAAAAAADr5RQQAAAAA2Oq8+Dr5RQQAAAAAAAAAAAAAAACL9NkAAAAAAAAAAAAAAAAAAAAAADr5RQQAAAAAAAAAAAAAAACL9NkAAAAAADr5RQQAAAAAAAAAADr5RQSL9NkAAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_okoqc"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 13.1089, 2.8592, 0.342241),
+"attribute_data": PackedByteArray("/////wAAAAAi/TbAjwxuP8Pznj3/////NjqvviL9NsDMXGk/w/OePf////82Oq++AAAAAMxcaT+O0nw+/////wAAAAAAAAAAjwxuP47SfD7/////AAAAAAAAAACPDG4/ed4TP/////82Oq++AAAAAMxcaT953hM//////zY6r74i/TbAzFxpP6Q0Pz//////AAAAACL9NsCPDG4/pDQ/P/////8AAAAANjqvvpGbjz1gc8g9/////wAAAAAAAAAAkZuPPcPznj3/////Dr5RwQAAAADodUU/w/OePf////8OvlHBNjqvvuh1RT9gc8g9/////wAAAAAAAAAAkZuPPQ9D2D7/////AAAAACL9NsCRm489uZaBPv////8OvlHBIv02wOh1RT+5loE+/////w6+UcEAAAAA6HVFPw9D2D7/////AAAAAAAAAACRm489bA4ZP/////8AAAAANjqvvpGbjz153hM//////w6+UcE2Oq++6HVFP3neEz//////Dr5RwQAAAADodUU/bA4ZP/////8AAAAAIv02wJGbjz2IIWw//////wAAAAAAAAAAkZuPPV3LQD//////Dr5RwQAAAADodUU/XctAP/////8OvlHBIv02wOh1RT+IIWw/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("AAAAACL9NkAAAAAAAAAAACL9NkA2Oq8+AAAAAAAAAAA2Oq8+AAAAAAAAAAAAAAAADr5RQQAAAAAAAAAADr5RQQAAAAA2Oq8+Dr5RQSL9NkA2Oq8+Dr5RQSL9NkAAAAAAAAAAACL9NkA2Oq8+AAAAACL9NkAAAAAADr5RQSL9NkAAAAAADr5RQSL9NkA2Oq8+AAAAAAAAAAA2Oq8+AAAAACL9NkA2Oq8+Dr5RQSL9NkA2Oq8+Dr5RQQAAAAA2Oq8+AAAAAAAAAAAAAAAAAAAAAAAAAAA2Oq8+Dr5RQQAAAAA2Oq8+Dr5RQQAAAAAAAAAAAAAAACL9NkAAAAAAAAAAAAAAAAAAAAAADr5RQQAAAAAAAAAADr5RQSL9NkAAAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/////8AAP+//////wAA/7//////AAD/v/////8AAP+/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_ksk5k")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_8uxgq"]
+points = PackedVector3Array(0, 2.8592, 0, 0, 2.8592, 0.342241, 0, 0, 0.342241, 0, 0, 0, 13.1089, 2.8592, 0, 13.1089, 2.8592, 0.342241, 13.1089, 0, 0.342241, 13.1089, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_87bex"]
+_surfaces = [{
+"aabb": AABB(-12.683, 0, -5.88, 13.068, 4.388, 6.604),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("ke1KwX9qjED2KLzAke1KwX9qjED0/aTAke1KwQAAAAAQWDk/ke1KwX9qjED2KLzAke1KwQAAAAAQWDk/ke1KwQAAAAAAAAAAuB7FPgAAAAAAAAAAuB7FPgAAAAAQWDk/uB7FPn9qjED0/aTAuB7FPgAAAAAAAAAAuB7FPn9qjED0/aTAuB7FPn9qjED2KLzAke1KwX9qjED0/aTAke1KwX9qjED2KLzAuB7FPn9qjED2KLzAke1KwX9qjED0/aTAuB7FPn9qjED2KLzAuB7FPn9qjED0/aTAke1KwQAAAAAQWDk/ke1KwX9qjED0/aTAuB7FPn9qjED0/aTAke1KwQAAAAAQWDk/uB7FPn9qjED0/aTAuB7FPgAAAAAQWDk/ke1KwQAAAAAAAAAAke1KwQAAAAAQWDk/uB7FPgAAAAAQWDk/ke1KwQAAAAAAAAAAuB7FPgAAAAAQWDk/uB7FPgAAAAAAAAAAke1KwX9qjED2KLzAke1KwQAAAAAAAAAAuB7FPgAAAAAAAAAAke1KwX9qjED2KLzAuB7FPgAAAAAAAAAAuB7FPn9qjED2KLzA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_mtake"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-12.683, 0, -5.88, 13.068, 4.388, 6.604),
+"attribute_data": PackedByteArray("//////YovEB/aozAsKJOP3fqJT//////9P2kQH9qjMDzTUo/L5UrP/////8QWDm/AAAAAPNNSj+8PHM//////wAAAAAAAAAAsKJOPwSSbT//////AAAAAAAAAADHBHA/L5UrP/////8QWDm/AAAAAAqwaz936iU///////T9pEB/aozACrBrPwSSbT//////9ii8QH9qjMDHBHA/vDxzP/////+R7UpB9P2kQMmLUT9FNEw9/////5HtSkH2KLxA801KP0U0TD3/////uB7FvvYovEDzTUo/7mMMP/////+4HsW+9P2kQMmLUT/uYww//////5HtSkEQWDm/AiJRPS5XzD7/////ke1KQfT9pEACIlE9RTRMPf////+4HsW+9P2kQLMpMD9FNEw9/////7gexb4QWDm/sykwPy5XzD7/////ke1KQQAAAADg7XI/RTRMPf////+R7UpBEFg5vwqwaz9FNEw9/////7gexb4QWDm/CrBrP+5jDD//////uB7FvgAAAADg7XI/7mMMP/////+R7UpB9ii8QAIiUT1zGlk//////5HtSkEAAAAAAiJRPT9k/z7/////uB7FvgAAAACzKTA/P2T/Pv////+4HsW+9ii8QLMpMD9zGlk/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("ke1KwX9qjED2KLzAke1KwX9qjED0/aTAke1KwQAAAAAQWDk/ke1KwQAAAAAAAAAAuB7FPgAAAAAAAAAAuB7FPgAAAAAQWDk/uB7FPn9qjED0/aTAuB7FPn9qjED2KLzAke1KwX9qjED0/aTAke1KwX9qjED2KLzAuB7FPn9qjED2KLzAuB7FPn9qjED0/aTAke1KwQAAAAAQWDk/ke1KwX9qjED0/aTAuB7FPn9qjED0/aTAuB7FPgAAAAAQWDk/ke1KwQAAAAAAAAAAke1KwQAAAAAQWDk/uB7FPgAAAAAQWDk/uB7FPgAAAAAAAAAAke1KwX9qjED2KLzAke1KwQAAAAAAAAAAuB7FPgAAAAAAAAAAuB7FPn9qjED2KLzAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD///9//////////3//////////f/////////9///////9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//39LyQAA/7//f0vJAAD/v/9/S8kAAP+//39LyQAA/7//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P7K2AAAAAP8/srYAAAAA/z+ytgAAAAD/P7K2AAAAAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_87bex")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_v88mn"]
+points = PackedVector3Array(-12.683, 4.388, -5.88, -12.683, 4.388, -5.156, -12.683, 0, 0.724, -12.683, 0, 0, 0.385, 4.388, -5.88, 0.385, 4.388, -5.156, 0.385, 0, 0.724, 0.385, 0, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_udjax"]
+_surfaces = [{
+"aabb": AABB(0, 2.20955, 0, 0.234361, 0.48858, 11.665),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("VPxvPimuLEDXozpBVPxvPkRpDUDXozpBAAAAAERpDUDXozpBVPxvPimuLEDXozpBAAAAAERpDUDXozpBAAAAACmuLEDXozpBAAAAAERpDUAAAAAAAAAAACmuLEAAAAAAAAAAACmuLEDXozpBAAAAAERpDUAAAAAAAAAAACmuLEDXozpBAAAAAERpDUDXozpBVPxvPkRpDUAAAAAAAAAAAERpDUAAAAAAAAAAAERpDUDXozpBVPxvPkRpDUAAAAAAAAAAAERpDUDXozpBVPxvPkRpDUDXozpBVPxvPimuLEAAAAAAVPxvPimuLEDXozpBAAAAACmuLEDXozpBVPxvPimuLEAAAAAAAAAAACmuLEDXozpBAAAAACmuLEAAAAAAVPxvPimuLEAAAAAAAAAAACmuLEAAAAAAAAAAAERpDUAAAAAAVPxvPimuLEAAAAAAAAAAAERpDUAAAAAAVPxvPkRpDUAAAAAAVPxvPimuLEAAAAAAVPxvPkRpDUAAAAAAVPxvPkRpDUDXozpBVPxvPimuLEAAAAAAVPxvPkRpDUDXozpBVPxvPimuLEDXozpB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_j2qb0"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 2.20955, 0, 0.234361, 0.48858, 11.665),
+"attribute_data": PackedByteArray("/////1T8b74prizAq6qqPaMgZT//////VPxvvkRpDcAwJvI9oyBlP/////8AAAAARGkNwDAm8j2Kn2k//////wAAAAAprizAq6qqPYqfaT//////AAAAAERpDcCrqqo9Uf79Pf////8AAAAAKa4swKuqqj23A7M9/////9ejOsEprizArKpqP7cDsz3/////16M6wURpDcCsqmo/Uf79Pf////9U/G++AAAAAKuqqj0+/6E+/////wAAAAAAAAAAq6qqPXABmT7/////AAAAANejOsGsqmo/cAGZPv////9U/G++16M6wayqaj8+/6E+/////1T8b74AAAAAq6qqPRqB+z7/////VPxvvtejOsGsqmo/GoH7Pv////8AAAAA16M6wayqaj90PwI//////wAAAAAAAAAAq6qqPXQ/Aj//////VPxvvimuLMDi3pE+ip9pP/////8AAAAAKa4swOLekT6jIGU//////wAAAABEaQ3Awr2jPqMgZT//////VPxvvkRpDcDCvaM+ip9pP/////8AAAAAKa4swKuqqj21Xzg//////wAAAABEaQ3Aq6qqPWIALz//////16M6wURpDcCsqmo/YgAvP//////XozrBKa4swKyqaj+1Xzg/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("VPxvPimuLEDXozpBVPxvPkRpDUDXozpBAAAAAERpDUDXozpBAAAAACmuLEDXozpBAAAAAERpDUAAAAAAAAAAACmuLEAAAAAAAAAAACmuLEDXozpBAAAAAERpDUDXozpBVPxvPkRpDUAAAAAAAAAAAERpDUAAAAAAAAAAAERpDUDXozpBVPxvPkRpDUDXozpBVPxvPimuLEAAAAAAVPxvPimuLEDXozpBAAAAACmuLEDXozpBAAAAACmuLEAAAAAAVPxvPimuLEAAAAAAAAAAACmuLEAAAAAAAAAAAERpDUAAAAAAVPxvPkRpDUAAAAAAVPxvPimuLEAAAAAAVPxvPkRpDUAAAAAAVPxvPkRpDUDXozpBVPxvPimuLEDXozpB/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_udjax")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_vfaw8"]
+points = PackedVector3Array(0.234361, 2.69813, 11.665, 0.234361, 2.20955, 11.665, 0, 2.20955, 11.665, 0, 2.69813, 11.665, 0, 2.20955, 0, 0, 2.69813, 0, 0.234361, 2.20955, 0, 0.234361, 2.69813, 0)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_27ejl"]
+_surfaces = [{
+"aabb": AABB(0, 0, 8.61629, 0.234361, 2.20998, 3.04871),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("VPxvPlBwDUDXozpBVPxvPgAAAADXozpBAAAAAAAAAADXozpBVPxvPlBwDUDXozpBAAAAAAAAAADXozpBAAAAAFBwDUDXozpBAAAAAAAAAABT3AlBAAAAAFBwDUBT3AlBAAAAAFBwDUDXozpBAAAAAAAAAABT3AlBAAAAAFBwDUDXozpBAAAAAAAAAADXozpBVPxvPgAAAABT3AlBAAAAAAAAAABT3AlBAAAAAAAAAADXozpBVPxvPgAAAABT3AlBAAAAAAAAAADXozpBVPxvPgAAAADXozpBVPxvPlBwDUBT3AlBVPxvPlBwDUDXozpBAAAAAFBwDUDXozpBVPxvPlBwDUBT3AlBAAAAAFBwDUDXozpBAAAAAFBwDUBT3AlBVPxvPlBwDUBT3AlBAAAAAFBwDUBT3AlBAAAAAAAAAABT3AlBVPxvPlBwDUBT3AlBAAAAAAAAAABT3AlBVPxvPgAAAABT3AlBVPxvPlBwDUBT3AlBVPxvPgAAAABT3AlBVPxvPgAAAADXozpBVPxvPlBwDUBT3AlBVPxvPgAAAADXozpBVPxvPlBwDUDXozpB")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_0bgky"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 8.61629, 0.234361, 2.20998, 3.04871),
+"attribute_data": PackedByteArray("/////1T8b75QcA3A8sM9P3V9HD//////VPxvvgAAAADywz0/CsBzP/////8AAAAAAAAAAJn8SD8KwHM//////wAAAABQcA3AmfxIP3V9HD//////U9wJwQAAAAC0jmk9eJjGPv////9T3AnBUHANwLSOaT1pmkA9/////9ejOsFQcA3AHJIgP2maQD3/////16M6wQAAAAAckiA/eJjGPv////9U/G++U9wJwbSOaT14oAQ//////wAAAABT3AnBtI5pPRK/9j7/////AAAAANejOsEckiA/Er/2Pv////9U/G++16M6wRySID94oAQ//////1T8b75T3AnB8sM9P2maQD3/////VPxvvtejOsHywz0/KGoEP/////8AAAAA16M6wZn8SD8oagQ//////wAAAABT3AnBmfxIP2maQD3/////VPxvvlBwDcAXZ3E/aZpAPf////8AAAAAUHANwHAuZj9pmkA9/////wAAAAAAAAAAcC5mP3iYxj7/////VPxvvgAAAAAXZ3E/eJjGPv////9T3AnBUHANwLSOaT1b9nM//////1PcCcEAAAAAtI5pPcWzHD//////16M6wQAAAAAckiA/xbMcP//////XozrBUHANwBySID9b9nM/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("VPxvPlBwDUDXozpBVPxvPgAAAADXozpBAAAAAAAAAADXozpBAAAAAFBwDUDXozpBAAAAAAAAAABT3AlBAAAAAFBwDUBT3AlBAAAAAFBwDUDXozpBAAAAAAAAAADXozpBVPxvPgAAAABT3AlBAAAAAAAAAABT3AlBAAAAAAAAAADXozpBVPxvPgAAAADXozpBVPxvPlBwDUBT3AlBVPxvPlBwDUDXozpBAAAAAFBwDUDXozpBAAAAAFBwDUBT3AlBVPxvPlBwDUBT3AlBAAAAAFBwDUBT3AlBAAAAAAAAAABT3AlBVPxvPgAAAABT3AlBVPxvPlBwDUBT3AlBVPxvPgAAAABT3AlBVPxvPgAAAADXozpBVPxvPlBwDUDXozpB/3//fwAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z8AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_27ejl")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_fmssf"]
+points = PackedVector3Array(0.234361, 2.20998, 11.665, 0.234361, 0, 11.665, 0, 0, 11.665, 0, 2.20998, 11.665, 0, 0, 8.61629, 0, 2.20998, 8.61629, 0.234361, 0, 8.61629, 0.234361, 2.20998, 8.61629)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_y86rq"]
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 0.234361, 0.00358915, 8.61629),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("VPxvPgAAAAAAAAAAVPxvPvI3azsAAAAAAAAAAPI3azsAAAAAVPxvPgAAAAAAAAAAAAAAAPI3azsAAAAAAAAAAAAAAAAAAAAAAAAAAPI3aztT3AlBAAAAAAAAAABT3AlBAAAAAAAAAAAAAAAAAAAAAPI3aztT3AlBAAAAAAAAAAAAAAAAAAAAAPI3azsAAAAAVPxvPvI3aztT3AlBAAAAAPI3aztT3AlBAAAAAPI3azsAAAAAVPxvPvI3aztT3AlBAAAAAPI3azsAAAAAVPxvPvI3azsAAAAAVPxvPgAAAABT3AlBVPxvPgAAAAAAAAAAAAAAAAAAAAAAAAAAVPxvPgAAAABT3AlBAAAAAAAAAAAAAAAAAAAAAAAAAABT3AlBVPxvPgAAAABT3AlBAAAAAAAAAABT3AlBAAAAAPI3aztT3AlBVPxvPgAAAABT3AlBAAAAAPI3aztT3AlBVPxvPvI3aztT3AlBVPxvPgAAAABT3AlBVPxvPvI3aztT3AlBVPxvPvI3azsAAAAAVPxvPgAAAABT3AlBVPxvPvI3azsAAAAAVPxvPgAAAAAAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_bpa64"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0, 0, 0.234361, 0.00358915, 8.61629),
+"attribute_data": PackedByteArray("/////1T8b74AAAAAqqqqPeC/Zz//////VPxvvvI3a7uqqqo9BaZnP/////8AAAAA8jdru2sW2T0Fpmc//////wAAAAAAAAAAaxbZPeC/Zz//////U9wJwfI3a7uqqqo93s/CPf////9T3AnBAAAAAKqqqj38AMI9/////wAAAAAAAAAAqqpqP/wAwj3/////AAAAAPI3a7uqqmo/3s/CPf////9U/G++U9wJwaqqqj2m5Z4+/////wAAAABT3AnBqqqqPXa0kT7/////AAAAAAAAAACqqmo/drSRPv////9U/G++AAAAAKqqaj+m5Z4+/////1T8b75T3AnBqqqqPSPm/z7/////VPxvvgAAAACqqmo/I+b/Pv////8AAAAAAAAAAKqqaj+qiwY//////wAAAABT3AnBqqqqPaqLBj//////VPxvvgAAAADwmos+BaZnP/////8AAAAAAAAAAOA1lz4Fpmc//////wAAAADyN2u74DWXPuC/Zz//////VPxvvvI3a7vwmos+4L9nP/////9T3AnBAAAAAKqqqj3FJTc//////1PcCcHyN2u7qqqqPekLNz//////AAAAAPI3a7uqqmo/6Qs3P/////8AAAAAAAAAAKqqaj/FJTc/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("VPxvPgAAAAAAAAAAVPxvPvI3azsAAAAAAAAAAPI3azsAAAAAAAAAAAAAAAAAAAAAAAAAAPI3aztT3AlBAAAAAAAAAABT3AlBAAAAAAAAAAAAAAAAAAAAAPI3azsAAAAAVPxvPvI3aztT3AlBAAAAAPI3aztT3AlBAAAAAPI3azsAAAAAVPxvPvI3azsAAAAAVPxvPgAAAABT3AlBVPxvPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABT3AlBVPxvPgAAAABT3AlBAAAAAAAAAABT3AlBAAAAAPI3aztT3AlBVPxvPvI3aztT3AlBVPxvPgAAAABT3AlBVPxvPvI3aztT3AlBVPxvPvI3azsAAAAAVPxvPgAAAAAAAAAA/////wAA/7//////AAD/v/////8AAP+//////wAA/78AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//f/9/AAD/P////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_y86rq")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_n8qy2"]
+points = PackedVector3Array(0.234361, 0, 0, 0.234361, 0.00358915, 0, 0, 0.00358915, 0, 0, 0, 0, 0, 0.00358915, 8.61629, 0, 0, 8.61629, 0.234361, 0.00358915, 8.61629, 0.234361, 0, 8.61629)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_5egdx"]
+_surfaces = [{
+"aabb": AABB(0, 0.00358915, 0, 0.234361, 2.21958, 7.0589),
+"format": 34359738369,
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("VPxvPvI3azsAAAAAVPxvPmtIDkAAAAAAAAAAAGtIDkAAAAAAVPxvPvI3azsAAAAAAAAAAGtIDkAAAAAAAAAAAPI3azsAAAAAAAAAAGtIDkCC4uFAAAAAAPI3azuC4uFAAAAAAPI3azsAAAAAAAAAAGtIDkCC4uFAAAAAAPI3azsAAAAAAAAAAGtIDkAAAAAAVPxvPmtIDkCC4uFAAAAAAGtIDkCC4uFAAAAAAGtIDkAAAAAAVPxvPmtIDkCC4uFAAAAAAGtIDkAAAAAAVPxvPmtIDkAAAAAAVPxvPvI3azuC4uFAVPxvPvI3azsAAAAAAAAAAPI3azsAAAAAVPxvPvI3azuC4uFAAAAAAPI3azsAAAAAAAAAAPI3azuC4uFAVPxvPvI3azuC4uFAAAAAAPI3azuC4uFAAAAAAGtIDkCC4uFAVPxvPvI3azuC4uFAAAAAAGtIDkCC4uFAVPxvPmtIDkCC4uFAVPxvPvI3azuC4uFAVPxvPmtIDkCC4uFAVPxvPmtIDkAAAAAAVPxvPvI3azuC4uFAVPxvPmtIDkAAAAAAVPxvPvI3azsAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_53vtp"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(0, 0.00358915, 0, 0.234361, 2.21958, 7.0589),
+"attribute_data": PackedByteArray("/////1T8b77yN2u7IDVoP/b2iD3/////VPxvvmtIDsAgNWg/feiNPv////8AAAAAa0gOwE0jbj996I0+/////wAAAADyN2u7TSNuP/b2iD3/////guLhwGtIDsCe5Y49feiNPv////+C4uHA8jdru57ljj329og9/////wAAAADyN2u7uXtEP/b2iD3/////AAAAAGtIDsC5e0Q/feiNPv////9U/G++guLhwJ7ljj1Dwt0+/////wAAAACC4uHAnuWOPflj0j7/////AAAAAAAAAAC5e0Q/+WPSPv////9U/G++AAAAALl7RD9Dwt0+/////1T8b76C4uHAnuWOPeAeET//////VPxvvgAAAAC5e0Q/4B4RP/////8AAAAAAAAAALl7RD8FzhY//////wAAAACC4uHAnuWOPQXOFj//////VPxvvvI3a7tNI24/+WPSPv////8AAAAA8jdruyA1aD/5Y9I+/////wAAAABrSA7AIDVoP1wHHz//////VPxvvmtIDsBNI24/XAcfP/////+C4uHA8jdru57ljj0i4W4//////4Li4cBrSA7AnuWOPcMLOT//////AAAAAGtIDsC5e0Q/wws5P/////8AAAAA8jdru7l7RD8i4W4/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("4_sgv3l"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("VPxvPvI3azsAAAAAVPxvPmtIDkAAAAAAAAAAAGtIDkAAAAAAAAAAAPI3azsAAAAAAAAAAGtIDkCC4uFAAAAAAPI3azuC4uFAAAAAAPI3azsAAAAAAAAAAGtIDkAAAAAAVPxvPmtIDkCC4uFAAAAAAGtIDkCC4uFAAAAAAGtIDkAAAAAAVPxvPmtIDkAAAAAAVPxvPvI3azuC4uFAVPxvPvI3azsAAAAAAAAAAPI3azsAAAAAAAAAAPI3azuC4uFAVPxvPvI3azuC4uFAAAAAAPI3azuC4uFAAAAAAGtIDkCC4uFAVPxvPmtIDkCC4uFAVPxvPvI3azuC4uFAVPxvPmtIDkCC4uFAVPxvPmtIDkAAAAAAVPxvPvI3azsAAAAA/////wAA/7//////AAD/v/////8AAP+//////wAA/78AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9///8AAP+//3///wAA/7//f///AAD/v/9///8AAP+//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//f/9/AAD/P/9//38AAP8//3//fwAA/z//f/9/AAD/P////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_5egdx")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_unuve"]
+points = PackedVector3Array(0.234361, 0.00358915, 0, 0.234361, 2.22317, 0, 0, 2.22317, 0, 0, 0.00358915, 0, 0, 2.22317, 7.0589, 0, 0.00358915, 7.0589, 0.234361, 2.22317, 7.0589, 0.234361, 0.00358915, 7.0589)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_kjt2d"]
+_surfaces = [{
+"aabb": AABB(-13.7145, 2.45858, 0, 3.81247, 1.73433, 4.38319),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("mG5bwVIshkBGmQdAmG5bwVIshkAAAAAAt24ewVIshkAAAAAAmG5bwVIshkBGmQdAt24ewVIshkAAAAAAt24ewVIshkBGmQdAt24ewWBZHUAYQ4xAt24ewVIshkBGmQdAt24ewVIshkAAAAAAt24ewWBZHUAYQ4xAt24ewVIshkAAAAAAt24ewWBZHUAAAAAAmG5bwWBZHUAAAAAAt24ewWBZHUAAAAAAt24ewVIshkAAAAAAmG5bwWBZHUAAAAAAt24ewVIshkAAAAAAmG5bwVIshkAAAAAAmG5bwVIshkBGmQdAt24ewVIshkBGmQdAt24ewWBZHUAYQ4xAmG5bwVIshkBGmQdAt24ewWBZHUAYQ4xAmG5bwWBZHUAYQ4xAmG5bwWBZHUAAAAAAmG5bwWBZHUAYQ4xAt24ewWBZHUAYQ4xAmG5bwWBZHUAAAAAAt24ewWBZHUAYQ4xAt24ewWBZHUAAAAAAmG5bwWBZHUAAAAAAmG5bwVIshkAAAAAAmG5bwVIshkBGmQdAmG5bwWBZHUAAAAAAmG5bwVIshkBGmQdAmG5bwWBZHUAYQ4xA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_xns2n"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-13.7145, 2.45858, 0, 3.81247, 1.73433, 4.38319),
+"attribute_data": PackedByteArray("/////5huW0FGmQfAogoXP+3WVD7/////mG5bQQAAAACiChc/t+8RPf////+3bh5BAAAAAGEtdT+37xE9/////7duHkFGmQfAYS11P+3WVD7/////GEOMwGBZHcANKi09FzcKP/////9GmQfAUiyGwLN4hT4uQMw+/////wAAAABSLIbA0RnuPi5AzD7/////AAAAAGBZHcDRGe4+FzcKP/////+YbltBYFkdwA0qLT0GylI//////7duHkFgWR3Av+rRPgbKUj//////t24eQVIshsC/6tE+BuF2P/////+YbltBUiyGwA0qLT0G4XY//////5huW0FGmQfADSotPbfvET3/////t24eQUaZB8BgZQE/t+8RPf////+3bh5BGEOMwGBlAT9BxKc+/////5huW0EYQ4zADSotPUHEpz7/////mG5bQQAAAAAqsgw/LkDMPv////+YbltBGEOMwCqyDD/qVUE//////7duHkEYQ4zA6dRqP+pVQT//////t24eQQAAAADp1Go/LkDMPv////8AAAAAYFkdwA0qLT0PjEA//////wAAAABSLIbADSotPQ51HD//////RpkHwFIshsC+jHw+DnUcP/////8YQ4zAYFkdwNEZ7j4PjEA/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("mG5bwVIshkBGmQdAmG5bwVIshkAAAAAAt24ewVIshkAAAAAAt24ewVIshkBGmQdAt24ewWBZHUAYQ4xAt24ewVIshkBGmQdAt24ewVIshkAAAAAAt24ewWBZHUAAAAAAmG5bwWBZHUAAAAAAt24ewWBZHUAAAAAAt24ewVIshkAAAAAAmG5bwVIshkAAAAAAmG5bwVIshkBGmQdAt24ewVIshkBGmQdAt24ewWBZHUAYQ4xAmG5bwWBZHUAYQ4xAmG5bwWBZHUAAAAAAmG5bwWBZHUAYQ4xAt24ewWBZHUAYQ4xAt24ewWBZHUAAAAAAmG5bwWBZHUAAAAAAmG5bwVIshkAAAAAAmG5bwVIshkBGmQdAmG5bwWBZHUAYQ4xA/3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/7////9//////////3//////////f/////////9///////////8AAP+//////wAA/7//////AAD/v/////8AAP+//397yAAA/7//f3vIAAD/v/9/e8gAAP+//397yAAA/7//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/PwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_kjt2d")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_a204x"]
+points = PackedVector3Array(-13.7145, 4.19291, 2.11873, -13.7145, 4.19291, 0, -9.90203, 4.19291, 0, -9.90203, 4.19291, 2.11873, -9.90203, 2.45858, 4.38319, -9.90203, 2.45858, 0, -13.7145, 2.45858, 0, -13.7145, 2.45858, 4.38319)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_t6f6t"]
+_surfaces = [{
+"aabb": AABB(-5.90203, 2.45858, 0, 6.2886, 1.73433, 4.38319),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("bt28wGBZHUAYQ4xAbt28wGBZHUAAAAAAbt28wFIshkAAAAAAbt28wGBZHUAYQ4xAbt28wFIshkAAAAAAbt28wFIshkBGmQdA++vFPlIshkBGmQdAbt28wFIshkBGmQdAbt28wFIshkAAAAAA++vFPlIshkBGmQdAbt28wFIshkAAAAAA2evFPlIshkAAAAAA2evFPmBZHUAAAAAA2evFPlIshkAAAAAAbt28wFIshkAAAAAA2evFPmBZHUAAAAAAbt28wFIshkAAAAAAbt28wGBZHUAAAAAAbt28wGBZHUAYQ4xAbt28wFIshkBGmQdA++vFPlIshkBGmQdAbt28wGBZHUAYQ4xA++vFPlIshkBGmQdA++vFPmBZHUAYQ4xA2evFPmBZHUAAAAAAbt28wGBZHUAAAAAAbt28wGBZHUAYQ4xA2evFPmBZHUAAAAAAbt28wGBZHUAYQ4xA++vFPmBZHUAYQ4xA2evFPmBZHUAAAAAA++vFPmBZHUAYQ4xA++vFPlIshkBGmQdA2evFPmBZHUAAAAAA++vFPlIshkBGmQdA2evFPlIshkAAAAAA")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_sfxmw"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-5.90203, 2.45858, 0, 6.2886, 1.73433, 4.38319),
+"attribute_data": PackedByteArray("/////xhDjMBgWR3AM88tPdptVT//////AAAAAGBZHcCvKK0+2m1VP/////8AAAAAUiyGwK8orT7wq3Q//////0aZB8BSLIbAeutHPvCrdD//////++vFvkaZB8Azzy09a2nFPv////9u3bxARpkHwOX87j5racU+/////27dvEAAAAAA5fzuPoDfCD//////2evFvgAAAABEzy09gN8IP//////Z68W+YFkdwDPPLT24xT4//////9nrxb5SLIbAM88tPaOHHz//////bt28QFIshsDj/O4+o4cfP/////9u3bxAYFkdwOP87j64xT4//////27dvEAYQ4zAM88tPScZmD7/////bt28QEaZB8Azzy09EEE1Pf/////768W+RpkHwMOxEz8QQTU9//////vrxb4YQ4zAw7ETPycZmD7/////2evFvgAAAABZOA0/bWnFPv////9u3bxAAAAAAFk4DT9f/VM//////27dvEAYQ4zAve9YP1/9Uz//////++vFvhhDjMC971g/a2nFPv////8AAAAAYFkdwKlrKT8QQTU9/////xhDjMBgWR3ADSN1PxBBNT3/////RpkHwFIshsAhBU4/mkgqPv////8AAAAAYFkdwKlrKT8QQTU9/////0aZB8BSLIbAIQVOP5pIKj7/////AAAAAFIshsCpayk/mkgqPg=="),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFwAYABkA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 26,
+"vertex_data": PackedByteArray("bt28wGBZHUAYQ4xAbt28wGBZHUAAAAAAbt28wFIshkAAAAAAbt28wFIshkBGmQdA++vFPlIshkBGmQdAbt28wFIshkBGmQdAbt28wFIshkAAAAAA2evFPlIshkAAAAAA2evFPmBZHUAAAAAA2evFPlIshkAAAAAAbt28wFIshkAAAAAAbt28wGBZHUAAAAAAbt28wGBZHUAYQ4xAbt28wFIshkBGmQdA++vFPlIshkBGmQdA++vFPmBZHUAYQ4xA2evFPmBZHUAAAAAAbt28wGBZHUAAAAAAbt28wGBZHUAYQ4xA++vFPmBZHUAYQ4xA2evFPmBZHUAAAAAA++vFPmBZHUAYQ4xA++vFPlIshkBGmQdA2evFPmBZHUAAAAAA++vFPlIshkBGmQdA2evFPlIshkAAAAAAAAD/f///AAAAAP9///8AAAAA/3///wAAAAD/f///AAD/f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//397yAAA/7//f3vIAAD/v/9/e8gAAP+//397yAAA/7//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/7//38AAP7//v//fwAA/v/+//9/AAD+//7//38AAP7//v//fwAA/v/+//9/AAD+/w==")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_t6f6t")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_gv672"]
+points = PackedVector3Array(-5.90203, 2.45858, 4.38319, -5.90203, 2.45858, 0, -5.90203, 4.19291, 0, -5.90203, 4.19291, 2.11873, 0.386566, 4.19291, 2.11873, 0.386565, 4.19291, 0, 0.386565, 2.45858, 0, 0.386566, 2.45858, 4.38319)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_7r4jw"]
+_surfaces = [{
+"aabb": AABB(-9.90203, 2.45858, 1.88623, 4, 1.73433, 2.49696),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("bt28wGBZHUAYQ4xAt24ewWBZHUAYQ4xAt24ewVIshkBGmQdAbt28wGBZHUAYQ4xAt24ewVIshkBGmQdAbt28wFIshkBGmQdAbt28wFIshkD8b/E/bt28wFIshkBGmQdAt24ewVIshkBGmQdAbt28wFIshkD8b/E/t24ewVIshkBGmQdAt24ewVIshkD8b/E/t24ewWBZHUD8b/E/t24ewVIshkD8b/E/t24ewVIshkBGmQdAt24ewWBZHUD8b/E/t24ewVIshkBGmQdAt24ewWBZHUAYQ4xAt24ewWBZHUD8b/E/bt28wGBZHUD8b/E/bt28wFIshkD8b/E/t24ewWBZHUD8b/E/bt28wFIshkD8b/E/t24ewVIshkD8b/E/t24ewWBZHUD8b/E/t24ewWBZHUAYQ4xAbt28wGBZHUAYQ4xAt24ewWBZHUD8b/E/bt28wGBZHUAYQ4xAbt28wGBZHUD8b/E/bt28wGBZHUAYQ4xAbt28wFIshkBGmQdAbt28wFIshkD8b/E/bt28wGBZHUAYQ4xAbt28wFIshkD8b/E/bt28wGBZHUD8b/E/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_y6630"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-9.90203, 2.45858, 1.88623, 4, 1.73433, 2.49696),
+"attribute_data": PackedByteArray("/////27dvEAYQ4zA31gzPXbVIj3/////t24eQRhDjMBuZhg/dtUiPf////+3bh5BRpkHwG5mGD+hLMs+/////27dvEBGmQfA31gzPaEsyz7/////bt28QPxv8b/fWDM9tLb/Pv////9u3bxARpkHwN9YMz3+4fM+/////7duHkFGmQfAMpr2Pv7h8z7/////t24eQfxv8b8ymvY+tLb/Pv/////8b/G/YFkdwGxFLz8B1zo///////xv8b9SLIbANbgRP9TOFz//////RpkHwFIshsBh5BY/CTYUP/////8YQ4zAYFkdwIrSZj8JNhQ//////7duHkFgWR3A31gzPQk2FD//////bt28QGBZHcAymvY+CTYUP/////9u3bxAUiyGwDKa9j5XVkA//////7duHkFSLIbA31gzPVdWQD//////t24eQfxv8b9zynQ/dtUiPf////+3bh5BGEOMwIvRLj921SI9/////27dvEAYQ4zAi9EuP4Ll3z7/////bt28QPxv8b9zynQ/guXfPv////8YQ4zAYFkdwDW4ET+vMU8//////0aZB8BSLIbAXqZhP68xTz///////G/xv1IshsCK0mY/fMpSP//////8b/G/YFkdwFRFST+p0nU/"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("bt28wGBZHUAYQ4xAt24ewWBZHUAYQ4xAt24ewVIshkBGmQdAbt28wFIshkBGmQdAbt28wFIshkD8b/E/bt28wFIshkBGmQdAt24ewVIshkBGmQdAt24ewVIshkD8b/E/t24ewWBZHUD8b/E/t24ewVIshkD8b/E/t24ewVIshkBGmQdAt24ewWBZHUAYQ4xAt24ewWBZHUD8b/E/bt28wGBZHUD8b/E/bt28wFIshkD8b/E/t24ewVIshkD8b/E/t24ewWBZHUD8b/E/t24ewWBZHUAYQ4xAbt28wGBZHUAYQ4xAbt28wGBZHUD8b/E/bt28wGBZHUAYQ4xAbt28wFIshkBGmQdAbt28wFIshkD8b/E/bt28wGBZHUD8b/E//397yAAA/7//f3vIAAD/v/9/e8gAAP+//397yAAA/7//f///AAD/v/9///8AAP+//3///wAA/7//f///AAD/vwAA/3///wAAAAD/f///AAAAAP9///8AAAAA/3///wAA/////wAA/7//////AAD/v/////8AAP+//////wAA/7//fwAAAAD/P/9/AAAAAP8//38AAAAA/z//fwAAAAD/P////3//////////f/////////9//////////3//////")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_7r4jw")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_qu4a2"]
+points = PackedVector3Array(-5.90203, 2.45858, 4.38319, -9.90203, 2.45858, 4.38319, -9.90203, 4.19291, 2.11873, -5.90203, 4.19291, 2.11873, -5.90203, 4.19291, 1.88623, -9.90203, 4.19291, 1.88623, -9.90203, 2.45858, 1.88623, -5.90203, 2.45858, 1.88623)
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_3c6ei"]
+_surfaces = [{
+"aabb": AABB(-9.90203, 2.45858, 0, 4, 1.73433, 0.88623),
+"format": 34359738369,
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 36,
+"vertex_data": PackedByteArray("bt28wFIshkD432I/bt28wFIshkAAAAAAbt28wGBZHUAAAAAAbt28wFIshkD432I/bt28wGBZHUAAAAAAbt28wGBZHUD432I/t24ewWBZHUAAAAAAbt28wGBZHUAAAAAAbt28wFIshkAAAAAAt24ewWBZHUAAAAAAbt28wFIshkAAAAAAt24ewVIshkAAAAAAt24ewWBZHUD432I/bt28wGBZHUD432I/bt28wGBZHUAAAAAAt24ewWBZHUD432I/bt28wGBZHUAAAAAAt24ewWBZHUAAAAAAt24ewVIshkD432I/t24ewVIshkAAAAAAbt28wFIshkAAAAAAt24ewVIshkD432I/bt28wFIshkAAAAAAbt28wFIshkD432I/t24ewVIshkD432I/t24ewWBZHUD432I/t24ewWBZHUAAAAAAt24ewVIshkD432I/t24ewWBZHUAAAAAAt24ewVIshkAAAAAAt24ewVIshkD432I/bt28wFIshkD432I/bt28wGBZHUD432I/t24ewVIshkD432I/bt28wGBZHUD432I/t24ewWBZHUD432I/")
+}]
+blend_shape_mode = 0
+
+[sub_resource type="ArrayMesh" id="ArrayMesh_58qgp"]
+lightmap_size_hint = Vector2i(1000, 1000)
+_surfaces = [{
+"aabb": AABB(-9.90203, 2.45858, 0, 4, 1.73433, 0.88623),
+"attribute_data": PackedByteArray("//////jfYr9SLIbAqoA1P8UyTT//////AAAAAFIshsCqgDU/pVEvP/////8AAAAAYFkdwMsJcj+lUS8///////jfYr9gWR3AywlyP8UyTT//////t24eQWBZHcBHY1898sZXPf////9u3bxAYFkdwEGUGT/yxlc9/////27dvEBSLIbAQZQZPyzrjz7/////t24eQVIshsBHY189LOuPPv////+3bh5B+N9iv0djXz3o3MU+/////27dvED432K/QZQZP+jcxT7/////bt28QAAAAABBlBk/lM8AP/////+3bh5BAAAAAEdjXz2UzwA//////7duHkH432K/R2NfPZOpOT//////t24eQQAAAABHY189c8gbP/////9u3bxAAAAAAEGUGT9zyBs//////27dvED432K/QZQZP5OpOT//////+N9iv1IshsBHY189cKJUP//////432K/YFkdwKv+lD5wolQ//////wAAAABgWR3Aq/6UPpGDcj//////AAAAAFIshsBHY189kYNyP/////+3bh5BUiyGwKqANT/yxlc9/////27dvEBSLIbAqoA1P8ZYFD//////bt28QGBZHcDLCXI/xlgUP/////+3bh5BYFkdwMsJcj/yxlc9"),
+"format": 34359742527,
+"index_count": 36,
+"index_data": PackedByteArray("AAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA"),
+"material": ExtResource("3_j4tkg"),
+"primitive": 3,
+"uv_scale": Vector4(0, 0, 0, 0),
+"vertex_count": 24,
+"vertex_data": PackedByteArray("bt28wFIshkD432I/bt28wFIshkAAAAAAbt28wGBZHUAAAAAAbt28wGBZHUD432I/t24ewWBZHUAAAAAAbt28wGBZHUAAAAAAbt28wFIshkAAAAAAt24ewVIshkAAAAAAt24ewWBZHUD432I/bt28wGBZHUD432I/bt28wGBZHUAAAAAAt24ewWBZHUAAAAAAt24ewVIshkD432I/t24ewVIshkAAAAAAbt28wFIshkAAAAAAbt28wFIshkD432I/t24ewVIshkD432I/t24ewWBZHUD432I/t24ewWBZHUAAAAAAt24ewVIshkAAAAAAt24ewVIshkD432I/bt28wFIshkD432I/bt28wGBZHUD432I/t24ewWBZHUD432I/////f/////////9//////////3//////////f///////////AAD/v/////8AAP+//////wAA/7//////AAD/v/9/AAAAAP8//38AAAAA/z//fwAAAAD/P/9/AAAAAP8//3///wAA/7//f///AAD/v/9///8AAP+//3///wAA/78AAP9///8AAAAA/3///wAAAAD/f///AAAAAP9///8AAP9//38AAP8//3//fwAA/z//f/9/AAD/P/9//38AAP8/")
+}]
+blend_shape_mode = 0
+shadow_mesh = SubResource("ArrayMesh_3c6ei")
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_uuk7r"]
+points = PackedVector3Array(-5.90203, 4.19291, 0.88623, -5.90203, 4.19291, 0, -5.90203, 2.45858, 0, -5.90203, 2.45858, 0.88623, -9.90203, 2.45858, 0, -9.90203, 4.19291, 0, -9.90203, 2.45858, 0.88623, -9.90203, 4.19291, 0.88623)
+
+[sub_resource type="CylinderShape3D" id="CylinderShape3D_3ew1m"]
+height = 3.47227
+radius = 3.23854
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n57cv"]
+transparency = 1
+albedo_texture = ExtResource("9_dpylm")
+roughness_texture = ExtResource("10_q7sve")
+normal_enabled = true
+normal_texture = ExtResource("10_q7sve")
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_rmiiq"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_a1btl"]
+size = Vector3(4, 1.63977, 5)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_e5yxg"]
+cull_mode = 2
+albedo_color = Color(0, 0, 0, 1)
+emission_enabled = true
+emission = Color(1, 1, 1, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_8m1cn"]
+size = Vector3(0.76416, 1.90765, 2.28064)
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_14b7m"]
+lifetime_randomness = 0.14
+emission_shape = 3
+emission_box_extents = Vector3(7.21, 5.485, 13.86)
+angle_min = -178.5
+angle_max = 171.7
+direction = Vector3(0, 0, 0)
+spread = 42.36
+initial_velocity_max = 0.1
+angular_velocity_min = 10.15
+angular_velocity_max = 29.4
+gravity = Vector3(0, 0.015, 0)
+linear_accel_min = -2.23517e-06
+linear_accel_max = -2.23517e-06
+scale_min = 0.1
+scale_max = 0.2
+turbulence_noise_strength = 16.53
+collision_mode = 2
+
+[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_3l82k"]
+lifetime_randomness = 0.14
+emission_shape = 3
+emission_box_extents = Vector3(7.897, 2.435, 8.435)
+angle_min = -178.5
+angle_max = 171.7
+direction = Vector3(0, 0, 0)
+spread = 8.345
+initial_velocity_max = 0.1
+gravity = Vector3(0, 0.015, 0)
+linear_accel_min = -2.23517e-06
+linear_accel_max = -2.23517e-06
+scale_min = 0.1
+scale_max = 0.2
+turbulence_noise_strength = 16.53
+collision_mode = 2
+
+[node name="Level1" type="Node3D"]
+
+[node name="Player" parent="." instance=ExtResource("1_2ewlq")]
+transform = Transform3D(0.72867, 0, 0.684865, 0, 1, 0, -0.684865, 0, 0.72867, -6.34549, 0, -5.72347)
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.764162, 0.632238, -0.127796, 0.397431, 0.617553, 0.678732, 0.508041, 0.467871, -0.723182, 0, 17.2518, 0)
+shadow_enabled = true
+directional_shadow_mode = 1
+directional_shadow_split_1 = 0.284
+directional_shadow_fade_start = 0.525
+directional_shadow_max_distance = 45.0
+directional_shadow_pancake_size = 15.0
+
+[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
+navigation_mesh = SubResource("NavigationMesh_hdiwy")
+
+[node name="Block_0" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, -1, -8)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_0"]
+mesh = SubResource("ArrayMesh_b0dem")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_0"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_0/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_ks5ww")
+
+[node name="Block_5" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 2.61711, -8)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_5"]
+mesh = SubResource("ArrayMesh_xye82")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_5"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_5/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_573tf")
+
+[node name="Block_2" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.22675, -1.19209e-07, -7.90353)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_2"]
+mesh = SubResource("ArrayMesh_27lps")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_2"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_2/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_2oeue")
+
+[node name="Block_3" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -7.87954, -9.53674e-07, -3.03458)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_3"]
+mesh = SubResource("ArrayMesh_5laxv")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_3"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_3/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_1b16g")
+
+[node name="Block_4" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 7.99486, -1.07288e-06, -3.16528)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_4"]
+mesh = SubResource("ArrayMesh_jro6u")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_4"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_4/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_5t0bo")
+
+[node name="Block_7" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_7"]
+mesh = SubResource("ArrayMesh_uf1y7")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_7"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_7/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_vkn26")
+
+[node name="Block_8" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_8"]
+mesh = SubResource("ArrayMesh_prpwy")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_8"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_8/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_5ftl2")
+
+[node name="Block_9" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.09605, 0, 1.85452)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_9"]
+mesh = SubResource("ArrayMesh_ns850")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_9"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_9/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_esyo4")
+
+[node name="Block_1" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.73693, -0.479077, 2)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_1"]
+mesh = SubResource("ArrayMesh_q6emc")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_1"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_1/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_1lv78")
+
+[node name="Block_10" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.54844, -0.499659, 9.62352)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_10"]
+mesh = SubResource("ArrayMesh_mq426")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_10"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_10/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_8pnv3")
+
+[node name="Block_11" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.52828, 0.0214276, 1.98818)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_11"]
+mesh = SubResource("ArrayMesh_ym55x")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_11"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_11/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_qusty")
+
+[node name="Block_6" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.80945, -0.176165, 14.2928)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_6"]
+mesh = SubResource("ArrayMesh_wg2ug")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_6"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_6/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_hk1my")
+
+[node name="Block_14" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_14"]
+mesh = SubResource("ArrayMesh_xag18")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_14"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_14/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_fnhql")
+
+[node name="Block_15" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_15"]
+mesh = SubResource("ArrayMesh_nns2b")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_15"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_15/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_u5ric")
+
+[node name="Block_16" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_16"]
+mesh = SubResource("ArrayMesh_rspiw")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_16"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_16/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_3s54x")
+
+[node name="Block_17" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.7971, 7.54221, -7.79748)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_17"]
+mesh = SubResource("ArrayMesh_tgata")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_17"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_17/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_gr45o")
+
+[node name="Block_12" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_12"]
+mesh = SubResource("ArrayMesh_ha825")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_12"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_12/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_nxwni")
+
+[node name="Block_18" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_18"]
+mesh = SubResource("ArrayMesh_66lia")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_18"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_18/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_kebix")
+
+[node name="Block_19" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_19"]
+mesh = SubResource("ArrayMesh_hykr2")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_19"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_19/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_pa4gv")
+
+[node name="Block_20" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_20"]
+mesh = SubResource("ArrayMesh_sylhf")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_20"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_20/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_6flwn")
+
+[node name="Block_21" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_21"]
+mesh = SubResource("ArrayMesh_bgpj6")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_21"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_21/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_fd1jt")
+
+[node name="Block_22" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_22"]
+mesh = SubResource("ArrayMesh_nm5sf")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_22"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_22/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_8o5fx")
+
+[node name="Block_23" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_23"]
+mesh = SubResource("ArrayMesh_lk6hb")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_23"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_23/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_hwigh")
+
+[node name="Block_24" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_24"]
+mesh = SubResource("ArrayMesh_g8jdo")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_24"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_24/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_rbvqr")
+
+[node name="Block_25" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_25"]
+mesh = SubResource("ArrayMesh_httos")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_25"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_25/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_no1sb")
+
+[node name="Block_26" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_26"]
+mesh = SubResource("ArrayMesh_xudhy")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_26"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_26/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_aiw4m")
+
+[node name="Block_29" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_29"]
+mesh = SubResource("ArrayMesh_fos3l")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_29"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_29/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_6ci4b")
+
+[node name="Block_30" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_30"]
+mesh = SubResource("ArrayMesh_r2ato")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_30"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_30/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_uhnxj")
+
+[node name="Block_31" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_31"]
+mesh = SubResource("ArrayMesh_7m1w5")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_31"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_31/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_6nufb")
+
+[node name="Block_32" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_32"]
+mesh = SubResource("ArrayMesh_725yh")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_32"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_32/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_op77g")
+
+[node name="Block_33" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_33"]
+mesh = SubResource("ArrayMesh_wfq43")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_33"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_33/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_80hi8")
+
+[node name="Block_27" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_27"]
+mesh = SubResource("ArrayMesh_1ttv3")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_27"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_27/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_8mb13")
+
+[node name="Block_28" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.31001, 3.61711, -1.42678)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_28"]
+mesh = SubResource("ArrayMesh_t4xdn")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_28"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_28/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_klscc")
+
+[node name="Block_37" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.78706, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_37"]
+mesh = SubResource("ArrayMesh_ep5bw")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_37"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_37/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_bydka")
+
+[node name="Block_13" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.02405, 7.98717, -7.87689)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_13"]
+mesh = SubResource("ArrayMesh_6igbr")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_13"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_13/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_7spni")
+
+[node name="Block_34" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -22.2367, 3.61192, -8.11062)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_34"]
+mesh = SubResource("ArrayMesh_iins8")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_34"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_34/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_ddg1r")
+
+[node name="Block_36" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.3769, 6.23453, -24.9331)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_36"]
+mesh = SubResource("ArrayMesh_osqfm")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_36"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_36/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_3uau3")
+
+[node name="Block_38" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.9298, 3.65562, -16.9701)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_38"]
+mesh = SubResource("ArrayMesh_p3b6g")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_38"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_38/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_1cxuh")
+
+[node name="Block_40" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.5013, 3.59214, -19.046)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_40"]
+mesh = SubResource("ArrayMesh_okoqc")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_40"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_40/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_8uxgq")
+
+[node name="Block_41" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.858, 3.614, 0.112)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_41"]
+mesh = SubResource("ArrayMesh_mtake")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_41"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_41/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_v88mn")
+
+[node name="Block_43" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_43"]
+mesh = SubResource("ArrayMesh_j2qb0")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_43"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_43/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_vfaw8")
+
+[node name="Block_44" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_44"]
+mesh = SubResource("ArrayMesh_0bgky")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_44"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_44/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_fmssf")
+
+[node name="Block_45" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_45"]
+mesh = SubResource("ArrayMesh_bpa64")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_45"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_45/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_n8qy2")
+
+[node name="Block_46" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.73058, 3.5364, -19.56)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_46"]
+mesh = SubResource("ArrayMesh_53vtp")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_46"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_46/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_unuve")
+
+[node name="Block_42" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_42"]
+mesh = SubResource("ArrayMesh_xns2n")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_42"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_42/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_a204x")
+
+[node name="Block_47" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_47"]
+mesh = SubResource("ArrayMesh_sfxmw")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_47"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_47/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_gv672")
+
+[node name="Block_48" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_48"]
+mesh = SubResource("ArrayMesh_y6630")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_48"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_48/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_qu4a2")
+
+[node name="Block_49" type="Node3D" parent="NavigationRegion3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.09797, 3.79426, -7.88623)
+
+[node name="mesh_instance" type="MeshInstance3D" parent="NavigationRegion3D/Block_49"]
+mesh = SubResource("ArrayMesh_58qgp")
+
+[node name="collision_body" type="StaticBody3D" parent="NavigationRegion3D/Block_49"]
+
+[node name="collision_shape" type="CollisionShape3D" parent="NavigationRegion3D/Block_49/collision_body"]
+shape = SubResource("ConvexPolygonShape3D_uuk7r")
+
+[node name="Node3D" type="Node3D" parent="."]
+transform = Transform3D(-0.127272, 0, 0.991868, 0, 1, 0, -0.991868, 0, -0.127272, -1.45819, -0.0530685, 5.7422)
+
+[node name="Enemy" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(0.886163, 0, -0.463373, 0, 1, 0, 0.463373, 0, 0.886163, 2.36963, 0, -1.44461)
+
+[node name="Enemy3" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(0.886162, 0, -0.463373, 0, 1, 0, 0.463373, 0, 0.886162, 11.7773, 7.85667, -3.75904)
+
+[node name="Enemy4" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792789, 0, -0.609495, 0, 1, 0, 0.609495, 0, -0.792789, 9.94873, 7.85667, 8.35039)
+
+[node name="Enemy5" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792789, 0, -0.609495, 0, 1, 0, 0.609495, 0, -0.792789, 4.42171, 7.85667, 8.19718)
+
+[node name="Enemy6" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792789, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792789, 3.68812, 3.62603, 7.24019)
+
+[node name="Enemy7" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792789, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792789, 11.2872, 3.62603, -1.26274)
+
+[node name="Enemy8" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 17.4707, 3.79078, -16.354)
+
+[node name="Enemy9" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 18.943, 3.79078, -16.2242)
+
+[node name="Enemy10" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 18.6843, 3.87412, -15.3698)
+
+[node name="Enemy11" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 19.4269, 3.87412, -12.0882)
+
+[node name="Enemy12" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(-0.792788, 0, -0.609494, 0, 1, 0, 0.609494, 0, -0.792788, 20.1413, 3.87412, -10.3197)
+
+[node name="Enemy13" parent="Node3D" instance=ExtResource("5_7de8f")]
+transform = Transform3D(0.999635, 0, -0.0269438, 0, 1, 0, 0.0269438, 0, 0.999635, 21.9169, 3.87412, -11.3213)
+
+[node name="Enemy2" parent="Node3D" instance=ExtResource("5_7de8f")]
+
+[node name="TubeSecret" parent="." node_paths=PackedStringArray("SecretHandler") instance=ExtResource("7_d6qhp")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.403744, 4.27109, -1.69066)
+SecretHandler = NodePath("../SecretHandler")
+TriggerID = "TubeEasterEgg"
+
+[node name="CollisionShape3D" parent="TubeSecret" index="0"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.198989, 0.319885, -0.0117693)
+shape = SubResource("CylinderShape3D_3ew1m")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="TubeSecret"]
+transform = Transform3D(-2.19796, 0, 1.92152e-07, 0, 2.19796, 0, -1.92152e-07, 0, -2.19796, -0.0113032, -0.653244, 0.348745)
+material_override = SubResource("StandardMaterial3D_n57cv")
+mesh = SubResource("PlaneMesh_rmiiq")
+
+[node name="SpotLight3D" type="SpotLight3D" parent="TubeSecret"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0881028, 3.12017, 0.0140528)
+shadow_enabled = true
+spot_range = 6.478
+spot_attenuation = 1.7
+
+[node name="TitanfallSecret" parent="." node_paths=PackedStringArray("SecretHandler") instance=ExtResource("7_d6qhp")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 11)
+SecretHandler = NodePath("../SecretHandler")
+TriggerID = "TitanfallEasterEgg"
+
+[node name="CollisionShape3D" parent="TitanfallSecret" index="0"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.5, 0.319885, 2)
+shape = SubResource("BoxShape3D_a1btl")
+
+[node name="SecretHandler" type="Node" parent="."]
+script = ExtResource("11_keyvq")
+
+[node name="Label3D" type="Label3D" parent="SecretHandler"]
+transform = Transform3D(-2.7838e-08, -0.879175, -0.476499, -3.37006e-08, 0.476499, -0.879175, 1, -8.41615e-09, -4.28935e-08, -3.3394, 1.48691, 12.2744)
+shaded = true
+text = "Standby for
+Titanfall"
+font = ExtResource("12_50ppg")
+font_size = 62
+outline_size = 3
+
+[node name="Label3D" type="Label3D" parent="." node_paths=PackedStringArray("secretHandler")]
+transform = Transform3D(-1, 0, -8.9407e-08, 0, 1, 0, 8.9407e-08, 0, -1, -12.7439, 4.8765, -7.91977)
+alpha_scissor_threshold = 0.51
+texture_filter = 1
+text = "Secrets found:
+0/2"
+font = ExtResource("13_lmvoj")
+font_size = 78
+outline_size = 0
+line_spacing = 8.445
+script = ExtResource("14_gubu6")
+secretHandler = NodePath("../SecretHandler")
+
+[node name="Label3D2" type="Label3D" parent="."]
+transform = Transform3D(1.33118e-07, 0, -1, 0, 1, 0, 1, 0, 1.33118e-07, -8.73379, 4.75402, -13.1583)
+alpha_scissor_threshold = 0.51
+texture_filter = 1
+text = "EXIT >"
+font = ExtResource("13_lmvoj")
+font_size = 78
+outline_size = 0
+line_spacing = 8.445
+
+[node name="CSGBox3D" type="CSGBox3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.11451, 4.76498, -11.6401)
+material_override = SubResource("StandardMaterial3D_e5yxg")
+size = Vector3(1, 2.32227, 2.01953)
+
+[node name="Area3D" type="Area3D" parent="CSGBox3D"]
+script = ExtResource("15_skkge")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="CSGBox3D/Area3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.11792, 0.126678, -0.0437622)
+shape = SubResource("BoxShape3D_8m1cn")
+
+[node name="Sofa" type="MeshInstance3D" parent="."]
+transform = Transform3D(0.866025, 0.5, 0, 2.18557e-08, -3.78552e-08, 1, 0.5, -0.866025, -4.37114e-08, 2.51683, -0.0579113, -5.13995)
+mesh = ExtResource("16_gg841")
+
+[node name="Console" type="MeshInstance3D" parent="."]
+transform = Transform3D(-0.485899, -0.398015, -6.06429e-09, 1.87189e-08, -3.24221e-08, 0.628103, -0.398015, 0.485899, 3.69434e-08, 6.45319, 0.450982, -2.36383)
+mesh = ExtResource("17_7mfba")
+
+[node name="Props" parent="." instance=ExtResource("18_t7vaq")]
+transform = Transform3D(0.379356, 0, -0.379356, 0, 0.536491, 0, 0.379356, 0, 0.379356, 5.8947, 1.47981, -1.52369)
+
+[node name="SpotLight3D" type="SpotLight3D" parent="Props"]
+transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -0.0529249, 0, 4.50775e-10)
+shadow_enabled = true
+spot_range = 8.245
+spot_attenuation = 2.08
+spot_angle = 64.94
+
+[node name="Table" type="MeshInstance3D" parent="Props"]
+transform = Transform3D(1.61424, -0.931983, -5.96046e-08, 5.67324e-08, -5.96597e-08, 1.86396, -0.931983, -1.61424, 5.96046e-08, -4.99424, -2.97375, -1.15214)
+mesh = ExtResource("20_3uy1u")
+
+[node name="Chair" type="MeshInstance3D" parent="Props"]
+transform = Transform3D(1.11156, -0.00104791, -3.55447e-08, 2.08551e-08, -1.30766e-08, 1.11156, -0.00104785, -1.11156, 3.55447e-08, -8.0301, -1.5956, 14.6172)
+mesh = ExtResource("21_fb4hp")
+
+[node name="OmniLight3D" type="OmniLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.41333, 1.31711, 5.22692)
+light_energy = 0.342
+omni_range = 5.096
+omni_attenuation = 6.57
+
+[node name="SpotLight3D2" type="SpotLight3D" parent="."]
+transform = Transform3D(0.960378, 0.00345009, -0.278678, 0.0247549, 0.994915, 0.0976273, 0.277598, -0.100658, 0.95541, 1.98901, 2.71689, 13.4963)
+shadow_enabled = true
+spot_range = 29.68
+spot_attenuation = 2.08
+spot_angle = 23.92
+
+[node name="GPUParticles3D" type="GPUParticles3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.09567, 0)
+amount = 200
+lifetime = 30.07
+speed_scale = 2.5
+explosiveness = 0.04
+randomness = 0.31
+visibility_aabb = AABB(-8.27, -7.28, -8.505, 16.915, 11.64, 26.17)
+process_material = SubResource("ParticleProcessMaterial_14b7m")
+draw_pass_1 = ExtResource("22_ad2hv")
+
+[node name="GPUParticles3D2" type="GPUParticles3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.6456, 6.09567, -9.72798)
+amount = 150
+lifetime = 30.07
+speed_scale = 2.5
+explosiveness = 0.04
+randomness = 0.31
+visibility_aabb = AABB(-7.31, -5.25, -10.41, 16.37, 8, 20.32)
+process_material = SubResource("ParticleProcessMaterial_3l82k")
+draw_pass_1 = ExtResource("22_ad2hv")
+
+[node name="SpotLight3D" type="SpotLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -16.492, 6.491, -6.3759)
+shadow_enabled = true
+spot_range = 6.875
+spot_attenuation = 2.18
+spot_angle = 79.3
+
+[connection signal="body_entered" from="CSGBox3D/Area3D" to="CSGBox3D/Area3D" method="_on_body_entered"]
+
+[editable path="TubeSecret"]
+[editable path="TitanfallSecret"]
+[editable path="Props"]
diff --git a/content/levels/MainMenu.tscn b/content/levels/MainMenu.tscn
new file mode 100644
index 0000000..506f7e8
--- /dev/null
+++ b/content/levels/MainMenu.tscn
@@ -0,0 +1,170 @@
+[gd_scene load_steps=15 format=3 uid="uid://bcywrfj8y3u1"]
+
+[ext_resource type="Texture2D" uid="uid://dnavx8bsrg2cp" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_Color.png" id="1_jjw1a"]
+[ext_resource type="Theme" uid="uid://c8qurdkgcv2yy" path="res://content/ui/Theme.tres" id="1_tgxtg"]
+[ext_resource type="Script" path="res://content/levels/main_menu.gd" id="2_ee486"]
+[ext_resource type="Texture2D" uid="uid://dku7f5fijokpn" path="res://content/rifle/Cardboard001_2K-PNG/Cardboard001_2K-PNG_NormalGL.png" id="3_7mv0l"]
+[ext_resource type="PackedScene" uid="uid://daryahp5bg8cw" path="res://content/levels/Level1Content.tscn" id="3_iivxj"]
+[ext_resource type="StyleBox" uid="uid://ccaebxxslc5dc" path="res://content/levels/MainMenu_PanelBG.tres" id="3_uxwy0"]
+[ext_resource type="Texture2D" uid="uid://cruj1ph2x2n2a" path="res://content/cardboard/Paper/Paper005_2K-PNG_Color.png" id="4_nni4k"]
+[ext_resource type="Texture2D" uid="uid://djtg2ykgjylk8" path="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png" id="5_8aynv"]
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_xpblf"]
+diffuse_texture = ExtResource("1_jjw1a")
+normal_texture = ExtResource("3_7mv0l")
+
+[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_oxyoo"]
+texture = SubResource("CanvasTexture_xpblf")
+axis_stretch_horizontal = 1
+axis_stretch_vertical = 1
+modulate_color = Color(0.362829, 0.362829, 0.362829, 1)
+
+[sub_resource type="LabelSettings" id="LabelSettings_wilqc"]
+line_spacing = 3.19
+font_size = 70
+font_color = Color(0, 0, 0, 1)
+
+[sub_resource type="LabelSettings" id="LabelSettings_g3dom"]
+line_spacing = 3.055
+font_color = Color(0, 0, 0, 1)
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_i2tij"]
+diffuse_texture = ExtResource("4_nni4k")
+normal_texture = ExtResource("5_8aynv")
+
+[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_vcu42"]
+texture = SubResource("CanvasTexture_i2tij")
+axis_stretch_horizontal = 1
+axis_stretch_vertical = 1
+modulate_color = Color(1, 0.706711, 0.61564, 1)
+
+[node name="MainMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_tgxtg")
+script = ExtResource("2_ee486")
+startGameScene = ExtResource("3_iivxj")
+
+[node name="Panel" type="Panel" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxTexture_oxyoo")
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 5
+anchor_left = 0.5
+anchor_right = 0.5
+offset_left = -278.0
+offset_top = 47.0
+offset_right = 214.0
+offset_bottom = 150.0
+grow_horizontal = 2
+rotation = -0.041969
+theme_override_styles/panel = ExtResource("3_uxwy0")
+
+[node name="Label" type="Label" parent="PanelContainer"]
+layout_mode = 2
+text = "A Papercut"
+label_settings = SubResource("LabelSettings_wilqc")
+horizontal_alignment = 1
+uppercase = true
+
+[node name="PanelContainer2" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -770.0
+offset_top = -119.0
+offset_right = -412.0
+offset_bottom = 121.0
+grow_horizontal = 2
+grow_vertical = 2
+rotation = 0.0705838
+theme_override_styles/panel = ExtResource("3_uxwy0")
+
+[node name="Label" type="Label" parent="PanelContainer2"]
+layout_mode = 2
+text = "CREDITS:
+
+DEVELOPMENT - Michel Fedde
+(iedSoftworks)
+
+Cardboard & Paper Textures - ambientCG
+Low-poly Viewmodel Arms V4 - Hozq
+HK437 - Heckler & Koch
+Enemies - MIXAMO
+Sounds - Ultimate SFX Bundle"
+label_settings = SubResource("LabelSettings_g3dom")
+horizontal_alignment = 1
+uppercase = true
+
+[node name="Play" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = 98.0
+offset_top = -143.0
+offset_right = 326.0
+offset_bottom = -68.0
+grow_horizontal = 2
+grow_vertical = 2
+rotation = -0.0958309
+theme_override_styles/panel = SubResource("StyleBoxTexture_vcu42")
+
+[node name="Button" type="Button" parent="Play"]
+layout_mode = 2
+theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
+theme_override_colors/font_hover_color = Color(0.321728, 0.321728, 0.321728, 1)
+theme_override_colors/font_pressed_color = Color(0.691357, 0.691357, 0.691357, 1)
+theme_override_colors/font_focus_color = Color(0.281187, 0.281187, 0.281187, 1)
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
+theme_override_font_sizes/font_size = 46
+text = "PLAY"
+flat = true
+
+[node name="Exit" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = 209.0
+offset_top = 77.0001
+offset_right = 437.0
+offset_bottom = 153.0
+grow_horizontal = 2
+grow_vertical = 2
+rotation = 0.162366
+theme_override_styles/panel = SubResource("StyleBoxTexture_vcu42")
+
+[node name="Button" type="Button" parent="Exit"]
+layout_mode = 2
+theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
+theme_override_colors/font_hover_color = Color(0.321728, 0.321728, 0.321728, 1)
+theme_override_colors/font_pressed_color = Color(0.691357, 0.691357, 0.691357, 1)
+theme_override_colors/font_focus_color = Color(0.281187, 0.281187, 0.281187, 1)
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
+theme_override_font_sizes/font_size = 46
+text = "EXIT"
+flat = true
+
+[connection signal="pressed" from="Play/Button" to="." method="_on_button_pressed"]
+[connection signal="pressed" from="Exit/Button" to="." method="_on_exit_press"]
diff --git a/content/levels/MainMenu_PanelBG.tres b/content/levels/MainMenu_PanelBG.tres
new file mode 100644
index 0000000..14b8861
--- /dev/null
+++ b/content/levels/MainMenu_PanelBG.tres
@@ -0,0 +1,13 @@
+[gd_resource type="StyleBoxTexture" load_steps=4 format=3 uid="uid://ccaebxxslc5dc"]
+
+[ext_resource type="Texture2D" uid="uid://cruj1ph2x2n2a" path="res://content/cardboard/Paper/Paper005_2K-PNG_Color.png" id="1_1d6ge"]
+[ext_resource type="Texture2D" uid="uid://djtg2ykgjylk8" path="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png" id="2_0udhe"]
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_i2tij"]
+diffuse_texture = ExtResource("1_1d6ge")
+normal_texture = ExtResource("2_0udhe")
+
+[resource]
+texture = SubResource("CanvasTexture_i2tij")
+axis_stretch_horizontal = 1
+axis_stretch_vertical = 1
diff --git a/content/levels/exit_script.gd b/content/levels/exit_script.gd
new file mode 100644
index 0000000..c31d54a
--- /dev/null
+++ b/content/levels/exit_script.gd
@@ -0,0 +1,10 @@
+extends Area3D
+
+func _on_body_entered(body: Node3D) -> void:
+ if body is not Player:
+ return
+
+ Player.Instance.cameraController.process_mode = Node.PROCESS_MODE_PAUSABLE
+ get_tree().paused = true
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ Player.Instance.ui.displayComplete()
diff --git a/content/levels/main_menu.gd b/content/levels/main_menu.gd
new file mode 100644
index 0000000..9bf5f54
--- /dev/null
+++ b/content/levels/main_menu.gd
@@ -0,0 +1,13 @@
+extends Control
+
+@export var startGameScene: PackedScene
+
+func _ready() -> void:
+ get_tree().paused = false;
+
+func _on_button_pressed() -> void:
+ get_tree().change_scene_to_packed(startGameScene)
+
+
+func _on_exit_press() -> void:
+ get_tree().quit()
diff --git a/content/props/Chair.res b/content/props/Chair.res
new file mode 100644
index 0000000..58d53b3
Binary files /dev/null and b/content/props/Chair.res differ
diff --git a/content/props/Console.res b/content/props/Console.res
new file mode 100644
index 0000000..229a0b3
Binary files /dev/null and b/content/props/Console.res differ
diff --git a/content/props/Paper_001.res b/content/props/Paper_001.res
new file mode 100644
index 0000000..b4e5814
Binary files /dev/null and b/content/props/Paper_001.res differ
diff --git a/content/props/Props.fbx b/content/props/Props.fbx
new file mode 100644
index 0000000..039d6c8
Binary files /dev/null and b/content/props/Props.fbx differ
diff --git a/content/props/Props.fbx.import b/content/props/Props.fbx.import
new file mode 100644
index 0000000..30fafad
--- /dev/null
+++ b/content/props/Props.fbx.import
@@ -0,0 +1,124 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://civa03txb27kr"
+path="res://.godot/imported/Props.fbx-cacf48bef963a17ce7e63d2ee69a6049.scn"
+
+[deps]
+
+source_file="res://content/props/Props.fbx"
+dest_files=["res://.godot/imported/Props.fbx-cacf48bef963a17ce7e63d2ee69a6049.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/apply_root_scale=true
+nodes/root_scale=75.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=true
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+_subresources={
+"materials": {
+"Chair": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard_material.tres"
+},
+"Console": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard_material.tres"
+},
+"Paper": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard/Paper/Paper.tres"
+},
+"Sofa": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard/Cardboard004/Cardboard004.tres"
+},
+"TV Shell": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard_material.tres"
+},
+"TVScreen": {
+"use_external/enabled": true,
+"use_external/path": "res://content/props/TV Screen.tres"
+},
+"Table": {
+"use_external/enabled": true,
+"use_external/path": "res://content/cardboard_material.tres"
+}
+},
+"meshes": {
+"Chair": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/Chair.res"
+},
+"Console": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/Console.res"
+},
+"Paper_001": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/Paper_001.res"
+},
+"Sofa": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/Sofa.res"
+},
+"TV": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/TV.res"
+},
+"Table": {
+"generate/lightmap_uv": 0,
+"generate/lods": 0,
+"generate/shadow_meshes": 0,
+"lods/normal_merge_angle": 60.0,
+"lods/normal_split_angle": 25.0,
+"save_to_file/enabled": true,
+"save_to_file/path": "res://content/props/Table.res"
+}
+}
+}
+fbx/importer=0
+fbx/allow_geometry_helper_nodes=false
+fbx/embedded_image_handling=1
diff --git a/content/props/Sofa.res b/content/props/Sofa.res
new file mode 100644
index 0000000..9217849
Binary files /dev/null and b/content/props/Sofa.res differ
diff --git a/content/props/TV Screen.tres b/content/props/TV Screen.tres
new file mode 100644
index 0000000..06865f6
--- /dev/null
+++ b/content/props/TV Screen.tres
@@ -0,0 +1,6 @@
+[gd_resource type="StandardMaterial3D" format=3 uid="uid://b15qw0h4jp04m"]
+
+[resource]
+render_priority = 1
+cull_mode = 2
+shading_mode = 0
diff --git a/content/props/TV.res b/content/props/TV.res
new file mode 100644
index 0000000..c9c4e95
Binary files /dev/null and b/content/props/TV.res differ
diff --git a/content/props/TV.tscn b/content/props/TV.tscn
new file mode 100644
index 0000000..3dcfefd
--- /dev/null
+++ b/content/props/TV.tscn
@@ -0,0 +1,10 @@
+[gd_scene load_steps=2 format=3 uid="uid://b7pmtf7c0rfke"]
+
+[ext_resource type="ArrayMesh" uid="uid://b2gllsowmqm0j" path="res://content/props/TV.res" id="1_3pc4a"]
+
+[node name="Props" type="Node3D"]
+
+[node name="TV" type="MeshInstance3D" parent="."]
+transform = Transform3D(1.06096e-07, 1.25478e-07, 1, -0.89, -7.26483e-08, 1.19209e-07, 3.89031e-08, -1.662, 7.54979e-08, 0, 0, 0)
+mesh = ExtResource("1_3pc4a")
+skeleton = NodePath("")
diff --git a/content/props/Table.res b/content/props/Table.res
new file mode 100644
index 0000000..553d767
Binary files /dev/null and b/content/props/Table.res differ
diff --git a/content/rifle/weapon.tscn b/content/rifle/weapon.tscn
index 50f4d26..d0e9d4d 100644
--- a/content/rifle/weapon.tscn
+++ b/content/rifle/weapon.tscn
@@ -390,8 +390,10 @@ advance_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_nrl4s"]
+xfade_time = 0.2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_d50tw"]
+xfade_time = 0.2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_yxl34"]
xfade_time = 0.01
@@ -466,8 +468,8 @@ bones/6/parent = 5
bones/6/rest = Transform3D(0.999755, -0.0152834, 0.0159934, 0.0152834, -0.0454936, -0.998848, 0.0159934, 0.998848, -0.0452489, 0, 4.76836e-11, -7.62939e-10)
bones/6/enabled = true
bones/6/position = Vector3(0, 4.76836e-11, -7.62939e-10)
-bones/6/rotation = Quaternion(0.685382, -0.0293075, 0.03841, 0.726579)
-bones/6/scale = Vector3(1.00716, 0.990373, 1.00756)
+bones/6/rotation = Quaternion(0.697626, -0.0272437, 0.0377774, 0.714947)
+bones/6/scale = Vector3(1.01704, 0.970755, 1.01744)
bones/7/name = "pole_line.R_end"
bones/7/parent = 6
bones/7/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.00391025, 0)
@@ -487,8 +489,8 @@ bones/9/parent = 8
bones/9/rest = Transform3D(0.999755, 0.0152834, -0.0159934, -0.0152834, -0.0454936, -0.998848, -0.0159934, 0.998848, -0.0452489, 0, 4.76836e-11, -7.62939e-10)
bones/9/enabled = true
bones/9/position = Vector3(0, 4.76836e-11, -7.62939e-10)
-bones/9/rotation = Quaternion(0.609096, 0.083777, -0.0899037, 0.783518)
-bones/9/scale = Vector3(0.910517, 1.21307, 0.910522)
+bones/9/rotation = Quaternion(0.597646, 0.0882558, -0.0949246, 0.791214)
+bones/9/scale = Vector3(0.89553, 1.24785, 0.895534)
bones/10/name = "pole_line.L_end"
bones/10/parent = 9
bones/10/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.00391025, 0)
@@ -515,21 +517,21 @@ bones/13/parent = 12
bones/13/rest = Transform3D(0.289313, -0.201567, -0.935772, 0.93034, 0.289312, 0.225315, 0.225315, -0.935772, 0.271228, 0, 0.00139584, 0)
bones/13/enabled = true
bones/13/position = Vector3(0, 0.00139584, 0)
-bones/13/rotation = Quaternion(-0.282023, -0.558744, 0.597224, 0.501589)
+bones/13/rotation = Quaternion(-0.288451, -0.584149, 0.584372, 0.483814)
bones/13/scale = Vector3(1, 1, 1)
bones/14/name = "forearm.R"
bones/14/parent = 13
bones/14/rest = Transform3D(0.967755, 0.113719, 0.224763, 0.180815, 0.307618, -0.934172, -0.175374, 0.94469, 0.277137, 3.1834e-10, 0.00266141, -1.26107e-11)
bones/14/enabled = true
bones/14/position = Vector3(3.1834e-10, 0.00266141, -1.26107e-11)
-bones/14/rotation = Quaternion(0.82986, 0.124679, -0.0240342, 0.543332)
+bones/14/rotation = Quaternion(0.847442, 0.123812, -0.0281606, 0.515479)
bones/14/scale = Vector3(1, 1, 1)
bones/15/name = "hand.R"
bones/15/parent = 14
bones/15/rest = Transform3D(0.998803, -0.0473205, -0.0123773, 0.0478966, 0.997532, 0.0513467, 0.00991698, -0.0518781, 0.998604, -1.36696e-10, 0.00253111, 1.61345e-10)
bones/15/enabled = true
bones/15/position = Vector3(-1.36696e-10, 0.00253111, 1.61345e-10)
-bones/15/rotation = Quaternion(-0.295409, -0.0342655, 0.139281, 0.944542)
+bones/15/rotation = Quaternion(-0.280002, -0.0683182, 0.00549664, 0.95755)
bones/15/scale = Vector3(1, 1, 1)
bones/16/name = "index_meta.R"
bones/16/parent = 15
@@ -585,14 +587,14 @@ bones/23/parent = 22
bones/23/rest = Transform3D(0.745321, -0.659727, 0.0962067, 0.665133, 0.745686, -0.0393761, -0.0457625, 0.0933381, 0.994582, 1.45929e-10, 0.000428075, 4.40949e-11)
bones/23/enabled = true
bones/23/position = Vector3(1.45929e-10, 0.000428075, 4.40949e-11)
-bones/23/rotation = Quaternion(0.0470606, 0.0222304, 0.674114, 0.736792)
+bones/23/rotation = Quaternion(0.0470606, 0.0222304, 0.674114, 0.736791)
bones/23/scale = Vector3(1, 1, 1)
bones/24/name = "middle3.R"
bones/24/parent = 23
bones/24/rest = Transform3D(0.927281, -0.373308, -0.028122, 0.373945, 0.927184, 0.0222794, 0.0177572, -0.0311754, 0.999356, -7.29711e-10, 0.000267549, 1.92479e-10)
bones/24/enabled = true
bones/24/position = Vector3(-7.29711e-10, 0.000267549, 1.92479e-10)
-bones/24/rotation = Quaternion(-0.0161525, -0.00781051, 0.43526, 0.900126)
+bones/24/rotation = Quaternion(-0.0161525, -0.0078105, 0.43526, 0.900126)
bones/24/scale = Vector3(1, 1, 1)
bones/25/name = "middle3.R_end"
bones/25/parent = 24
@@ -690,7 +692,7 @@ bones/38/parent = 37
bones/38/rest = Transform3D(0.997504, -0.0704799, -0.00439951, 0.0704634, 0.989289, 0.127834, -0.00465736, -0.127825, 0.991786, 3.60323e-12, 0.000226731, -4.33635e-10)
bones/38/enabled = true
bones/38/position = Vector3(3.60323e-12, 0.000226731, -4.33635e-10)
-bones/38/rotation = Quaternion(0.03212, 0.0582583, 0.363318, 0.929287)
+bones/38/rotation = Quaternion(0.0321199, 0.0582583, 0.363318, 0.929287)
bones/38/scale = Vector3(1, 1, 1)
bones/39/name = "thumb2.R_end"
bones/39/parent = 38
@@ -704,7 +706,7 @@ bones/40/parent = 14
bones/40/rest = Transform3D(0.999974, 0.00428396, 0.00580153, -0.00412317, 0.999615, -0.0274478, -0.00591688, 0.0274232, 0.999606, -8.46418e-06, 0.000215645, -6.06698e-05)
bones/40/enabled = true
bones/40/position = Vector3(-8.46418e-06, 0.000215645, -6.06698e-05)
-bones/40/rotation = Quaternion(0.0136922, -0.0106461, -0.00228802, 0.999847)
+bones/40/rotation = Quaternion(0.0136791, -0.016199, -0.00236402, 0.999772)
bones/40/scale = Vector3(1, 1, 1)
bones/41/name = "twist1.R_end"
bones/41/parent = 40
@@ -718,7 +720,7 @@ bones/42/parent = 14
bones/42/rest = Transform3D(0.999974, 0.00428394, 0.00580145, -0.00412316, 0.999615, -0.0274474, -0.0059168, 0.0274228, 0.999606, -2.00581e-06, 0.00172259, -1.93289e-05)
bones/42/enabled = true
bones/42/position = Vector3(-2.00581e-06, 0.00172259, -1.93289e-05)
-bones/42/rotation = Quaternion(0.0136163, -0.0397939, -0.00268602, 0.999112)
+bones/42/rotation = Quaternion(0.0135754, -0.0538977, -0.00287833, 0.99845)
bones/42/scale = Vector3(1, 1, 1)
bones/43/name = "twist3.R_end"
bones/43/parent = 42
@@ -732,7 +734,7 @@ bones/44/parent = 14
bones/44/rest = Transform3D(0.999974, 0.00428412, 0.00580153, -0.00412334, 0.999615, -0.0274478, -0.00591688, 0.0274232, 0.999606, -5.23504e-06, 0.000969117, -3.99994e-05)
bones/44/enabled = true
bones/44/position = Vector3(-5.23504e-06, 0.000969117, -3.99994e-05)
-bones/44/rotation = Quaternion(0.0136523, -0.0268674, -0.00250983, 0.999543)
+bones/44/rotation = Quaternion(0.01362, -0.0377659, -0.00265874, 0.99919)
bones/44/scale = Vector3(1, 1, 1)
bones/45/name = "twist2.R_end"
bones/45/parent = 44
@@ -760,7 +762,7 @@ bones/48/parent = 14
bones/48/rest = Transform3D(0.999974, 0.00428436, 0.00580146, -0.00412358, 0.999615, -0.0274474, -0.00591682, 0.0274228, 0.999606, -2.00581e-06, 0.00172259, -1.93289e-05)
bones/48/enabled = true
bones/48/position = Vector3(-2.00581e-06, 0.00172259, -1.93289e-05)
-bones/48/rotation = Quaternion(0.013692, -0.0106462, -0.00228823, 0.999847)
+bones/48/rotation = Quaternion(0.0136789, -0.0161991, -0.00236423, 0.999772)
bones/48/scale = Vector3(1, 1, 1)
bones/49/name = "armweartwist.R_end"
bones/49/parent = 48
@@ -774,7 +776,7 @@ bones/50/parent = 14
bones/50/rest = Transform3D(0.998924, -0.0448829, -0.0116821, 0.0454014, 0.99778, 0.0487276, 0.00946913, -0.0492056, 0.998744, 1.22313e-06, 0.00247606, 1.34137e-06)
bones/50/enabled = true
bones/50/position = Vector3(1.22313e-06, 0.00247606, 1.34137e-06)
-bones/50/rotation = Quaternion(-0.0242033, -0.0187956, 0.0229129, 0.999268)
+bones/50/rotation = Quaternion(-0.0240691, -0.0243315, 0.0230464, 0.999148)
bones/50/scale = Vector3(1, 1, 1)
bones/51/name = "cuff.R_end"
bones/51/parent = 50
@@ -788,7 +790,7 @@ bones/52/parent = 14
bones/52/rest = Transform3D(1, -2.01654e-09, 3.1474e-08, 2.01654e-09, 1, 7.23308e-08, -3.1474e-08, -7.23308e-08, 1, -1.54241e-10, -1.23587e-10, 3.80932e-10)
bones/52/enabled = true
bones/52/position = Vector3(-1.54241e-10, -1.23587e-10, 3.80932e-10)
-bones/52/rotation = Quaternion(1.53914e-08, -0.0923886, 1.06304e-08, 0.995723)
+bones/52/rotation = Quaternion(9.57245e-08, -0.101219, 2.37833e-08, 0.994864)
bones/52/scale = Vector3(1, 1, 1)
bones/53/name = "blouse.R_end"
bones/53/parent = 52
@@ -809,21 +811,21 @@ bones/55/parent = 54
bones/55/rest = Transform3D(0.289313, 0.201567, 0.935772, -0.93034, 0.289312, 0.225315, -0.225315, -0.935772, 0.271228, 0, 0.00139584, 0)
bones/55/enabled = true
bones/55/position = Vector3(0, 0.00139584, 0)
-bones/55/rotation = Quaternion(-0.0166644, 0.462827, -0.758183, 0.45899)
+bones/55/rotation = Quaternion(-0.0101907, 0.432429, -0.778612, 0.454603)
bones/55/scale = Vector3(1, 1, 1)
bones/56/name = "forearm.L"
bones/56/parent = 55
bones/56/rest = Transform3D(0.967755, -0.113719, -0.224763, -0.180815, 0.307618, -0.934172, 0.175374, 0.94469, 0.277137, -3.1834e-10, 0.00266141, -1.26107e-11)
bones/56/enabled = true
bones/56/position = Vector3(-3.1834e-10, 0.00266141, -1.26107e-11)
-bones/56/rotation = Quaternion(0.59871, -0.125498, -0.0193099, 0.790837)
+bones/56/rotation = Quaternion(0.569612, -0.124714, -0.0238521, 0.812047)
bones/56/scale = Vector3(1, 1, 1)
bones/57/name = "hand.L"
bones/57/parent = 56
bones/57/rest = Transform3D(0.998803, 0.0473205, 0.0123773, -0.0478966, 0.997532, 0.0513467, -0.00991698, -0.0518781, 0.998604, 1.36696e-10, 0.00253111, 1.61345e-10)
bones/57/enabled = true
bones/57/position = Vector3(1.36696e-10, 0.00253111, 1.61345e-10)
-bones/57/rotation = Quaternion(-0.201574, -0.0350852, 0.238639, 0.949309)
+bones/57/rotation = Quaternion(-0.207822, -0.0285846, 0.115176, 0.970942)
bones/57/scale = Vector3(1, 1, 1)
bones/58/name = "index_meta.L"
bones/58/parent = 57
@@ -836,7 +838,7 @@ bones/59/name = "index1.L"
bones/59/parent = 58
bones/59/rest = Transform3D(0.917378, 0.395351, 0.0459933, -0.396836, 0.899629, 0.18218, 0.0306483, -0.18538, 0.982189, -4.75825e-10, 0.000649227, 3.51402e-10)
bones/59/enabled = true
-bones/59/position = Vector3(5.26133e-06, 0.00062688, 4.6132e-06)
+bones/59/position = Vector3(5.26137e-06, 0.00062688, 4.61328e-06)
bones/59/rotation = Quaternion(-0.0921515, 0.0504243, 0.195935, 0.974974)
bones/59/scale = Vector3(1, 1, 1)
bones/60/name = "index2.L"
@@ -871,8 +873,8 @@ bones/64/name = "middle1.L"
bones/64/parent = 63
bones/64/rest = Transform3D(0.897312, 0.438301, 0.0521827, -0.441346, 0.892693, 0.0911681, -0.00662413, -0.104837, 0.994467, -7.31031e-10, 0.00064664, 2.63282e-10)
bones/64/enabled = true
-bones/64/position = Vector3(-1.39905e-05, 0.000607788, 8.67968e-07)
-bones/64/rotation = Quaternion(-0.0560438, 0.00556503, 0.0915036, 0.994211)
+bones/64/position = Vector3(-1.39906e-05, 0.000607789, 8.67999e-07)
+bones/64/rotation = Quaternion(-0.0560437, 0.00556503, 0.0915036, 0.994211)
bones/64/scale = Vector3(1, 1, 1)
bones/65/name = "middle2.L"
bones/65/parent = 64
@@ -886,7 +888,7 @@ bones/66/parent = 65
bones/66/rest = Transform3D(0.927281, 0.373308, 0.028122, -0.373945, 0.927184, 0.0222794, -0.0177572, -0.0311754, 0.999356, 7.29711e-10, 0.000267549, 1.92479e-10)
bones/66/enabled = true
bones/66/position = Vector3(7.29711e-10, 0.000267549, 1.92479e-10)
-bones/66/rotation = Quaternion(-0.0173623, 0.00452311, -0.601864, 0.798397)
+bones/66/rotation = Quaternion(-0.0173623, 0.00452312, -0.601864, 0.798397)
bones/66/scale = Vector3(1, 1, 1)
bones/67/name = "middle3.L_end"
bones/67/parent = 66
@@ -906,7 +908,7 @@ bones/69/name = "ring1.L"
bones/69/parent = 68
bones/69/rest = Transform3D(0.786512, 0.617183, 0.0219931, -0.611213, 0.783015, -0.115352, -0.0884141, 0.0772832, 0.993081, -7.66656e-10, 0.000618599, -1.41676e-10)
bones/69/enabled = true
-bones/69/position = Vector3(1.30419e-06, 0.000602323, -2.71001e-06)
+bones/69/position = Vector3(1.30467e-06, 0.000602322, -2.7101e-06)
bones/69/rotation = Quaternion(0.0409456, -0.0210825, 0.0696472, 0.996508)
bones/69/scale = Vector3(1, 1, 1)
bones/70/name = "ring2.L"
@@ -941,7 +943,7 @@ bones/74/name = "pinky1.L"
bones/74/parent = 73
bones/74/rest = Transform3D(0.884728, 0.463521, -0.049038, -0.464454, 0.867839, -0.176462, -0.0392369, 0.178897, 0.983085, -1.94544e-10, 0.000594951, 7.62025e-11)
bones/74/enabled = true
-bones/74/position = Vector3(-8.65005e-06, 0.000669554, 2.38654e-05)
+bones/74/position = Vector3(-8.65025e-06, 0.000669554, 2.38656e-05)
bones/74/rotation = Quaternion(0.0945839, -0.108202, 0.236727, 0.960889)
bones/74/scale = Vector3(1, 1, 1)
bones/75/name = "pinky2.L"
@@ -949,7 +951,7 @@ bones/75/parent = 74
bones/75/rest = Transform3D(0.900206, 0.418934, -0.118847, -0.430854, 0.896472, -0.103453, 0.0632026, 0.144335, 0.987509, -3.57407e-10, 0.000294327, -1.2612e-10)
bones/75/enabled = true
bones/75/position = Vector3(-3.57407e-10, 0.000294327, -1.2612e-10)
-bones/75/rotation = Quaternion(0.0550313, -0.0567217, -0.0538149, 0.995419)
+bones/75/rotation = Quaternion(0.0550313, -0.0567217, -0.0538148, 0.995419)
bones/75/scale = Vector3(1, 1, 1)
bones/76/name = "pinky3.L"
bones/76/parent = 75
@@ -969,7 +971,7 @@ bones/78/name = "thumb_meta.L"
bones/78/parent = 57
bones/78/rest = Transform3D(0.240535, 0.343136, 0.907965, 0.585873, 0.694485, -0.417665, -0.773884, 0.632415, -0.0339863, 7.71225e-05, 0.000177085, 8.39641e-05)
bones/78/enabled = true
-bones/78/position = Vector3(-8.8677e-05, 0.000149522, 3.81398e-05)
+bones/78/position = Vector3(-8.86769e-05, 0.000149522, 3.81398e-05)
bones/78/rotation = Quaternion(0.293949, 0.638458, 0.159887, 0.69311)
bones/78/scale = Vector3(1, 1, 1)
bones/79/name = "thumb1.L"
@@ -977,14 +979,14 @@ bones/79/parent = 78
bones/79/rest = Transform3D(0.81243, 0.582946, 0.0114595, -0.582738, 0.811175, 0.0491062, 0.0193306, -0.0465732, 0.998728, -3.87094e-10, 0.000535015, -2.64585e-10)
bones/79/enabled = true
bones/79/position = Vector3(-3.87094e-10, 0.000535015, -2.64585e-10)
-bones/79/rotation = Quaternion(-0.246789, -0.068873, -0.208296, 0.943909)
+bones/79/rotation = Quaternion(-0.24679, -0.068873, -0.208296, 0.943909)
bones/79/scale = Vector3(1, 1, 1)
bones/80/name = "thumb2.L"
bones/80/parent = 79
bones/80/rest = Transform3D(0.997504, 0.0704799, 0.00439951, -0.0704634, 0.989289, 0.127834, 0.00465736, -0.127825, 0.991786, -3.60323e-12, 0.000226731, -4.33635e-10)
bones/80/enabled = true
bones/80/position = Vector3(-3.60323e-12, 0.000226731, -4.33635e-10)
-bones/80/rotation = Quaternion(0.0181542, -0.258237, 0.0705999, 0.963328)
+bones/80/rotation = Quaternion(0.0181542, -0.258237, 0.0705999, 0.963327)
bones/80/scale = Vector3(1, 1, 1)
bones/81/name = "thumb2.L_end"
bones/81/parent = 80
@@ -998,7 +1000,7 @@ bones/82/parent = 56
bones/82/rest = Transform3D(0.999974, -0.00428396, -0.00580153, 0.00412317, 0.999615, -0.0274478, 0.00591688, 0.0274232, 0.999606, 8.46418e-06, 0.000215645, -6.06698e-05)
bones/82/enabled = true
bones/82/position = Vector3(8.46418e-06, 0.000215645, -6.06698e-05)
-bones/82/rotation = Quaternion(0.013749, -0.0122868, 0.0019736, 0.999828)
+bones/82/rotation = Quaternion(0.0137262, -0.00583264, 0.00206218, 0.999887)
bones/82/scale = Vector3(1, 1, 1)
bones/83/name = "twist1.L_end"
bones/83/parent = 82
@@ -1012,7 +1014,7 @@ bones/84/parent = 56
bones/84/rest = Transform3D(0.999974, -0.00428394, -0.00580145, 0.00412316, 0.999615, -0.0274474, 0.0059168, 0.0274228, 0.999606, 2.00581e-06, 0.00172259, -1.93289e-05)
bones/84/enabled = true
bones/84/position = Vector3(2.00581e-06, 0.00172259, -1.93289e-05)
-bones/84/rotation = Quaternion(0.0137985, -0.0378184, 0.00162178, 0.999188)
+bones/84/rotation = Quaternion(0.0137596, -0.0189521, 0.00188206, 0.999724)
bones/84/scale = Vector3(1, 1, 1)
bones/85/name = "twist3.L_end"
bones/85/parent = 84
@@ -1026,7 +1028,7 @@ bones/86/parent = 56
bones/86/rest = Transform3D(0.999974, -0.00428412, -0.00580153, 0.00412334, 0.999615, -0.0274478, 0.00591688, 0.0274232, 0.999606, 5.23504e-06, 0.000969117, -3.99994e-05)
bones/86/enabled = true
bones/86/position = Vector3(5.23504e-06, 0.000969117, -3.99994e-05)
-bones/86/rotation = Quaternion(0.0137753, -0.0253152, 0.00179422, 0.999583)
+bones/86/rotation = Quaternion(0.0137428, -0.012407, 0.00197201, 0.999827)
bones/86/scale = Vector3(1, 1, 1)
bones/87/name = "twist2.L_end"
bones/87/parent = 86
@@ -1054,7 +1056,7 @@ bones/90/parent = 56
bones/90/rest = Transform3D(0.999974, -0.00428436, -0.00580146, 0.00412358, 0.999615, -0.0274474, 0.00591682, 0.0274228, 0.999606, 2.00581e-06, 0.00172259, -1.93289e-05)
bones/90/enabled = true
bones/90/position = Vector3(2.00581e-06, 0.00172259, -1.93289e-05)
-bones/90/rotation = Quaternion(0.0137488, -0.0122868, 0.00197381, 0.999828)
+bones/90/rotation = Quaternion(0.0137261, -0.0058326, 0.00206239, 0.999887)
bones/90/scale = Vector3(1, 1, 1)
bones/91/name = "armweartwist.L_end"
bones/91/parent = 90
@@ -1068,7 +1070,7 @@ bones/92/parent = 56
bones/92/rest = Transform3D(0.998924, 0.0448829, 0.0116821, -0.0454014, 0.99778, 0.0487276, -0.00946913, -0.0492056, 0.998744, -1.22313e-06, 0.00247606, 1.34137e-06)
bones/92/enabled = true
bones/92/position = Vector3(-1.22313e-06, 0.00247606, 1.34137e-06)
-bones/92/rotation = Quaternion(-0.0247151, -0.00402103, -0.0223549, 0.999436)
+bones/92/rotation = Quaternion(-0.0245623, 0.00240149, -0.0225135, 0.999442)
bones/92/scale = Vector3(1, 1, 1)
bones/93/name = "cuff.L_end"
bones/93/parent = 92
@@ -1082,7 +1084,7 @@ bones/94/parent = 56
bones/94/rest = Transform3D(1, 2.01654e-09, -3.1474e-08, -2.01654e-09, 1, 7.23308e-08, 3.1474e-08, -7.23308e-08, 1, 1.54241e-10, -1.23587e-10, 3.80932e-10)
bones/94/enabled = true
bones/94/position = Vector3(1.54241e-10, -1.23587e-10, 3.80932e-10)
-bones/94/rotation = Quaternion(-2.23139e-09, 0.0478625, 3.25588e-08, 0.998854)
+bones/94/rotation = Quaternion(2.7622e-09, 0.0458541, 4.00819e-08, 0.998948)
bones/94/scale = Vector3(1, 1, 1)
bones/95/name = "blouse.L_end"
bones/95/parent = 94
@@ -1095,8 +1097,8 @@ bones/96/name = "handIK.R"
bones/96/parent = 0
bones/96/rest = Transform3D(0.931583, 0.363365, 0.0109084, 0.0109083, -0.0579345, 0.998261, 0.363365, -0.929844, -0.0579345, 0.00338212, 0.00270377, -0.00286581)
bones/96/enabled = true
-bones/96/position = Vector3(0.00138322, 0.0042643, -0.00268604)
-bones/96/rotation = Quaternion(-0.65666, 0.0629605, 0.234883, 0.713907)
+bones/96/position = Vector3(0.00148682, 0.00419669, -0.00257247)
+bones/96/rotation = Quaternion(-0.664603, 0.0222567, 0.12274, 0.73671)
bones/96/scale = Vector3(1, 1, 1)
bones/97/name = "handIK.R_end"
bones/97/parent = 96
@@ -1109,8 +1111,8 @@ bones/98/name = "handIK.L"
bones/98/parent = 0
bones/98/rest = Transform3D(0.931583, -0.363365, -0.0109084, -0.0109083, -0.0579345, 0.998261, -0.363365, -0.929844, -0.0579345, -0.00338212, 0.00270377, -0.00286581)
bones/98/enabled = true
-bones/98/position = Vector3(0.000460905, 0.00427366, -0.00350029)
-bones/98/rotation = Quaternion(-0.727027, -0.103511, -0.060138, 0.676093)
+bones/98/position = Vector3(0.000566952, 0.00437002, -0.00361386)
+bones/98/rotation = Quaternion(-0.688275, -0.150903, -0.173687, 0.687996)
bones/98/scale = Vector3(1, 1, 1)
bones/99/name = "handIK.L_end"
bones/99/parent = 98
@@ -1153,7 +1155,7 @@ mesh = SubResource("ArrayMesh_lxa88")
skin = SubResource("Skin_1i0dj")
[node name="BoneAttachment3D" type="BoneAttachment3D" parent="LVA4_Armature/Skeleton3D"]
-transform = Transform3D(0.882008, -0.451171, -0.136039, 0.441757, 0.892132, -0.0946076, 0.164049, 0.0233483, 0.986176, 0.00146523, 0.00273574, -0.000998727)
+transform = Transform3D(0.968822, -0.210556, -0.130574, 0.196214, 0.97385, -0.114522, 0.151273, 0.0853308, 0.984802, 0.00147449, 0.00257344, -0.00102714)
bone_name = "hand.R"
bone_idx = 15
@@ -1175,7 +1177,7 @@ root_node = NodePath("../..")
tree_root = SubResource("AnimationNodeStateMachine_xt052")
anim_player = NodePath("..")
"parameters/Crouched Fire/blend_position" = 0
-"parameters/Crouched Movement/blend_position" = Vector2(-0.531895, -0.033419)
+"parameters/Crouched Movement/blend_position" = Vector2(0, 0)
parameters/Fire/blend_position = 0.0
-parameters/Movement/blend_position = 0.248419
-parameters/Movement/0/blend_position = Vector2(-0.437148, -0.851852)
+parameters/Movement/blend_position = 0.0
+parameters/Movement/0/blend_position = Vector2(0, 0)
diff --git a/content/ui/Theme.tres b/content/ui/Theme.tres
new file mode 100644
index 0000000..d54870c
--- /dev/null
+++ b/content/ui/Theme.tres
@@ -0,0 +1,6 @@
+[gd_resource type="Theme" load_steps=2 format=3 uid="uid://c8qurdkgcv2yy"]
+
+[ext_resource type="FontFile" uid="uid://dxyqmtf7gmhfk" path="res://content/IndieFlower-Regular.ttf" id="1_86qkj"]
+
+[resource]
+default_font = ExtResource("1_86qkj")
diff --git a/content/ui/UI.tscn b/content/ui/UI.tscn
index 489b729..3ad665b 100644
--- a/content/ui/UI.tscn
+++ b/content/ui/UI.tscn
@@ -1,12 +1,164 @@
-[gd_scene format=3 uid="uid://bwnlcxfwxyj7j"]
+[gd_scene load_steps=16 format=3 uid="uid://bwnlcxfwxyj7j"]
+
+[ext_resource type="Theme" uid="uid://c8qurdkgcv2yy" path="res://content/ui/Theme.tres" id="1_bwxgq"]
+[ext_resource type="Script" path="res://content/ui/ui.gd" id="1_dy6lw"]
+[ext_resource type="StyleBox" uid="uid://ccaebxxslc5dc" path="res://content/levels/MainMenu_PanelBG.tres" id="4_8esrw"]
+[ext_resource type="Texture2D" uid="uid://cruj1ph2x2n2a" path="res://content/cardboard/Paper/Paper005_2K-PNG_Color.png" id="5_ttvs6"]
+[ext_resource type="Texture2D" uid="uid://djtg2ykgjylk8" path="res://content/cardboard/Paper/Paper005_2K-PNG_NormalGL.png" id="6_w7xvv"]
+
+[sub_resource type="LabelSettings" id="LabelSettings_shi77"]
+font_size = 36
+font_color = Color(0.95, 0.95, 0.95, 1)
+outline_size = 3
+outline_color = Color(0, 0, 0, 1)
+
+[sub_resource type="CanvasTexture" id="CanvasTexture_333we"]
+diffuse_texture = ExtResource("5_ttvs6")
+normal_texture = ExtResource("6_w7xvv")
+
+[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_c7rki"]
+texture = SubResource("CanvasTexture_333we")
+axis_stretch_horizontal = 1
+axis_stretch_vertical = 1
+modulate_color = Color(0.776128, 0.293527, 0.072986, 1)
+
+[sub_resource type="LabelSettings" id="LabelSettings_vv6d4"]
+line_spacing = 0.0
+font_size = 40
+font_color = Color(0, 0, 0, 1)
+
+[sub_resource type="LabelSettings" id="LabelSettings_p4q71"]
+font_size = 20
+font_color = Color(0, 0, 0, 1)
+
+[sub_resource type="Animation" id="Animation_x56u5"]
+resource_name = "Death"
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("You DIED:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(6.96439, 0.5),
+"update": 0,
+"values": [Vector2(729, -98), Vector2(729, 291)]
+}
+
+[sub_resource type="Animation" id="Animation_25rtn"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("VBoxContainer:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(690, 476)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("VBoxContainer:modulate")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("PanelContainer:position")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(-467, 118)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("You DIED:position")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(769, -92)]
+}
+
+[sub_resource type="Animation" id="Animation_cfn84"]
+resource_name = "SecretDiscovery"
+length = 5.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("PanelContainer:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.6, 4.3, 5),
+"transitions": PackedFloat32Array(2, 1, 2.21914, 1),
+"update": 0,
+"values": [Vector2(-463, 101), Vector2(0, 101), Vector2(0, 101), Vector2(-463, 101)]
+}
+
+[sub_resource type="Animation" id="Animation_j0xea"]
+resource_name = "SuccessAnimation"
+length = 0.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("VBoxContainer:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1.51572),
+"update": 0,
+"values": [Vector2(729, 300), Vector2(729, 442)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("VBoxContainer:modulate")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1.51572),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_fu14e"]
+_data = {
+"Death": SubResource("Animation_x56u5"),
+"RESET": SubResource("Animation_25rtn"),
+"SecretDiscovery": SubResource("Animation_cfn84"),
+"SuccessAnimation": SubResource("Animation_j0xea")
+}
[node name="Ui" type="Control"]
+process_mode = 3
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+theme = ExtResource("1_bwxgq")
+script = ExtResource("1_dy6lw")
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
@@ -15,9 +167,123 @@ anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
-offset_left = -4.5
-offset_top = -4.5
-offset_right = 4.5
-offset_bottom = 4.5
+offset_left = -1.0
+offset_top = -1.0
+offset_right = 1.0
+offset_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+
+[node name="Fade" type="ColorRect" parent="."]
+visible = false
+z_index = 1000
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+layout_mode = 1
+offset_left = -467.0
+offset_top = 118.0
+offset_right = -4.0
+offset_bottom = 205.0
+theme_override_styles/panel = ExtResource("4_8esrw")
+
+[node name="Label" type="Label" parent="PanelContainer"]
+layout_mode = 2
+text = "Secret Discovered"
+label_settings = SubResource("LabelSettings_shi77")
+horizontal_alignment = 1
+
+[node name="You DIED" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 5
+anchor_left = 0.5
+anchor_right = 0.5
+offset_left = -191.0
+offset_top = -92.0
+offset_right = 272.0
+offset_bottom = -5.0
+grow_horizontal = 2
+theme_override_styles/panel = SubResource("StyleBoxTexture_c7rki")
+
+[node name="Label" type="Label" parent="You DIED"]
+layout_mode = 2
+text = "YOu Died"
+label_settings = SubResource("LabelSettings_shi77")
+horizontal_alignment = 1
+
+[node name="VBoxContainer" type="Control" parent="."]
+modulate = Color(1, 1, 1, 0)
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -270.0
+offset_top = -64.0
+offset_right = 329.0
+offset_bottom = 208.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Label" type="Label" parent="VBoxContainer"]
+layout_mode = 1
+anchors_preset = 5
+anchor_left = 0.5
+anchor_right = 0.5
+offset_left = -63.5
+offset_right = 63.5
+offset_bottom = 36.0
+grow_horizontal = 2
+rotation = 0.00546251
+theme_override_styles/normal = ExtResource("4_8esrw")
+text = " Escaped "
+label_settings = SubResource("LabelSettings_vv6d4")
+horizontal_alignment = 1
+
+[node name="Label2" type="Label" parent="VBoxContainer"]
+layout_mode = 1
+anchors_preset = 14
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_top = -46.0
+offset_bottom = 83.0
+grow_horizontal = 2
+grow_vertical = 2
+rotation = -0.0178222
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_styles/normal = ExtResource("4_8esrw")
+text = "You finally escaped...
+But what happend? You haven't found a clue for what happend.
+What were those things and why is everything out of Cardboard?
+But maybe the outside world has infos for you..."
+label_settings = SubResource("LabelSettings_p4q71")
+horizontal_alignment = 1
+
+[node name="Button" type="Button" parent="VBoxContainer"]
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -42.0
+offset_top = -31.0
+offset_right = 43.0
+grow_horizontal = 2
+grow_vertical = 0
+text = "Return"
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_fu14e")
+}
+
+[connection signal="pressed" from="VBoxContainer/Button" to="." method="_on_button_pressed"]
diff --git a/content/ui/ui.gd b/content/ui/ui.gd
new file mode 100644
index 0000000..9df3a36
--- /dev/null
+++ b/content/ui/ui.gd
@@ -0,0 +1,21 @@
+extends Control
+
+class_name UI
+
+@onready var FadeRect: ColorRect = $Fade
+@onready var animationPlayer: AnimationPlayer = $AnimationPlayer
+
+func setFade(target: float) -> void:
+ create_tween().bind_node(self).tween_property(FadeRect, 'color', Color(0,0,0, target), 1.0).set_trans(Tween.TRANS_EXPO)
+
+func displayComplete() -> void:
+ animationPlayer.play('SuccessAnimation')
+
+func _on_button_pressed() -> void:
+ get_tree().change_scene_to_file("res://content/levels/MainMenu.tscn")
+
+func displaySecret() -> void:
+ animationPlayer.play('SecretDiscovery')
+
+func displayYouDied() -> void:
+ animationPlayer.play('Death')
diff --git a/credits.md b/credits.md
index 3de6b3f..93e9e78 100644
--- a/credits.md
+++ b/credits.md
@@ -4,8 +4,16 @@ aliases: []
tags: []
---
+Cardboard - ambientCG:
+ https://ambientcg.com/a/Cardboard004
+ https://ambientcg.com/a/Paper005
+ https://ambientcg.com/a/Cardboard001
+
Low-poly Viewmodel Arms V4 for Blender by Hozq
https://hozq3d.gumroad.com/l/LVA4
HK437 - Heckler & Koch (pls don't sue me)
https://www.heckler-koch.com/en/Products/Military%20and%20Law%20Enforcement/Assault%20rifles/HK437
+
+Mixamo - Enemies:
+https://www.mixamo.com/#/?page=1&query=Ninja&type=Character
diff --git a/cyclops_settings.config b/cyclops_settings.config
new file mode 100644
index 0000000..92854c7
--- /dev/null
+++ b/cyclops_settings.config
@@ -0,0 +1,3 @@
+snapping/enabled=true
+snapping/grid/unit_size=0.001
+snapping/grid/use_subdivisions=false
diff --git a/default_bus_layout.tres b/default_bus_layout.tres
new file mode 100644
index 0000000..b715eb7
--- /dev/null
+++ b/default_bus_layout.tres
@@ -0,0 +1,31 @@
+[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://cptjtmi4p8frl"]
+
+[sub_resource type="AudioEffectReverb" id="AudioEffectReverb_veduw"]
+resource_name = "Reverb"
+predelay_feedback = 0.05
+room_size = 0.03
+damping = 0.07
+spread = 0.08
+wet = 0.12
+
+[resource]
+bus/1/name = &"Effects"
+bus/1/solo = false
+bus/1/mute = false
+bus/1/bypass_fx = false
+bus/1/volume_db = 0.0
+bus/1/send = &"Master"
+bus/2/name = &"Player"
+bus/2/solo = false
+bus/2/mute = false
+bus/2/bypass_fx = false
+bus/2/volume_db = -12.9785
+bus/2/send = &"Effects"
+bus/3/name = &"Enemies"
+bus/3/solo = false
+bus/3/mute = false
+bus/3/bypass_fx = false
+bus/3/volume_db = -18.9844
+bus/3/send = &"Master"
+bus/3/effect/0/effect = SubResource("AudioEffectReverb_veduw")
+bus/3/effect/0/enabled = true
diff --git a/demo/agents/scripts/health.gd b/demo/agents/scripts/health.gd
index 181c36c..86b152f 100644
--- a/demo/agents/scripts/health.gd
+++ b/demo/agents/scripts/health.gd
@@ -23,12 +23,11 @@ signal damaged(amount: float, knockback: Vector2)
var _current: float
-
func _ready() -> void:
_current = max_health
-func take_damage(amount: float, knockback: Vector2) -> void:
+func take_damage(amount: float, knockback: Vector2, from: Node3D = null) -> void:
if _current <= 0.0:
return
@@ -37,6 +36,7 @@ func take_damage(amount: float, knockback: Vector2) -> void:
if _current <= 0.0:
death.emit()
+ death.emit(from)
else:
damaged.emit(amount, knockback)
diff --git a/environment.tres b/environment.tres
index 8e75cc7..cc50ff0 100644
--- a/environment.tres
+++ b/environment.tres
@@ -9,10 +9,15 @@ sky_material = SubResource("ProceduralSkyMaterial_72mw6")
background_mode = 2
sky = SubResource("Sky_jar1y")
ambient_light_source = 3
-ambient_light_color = Color(0.834614, 0.834614, 0.834614, 1)
-ambient_light_energy = 4.85
+ambient_light_color = Color(0.28485, 0.28485, 0.28485, 1)
+ambient_light_sky_contribution = 0.0
+ambient_light_energy = 0.95
reflected_light_source = 2
tonemap_mode = 3
+glow_enabled = true
+glow_intensity = 1.0
+glow_bloom = 0.18
+glow_hdr_scale = 1.0
adjustment_enabled = true
adjustment_brightness = 1.03
adjustment_contrast = 1.05
diff --git a/examples_dd3d/DebugDrawDemoScene.gd b/examples_dd3d/DebugDrawDemoScene.gd
new file mode 100644
index 0000000..6484399
--- /dev/null
+++ b/examples_dd3d/DebugDrawDemoScene.gd
@@ -0,0 +1,621 @@
+@tool
+extends Node3D
+
+@export var custom_font : Font
+@export var zylann_example := false
+@export var update_in_physics := false
+@export var test_text := true
+@export var test_graphs := false
+@export var test_fps_graph := true
+@export var more_test_cases := true
+@export var draw_array_of_boxes := false
+@export var draw_1m_boxes := false
+@export_range(0, 5, 0.001) var debug_thickness := 0.1
+@export_range(0, 1, 0.001) var debug_center_brightness := 0.8
+@export_range(0, 1) var camera_frustum_scale := 0.9
+
+@export_group("Text groups", "text_groups")
+@export var text_groups_show_hints := true
+@export var text_groups_show_stats := false
+@export var text_groups_show_stats_2d := false
+@export var text_groups_position := DebugDraw2DConfig.POSITION_LEFT_TOP
+@export var text_groups_offset := Vector2i(8, 8)
+@export var text_groups_padding := Vector2i(3, 1)
+@export_range(1, 100) var text_groups_default_font_size := 15
+@export_range(1, 100) var text_groups_title_font_size := 20
+@export_range(1, 100) var text_groups_text_font_size := 17
+
+@export_group("Graphs", "graph")
+@export var graph_offset := Vector2i(8, 8)
+@export var graph_size := Vector2i(200, 80)
+@export_range(1, 100) var graph_title_font_size := 14
+@export_range(1, 100) var graph_text_font_size := 12
+@export_range(0, 64) var graph_text_precision := 1
+@export_range(1, 32) var graph_line_width := 1.0
+@export_range(1, 512) var graph_buffer_size := 128
+@export var graph_frame_time_mode := true
+@export var graph_is_enabled := true
+
+var button_presses := {}
+var frame_rendered := false
+var physics_tick_processed := false
+
+var timer_1 := 0.0
+var timer_cubes := 0.0
+var timer_3 := 0.0
+var timer_text := 0.0
+
+# TODO remove after moving to 4.2
+var is_4_2_and_higher = Engine.get_version_info()["major"] >= 4 && Engine.get_version_info()["minor"] >= 2
+
+
+func _process(delta) -> void:
+ $OtherWorld.mesh.material.set_shader_parameter("albedo_texture", $OtherWorld/SubViewport.get_texture())
+
+ physics_tick_processed = false
+ if !update_in_physics:
+ main_update(delta)
+ _update_timers(delta)
+
+
+## Since physics frames may not be called every frame or may be called multiple times in one frame,
+## there is an additional check to ensure that a new frame has been drawn before updating the data.
+func _physics_process(delta: float) -> void:
+ if !physics_tick_processed:
+ physics_tick_processed = true
+ if update_in_physics:
+ main_update(delta)
+ _update_timers(delta)
+
+ # Physics specific:
+ if not zylann_example:
+ DebugDraw3D.draw_line($"Lines/8".global_position, $Lines/Target.global_position, Color.YELLOW)
+
+ if more_test_cases:
+ _draw_rays_casts()
+
+ ## Additional drawing in the Viewport
+ if true:
+ var _w1 = DebugDraw3D.new_scoped_config().set_viewport(%OtherWorldBox.get_viewport()).set_thickness(0.01).set_center_brightness(1).set_no_depth_test(true)
+ DebugDraw3D.draw_box_xf(Transform3D(Basis()
+ .scaled(Vector3.ONE*0.3)
+ .rotated(Vector3(0,0,1), PI/4)
+ .rotated(Vector3(0,1,0), wrapf(Time.get_ticks_msec() / -1500.0, 0, TAU) - PI/4), %OtherWorldBox.global_transform.origin),
+ Color.BROWN, true, 0.4)
+
+
+func main_update(delta: float) -> void:
+ DebugDraw3D.scoped_config().set_thickness(debug_thickness).set_center_brightness(debug_center_brightness)
+ if false: #test
+ var _s11 = DebugDraw3D.new_scoped_config().set_thickness(1)
+ if true:
+ pass
+ var _s13 = DebugDraw3D.new_scoped_config()
+ _s13.set_thickness(3)
+
+ _update_keys_just_press()
+
+ if _is_key_just_pressed(KEY_F1):
+ zylann_example = !zylann_example
+
+ # Zylann's example :D
+ if zylann_example:
+ DebugDraw2D.clear_graphs()
+ var _time = Time.get_ticks_msec() / 1000.0
+ var box_pos = Vector3(0, sin(_time * 4), 0)
+ var line_begin = Vector3(-1, sin(_time * 4), 0)
+ var line_end = Vector3(1, cos(_time * 4), 0)
+
+ DebugDraw3D.draw_box(box_pos, Quaternion.IDENTITY, Vector3(1, 2, 1), Color(0, 1, 0))
+ DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0))
+ DebugDraw2D.set_text("Time", _time)
+ DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn())
+ DebugDraw2D.set_text("FPS", Engine.get_frames_per_second())
+ DebugDraw2D.set_text("delta", delta)
+
+ $HitTest.visible = false
+ $LagTest.visible = false
+ $PlaneOrigin.visible = false
+ $OtherWorld.visible = false
+ %ZDepthTestCube.visible = false
+ return
+
+ $HitTest.visible = true
+ $LagTest.visible = true
+ $PlaneOrigin.visible = true
+ $OtherWorld.visible = true
+ %ZDepthTestCube.visible = true
+
+ # Testing the rendering layers by showing the image from the second camera inside the 2D panel
+ DebugDraw3D.config.geometry_render_layers = 1 if !Input.is_key_pressed(KEY_ALT) else 0b10010
+ $Panel.visible = Input.is_key_pressed(KEY_ALT)
+ DebugDraw2D.custom_canvas = %CustomCanvas if Input.is_key_pressed(KEY_ALT) else null
+
+ # More property toggles
+ DebugDraw3D.config.freeze_3d_render = Input.is_key_pressed(KEY_DOWN)
+ DebugDraw3D.config.visible_instance_bounds = Input.is_key_pressed(KEY_RIGHT)
+
+ # Regenerate meshes
+ if Input.is_action_just_pressed("ui_end"):
+ DebugDraw3D.regenerate_geometry_meshes()
+
+ # Some property toggles
+ if _is_key_just_pressed(KEY_LEFT):
+ DebugDraw3D.config.use_frustum_culling = !DebugDraw3D.config.use_frustum_culling
+ if _is_key_just_pressed(KEY_UP):
+ DebugDraw3D.config.force_use_camera_from_scene = !DebugDraw3D.config.force_use_camera_from_scene
+ if _is_key_just_pressed(KEY_CTRL):
+ if !Engine.is_editor_hint():
+ get_viewport().msaa_3d = Viewport.MSAA_DISABLED if get_viewport().msaa_3d == Viewport.MSAA_4X else Viewport.MSAA_4X
+
+ if !Engine.is_editor_hint():
+ if _is_key_just_pressed(KEY_1):
+ DebugDraw3D.debug_enabled = !DebugDraw3D.debug_enabled
+ if _is_key_just_pressed(KEY_2):
+ DebugDraw2D.debug_enabled = !DebugDraw2D.debug_enabled
+ if _is_key_just_pressed(KEY_3):
+ DebugDrawManager.debug_enabled = !DebugDrawManager.debug_enabled
+
+
+ DebugDraw3D.config.frustum_length_scale = camera_frustum_scale
+
+ # Zones with black borders
+ for z in $Zones.get_children():
+ DebugDraw3D.draw_box_xf(z.global_transform, Color.BLACK)
+
+ # Spheres
+ DebugDraw3D.draw_sphere_xf($Spheres/SphereTransform.global_transform, Color.CRIMSON)
+ if true:
+ var _shd = DebugDraw3D.new_scoped_config().set_hd_sphere(true)
+ DebugDraw3D.draw_sphere_xf($Spheres/SphereHDTransform.global_transform, Color.ORANGE_RED)
+
+ # Delayed spheres
+ if timer_1 < 0:
+ DebugDraw3D.draw_sphere($Spheres/SpherePosition.global_position, 2.0, Color.BLUE_VIOLET, 2.0)
+ var _shd = DebugDraw3D.new_scoped_config().set_hd_sphere(true)
+ DebugDraw3D.draw_sphere($Spheres/SpherePosition.global_position + Vector3.FORWARD * 4, 2.0, Color.CORNFLOWER_BLUE, 2.0)
+ timer_1 = 2
+
+ # Cylinders
+ DebugDraw3D.draw_cylinder($Cylinders/Cylinder1.global_transform, Color.CRIMSON)
+ DebugDraw3D.draw_cylinder(Transform3D(Basis.IDENTITY.scaled(Vector3(1,2,1)), $Cylinders/Cylinder2.global_position), Color.RED)
+ DebugDraw3D.draw_cylinder_ab($"Cylinders/Cylinder3/1".global_position, $"Cylinders/Cylinder3/2".global_position, 0.7)
+
+ # Boxes
+ DebugDraw3D.draw_box_xf($Boxes/Box1.global_transform, Color.MEDIUM_PURPLE)
+ DebugDraw3D.draw_box($Boxes/Box2.global_position, Quaternion.from_euler(Vector3(0, deg_to_rad(45), deg_to_rad(45))), Vector3.ONE, Color.REBECCA_PURPLE)
+ DebugDraw3D.draw_box_xf(Transform3D(Basis(Vector3.UP, PI * 0.25).scaled(Vector3.ONE * 2), $Boxes/Box3.global_position), Color.ROSY_BROWN)
+
+ DebugDraw3D.draw_aabb(AABB($Boxes/AABB_fixed.global_position, Vector3(2, 1, 2)), Color.AQUA)
+ DebugDraw3D.draw_aabb_ab($Boxes/AABB/a.global_position, $Boxes/AABB/b.global_position, Color.DEEP_PINK)
+
+ # Boxes AB
+ DebugDraw3D.draw_arrow($Boxes/BoxAB.global_position, $Boxes/BoxAB/o/up.global_position, Color.GOLD, 0.1, true)
+ DebugDraw3D.draw_box_ab($Boxes/BoxAB/a.global_position, $Boxes/BoxAB/b.global_position, $Boxes/BoxAB/o/up.global_position - $Boxes/BoxAB.global_position, Color.PERU)
+
+ DebugDraw3D.draw_arrow($Boxes/BoxABEdge.global_position, $Boxes/BoxABEdge/o/up.global_position, Color.DARK_RED, 0.1, true)
+ DebugDraw3D.draw_box_ab($Boxes/BoxABEdge/a.global_position, $Boxes/BoxABEdge/b.global_position, $Boxes/BoxABEdge/o/up.global_position - $Boxes/BoxABEdge.global_position, Color.DARK_OLIVE_GREEN, false)
+
+ # Lines
+ var target = $Lines/Target
+ DebugDraw3D.draw_square(target.global_position, 0.5, Color.RED)
+
+ DebugDraw3D.draw_line($"Lines/1".global_position, target.global_position, Color.FUCHSIA)
+ DebugDraw3D.draw_ray($"Lines/3".global_position, (target.global_position - $"Lines/3".global_position).normalized(), 3.0, Color.CRIMSON)
+
+ if timer_3 < 0:
+ DebugDraw3D.draw_line($"Lines/6".global_position, target.global_position, Color.FUCHSIA, 2.0)
+ timer_3 = 2
+
+ # Test UP vector
+ DebugDraw3D.draw_line($"Lines/7".global_position, target.global_position, Color.RED)
+
+ # Lines with Arrow
+ DebugDraw3D.draw_arrow($"Lines/2".global_position, target.global_position, Color.BLUE, 0.5, true)
+ DebugDraw3D.draw_arrow_ray($"Lines/4".global_position, (target.global_position - $"Lines/4".global_position).normalized(), 8.0, Color.LAVENDER, 0.5, true)
+
+ DebugDraw3D.draw_line_hit_offset($"Lines/5".global_position, target.global_position, true, abs(sin(Time.get_ticks_msec() / 1000.0)), 0.25, Color.AQUA)
+
+ # Path
+
+ ## preparing data
+ var points: PackedVector3Array = []
+ var points_below: PackedVector3Array = []
+ var points_below2: PackedVector3Array = []
+ var points_below3: PackedVector3Array = []
+ var points_below4: PackedVector3Array = []
+ var lines_above: PackedVector3Array = []
+
+ for c in $LinePath.get_children():
+ if not c is Node3D:
+ break
+ points.append(c.global_position)
+ points_below.append(c.global_position + Vector3.DOWN)
+ points_below2.append(c.global_position + Vector3.DOWN * 2)
+ points_below3.append(c.global_position + Vector3.DOWN * 3)
+ points_below4.append(c.global_position + Vector3.DOWN * 4)
+
+ for x in points.size()-1:
+ lines_above.append(points[x] + Vector3.UP)
+ lines_above.append(points[x+1] + Vector3.UP)
+
+ ## drawing lines
+ DebugDraw3D.draw_lines(lines_above)
+ DebugDraw3D.draw_line_path(points, Color.BEIGE)
+ DebugDraw3D.draw_points(points_below, DebugDraw3D.POINT_TYPE_SQUARE, 0.2, Color.DARK_GREEN)
+ DebugDraw3D.draw_point_path(points_below2, DebugDraw3D.POINT_TYPE_SQUARE, 0.25, Color.BLUE, Color.TOMATO)
+ DebugDraw3D.draw_arrow_path(points_below3, Color.GOLD, 0.5)
+ if true:
+ var _sl = DebugDraw3D.new_scoped_config().set_thickness(0.05)
+ DebugDraw3D.draw_point_path(points_below4, DebugDraw3D.POINT_TYPE_SPHERE, 0.25, Color.MEDIUM_SEA_GREEN, Color.MEDIUM_VIOLET_RED)
+
+ # Other world
+
+ if true:
+ var _w1 = DebugDraw3D.new_scoped_config().set_viewport(%OtherWorldBox.get_viewport())
+ DebugDraw3D.draw_box_xf(%OtherWorldBox.global_transform.rotated_local(Vector3(1,1,-1).normalized(), wrapf(Time.get_ticks_msec() / 1000.0, 0, TAU)), Color.SANDY_BROWN)
+ DebugDraw3D.draw_box_xf(%OtherWorldBox.global_transform.rotated_local(Vector3(-1,1,-1).normalized(), wrapf(Time.get_ticks_msec() / -1000.0, 0, TAU) - PI/4), Color.SANDY_BROWN)
+
+ # Misc
+ if Engine.is_editor_hint():
+ #for i in 1000:
+ var _a11 = DebugDraw3D.new_scoped_config().set_thickness(0)
+ DebugDraw3D.draw_camera_frustum($Camera, Color.DARK_ORANGE)
+
+ if true:
+ var _s123 = DebugDraw3D.new_scoped_config().set_center_brightness(0.1)
+ DebugDraw3D.draw_arrowhead($Misc/Arrow.global_transform, Color.YELLOW_GREEN)
+
+ DebugDraw3D.draw_square($Misc/Billboard.global_position, 0.5, Color.GREEN)
+
+ DebugDraw3D.draw_position($Misc/Position.global_transform, Color.BROWN)
+
+ DebugDraw3D.draw_gizmo($Misc/GizmoTransform.global_transform, DebugDraw3D.empty_color, true)
+ DebugDraw3D.draw_gizmo($Misc/GizmoOneColor.global_transform, Color.BROWN, true)
+ if true:
+ var _s123 = DebugDraw3D.new_scoped_config().set_center_brightness(0.5).set_no_depth_test(true)
+ DebugDraw3D.draw_gizmo($Misc/GizmoNormal.global_transform.orthonormalized(), DebugDraw3D.empty_color, false)
+
+ var tg : Transform3D = $Grids/Grid.global_transform
+ var tn : Vector3 = $Grids/Grid/Subdivision.transform.origin
+ DebugDraw3D.draw_grid(tg.origin, tg.basis.x, tg.basis.z, Vector2i(int(tn.x*10), int(tn.z*10)), Color.LIGHT_CORAL, false)
+
+ var tn1 = $Grids/GridCentered/Subdivision.transform.origin
+ DebugDraw3D.draw_grid_xf($Grids/GridCentered.global_transform, Vector2i(tn1.x*10, tn1.z*10))
+
+ if true:
+ var _s32 = DebugDraw3D.new_scoped_config().set_thickness(0.05)
+ DebugDraw3D.draw_box_xf($PostProcess.global_transform, Color.SEA_GREEN)
+
+ # 2D
+ DebugDraw2D.config.text_default_size = text_groups_default_font_size
+ DebugDraw2D.config.text_block_offset = text_groups_offset
+ DebugDraw2D.config.text_block_position = text_groups_position
+ DebugDraw2D.config.text_padding = text_groups_padding
+
+ DebugDraw2D.config.text_custom_font = custom_font
+
+ if test_text:
+ _text_tests()
+
+ # Graphs
+ # Enable FPSGraph if not exists
+ _create_graph(&"FPS", true, false, DebugDraw2DGraph.TEXT_CURRENT | DebugDraw2DGraph.TEXT_AVG | DebugDraw2DGraph.TEXT_MAX | DebugDraw2DGraph.TEXT_MIN, &"", DebugDraw2DGraph.SIDE_BOTTOM, DebugDraw2DGraph.POSITION_LEFT_TOP if Engine.is_editor_hint() else DebugDraw2DGraph.POSITION_RIGHT_TOP, Vector2i(200, 80), custom_font)
+ if Engine.is_editor_hint():
+ if DebugDraw2D.get_graph(&"FPS"):
+ DebugDraw2D.get_graph(&"FPS").offset = Vector2i(0, 64)
+
+ # Adding more graphs
+ if test_graphs and DebugDraw2D.debug_enabled:
+ _graph_test()
+ else:
+ _remove_graphs()
+ _upd_graph_params()
+
+ # Lag Test
+ $LagTest.position = $LagTest/RESET.get_animation("RESET").track_get_key_value(0,0) + Vector3(sin(Time.get_ticks_msec() / 100.0) * 2.5, 0, 0)
+ DebugDraw3D.draw_box($LagTest.global_position, Quaternion.IDENTITY, Vector3.ONE * 2.01, Color.CHOCOLATE, true)
+
+ if more_test_cases:
+ for ray in $HitTest/RayEmitter.get_children():
+ ray.set_physics_process_internal(true)
+
+ _more_tests()
+ else:
+ for ray in $HitTest/RayEmitter.get_children():
+ ray.set_physics_process_internal(false)
+
+ if draw_array_of_boxes:
+ _draw_array_of_boxes()
+
+
+func _text_tests():
+ if timer_text < 0:
+ DebugDraw2D.set_text("Some delayed text", "for 2.5s", -1, Color.BLACK, 2.5) # it's supposed to show text for 2.5 seconds
+ timer_text = 5
+
+ DebugDraw2D.set_text("FPS", "%.2f" % Engine.get_frames_per_second(), 0, Color.GOLD)
+
+ DebugDraw2D.begin_text_group("-- First Group --", 2, Color.LIME_GREEN, true, text_groups_title_font_size, text_groups_text_font_size)
+ DebugDraw2D.set_text("Simple text")
+ DebugDraw2D.set_text("Text", "Value", 0, Color.AQUAMARINE)
+ DebugDraw2D.set_text("Text out of order", null, -1, Color.SILVER)
+ DebugDraw2D.begin_text_group("-- Second Group --", 1, Color.BEIGE)
+ DebugDraw2D.set_text("Rendered frames", Engine.get_frames_drawn())
+ DebugDraw2D.end_text_group()
+
+ if text_groups_show_stats or text_groups_show_stats_2d:
+ DebugDraw2D.begin_text_group("-- Stats --", 3, Color.WHEAT)
+
+ var render_stats := DebugDraw3D.get_render_stats()
+ if render_stats && text_groups_show_stats:
+ DebugDraw2D.set_text("Total", render_stats.total_geometry)
+ DebugDraw2D.set_text("Instances", render_stats.instances + render_stats.instances_physics, 1)
+ DebugDraw2D.set_text("Lines", render_stats.lines + render_stats.lines_physics, 2)
+ DebugDraw2D.set_text("Total Visible", render_stats.total_visible, 3)
+ DebugDraw2D.set_text("Visible Instances", render_stats.visible_instances, 4)
+ DebugDraw2D.set_text("Visible Lines", render_stats.visible_lines, 5)
+
+ DebugDraw2D.set_text("---", null, 6)
+
+ DebugDraw2D.set_text("Culling time", "%.2f ms" % (render_stats.total_time_culling_usec / 1000.0), 7)
+ DebugDraw2D.set_text("Filling instances buffer", "%.2f ms" % (render_stats.time_filling_buffers_instances_usec / 1000.0), 8)
+ DebugDraw2D.set_text("Filling lines buffer", "%.2f ms" % (render_stats.time_filling_buffers_lines_usec / 1000.0), 9)
+ DebugDraw2D.set_text("Filling time", "%.2f ms" % (render_stats.total_time_filling_buffers_usec / 1000.0), 10)
+ DebugDraw2D.set_text("Total time", "%.2f ms" % (render_stats.total_time_spent_usec / 1000.0), 11)
+
+ DebugDraw2D.set_text("---", null, 14)
+
+ DebugDraw2D.set_text("Created scoped configs", "%d" % render_stats.created_scoped_configs, 15)
+
+ if text_groups_show_stats && text_groups_show_stats_2d:
+ DebugDraw2D.set_text("----", null, 19)
+
+ var render_stats_2d := DebugDraw2D.get_render_stats()
+ if render_stats_2d && text_groups_show_stats_2d:
+ DebugDraw2D.set_text("Text groups", render_stats_2d.overlay_text_groups, 20)
+ DebugDraw2D.set_text("Text lines", render_stats_2d.overlay_text_lines, 21)
+ DebugDraw2D.set_text("Graphs total", render_stats_2d.overlay_graphs_total, 22)
+ DebugDraw2D.set_text("Graphs enabled", render_stats_2d.overlay_graphs_enabled, 23)
+
+ DebugDraw2D.end_text_group()
+
+ if text_groups_show_hints:
+ DebugDraw2D.begin_text_group("controls", 1024, Color.WHITE, false)
+ if not Engine.is_editor_hint():
+ DebugDraw2D.set_text("WASD QE, LMB", "To move", 0)
+ DebugDraw2D.set_text("Alt: change render layers", DebugDraw3D.config.geometry_render_layers, 1)
+ if not OS.has_feature("web"):
+ DebugDraw2D.set_text("Ctrl: toggle anti-aliasing", "MSAA 4x" if get_viewport().msaa_3d == Viewport.MSAA_4X else "Disabled", 2)
+ DebugDraw2D.set_text("Down: freeze render", DebugDraw3D.config.freeze_3d_render, 3)
+ if Engine.is_editor_hint():
+ DebugDraw2D.set_text("Up: use scene camera", DebugDraw3D.config.force_use_camera_from_scene, 4)
+ DebugDraw2D.set_text("1,2,3: toggle debug", "%s, %s 😐, %s 😏" % [DebugDraw3D.debug_enabled, DebugDraw2D.debug_enabled, DebugDrawManager.debug_enabled], 5)
+ DebugDraw2D.set_text("Left: toggle frustum culling", DebugDraw3D.config.use_frustum_culling, 6)
+ DebugDraw2D.set_text("Right: draw bounds for culling", DebugDraw3D.config.visible_instance_bounds, 7)
+ DebugDraw2D.end_text_group()
+
+
+func _draw_rays_casts():
+ # Line hits render
+ for ray in $HitTest/RayEmitter.get_children():
+ if ray is RayCast3D:
+ ray.force_raycast_update()
+ DebugDraw3D.draw_line_hit(ray.global_position, ray.to_global(ray.target_position), ray.get_collision_point(), ray.is_colliding(), 0.3)
+
+
+func _more_tests():
+ # Delayed line render
+ if true:
+ var _a12 = DebugDraw3D.new_scoped_config().set_thickness(0.035)
+ DebugDraw3D.draw_line($LagTest.global_position + Vector3.UP, $LagTest.global_position + Vector3(0,3,sin(Time.get_ticks_msec() / 50.0)), DebugDraw3D.empty_color, 0.5)
+
+ # Draw plane
+ if true:
+ var _s11 = DebugDraw3D.new_scoped_config().set_thickness(0.02).set_plane_size(10)
+
+ var pl_node: Node3D = $PlaneOrigin
+ var xf: Transform3D = pl_node.global_transform
+ var normal: = xf.basis.y.normalized()
+ var plane = Plane(normal, xf.origin.dot(normal))
+
+ var vp: Viewport = get_viewport()
+ if is_4_2_and_higher:
+ if Engine.is_editor_hint() and Engine.get_singleton(&"EditorInterface").get_editor_viewport_3d(0):
+ vp = Engine.get_singleton(&"EditorInterface").get_editor_viewport_3d(0)
+
+ var cam = vp.get_camera_3d()
+ if cam:
+ var dir = vp.get_camera_3d().project_ray_normal(vp.get_mouse_position())
+ var intersect = plane.intersects_ray(cam.global_position, dir)
+
+ DebugDraw3D.draw_plane(plane, Color.CORAL * Color(1,1,1, 0.4), pl_node.global_position)
+ if is_4_2_and_higher:
+ if intersect and intersect.distance_to(pl_node.global_position) < _s11.get_plane_size() * 0.5:
+ # Need to test different colors on both sides of the plane
+ var col = Color.FIREBRICK if plane.is_point_over(cam.global_position) else Color.AQUAMARINE
+ DebugDraw3D.draw_sphere(intersect, 0.3, col)
+
+
+func _draw_array_of_boxes():
+ # Lots of boxes to check performance..
+ var x_size := 50
+ var y_size := 50
+ var z_size := 3
+ var mul := 1
+ var cubes_max_time := 1.25
+ var cfg = DebugDraw3D.new_scoped_config()
+
+ if draw_1m_boxes:
+ x_size = 100
+ y_size = 100
+ z_size = 100
+ mul = 4
+ cubes_max_time = 60
+
+ if timer_cubes < 0:
+ var _start_time = Time.get_ticks_usec()
+ for x in x_size:
+ for y in y_size:
+ for z in z_size:
+ var size = Vector3.ONE
+ cfg.set_thickness(randf_range(0, 0.1))
+ #var size = Vector3(randf_range(0.1, 100),randf_range(0.1, 100),randf_range(0.1, 100))
+ DebugDraw3D.draw_box(Vector3(x * mul, (-4-z) * mul, y * mul) + global_position, Quaternion.IDENTITY, size, DebugDraw3D.empty_color, false, cubes_max_time)
+ #print("Draw Cubes: %fms" % ((Time.get_ticks_usec() - _start_time) / 1000.0))
+ timer_cubes = cubes_max_time
+
+
+func _graph_test():
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps", true, true, DebugDraw2DGraph.TEXT_CURRENT, &"", DebugDraw2DGraph.SIDE_LEFT, DebugDraw2DGraph.POSITION_RIGHT_TOP)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps2", true, false, DebugDraw2DGraph.TEXT_CURRENT, &"fps", DebugDraw2DGraph.SIDE_BOTTOM, 0, Vector2i(200, 100))
+# warning-ignore:return_value_discarded
+ _create_graph(&"Sin Wave!", false, true, DebugDraw2DGraph.TEXT_CURRENT, &"fps2", DebugDraw2DGraph.SIDE_BOTTOM)
+
+# warning-ignore:return_value_discarded
+ _create_graph(&"randf", false, true, DebugDraw2DGraph.TEXT_AVG, &"", DebugDraw2DGraph.SIDE_LEFT, DebugDraw2DGraph.POSITION_RIGHT_BOTTOM, Vector2i(256, 60), custom_font)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps5", true, true, DebugDraw2DGraph.TEXT_ALL, &"randf", DebugDraw2DGraph.SIDE_TOP)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps6", true, true, DebugDraw2DGraph.TEXT_ALL, &"fps5", DebugDraw2DGraph.SIDE_TOP)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps12", true, true, DebugDraw2DGraph.TEXT_ALL, &"fps5", DebugDraw2DGraph.SIDE_LEFT)
+
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps7", true, false, DebugDraw2DGraph.TEXT_ALL, &"FPS", DebugDraw2DGraph.SIDE_BOTTOM)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps8", true, true, DebugDraw2DGraph.TEXT_ALL, &"", DebugDraw2DGraph.SIDE_TOP, DebugDraw2DGraph.POSITION_LEFT_BOTTOM)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps9", true, false, DebugDraw2DGraph.TEXT_ALL, &"fps8", DebugDraw2DGraph.SIDE_RIGHT)
+# warning-ignore:return_value_discarded
+ _create_graph(&"fps10", true, false, DebugDraw2DGraph.TEXT_ALL, &"fps8", DebugDraw2DGraph.SIDE_TOP)
+ # warning-ignore:return_value_discarded
+ _create_graph(&"fps11", true, true, DebugDraw2DGraph.TEXT_ALL, &"fps9", DebugDraw2DGraph.SIDE_RIGHT)
+ # warning-ignore:return_value_discarded
+ _create_graph(&"fps13", true, true, DebugDraw2DGraph.TEXT_ALL, &"", DebugDraw2DGraph.SIDE_RIGHT)
+ if not DebugDraw2D.get_graph(&"fps13"):
+ return
+
+ DebugDraw2D.get_graph(&"fps13").enabled = false
+
+ # If graphs exists, then more tests are done
+ DebugDraw2D.get_graph(&"Sin Wave!").data_getter = Callable(self, &"_get_sin_wave_for_graph")
+ DebugDraw2D.get_graph(&"Sin Wave!").upside_down =false
+
+ DebugDraw2D.get_graph(&"randf").text_suffix = "utf8 ноль zéro"
+ #DebugDraw2D.get_graph(&"fps9").line_position = DebugDraw2DGraph.LINE_TOP
+ DebugDraw2D.get_graph(&"fps9").offset = Vector2i(0, 0)
+ #DebugDraw2D.get_graph(&"fps11").line_position = DebugDraw2DGraph.LINE_BOTTOM
+ DebugDraw2D.get_graph(&"fps11").offset = Vector2i(16, 0)
+ DebugDraw2D.get_graph(&"fps6").offset = Vector2i(0, 32)
+ DebugDraw2D.get_graph(&"fps").offset = Vector2i(16, 72)
+
+ DebugDraw2D.get_graph(&"fps9").enabled = graph_is_enabled
+ if !Engine.is_editor_hint():
+ DebugDraw2D.get_graph(&"fps").corner = DebugDraw2DGraph.POSITION_LEFT_TOP
+
+ # Just sending random data to the graph
+ DebugDraw2D.graph_update_data(&"randf", randf())
+
+
+func _upd_graph_params():
+ DebugDraw2D.config.graphs_base_offset = graph_offset
+ for g in [&"FPS", &"fps5", &"fps8"]:
+ var graph := DebugDraw2D.get_graph(g) as DebugDraw2DFPSGraph
+ if graph:
+ graph.size = graph_size
+ graph.title_size = graph_title_font_size
+ graph.text_size = graph_text_font_size
+ graph.line_width = graph_line_width
+ graph.text_precision = graph_text_precision
+ graph.buffer_size = graph_buffer_size
+ if Engine.is_editor_hint() or g != &"FPS":
+ graph.frame_time_mode = graph_frame_time_mode
+
+
+func _get_sin_wave_for_graph() -> float:
+ var mul = 4 if Input.is_key_pressed(KEY_END) else 2
+ return sin(Engine.get_frames_drawn() * 0.5) * mul
+
+
+func _remove_graphs():
+ if not test_fps_graph:
+ DebugDraw2D.remove_graph(&"FPS")
+ DebugDraw2D.remove_graph(&"randf")
+ DebugDraw2D.remove_graph(&"fps")
+ DebugDraw2D.remove_graph(&"fps2")
+ DebugDraw2D.remove_graph(&"Sin Wave!")
+ DebugDraw2D.remove_graph(&"fps5")
+ DebugDraw2D.remove_graph(&"fps6")
+ DebugDraw2D.remove_graph(&"fps7")
+ DebugDraw2D.remove_graph(&"fps8")
+ DebugDraw2D.remove_graph(&"fps9")
+ DebugDraw2D.remove_graph(&"fps10")
+ DebugDraw2D.remove_graph(&"fps11")
+ DebugDraw2D.remove_graph(&"fps12")
+ DebugDraw2D.remove_graph(&"fps13")
+
+
+func _create_graph(title, is_fps, show_title, flags, parent := &"", parent_side := DebugDraw2DGraph.SIDE_BOTTOM, pos = DebugDraw2DGraph.POSITION_LEFT_BOTTOM, size := Vector2i(256, 60), font = null) -> DebugDraw2DGraph:
+ var graph := DebugDraw2D.get_graph(title)
+ if !graph:
+ if is_fps:
+ graph = DebugDraw2D.create_fps_graph(title)
+ else:
+ graph = DebugDraw2D.create_graph(title)
+
+ if graph:
+ graph.size = size
+ graph.buffer_size = 50
+ graph.corner = pos
+ graph.show_title = show_title
+ graph.show_text_flags = flags
+ graph.custom_font = font
+ graph.set_parent(parent, parent_side)
+
+ return graph
+
+
+func _ready() -> void:
+ _update_keys_just_press()
+
+ await get_tree().process_frame
+
+ # this check is required for inherited scenes, because an instance of this
+ # script is created first, and then overridden by another
+ if !is_inside_tree():
+ return
+
+
+func _is_key_just_pressed(key):
+ if (button_presses[key] == 1):
+ button_presses[key] = 2
+ return true
+ return false
+
+
+func _update_keys_just_press():
+ var set_key = func (k: Key):
+ if Input.is_key_pressed(k) and button_presses.has(k):
+ if button_presses[k] == 0:
+ return 1
+ else:
+ return button_presses[k]
+ else:
+ return 0
+ button_presses[KEY_LEFT] = set_key.call(KEY_LEFT)
+ button_presses[KEY_UP] = set_key.call(KEY_UP)
+ button_presses[KEY_CTRL] = set_key.call(KEY_CTRL)
+ button_presses[KEY_F1] = set_key.call(KEY_F1)
+ button_presses[KEY_1] = set_key.call(KEY_1)
+ button_presses[KEY_2] = set_key.call(KEY_2)
+ button_presses[KEY_3] = set_key.call(KEY_3)
+
+
+func _update_timers(delta : float):
+ timer_1 -= delta
+ timer_cubes -= delta
+ timer_3 -= delta
+ timer_text -= delta
diff --git a/examples_dd3d/DebugDrawDemoScene.tscn b/examples_dd3d/DebugDrawDemoScene.tscn
new file mode 100644
index 0000000..7e7d953
--- /dev/null
+++ b/examples_dd3d/DebugDrawDemoScene.tscn
@@ -0,0 +1,979 @@
+[gd_scene load_steps=39 format=3 uid="uid://c3sccy6x0ht5j"]
+
+[ext_resource type="Script" path="res://examples_dd3d/DebugDrawDemoScene.gd" id="1"]
+[ext_resource type="FontFile" uid="uid://erdgllynwqkw" path="res://examples_dd3d/Roboto-Bold.ttf" id="2_aedbq"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_camera_movement.gd" id="3_3m1mp"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_music_visualizer.gd" id="4_eq2lt"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_settings_panel.gd" id="5_31v5h"]
+[ext_resource type="Script" path="res://examples_dd3d/demo_web_docs_version_select.gd" id="6_07f7q"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_87638"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_4jfme"]
+sky_material = SubResource("ProceduralSkyMaterial_87638")
+
+[sub_resource type="Environment" id="Environment_38m85"]
+sky = SubResource("Sky_4jfme")
+tonemap_mode = 2
+fog_light_energy = 0.41
+fog_density = 0.0757
+fog_height = 0.5
+fog_height_density = 4.6102
+
+[sub_resource type="Animation" id="9"]
+resource_name = "New Anim"
+length = 1.5
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spatial2:transform")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.7),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.31558, 1)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Spatial5:transform")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1.5801, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Spatial4:transform")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767), Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, -0.791383, 1.53767)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Spatial7:position")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0.4, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(1.33, -0.119, -0.025), Vector3(1.32989, -0.583818, -0.025198)]
+}
+
+[sub_resource type="Animation" id="10"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spatial2:transform")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Spatial5:transform")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Spatial4:transform")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Spatial7:position")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(1.32989, -0.583818, -0.025198)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_nj4nv"]
+_data = {
+"New Anim": SubResource("9"),
+"RESET": SubResource("10")
+}
+
+[sub_resource type="Shader" id="Shader_621vv"]
+code = "shader_type spatial;
+render_mode unshaded;
+
+uniform sampler2D albedo_texture : source_color;
+
+void fragment() {
+ ALBEDO = texture(albedo_texture,UV).rgb;
+}
+"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_ho0aq"]
+render_priority = 0
+shader = SubResource("Shader_621vv")
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_c6mie"]
+material = SubResource("ShaderMaterial_ho0aq")
+size = Vector2(4, 4)
+
+[sub_resource type="Animation" id="Animation_ucqh5"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".:mesh:material:shader_parameter/albedo_texture")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [null]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_cq37i"]
+_data = {
+"RESET": SubResource("Animation_ucqh5")
+}
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_tigpa"]
+radius = 0.395
+height = 1.825
+
+[sub_resource type="BoxMesh" id="BoxMesh_b14rm"]
+
+[sub_resource type="SphereShape3D" id="4"]
+radius = 1.0
+
+[sub_resource type="StandardMaterial3D" id="5"]
+transparency = 1
+albedo_color = Color(0.54902, 0.54902, 0.729412, 0.403922)
+emission_enabled = true
+emission = Color(0.752941, 0.741176, 0.862745, 1)
+
+[sub_resource type="Animation" id="6"]
+resource_name = "New Anim"
+length = 3.0
+loop_mode = 1
+tracks/0/type = "rotation_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("RayEmitter")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, 0, 0, 0, 1, 1.3, 1, 1.31237e-06, -9.55543e-07, -2.2333e-06, 1, 2.3, 1, -0.158418, 0.0315871, 0.980558, -0.111409)
+tracks/1/type = "position_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("RayEmitter")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, -1.03574, 2.47907, -0.819963, 0.5, 1, 0.914907, 1.78507, -0.103575, 1.3, 1, 0.00863326, 2.47907, -0.595551, 2.3, 1, 1.00051, 1.4046, 1.02585)
+
+[sub_resource type="Animation" id="7"]
+length = 0.001
+tracks/0/type = "position_3d"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("RayEmitter")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = PackedFloat32Array(0, 1, -1.03574, 2.47907, -0.819963)
+tracks/1/type = "rotation_3d"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("RayEmitter")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = PackedFloat32Array(0, 1, 0, 0, 0, 1)
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_vh8ml"]
+_data = {
+"New Anim": SubResource("6"),
+"RESET": SubResource("7")
+}
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_rbfyn"]
+transparency = 1
+cull_mode = 2
+shading_mode = 0
+albedo_color = Color(0.215686, 0.215686, 0.215686, 0.764706)
+
+[sub_resource type="QuadMesh" id="QuadMesh_1t0id"]
+material = SubResource("StandardMaterial3D_rbfyn")
+orientation = 1
+
+[sub_resource type="StandardMaterial3D" id="1"]
+shading_mode = 0
+albedo_color = Color(0.533333, 0.105882, 0.105882, 1)
+
+[sub_resource type="Animation" id="8"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(7, -2, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_a7f1a"]
+_data = {
+"RESET": SubResource("8")
+}
+
+[sub_resource type="Shader" id="Shader_3cmiq"]
+code = "shader_type spatial;
+render_mode unshaded;
+
+uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
+
+void fragment() {
+ vec4 col = texture(screen_texture, SCREEN_UV);
+ ALBEDO = col.brg;
+ ALPHA = col.a;
+}
+"
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_t3isk"]
+render_priority = 0
+shader = SubResource("Shader_3cmiq")
+
+[sub_resource type="BoxMesh" id="BoxMesh_0xv07"]
+material = SubResource("ShaderMaterial_t3isk")
+
+[sub_resource type="Gradient" id="Gradient_tup4c"]
+offsets = PackedFloat32Array(0.00471698, 0.316038, 0.646226, 1)
+colors = PackedColorArray(0, 0.0156863, 1, 1, 0.0988327, 1, 0.122977, 1, 1, 0.111986, 0.118936, 1, 0, 0.0156863, 1, 1)
+
+[sub_resource type="Animation" id="Animation_n750a"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("../MusicPlayer:stream")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [null]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_0ity1"]
+_data = {
+"RESET": SubResource("Animation_n750a")
+}
+
+[sub_resource type="Theme" id="3"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_oj5gf"]
+content_margin_top = 5.0
+content_margin_bottom = 7.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_boyhr"]
+content_margin_left = 5.0
+content_margin_top = 5.0
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0.0705882, 0.0705882, 0.0705882, 0.784314)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[node name="DebugDrawDemoScene" type="Node3D"]
+process_priority = 1
+script = ExtResource("1")
+custom_font = ExtResource("2_aedbq")
+text_groups_position = 2
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
+visible = false
+directional_shadow_max_distance = 200.0
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_38m85")
+
+[node name="Camera" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.953191, 0.30237, 0, -0.30237, 0.953191, -6.988, 10.986, 29.2059)
+cull_mask = 1
+current = true
+fov = 53.0
+far = 100.0
+script = ExtResource("3_3m1mp")
+
+[node name="Panel" type="PanelContainer" parent="."]
+visible = false
+custom_minimum_size = Vector2(300, 300)
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_top = -300.0
+offset_right = 300.0
+grow_vertical = 0
+
+[node name="ViewportContainer" type="SubViewportContainer" parent="Panel"]
+layout_mode = 2
+
+[node name="Viewport" type="SubViewport" parent="Panel/ViewportContainer"]
+handle_input_locally = false
+size = Vector2i(300, 300)
+render_target_update_mode = 0
+
+[node name="CameraLayer2_5" type="Camera3D" parent="Panel/ViewportContainer/Viewport"]
+transform = Transform3D(1, 0, 0, 0, 0.34202, 0.939693, 0, -0.939693, 0.34202, -3.988, 39.474, 14.053)
+cull_mask = 2
+current = true
+fov = 38.8
+near = 2.63
+far = 52.5
+
+[node name="Zones" type="Node3D" parent="."]
+
+[node name="Spheres" type="Node3D" parent="Zones"]
+transform = Transform3D(8.3761, 0, 0, 0, 4.89771, 0, 0, 0, 9.36556, -11.1864, 0.645876, -7.86506)
+
+[node name="Path" type="Node3D" parent="Zones"]
+transform = Transform3D(5.95153, 0, 0, 0, 7.71864, 0, 0, 0, 6.31617, 0.184938, 1.12881, -7.18731)
+
+[node name="Boxes" type="Node3D" parent="Zones"]
+transform = Transform3D(10.0513, 0, 0, 0, 5.99877, 0, 0, 0, 12.1174, -16.0257, -0.206735, 6.27643)
+
+[node name="Misc" type="Node3D" parent="Zones"]
+transform = Transform3D(4.38886, 0, 0, 0, 2.72083, 0, 0, 0, 7.17107, -5.69728, -0.206735, 4.4244)
+
+[node name="Cylinders" type="Node3D" parent="Zones"]
+transform = Transform3D(9.78549, 0, 0, 0, 4.20302, 0, 0, 0, 5.62455, -23.6827, -0.015712, -6.19233)
+
+[node name="Lines" type="Node3D" parent="Zones"]
+transform = Transform3D(10.7186, 0, 0, 0, 3.9777, 0, 0, 0, 7.05487, 10.6302, 1.91174, -7.11416)
+
+[node name="Label3D" type="Label3D" parent="."]
+transform = Transform3D(1, -1.93359e-07, -8.48396e-08, -1.17881e-07, 1, 5.96046e-08, 6.22839e-09, 0, 1, 0, 0, 0)
+visible = false
+pixel_size = 0.0025
+billboard = 1
+double_sided = false
+modulate = Color(0, 0, 0, 1)
+outline_modulate = Color(1, 1, 1, 1)
+text = "TestTestTestTest
+TestTestTest"
+font_size = 80
+
+[node name="LinesAnim" type="AnimationPlayer" parent="."]
+root_node = NodePath("../LinePath")
+libraries = {
+"": SubResource("AnimationLibrary_nj4nv")
+}
+autoplay = "New Anim"
+
+[node name="LinePath" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.0543, -8)
+
+[node name="Spatial" type="Node3D" parent="LinePath"]
+
+[node name="Spatial2" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1)
+
+[node name="Spatial3" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.462435, 0, 3)
+
+[node name="Spatial4" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.443643, 0, 1.53767)
+
+[node name="Spatial5" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -1, 1)
+
+[node name="Spatial6" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, -1)
+
+[node name="Spatial7" type="Node3D" parent="LinePath"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.32989, -0.583818, -0.025198)
+
+[node name="Cylinders" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -23.5266, 4.76837e-07, -5.82213)
+
+[node name="Cylinder1" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1.20775, 0.591481, -3.4521e-07, 0.554162, -1.12986, 0.858242, 0.208031, -0.424147, -2.28622, -3.03832, 0, -0.377882)
+
+[node name="Cylinder2" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.234978, -0.4237, 0.332998)
+
+[node name="Cylinder3" type="Node3D" parent="Cylinders"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.35527, -0.655492, -0.352802)
+
+[node name="1" type="Node3D" parent="Cylinders/Cylinder3"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.419773, -2.38419e-07, -1.40591)
+
+[node name="2" type="Node3D" parent="Cylinders/Cylinder3"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.01018, 0.486778, 1.32635)
+
+[node name="Spheres" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.1201, 0.166728, -7.893)
+
+[node name="SphereTransform" type="Node3D" parent="Spheres"]
+transform = Transform3D(3.018, 0, 0, 0, 0.945452, -3.30182, 0, 1.04515, 2.98686, -2.14465, 4.76837e-07, 2.11952)
+
+[node name="SphereHDTransform" type="Node3D" parent="Spheres"]
+transform = Transform3D(1.26984, 1.16629, -2.42095, 0.098772, 0.80937, 4.21576, -2.65493, 0.587941, -1.00109, -2.13175, 4.76837e-07, -2.62531)
+
+[node name="SpherePosition" type="Node3D" parent="Spheres"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.76745, 0.458486, 1.95921)
+
+[node name="Boxes" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15.2493, 0, 6.42043)
+
+[node name="Box1" type="Node3D" parent="Boxes"]
+transform = Transform3D(2.90583, -0.000527017, -5.34615, 0.00469241, 3.92788, 0.0141019, 0.556318, -0.0303774, 1.91619, -0.961557, 0, -3.78672)
+rotation_edit_mode = 2
+
+[node name="Box2" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.531922, -1.34723, 1.44924)
+
+[node name="Box3" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.34837, -1.08298, 4.36414)
+
+[node name="AABB_fixed" type="Node3D" parent="Boxes"]
+transform = Transform3D(0.834492, 0, -0.551019, 0, 1, 0, 0.55102, 0, 0.834493, -3.71325, -1.03995, 0.470324)
+
+[node name="AABB" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.99963, -0.869998, 0.205034)
+
+[node name="a" type="Node3D" parent="Boxes/AABB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, 1.48526, -1.45318, 1.96619)
+
+[node name="b" type="Node3D" parent="Boxes/AABB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, -1.24128, 1.47773, -2.13102)
+
+[node name="BoxAB" type="Node3D" parent="Boxes"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.66169, -2.2624, 4.04042)
+
+[node name="a" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, 0.556136, -0.666145, 0.951601)
+
+[node name="b" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.864099, 0.258702, 0.431747, -1.49012e-08, 0.857796, -0.51399, -0.503322, 0.444139, 0.741221, -0.548804, 0.715255, -0.942184)
+
+[node name="o" type="Node3D" parent="Boxes/BoxAB"]
+transform = Transform3D(0.826805, 0.360538, 0.431748, -0.102949, 0.851596, -0.51399, -0.552988, 0.380522, 0.741221, 0, 0, 0)
+metadata/_edit_group_ = true
+
+[node name="up" type="Node3D" parent="Boxes/BoxAB/o"]
+transform = Transform3D(1, -1.49012e-08, 0, -1.04308e-07, 1, 0, 0, 0, 1, 0, 0.553809, -0.331842)
+
+[node name="BoxABEdge" type="Node3D" parent="Boxes"]
+transform = Transform3D(0.965926, -0.0669873, -0.25, 0, 0.965926, -0.258819, 0.258819, 0.25, 0.933013, 0.348115, -1.30239, 4.88007)
+
+[node name="a" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(0.241143, 0.650584, 0.720132, -0.123077, 0.756539, -0.642262, -0.962654, 0.066246, 0.262507, 0.384618, -0.635015, 0.0956135)
+
+[node name="b" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(0.241143, 0.650584, 0.720133, -0.123077, 0.756539, -0.642261, -0.962654, 0.0662459, 0.262507, -0.287622, 0.997905, -0.144578)
+
+[node name="o" type="Node3D" parent="Boxes/BoxABEdge"]
+transform = Transform3D(1, 1.49012e-08, 2.98023e-08, 7.45058e-09, 1, -1.49012e-08, -1.49012e-08, -1.49012e-08, 1, 0, 0, 0)
+metadata/_edit_group_ = true
+
+[node name="up" type="Node3D" parent="Boxes/BoxABEdge/o"]
+transform = Transform3D(1, -7.45058e-09, 0, -7.45058e-09, 1, 0, 2.98023e-08, -1.49012e-08, 1, -9.53674e-07, 0.6, 0)
+
+[node name="OtherWorld" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.53219, -2.5, 5.30229)
+mesh = SubResource("PlaneMesh_c6mie")
+skeleton = NodePath("")
+
+[node name="RESET" type="AnimationPlayer" parent="OtherWorld"]
+libraries = {
+"": SubResource("AnimationLibrary_cq37i")
+}
+
+[node name="SubViewport" type="SubViewport" parent="OtherWorld"]
+own_world_3d = true
+render_target_update_mode = 4
+
+[node name="Camera3D" type="Camera3D" parent="OtherWorld/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 7.25557)
+current = true
+far = 5.0
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="OtherWorld/SubViewport"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 5.72253)
+mesh = SubResource("CapsuleMesh_tigpa")
+
+[node name="OtherWorldBox" type="Node3D" parent="OtherWorld/SubViewport"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.57063, 0.6, 5.72253)
+
+[node name="Misc" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.68259, 0, 4.46741)
+
+[node name="Billboard" type="Node3D" parent="Misc"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.403353, -0.331599, 2.22542)
+
+[node name="Arrow" type="Node3D" parent="Misc"]
+transform = Transform3D(0.802141, -0.286294, -0.524028, -0.539546, 0.0285125, -0.841473, 0.25585, 0.957718, -0.131597, -0.475607, -0.670307, 2.30581)
+
+[node name="Position" type="Node3D" parent="Misc"]
+transform = Transform3D(1.51514, 0.589536, 1.00858, -1.34875, 0.662262, 1.133, 0, -0.462445, 2.90833, 0.853743, 0.0843356, -1.73676)
+
+[node name="GizmoNormal" type="Node3D" parent="Misc"]
+transform = Transform3D(0.965926, 0, -0.258819, 0, 1, 0, 0.258819, 0, 0.965926, 0.890203, -0.306246, 0.356159)
+
+[node name="ZDepthTestCube" type="MeshInstance3D" parent="Misc/GizmoNormal"]
+unique_name_in_owner = true
+transform = Transform3D(0.591801, 0, 4.47035e-08, 0, 0.591801, 0, -4.47035e-08, 0, 0.591801, 0, 0, 0)
+mesh = SubResource("BoxMesh_b14rm")
+
+[node name="GizmoTransform" type="Node3D" parent="Misc"]
+transform = Transform3D(0.879881, 0.248446, -0.405072, -0.346604, 0.918688, -0.189411, 0.325077, 0.307059, 0.894449, -0.838587, -0.458, -0.176491)
+
+[node name="GizmoOneColor" type="Node3D" parent="Misc"]
+transform = Transform3D(0.385568, 0.0415614, 0.921743, 0.082879, 0.993386, -0.0794599, -0.91895, 0.107031, 0.379573, -0.838587, -0.139425, -1.93055)
+
+[node name="HitTest" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.724359, -1.03227, 7.85404)
+
+[node name="StaticBody" type="StaticBody3D" parent="HitTest"]
+
+[node name="CollisionShape" type="CollisionShape3D" parent="HitTest/StaticBody"]
+shape = SubResource("4")
+
+[node name="CSGSphere" type="CSGSphere3D" parent="HitTest/StaticBody"]
+radius = 1.0
+radial_segments = 16
+rings = 10
+material = SubResource("5")
+
+[node name="RayEmitter" type="Node3D" parent="HitTest"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.03574, 2.47907, -0.819963)
+
+[node name="RayCast" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.732104, 0, -0.814761)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast2" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.684873, 0, -0.791145)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast3" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.708488, 0, 0.543175)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast4" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.708489, 0, 0.566791)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast5" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(0.974217, -0.225614, 0, 0.225614, 0.974217, 0, 0, 0, 1, -0.447564, 0, -0.259778)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayCast6" type="RayCast3D" parent="HitTest/RayEmitter"]
+transform = Transform3D(0.935992, 0.352021, 0, -0.352021, 0.935992, 0, 0, 0, 1, 0.35227, -0.245904, -0.25849)
+enabled = false
+target_position = Vector3(0, -3.464, 0)
+
+[node name="RayEmitterAnimationPlayer" type="AnimationPlayer" parent="HitTest"]
+unique_name_in_owner = true
+libraries = {
+"": SubResource("AnimationLibrary_vh8ml")
+}
+autoplay = "New Anim"
+
+[node name="Grids" type="Node3D" parent="."]
+transform = Transform3D(0.707106, 0, -0.707108, 0, 1, 0, 0.707108, 0, 0.707106, 0.730597, -2.5, 2.76274)
+
+[node name="GridCentered" type="Node3D" parent="Grids"]
+transform = Transform3D(1.74492, 0.723785, -1.74493, -1.24976, -7.72562e-08, -1.24975, -1.74493, 0.723783, 1.74493, 1.74919, -0.0010004, 1.75466)
+rotation_edit_mode = 2
+
+[node name="Subdivision" type="Node3D" parent="Grids/GridCentered"]
+transform = Transform3D(1, -6.03961e-14, -2.68221e-07, 3.55271e-13, 1, 1.42109e-14, -1.19209e-07, 1.1724e-13, 1, -0.2, 4.76837e-07, 0.4)
+
+[node name="Grid" type="Node3D" parent="Grids"]
+transform = Transform3D(5, 0, 2.38419e-07, 0, 1, 0, -2.38419e-07, 0, 5, 0, 0, 0)
+
+[node name="Subdivision" type="Node3D" parent="Grids/Grid"]
+transform = Transform3D(1, 0, -2.98023e-08, 0, 0.999999, 1.90735e-05, 0, 4.65661e-10, 0.999999, 1, 0, 1)
+
+[node name="PlaneOrigin" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 11.0482, 7.33669, -13.1715)
+mesh = SubResource("QuadMesh_1t0id")
+
+[node name="Lines" type="Node3D" parent="."]
+transform = Transform3D(1.51514, 0.589536, 1.00858, -1.34875, 0.662262, 1.133, 0, -0.462445, 2.90833, 10.2488, -0.331599, -10.3326)
+
+[node name="1" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 9.40939e-07, 1, 0, -2.76085e-08, -1.49012e-08, 1, -1.46213, -4.03317, 0.61692)
+
+[node name="2" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 9.40939e-07, 1, 0, -2.76085e-08, -1.49012e-08, 1, -1.01875, -1.79584, -0.163045)
+
+[node name="3" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 6.87561e-07, 1, 0, -2.87275e-08, -1.49012e-08, 1, -0.1559, -0.407045, 0.0523388)
+
+[node name="4" type="Node3D" parent="Lines"]
+transform = Transform3D(1, 6.61592e-09, 2.23038e-08, 4.9239e-07, 1, 0, -3.40677e-08, -1.49012e-08, 1, 1.18591, 1.8987, 0.301906)
+
+[node name="5" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998871, -0.0207882, -0.0355643, 0.0855375, -0.5714, -2.68836, 0.0136011, -0.249864, 0.572532, 1.43126, 0.26242, 1.92347)
+
+[node name="6" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998872, -0.0207882, -0.0355643, 0.085537, -0.5714, -2.68836, 0.0136012, -0.249864, 0.572533, 1.43441, 1.50606, 1.20028)
+
+[node name="7" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998873, -0.0207882, -0.0355641, 0.0855357, -0.5714, -2.68836, 0.0136014, -0.249864, 0.572533, 0.0511096, -1.3236, 1.06745)
+
+[node name="8" type="Node3D" parent="Lines"]
+transform = Transform3D(-0.998873, -0.0207882, -0.0355641, 0.0855353, -0.5714, -2.68836, 0.0136016, -0.249864, 0.572533, -1.01372, -3.80486, 1.25019)
+
+[node name="Target" type="Node3D" parent="Lines"]
+transform = Transform3D(1, -2.7352e-06, 2.60722e-07, 4.10378e-06, 1, 0, -4.28605e-07, -1.49012e-08, 1, -0.69134, 0.176475, 1.30597)
+
+[node name="LagTest" type="CSGBox3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, -2, 0)
+size = Vector3(2, 2, 2)
+material = SubResource("1")
+
+[node name="RESET" type="AnimationPlayer" parent="LagTest"]
+libraries = {
+"": SubResource("AnimationLibrary_a7f1a")
+}
+
+[node name="PostProcess" type="MeshInstance3D" parent="."]
+transform = Transform3D(-2.18557e-07, 0, 1.5, 0, 5, 0, -5, 0, -6.55671e-08, 16, 0, 0)
+mesh = SubResource("BoxMesh_0xv07")
+skeleton = NodePath("../Lines")
+
+[node name="MusicVisualizer" type="VBoxContainer" parent="."]
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 50.0
+offset_bottom = 50.0
+script = ExtResource("4_eq2lt")
+colors = SubResource("Gradient_tup4c")
+
+[node name="OpenFile" type="Button" parent="MusicVisualizer"]
+layout_mode = 2
+size_flags_horizontal = 0
+text = "Open music"
+
+[node name="RESET" type="AnimationPlayer" parent="MusicVisualizer"]
+root_node = NodePath("../OpenFile")
+libraries = {
+"": SubResource("AnimationLibrary_0ity1")
+}
+
+[node name="MusicPlayer" type="AudioStreamPlayer" parent="MusicVisualizer"]
+unique_name_in_owner = true
+autoplay = true
+bus = &"MusicAnalyzer"
+
+[node name="VBox" type="VBoxContainer" parent="MusicVisualizer"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MusicVisualizer/VBox"]
+layout_mode = 2
+
+[node name="VolumeSlider" type="HSlider" parent="MusicVisualizer/VBox/HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 1.0
+step = 0.01
+value = 0.1
+
+[node name="MuteMaster" type="CheckBox" parent="MusicVisualizer/VBox/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+button_pressed = true
+text = "Mute"
+
+[node name="AudioVisualizer" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.2, 0, 0, 0, 5, 0, 0, 0, 0.2, -5.31036, -1.422, 14.14)
+
+[node name="CustomCanvas" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 3
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -545.0
+offset_top = 46.0
+offset_right = -37.0
+offset_bottom = 638.0
+grow_horizontal = 0
+mouse_filter = 2
+metadata/_edit_lock_ = true
+
+[node name="Settings" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme = SubResource("3")
+script = ExtResource("5_31v5h")
+switch_to_scene = "res://examples_dd3d/DebugDrawDemoSceneCS.tscn"
+metadata/_edit_lock_ = true
+
+[node name="HBox" type="HBoxContainer" parent="Settings"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -497.0
+offset_top = -372.0
+offset_right = -10.0006
+offset_bottom = -10.0
+grow_horizontal = 0
+grow_vertical = 0
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Settings/HBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+
+[node name="VersionBlock" type="HBoxContainer" parent="Settings/HBox/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+script = ExtResource("6_07f7q")
+
+[node name="Label" type="Label" parent="Settings/HBox/VBoxContainer/VersionBlock"]
+layout_mode = 2
+size_flags_horizontal = 10
+theme_override_font_sizes/font_size = 13
+text = "Demo version:"
+
+[node name="OptionButton" type="OptionButton" parent="Settings/HBox/VBoxContainer/VersionBlock"]
+layout_mode = 2
+size_flags_horizontal = 8
+theme_override_font_sizes/font_size = 13
+item_count = 1
+popup/item_0/text = "1.0.0"
+popup/item_0/id = 0
+
+[node name="Label" type="Label" parent="Settings/HBox/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+theme_override_styles/normal = SubResource("StyleBoxEmpty_oj5gf")
+text = "GDScript example"
+horizontal_alignment = 2
+metadata/_edit_use_anchors_ = true
+
+[node name="VBox" type="VBoxContainer" parent="Settings/HBox"]
+layout_mode = 2
+alignment = 2
+
+[node name="HideShowPanelButton" type="Button" parent="Settings/HBox/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+theme_override_font_sizes/font_size = 13
+text = "Hide panel"
+
+[node name="SettingsPanel" type="PanelContainer" parent="Settings/HBox/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+theme_override_styles/panel = SubResource("StyleBoxFlat_boyhr")
+
+[node name="VBox" type="VBoxContainer" parent="Settings/HBox/VBox/SettingsPanel"]
+layout_mode = 2
+size_flags_horizontal = 3
+alignment = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "Common:"
+
+[node name="HBox3" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox3"]
+layout_mode = 2
+text = "Thickness "
+
+[node name="ThicknessSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox3"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 0.5
+step = 0.001
+value = 0.05
+
+[node name="HBox5" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox5"]
+layout_mode = 2
+text = "Frustum Scale"
+
+[node name="FrustumScaleSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox5"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+max_value = 1.0
+step = 0.001
+value = 0.5
+
+[node name="UpdateInPhysics" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Update in physics (15 Ticks) *"
+
+[node name="Label2" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "FPS Graph:"
+
+[node name="FPSEnabled" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+button_pressed = true
+text = "FPS Graph enabled"
+
+[node name="FPSMS" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+button_pressed = true
+text = "FPS Graph ms or FPS"
+
+[node name="HBox" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox"]
+layout_mode = 2
+text = "Width "
+
+[node name="WidthSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+min_value = 50.0
+max_value = 1000.0
+value = 200.0
+
+[node name="HBox2" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox2"]
+layout_mode = 2
+text = "BufferSize"
+
+[node name="BufferSlider" type="HSlider" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox2"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+min_value = 50.0
+max_value = 1000.0
+value = 128.0
+
+[node name="Label3" type="Label" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.792157, 0.792157, 0.792157, 1)
+text = "Misc:"
+
+[node name="ShowStats" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Show debug stats"
+
+[node name="HBox4" type="HBoxContainer" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+layout_mode = 2
+
+[node name="DrawBoxes" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox4"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Draw an array of boxes"
+
+[node name="Draw1MBoxes" type="CheckBox" parent="Settings/HBox/VBox/SettingsPanel/VBox/HBox4"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Draw 1 Million boxes, otherwise 7500pcs."
+text = "1M"
+
+[node name="SwitchLang" type="Button" parent="Settings/HBox/VBox/SettingsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Switch to C#"
+
+[connection signal="pressed" from="MusicVisualizer/OpenFile" to="MusicVisualizer" method="_pressed"]
+[connection signal="value_changed" from="MusicVisualizer/VBox/HBoxContainer/VolumeSlider" to="MusicVisualizer" method="_on_volume_slider_value_changed"]
+[connection signal="toggled" from="MusicVisualizer/VBox/HBoxContainer/MuteMaster" to="MusicVisualizer" method="_on_mute_master_toggled"]
+[connection signal="pressed" from="Settings/HBox/VBox/HideShowPanelButton" to="Settings" method="_on_hide_show_panel_pressed"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox3/ThicknessSlider" to="Settings" method="_on_thickness_slider_value_changed"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox5/FrustumScaleSlider" to="Settings" method="_on_frustum_scale_slider_value_changed"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/UpdateInPhysics" to="Settings" method="_on_update_in_physics_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/FPSEnabled" to="Settings" method="_on_CheckBox_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/FPSMS" to="Settings" method="_on_FPSMS_toggled"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox/WidthSlider" to="Settings" method="_on_width_slider_value_changed"]
+[connection signal="value_changed" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox2/BufferSlider" to="Settings" method="_on_buffer_slider_value_changed"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/ShowStats" to="Settings" method="_on_show_stats_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox4/DrawBoxes" to="Settings" method="_on_draw_boxes_toggled"]
+[connection signal="toggled" from="Settings/HBox/VBox/SettingsPanel/VBox/HBox4/Draw1MBoxes" to="Settings" method="_on_draw_1m_boxes_toggled"]
+[connection signal="pressed" from="Settings/HBox/VBox/SettingsPanel/VBox/SwitchLang" to="Settings" method="_on_Button_pressed"]
diff --git a/examples_dd3d/DebugDrawDemoSceneCS.cs b/examples_dd3d/DebugDrawDemoSceneCS.cs
new file mode 100644
index 0000000..a0bbad8
--- /dev/null
+++ b/examples_dd3d/DebugDrawDemoSceneCS.cs
@@ -0,0 +1,871 @@
+
+using Godot;
+using System;
+using System.Collections.Generic;
+
+[Tool]
+public partial class DebugDrawDemoSceneCS : Node3D
+{
+ Random random = new Random();
+
+ [Export] Font custom_font;
+ [Export] bool zylann_example = false;
+ [Export] bool update_in_physics = false;
+ [Export] bool test_text = true;
+ [Export] bool test_graphs = false;
+ [Export] bool test_fps_graph = true;
+ [Export] bool more_test_cases = true;
+ [Export] bool draw_array_of_boxes = false;
+ [Export] bool draw_1m_boxes = false;
+ [Export(PropertyHint.Range, "0, 5, 0.001")] float debug_thickness = 0.1f;
+ [Export(PropertyHint.Range, "0, 1")] float camera_frustum_scale = 0.9f;
+
+ [ExportGroup("Text groups", "text_groups")]
+ [Export] bool text_groups_show_hints = true;
+ [Export] bool text_groups_show_stats = true;
+ [Export] bool text_groups_show_stats_2d = true;
+ [Export] DebugDraw2DConfig.BlockPosition text_groups_position = DebugDraw2DConfig.BlockPosition.LeftTop;
+ [Export] Vector2I text_groups_offset = new Vector2I(8, 8);
+ [Export] Vector2I text_groups_padding = new Vector2I(3, 1);
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_default_font_size = 15;
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_title_font_size = 20;
+ [Export(PropertyHint.Range, "1, 100")] int text_groups_text_font_size = 17;
+
+ [ExportGroup("Graphs", "graph")]
+ [Export] Vector2I graph_offset = new Vector2I(8, 8);
+ [Export] Vector2I graph_size = new Vector2I(200, 80);
+ [Export(PropertyHint.Range, "1, 100")] int graph_title_font_size = 14;
+ [Export(PropertyHint.Range, "1, 100")] int graph_text_font_size = 12;
+ [Export(PropertyHint.Range, "0, 64")] int graph_text_precision = 1;
+ [Export(PropertyHint.Range, "1, 32")] float graph_line_width = 1.0f;
+ [Export(PropertyHint.Range, "1, 512")] int graph_buffer_size = 128;
+ [Export] bool graph_frame_time_mode = true;
+ [Export] bool graph_is_enabled = true;
+
+ Dictionary button_presses = new Dictionary() {
+ { Key.Left, 0 },
+ { Key.Up, 0 },
+ { Key.Ctrl, 0 },
+ { Key.F1, 0 },
+ { Key.Key1, 0 },
+ { Key.Key2, 0 },
+ { Key.Key3, 0 },
+ };
+
+ double timer_1 = 0.0;
+ double timer_cubes = 0.0;
+ double timer_3 = 0.0;
+ double timer_text = 0.0;
+
+ // TODO remove after moving to 4.2
+ bool is_4_2_and_higher = ((int)Engine.GetVersionInfo()["major"]) >= 4 && ((int)Engine.GetVersionInfo()["minor"]) >= 2;
+
+ Node3D dHitTest;
+ CsgBox3D dLagTest;
+ PanelContainer dPanel;
+ Node3D dZones;
+ Node3D dSpherePosition;
+ Node3D dSphereTransform;
+ Node3D dSphereHDTransform;
+ Node3D dAABB;
+ Node3D dAABB_fixed;
+ Node3D dBox1;
+ Node3D dBox2;
+ Node3D dBox3;
+ Node3D dBoxAB;
+ Node3D dBoxABa;
+ Node3D dBoxABb;
+ Node3D dBoxABup;
+ Node3D dBoxABEdge;
+ Node3D dBoxABEdgea;
+ Node3D dBoxABEdgeb;
+ Node3D dBoxABEdgeup;
+ Node3D dLines_1;
+ Node3D dLines_2;
+ Node3D dLines_3;
+ Node3D dLines_4;
+ Node3D dLines_5;
+ Node3D dLines_6;
+ Node3D dLines_7;
+ Node3D dLines_8;
+ Node3D dLines_Target;
+ Node3D dLinePath;
+ Node3D dCylinder1;
+ Node3D dCylinder2;
+ Node3D dCylinder3a;
+ Node3D dCylinder3b;
+
+ MeshInstance3D dPlaneOrigin;
+ MeshInstance3D pZDepthTestCube;
+
+ MeshInstance3D dOtherWorld;
+ SubViewport dOtherWorldViewport;
+ Node3D dOtherWorldBox;
+
+ Control dCustomCanvas;
+ Node3D dMisc_Arrow;
+ Camera3D dCamera;
+ Node3D dMisc_Billboard;
+ Node3D dMisc_Position;
+ Node3D dMisc_GizmoTransform;
+ Node3D dMisc_GizmoNormal;
+ Node3D dMisc_GizmoOneColor;
+
+ Node3D dMisc_Grids_Grid;
+ Node3D dMisc_Grids_Grid_Subdivision;
+ Node3D dMisc_Grids_GridCentered_Subdivision;
+ Node3D dMisc_Grids_GridCentered;
+
+ MeshInstance3D dPostProcess;
+ AnimationPlayer dLagTest_RESET;
+ Node3D dHitTest_RayEmitter;
+
+
+ public override async void _Ready()
+ {
+ dHitTest = GetNode("HitTest");
+ dLagTest = GetNode("LagTest");
+ dPanel = GetNode("Panel");
+ dZones = GetNode("Zones");
+ dSpherePosition = GetNode("Spheres/SpherePosition");
+ dSphereTransform = GetNode("Spheres/SphereTransform");
+ dSphereHDTransform = GetNode("Spheres/SphereHDTransform");
+ dAABB = GetNode("Boxes/AABB");
+ dAABB_fixed = GetNode("Boxes/AABB_fixed");
+ dBox1 = GetNode("Boxes/Box1");
+ dBox2 = GetNode("Boxes/Box2");
+ dBox3 = GetNode("Boxes/Box3");
+ dBoxAB = GetNode("Boxes/BoxAB");
+ dBoxABa = GetNode("Boxes/BoxAB/a");
+ dBoxABb = GetNode("Boxes/BoxAB/b");
+ dBoxABup = GetNode("Boxes/BoxAB/o/up");
+ dBoxABEdge = GetNode("Boxes/BoxABEdge");
+ dBoxABEdgea = GetNode("Boxes/BoxABEdge/a");
+ dBoxABEdgeb = GetNode("Boxes/BoxABEdge/b");
+ dBoxABEdgeup = GetNode("Boxes/BoxABEdge/o/up");
+ dLines_1 = GetNode("Lines/1");
+ dLines_2 = GetNode("Lines/2");
+ dLines_3 = GetNode("Lines/3");
+ dLines_4 = GetNode("Lines/4");
+ dLines_5 = GetNode("Lines/5");
+ dLines_6 = GetNode("Lines/6");
+ dLines_7 = GetNode("Lines/7");
+ dLines_8 = GetNode("Lines/8");
+ dLines_Target = GetNode("Lines/Target");
+ dLinePath = GetNode("LinePath");
+ dCylinder1 = GetNode("Cylinders/Cylinder1");
+ dCylinder2 = GetNode("Cylinders/Cylinder2");
+ dCylinder3a = GetNode("Cylinders/Cylinder3/1");
+ dCylinder3b = GetNode("Cylinders/Cylinder3/2");
+
+ dPlaneOrigin = GetNode("PlaneOrigin");
+ pZDepthTestCube = GetNode("%ZDepthTestCube");
+
+ dOtherWorld = GetNode("OtherWorld");
+ dOtherWorldViewport = GetNode("OtherWorld/SubViewport");
+ dOtherWorldBox = GetNode("OtherWorld/SubViewport/OtherWorldBox");
+
+ dCustomCanvas = GetNode("CustomCanvas");
+ dMisc_Arrow = GetNode("Misc/Arrow");
+ dCamera = GetNode("Camera");
+ dMisc_Billboard = GetNode("Misc/Billboard");
+ dMisc_Position = GetNode("Misc/Position");
+ dMisc_GizmoTransform = GetNode("Misc/GizmoTransform");
+ dMisc_GizmoNormal = GetNode("Misc/GizmoNormal");
+ dMisc_GizmoOneColor = GetNode("Misc/GizmoOneColor");
+
+ dMisc_Grids_Grid = GetNode("Grids/Grid");
+ dMisc_Grids_Grid_Subdivision = GetNode("Grids/Grid/Subdivision");
+ dMisc_Grids_GridCentered_Subdivision = GetNode("Grids/GridCentered/Subdivision");
+ dMisc_Grids_GridCentered = GetNode("Grids/GridCentered");
+
+ dPostProcess = GetNode("PostProcess");
+
+ dLagTest_RESET = GetNode("LagTest/RESET");
+ dHitTest_RayEmitter = GetNode("HitTest/RayEmitter");
+
+ _update_keys_just_press();
+
+ await new SignalAwaiter(GetTree(), "process_frame", this);
+
+ // this check is required for inherited scenes, because an instance of this
+ // script is created first, and then overridden by another
+ if (!IsInsideTree())
+ return;
+
+ }
+
+ bool _is_key_just_pressed(Key key)
+ {
+ if (button_presses[key] == 1)
+ {
+ button_presses[key] = 2;
+ return true;
+ }
+ return false;
+ }
+
+ void _update_timers(double delta)
+ {
+ timer_1 -= delta;
+ timer_cubes -= delta;
+ timer_3 -= delta;
+ timer_text -= delta;
+ }
+
+ void _update_keys_just_press()
+ {
+ var set = (Key k) => Input.IsKeyPressed(k) ? (button_presses[k] == 0 ? 1 : button_presses[k]) : 0;
+ button_presses[Key.Left] = set(Key.Left);
+ button_presses[Key.Up] = set(Key.Up);
+ button_presses[Key.Ctrl] = set(Key.Ctrl);
+ button_presses[Key.F1] = set(Key.F1);
+ button_presses[Key.Key1] = set(Key.Key1);
+ button_presses[Key.Key2] = set(Key.Key2);
+ button_presses[Key.Key3] = set(Key.Key3);
+ }
+
+ bool phys_frame_called = false;
+ public override void _Process(double delta)
+ {
+ ((ShaderMaterial)((PrimitiveMesh)dOtherWorld.Mesh).Material).SetShaderParameter("albedo_texture", dOtherWorldViewport.GetTexture());
+
+ phys_frame_called = false;
+ if (!update_in_physics)
+ {
+ MainUpdate(delta);
+ _update_timers(delta);
+ }
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ if (!phys_frame_called)
+ {
+ phys_frame_called = true;
+ if (update_in_physics)
+ {
+ MainUpdate(delta);
+ _update_timers(delta);
+ }
+ }
+
+ // Physics specific:
+ if (!zylann_example)
+ {
+ DebugDraw3D.DrawLine(dLines_8.GlobalPosition, dLines_Target.GlobalPosition, Colors.Yellow);
+ if (more_test_cases)
+ {
+ _draw_rays_casts();
+ }
+
+ // Additional drawing in the Viewport
+ using (var _w1 = DebugDraw3D.NewScopedConfig().SetViewport(dOtherWorldBox.GetViewport()).SetThickness(0.01f).SetCenterBrightness(1).SetNoDepthTest(true))
+ {
+ DebugDraw3D.DrawBoxXf(new Transform3D(Basis.Identity
+ .Scaled(Vector3.One * 0.3f)
+ .Rotated(new Vector3(0, 0, 1), Mathf.Pi / 4)
+ .Rotated(new Vector3(0, 1, 0), Mathf.Wrap(Time.GetTicksMsec() / -1500.0f, 0, Mathf.Tau) - Mathf.Pi / 4), dOtherWorldBox.GlobalPosition),
+ Colors.Brown, true, 0.4f);
+ }
+ }
+ }
+
+ void MainUpdate(double delta)
+ {
+ DebugDraw3D.ScopedConfig().SetThickness(debug_thickness);
+#pragma warning disable CS0162 // Unreachable code detected
+ if (false) // #test
+ {
+ using var s11 = DebugDraw3D.NewScopedConfig().SetThickness(1);
+ using var s13 = DebugDraw3D.NewScopedConfig();
+ s13.SetThickness(3);
+ }
+#pragma warning restore CS0162 // Unreachable code detected
+
+ _update_keys_just_press();
+
+ if (_is_key_just_pressed(Key.F1))
+ zylann_example = !zylann_example;
+
+ // Zylann's example :D
+ if (zylann_example)
+ {
+ DebugDraw2D.ClearGraphs();
+ var _time = Time.GetTicksMsec() / 1000.0f;
+ var box_pos = new Vector3(0, Mathf.Sin(_time * 4f), 0);
+ var line_begin = new Vector3(-1, Mathf.Sin(_time * 4f), 0);
+ var line_end = new Vector3(1, Mathf.Cos(_time * 4f), 0);
+ DebugDraw3D.DrawBox(box_pos, Quaternion.Identity, new Vector3(1, 2, 1), new Color(0, 1, 0));
+ DebugDraw3D.DrawLine(line_begin, line_end, new Color(1, 1, 0));
+ DebugDraw2D.SetText("Time", _time);
+ DebugDraw2D.SetText("Frames drawn", Engine.GetFramesDrawn());
+ DebugDraw2D.SetText("FPS", Engine.GetFramesPerSecond());
+ DebugDraw2D.SetText("delta", delta);
+
+ dHitTest.Visible = false;
+ dLagTest.Visible = false;
+ dPlaneOrigin.Visible = false;
+ pZDepthTestCube.Visible = false;
+ dOtherWorld.Visible = false;
+ return;
+ }
+
+ dHitTest.Visible = true;
+ dLagTest.Visible = true;
+ dPlaneOrigin.Visible = true;
+ pZDepthTestCube.Visible = true;
+ dOtherWorld.Visible = true;
+
+ // Testing the rendering layers by showing the image from the second camera inside the 2D panel
+ DebugDraw3D.Config.GeometryRenderLayers = !Input.IsKeyPressed(Key.Alt) ? 1 : 0b10010;
+ dPanel.Visible = Input.IsKeyPressed(Key.Alt);
+ DebugDraw2D.CustomCanvas = Input.IsKeyPressed(Key.Alt) ? dCustomCanvas : null;
+
+ // More property toggles
+ DebugDraw3D.Config.Freeze3dRender = Input.IsKeyPressed(Key.Down);
+ DebugDraw3D.Config.VisibleInstanceBounds = Input.IsKeyPressed(Key.Right);
+
+ // Regenerate meshes
+ if (Input.IsActionJustPressed("ui_end"))
+ DebugDraw3D.RegenerateGeometryMeshes();
+
+ // Some property toggles
+ if (_is_key_just_pressed(Key.Left))
+ DebugDraw3D.Config.UseFrustumCulling = !DebugDraw3D.Config.UseFrustumCulling;
+
+ if (_is_key_just_pressed(Key.Up))
+ DebugDraw3D.Config.ForceUseCameraFromScene = !DebugDraw3D.Config.ForceUseCameraFromScene;
+
+ if (_is_key_just_pressed(Key.Ctrl))
+ if (!Engine.IsEditorHint())
+ GetViewport().Msaa3D = GetViewport().Msaa3D == Viewport.Msaa.Msaa4X ? Viewport.Msaa.Disabled : Viewport.Msaa.Msaa4X;
+
+ if (!Engine.IsEditorHint())
+ {
+ if (_is_key_just_pressed(Key.Key1))
+ DebugDraw3D.DebugEnabled = !DebugDraw3D.DebugEnabled;
+ if (_is_key_just_pressed(Key.Key2))
+ DebugDraw2D.DebugEnabled = !DebugDraw2D.DebugEnabled;
+ if (_is_key_just_pressed(Key.Key3))
+ DebugDrawManager.DebugEnabled = !DebugDrawManager.DebugEnabled;
+ }
+
+
+ DebugDraw3D.Config.FrustumLengthScale = camera_frustum_scale;
+
+ // Zones with black borders
+ foreach (var node in dZones.GetChildren())
+ {
+ if (node is Node3D z)
+ {
+ DebugDraw3D.DrawBoxXf(z.GlobalTransform, Colors.Black);
+ }
+ }
+
+ // Spheres
+ DebugDraw3D.DrawSphereXf(dSphereTransform.GlobalTransform, Colors.Crimson);
+ using (var _s1 = DebugDraw3D.NewScopedConfig().SetHdSphere(true))
+ DebugDraw3D.DrawSphereXf(dSphereHDTransform.GlobalTransform, Colors.OrangeRed);
+
+ // Delayed spheres
+ if (timer_1 <= 0)
+ {
+ DebugDraw3D.DrawSphere(dSpherePosition.GlobalPosition, 2.0f, Colors.BlueViolet, 2.0f);
+ using (var _s1 = DebugDraw3D.NewScopedConfig().SetHdSphere(true))
+ DebugDraw3D.DrawSphere(dSpherePosition.GlobalPosition + Vector3.Forward * 4, 2.0f, Colors.CornflowerBlue, 2.0f);
+ timer_1 = 2;
+ }
+
+ timer_1 -= delta;
+
+ // Cylinders
+ DebugDraw3D.DrawCylinder(dCylinder1.GlobalTransform, Colors.Crimson);
+ DebugDraw3D.DrawCylinder(new Transform3D(Basis.Identity.Scaled(new Vector3(1, 2, 1)), dCylinder2.GlobalPosition), Colors.Red);
+ DebugDraw3D.DrawCylinderAb(dCylinder3a.GlobalPosition, dCylinder3b.GlobalPosition, 0.7f);
+
+ // Boxes
+ DebugDraw3D.DrawBoxXf(dBox1.GlobalTransform, Colors.MediumPurple);
+ DebugDraw3D.DrawBox(dBox2.GlobalPosition, Quaternion.FromEuler(new Vector3(0, Mathf.DegToRad(45), Mathf.DegToRad(45))), Vector3.One, Colors.RebeccaPurple);
+ DebugDraw3D.DrawBoxXf(new Transform3D(new Basis(Vector3.Up, Mathf.Pi * 0.25f).Scaled(Vector3.One * 2), dBox3.GlobalPosition), Colors.RosyBrown);
+
+ DebugDraw3D.DrawAabb(new Aabb(dAABB_fixed.GlobalPosition, new Vector3(2, 1, 2)), Colors.Aqua);
+ DebugDraw3D.DrawAabbAb(dAABB.GetChild(0).GlobalPosition, dAABB.GetChild(1).GlobalPosition, Colors.DeepPink);
+
+ // Boxes AB
+
+ DebugDraw3D.DrawArrow(dBoxAB.GlobalPosition, dBoxABup.GlobalPosition, Colors.Gold, 0.1f, true);
+ DebugDraw3D.DrawBoxAb(dBoxABa.GlobalPosition, dBoxABb.GlobalPosition, dBoxABup.GlobalPosition - dBoxAB.GlobalPosition, Colors.Peru);
+
+ DebugDraw3D.DrawArrow(dBoxABEdge.GlobalPosition, dBoxABEdgeup.GlobalPosition, Colors.DarkRed, 0.1f, true);
+ DebugDraw3D.DrawBoxAb(dBoxABEdgea.GlobalPosition, dBoxABEdgeb.GlobalPosition, dBoxABEdgeup.GlobalPosition - dBoxABEdge.GlobalPosition, Colors.DarkOliveGreen, false);
+
+ // Lines
+ DebugDraw3D.DrawSquare(dLines_Target.GlobalPosition, 0.5f, Colors.Red);
+
+ DebugDraw3D.DrawLine(dLines_1.GlobalPosition, dLines_Target.GlobalPosition, Colors.Fuchsia);
+ DebugDraw3D.DrawRay(dLines_3.GlobalPosition, (dLines_Target.GlobalPosition - dLines_3.GlobalPosition).Normalized(), 3.0f, Colors.Crimson);
+
+
+ if (timer_3 <= 0)
+ {
+ DebugDraw3D.DrawLine(dLines_6.GlobalPosition, dLines_Target.GlobalPosition, Colors.Fuchsia, 2.0f);
+ timer_3 = 2;
+ }
+
+ timer_3 -= delta;
+
+ // Test UP vector
+ DebugDraw3D.DrawLine(dLines_7.GlobalPosition, dLines_Target.GlobalPosition, Colors.Red);
+
+ // Lines with Arrow
+ DebugDraw3D.DrawArrow(dLines_2.GlobalPosition, dLines_Target.GlobalPosition, Colors.Blue, 0.5f, true);
+ DebugDraw3D.DrawArrowRay(dLines_4.GlobalPosition, (dLines_Target.GlobalPosition - dLines_4.GlobalPosition).Normalized(), 8.0f, Colors.Lavender, 0.5f, true);
+
+ DebugDraw3D.DrawLineHitOffset(dLines_5.GlobalPosition, dLines_Target.GlobalPosition, true, Mathf.Abs(Mathf.Sin(Time.GetTicksMsec() / 1000.0f)), 0.25f, Colors.Aqua);
+
+ // Path
+
+ // preparing data
+ List points = new List();
+ List points_below = new List();
+ List points_below2 = new List();
+ List points_below3 = new List();
+ List points_below4 = new List();
+ List lines_above = new List();
+
+ foreach (var node in dLinePath.GetChildren())
+ {
+ if (node is Node3D c)
+ {
+ points.Add(c.GlobalPosition);
+ points_below.Add(c.GlobalPosition + Vector3.Down);
+ points_below2.Add(c.GlobalPosition + Vector3.Down * 2);
+ points_below3.Add(c.GlobalPosition + Vector3.Down * 3);
+ points_below4.Add(c.GlobalPosition + Vector3.Down * 4);
+ }
+ }
+
+ for (int x = 0; x < points.Count - 1; x++)
+ {
+ lines_above.Add(points[x] + Vector3.Up);
+ lines_above.Add(points[x + 1] + Vector3.Up);
+ }
+
+ // drawing lines
+ DebugDraw3D.DrawLines(lines_above.ToArray());
+ DebugDraw3D.DrawLinePath(points.ToArray(), Colors.Beige);
+ DebugDraw3D.DrawPoints(points_below.ToArray(), DebugDraw3D.PointType.TypeSquare, 0.2f, Colors.DarkGreen);
+ DebugDraw3D.DrawPointPath(points_below2.ToArray(), DebugDraw3D.PointType.TypeSquare, 0.25f, Colors.Blue, Colors.Tomato);
+ DebugDraw3D.DrawArrowPath(points_below3.ToArray(), Colors.Gold, 0.5f);
+ using (var _sl = DebugDraw3D.NewScopedConfig().SetThickness(0.05f))
+ DebugDraw3D.DrawPointPath(points_below4.ToArray(), DebugDraw3D.PointType.TypeSphere, 0.25f, Colors.MediumSeaGreen, Colors.MediumVioletRed);
+
+ // Other world
+
+ using (var s = DebugDraw3D.NewScopedConfig().SetViewport(dOtherWorldBox.GetViewport()))
+ {
+ DebugDraw3D.DrawBoxXf(dOtherWorldBox.GlobalTransform.RotatedLocal(new Vector3(1, 1, -1).Normalized(), Mathf.Wrap(Time.GetTicksMsec() / 1000.0f, 0f, Mathf.Tau)), Colors.SandyBrown);
+ DebugDraw3D.DrawBoxXf(dOtherWorldBox.GlobalTransform.RotatedLocal(new Vector3(-1, 1, -1).Normalized(), Mathf.Wrap(Time.GetTicksMsec() / 1000.0f, 0f, Mathf.Tau) - Mathf.Pi / 4), Colors.SandyBrown);
+ }
+
+ // Misc
+ if (Engine.IsEditorHint())
+ {
+ using var s = DebugDraw3D.NewScopedConfig().SetThickness(0);
+ DebugDraw3D.DrawCameraFrustum(dCamera, Colors.DarkOrange);
+ }
+
+ using (var s = DebugDraw3D.NewScopedConfig().SetCenterBrightness(0.1f))
+ {
+ DebugDraw3D.DrawArrowhead(dMisc_Arrow.GlobalTransform, Colors.YellowGreen);
+ }
+
+ DebugDraw3D.DrawSquare(dMisc_Billboard.GlobalPosition, 0.5f, Colors.Green);
+
+ DebugDraw3D.DrawPosition(dMisc_Position.GlobalTransform, Colors.Brown);
+
+ DebugDraw3D.DrawGizmo(dMisc_GizmoTransform.GlobalTransform, null, true);
+ DebugDraw3D.DrawGizmo(dMisc_GizmoOneColor.GlobalTransform, Colors.Brown, true);
+ using (var s = DebugDraw3D.NewScopedConfig().SetCenterBrightness(0.5f).SetNoDepthTest(true))
+ {
+ DebugDraw3D.DrawGizmo(dMisc_GizmoNormal.GlobalTransform.Orthonormalized(), null, false);
+ }
+
+ Transform3D tg = dMisc_Grids_Grid.GlobalTransform;
+ Vector3 tn = dMisc_Grids_Grid_Subdivision.Transform.Origin;
+ DebugDraw3D.DrawGrid(tg.Origin, tg.Basis.X, tg.Basis.Z, new Vector2I((int)tn.X * 10, (int)tn.Z * 10), Colors.LightCoral, false);
+
+ var tn1 = dMisc_Grids_GridCentered_Subdivision.Transform.Origin;
+ DebugDraw3D.DrawGridXf(dMisc_Grids_GridCentered.GlobalTransform, new Vector2I((int)(tn1.X * 10), (int)(tn1.Z * 10)));
+
+ using (var s = DebugDraw3D.NewScopedConfig().SetThickness(0.05f))
+ {
+ DebugDraw3D.DrawBoxXf(dPostProcess.GlobalTransform, Colors.SeaGreen);
+ }
+
+ // 2D
+ DebugDraw2D.Config.TextDefaultSize = text_groups_default_font_size;
+ DebugDraw2D.Config.TextBlockOffset = text_groups_offset;
+ DebugDraw2D.Config.TextBlockPosition = text_groups_position;
+ DebugDraw2D.Config.TextPadding = text_groups_padding;
+
+ DebugDraw2D.Config.TextCustomFont = custom_font;
+
+
+ if (test_text)
+ {
+ timer_text -= delta;
+ _text_tests();
+ }
+
+ // Graphs
+ // Enable FPSGraph if not exists
+ _create_graph("FPS", true, false, DebugDraw2DGraph.TextFlags.Current | DebugDraw2DGraph.TextFlags.Avg | DebugDraw2DGraph.TextFlags.Max | DebugDraw2DGraph.TextFlags.Min, "", DebugDraw2DGraph.GraphSide.Bottom, Engine.IsEditorHint() ? DebugDraw2DGraph.GraphPosition.LeftTop : DebugDraw2DGraph.GraphPosition.RightTop, new Vector2I(200, 80), custom_font);
+ if (Engine.IsEditorHint())
+ {
+ if (DebugDraw2D.GetGraph("FPS") != null)
+ {
+ DebugDraw2D.GetGraph("FPS").Offset = new Vector2I(0, 64);
+ }
+ }
+
+ // Adding more graphs
+ if (test_graphs)
+ {
+ _graph_test();
+ }
+ else
+ {
+ _remove_graphs();
+ }
+ _upd_graph_params();
+
+ // Lag Test
+ dLagTest.Position = ((Vector3)dLagTest_RESET.GetAnimation("RESET").TrackGetKeyValue(0, 0)) + new Vector3(Mathf.Sin(Time.GetTicksMsec() / 100.0f) * 2.5f, 0, 0);
+ DebugDraw3D.DrawBox(dLagTest.GlobalPosition, Quaternion.Identity, Vector3.One * 2.01f, Colors.Chocolate, true);
+
+ if (more_test_cases)
+ {
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ ray.SetPhysicsProcessInternal(true);
+ }
+
+ _more_tests();
+ }
+ else
+ {
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ ray.SetPhysicsProcessInternal(false);
+ }
+ }
+
+ if (draw_array_of_boxes)
+ {
+ _draw_array_of_boxes();
+ }
+
+ }
+
+ void _text_tests()
+ {
+
+ if (timer_text < 0)
+ {
+ DebugDraw2D.SetText("Some delayed text", "for 2.5s", -1, Colors.Black, 2.5f); // it's supposed to show text for 2.5 seconds
+ timer_text += 5;
+ }
+
+ DebugDraw2D.SetText("FPS", $"{Engine.GetFramesPerSecond():F2}", 0, Colors.Gold);
+ DebugDraw2D.BeginTextGroup("-- First Group --", 2, Colors.LimeGreen, true, text_groups_title_font_size, text_groups_text_font_size);
+ DebugDraw2D.SetText("Simple text");
+ DebugDraw2D.SetText("Text", "Value", 0, Colors.Aquamarine);
+ DebugDraw2D.SetText("Text out of order", null, -1, Colors.Silver);
+ DebugDraw2D.BeginTextGroup("-- Second Group --", 1, Colors.Beige);
+ DebugDraw2D.SetText("Rendered frames", Engine.GetFramesDrawn());
+ DebugDraw2D.EndTextGroup();
+
+ if (text_groups_show_stats)
+ {
+ DebugDraw2D.BeginTextGroup("-- Stats --", 3, Colors.Wheat);
+ var render_stats = DebugDraw3D.GetRenderStats();
+
+ if (render_stats != null && text_groups_show_stats)
+ {
+ DebugDraw2D.SetText("Total", render_stats.TotalGeometry);
+ DebugDraw2D.SetText("Instances", render_stats.Instances, 1);
+ DebugDraw2D.SetText("Lines", render_stats.Lines, 2);
+ DebugDraw2D.SetText("Total Visible", render_stats.TotalVisible, 3);
+ DebugDraw2D.SetText("Visible Instances", render_stats.VisibleInstances, 4);
+ DebugDraw2D.SetText("Visible Lines", render_stats.VisibleLines, 5);
+
+ DebugDraw2D.SetText("---", "", 6);
+
+ DebugDraw2D.SetText("Culling time", $"{(render_stats.TotalTimeCullingUsec / 1000.0):F2} ms", 7);
+ DebugDraw2D.SetText("Filling instances buffer", $"{(render_stats.TimeFillingBuffersInstancesUsec / 1000.0):F2} ms", 8);
+ DebugDraw2D.SetText("Filling lines buffer", $"{(render_stats.TimeFillingBuffersLinesUsec / 1000.0):F2} ms", 9);
+ DebugDraw2D.SetText("Filling time", $"{(render_stats.TotalTimeFillingBuffersUsec / 1000.0):F2} ms", 10);
+ DebugDraw2D.SetText("Total time", $"{(render_stats.TotalTimeSpentUsec / 1000.0):F2} ms", 11);
+
+ DebugDraw2D.SetText("---", null, 14);
+
+ DebugDraw2D.SetText("Created scoped configs", $"{render_stats.CreatedScopedConfigs}", 15);
+ }
+
+ if (text_groups_show_stats && text_groups_show_stats_2d)
+ {
+ DebugDraw2D.SetText("----", null, 19);
+ }
+
+ var render_stats_2d = DebugDraw2D.GetRenderStats();
+ if (render_stats_2d != null && text_groups_show_stats_2d)
+ {
+ DebugDraw2D.SetText("Text groups", render_stats_2d.OverlayTextGroups, 20);
+ DebugDraw2D.SetText("Text lines", render_stats_2d.OverlayTextLines, 21);
+ DebugDraw2D.SetText("Graphs total", render_stats_2d.OverlayGraphsTotal, 22);
+ DebugDraw2D.SetText("Graphs enabled", render_stats_2d.OverlayGraphsEnabled, 23);
+ }
+ DebugDraw2D.EndTextGroup();
+ }
+
+ if (text_groups_show_hints)
+ {
+ DebugDraw2D.BeginTextGroup("controls", 1024, Colors.White, false);
+ if (!Engine.IsEditorHint())
+ {
+ DebugDraw2D.SetText("WASD QE, LMB", "To move", 0);
+ }
+ DebugDraw2D.SetText("Alt: change render layers", DebugDraw3D.Config.GeometryRenderLayers, 1);
+ if (!OS.HasFeature("web"))
+ {
+ DebugDraw2D.SetText("Ctrl: toggle anti-aliasing", GetViewport().Msaa3D == Viewport.Msaa.Msaa4X ? "MSAA 4x" : "Disabled", 2);
+ }
+ DebugDraw2D.SetText("Down: freeze render", DebugDraw3D.Config.Freeze3dRender, 3);
+ if (Engine.IsEditorHint())
+ {
+ DebugDraw2D.SetText("Up: use scene camera", DebugDraw3D.Config.ForceUseCameraFromScene, 4);
+ }
+ DebugDraw2D.SetText("1,2,3: toggle debug", $"{DebugDraw3D.DebugEnabled}, {DebugDraw2D.DebugEnabled} 😐, {DebugDrawManager.DebugEnabled} 😏", 5);
+ DebugDraw2D.SetText("Left: toggle frustum culling", DebugDraw3D.Config.UseFrustumCulling, 6);
+ DebugDraw2D.SetText("Right: draw bounds for culling", DebugDraw3D.Config.VisibleInstanceBounds, 7);
+ }
+ }
+
+ void _draw_rays_casts()
+ {
+ // Line hits render
+ foreach (var node in dHitTest_RayEmitter.GetChildren())
+ {
+ if (node is RayCast3D ray)
+ {
+ ray.ForceRaycastUpdate();
+ DebugDraw3D.DrawLineHit(ray.GlobalPosition, ray.ToGlobal(ray.TargetPosition), ray.GetCollisionPoint(), ray.IsColliding(), 0.3f);
+ }
+ }
+ }
+
+ void _more_tests()
+ {
+ // Delayed line render
+ using (var s = DebugDraw3D.NewScopedConfig().SetThickness(0.035f))
+ {
+ DebugDraw3D.DrawLine(dLagTest.GlobalPosition + Vector3.Up, dLagTest.GlobalPosition + new Vector3(0, 3, Mathf.Sin(Time.GetTicksMsec() / 50.0f)), null, 0.5f);
+ }
+
+ // Draw plane
+ using (var _s11 = DebugDraw3D.NewScopedConfig().SetThickness(0.02f).SetPlaneSize(10))
+ {
+ var pl_node = GetNode("PlaneOrigin");
+ var xf = pl_node.GlobalTransform;
+ var normal = xf.Basis.Y.Normalized();
+ var plane = new Plane(normal, xf.Origin.Dot(normal));
+
+ var vp = GetViewport();
+ if (is_4_2_and_higher)
+ {
+ if (Engine.IsEditorHint() && (Viewport)Engine.GetSingleton("EditorInterface").Call("get_editor_viewport_3d", 0) != null)
+ {
+ vp = (Viewport)Engine.GetSingleton("EditorInterface").Call("get_editor_viewport_3d", 0);
+ }
+ }
+
+ var cam = vp.GetCamera3D();
+ if (cam != null)
+ {
+ var dir = vp.GetCamera3D().ProjectRayNormal(vp.GetMousePosition());
+ Vector3? intersect = plane.IntersectsRay(cam.GlobalPosition, dir);
+
+ DebugDraw3D.DrawPlane(plane, Colors.Coral * new Color(1, 1, 1, 0.4f), pl_node.GlobalPosition);
+ if (is_4_2_and_higher)
+ {
+ if (intersect.HasValue && intersect.Value.DistanceTo(pl_node.GlobalPosition) < _s11.GetPlaneSize() * 0.5f)
+ {
+ // Need to test different colors on both sides of the plane
+ var col = plane.IsPointOver(cam.GlobalPosition) ? Colors.Firebrick : Colors.Aquamarine;
+ DebugDraw3D.DrawSphere(intersect.Value, 0.3f, col);
+ }
+ }
+ }
+ }
+ }
+
+ void _draw_array_of_boxes()
+ {
+ // Lots of boxes to check performance..
+ var x_size = 50;
+ var y_size = 50;
+ var z_size = 3;
+ var mul = 1.0f;
+ var cubes_max_time = 1.25f;
+ using var cfg = DebugDraw3D.NewScopedConfig();
+
+ if (draw_1m_boxes)
+ {
+ x_size = 100;
+ y_size = 100;
+ z_size = 100;
+ mul = 4.0f;
+ cubes_max_time = 60f;
+ }
+
+ if (timer_cubes <= 0)
+ {
+ var start_time = Time.GetTicksUsec();
+ for (int x = 0; x < x_size; x++)
+ {
+ for (int y = 0; y < y_size; y++)
+ {
+ for (int z = 0; z < z_size; z++)
+ {
+ var size = Vector3.One;
+ cfg.SetThickness(Random.Shared.NextSingle() * 0.1f);
+ //size = new Vector3(Random.Shared.NextSingle() * 100 + 0.1f, Random.Shared.NextSingle() * 100 + 0.1f, Random.Shared.NextSingle() * 100 + 0.1f);
+ DebugDraw3D.DrawBox(new Vector3(x * mul, (-4 - z) * mul, y * mul) + GlobalPosition, Quaternion.Identity, size, null, false, cubes_max_time);
+ }
+ }
+ }
+ //GD.Print($"Draw Cubes: {((Time.GetTicksUsec() - start_time) / 1000.0):F2}ms");
+ timer_cubes = cubes_max_time;
+ }
+ }
+
+ void _graph_test()
+ {
+ _create_graph("fps", true, true, DebugDraw2DGraph.TextFlags.Current, "", DebugDraw2DGraph.GraphSide.Left, DebugDraw2DGraph.GraphPosition.RightTop);
+ _create_graph("fps2", true, false, DebugDraw2DGraph.TextFlags.Current, "fps", DebugDraw2DGraph.GraphSide.Bottom, 0, new Vector2I(200, 100));
+
+ _create_graph("Sin Wave!", false, true, DebugDraw2DGraph.TextFlags.Current, "fps2", DebugDraw2DGraph.GraphSide.Bottom);
+
+ _create_graph("randf", false, true, DebugDraw2DGraph.TextFlags.Avg, "", DebugDraw2DGraph.GraphSide.Left, DebugDraw2DGraph.GraphPosition.RightBottom, new Vector2I(256, 60), custom_font);
+
+ _create_graph("fps5", true, true, DebugDraw2DGraph.TextFlags.All, "randf", DebugDraw2DGraph.GraphSide.Top);
+ _create_graph("fps6", true, true, DebugDraw2DGraph.TextFlags.All, "fps5", DebugDraw2DGraph.GraphSide.Top);
+ _create_graph("fps12", true, true, DebugDraw2DGraph.TextFlags.All, "fps5", DebugDraw2DGraph.GraphSide.Left);
+
+ _create_graph("fps7", true, false, DebugDraw2DGraph.TextFlags.All, "FPS", DebugDraw2DGraph.GraphSide.Bottom);
+ _create_graph("fps8", true, true, DebugDraw2DGraph.TextFlags.All, "", DebugDraw2DGraph.GraphSide.Top, DebugDraw2DGraph.GraphPosition.LeftBottom);
+ _create_graph("fps9", true, false, DebugDraw2DGraph.TextFlags.All, "fps8", DebugDraw2DGraph.GraphSide.Right);
+ _create_graph("fps10", true, false, DebugDraw2DGraph.TextFlags.All, "fps8", DebugDraw2DGraph.GraphSide.Top);
+ _create_graph("fps11", true, true, DebugDraw2DGraph.TextFlags.All, "fps9", DebugDraw2DGraph.GraphSide.Right);
+
+ // If graphs exists, then more tests are done
+ DebugDraw2D.GetGraph("Sin Wave!").DataGetter = new Callable(this, "_get_sin_wave_for_graph");
+ DebugDraw2D.GetGraph("Sin Wave!").UpsideDown = false;
+
+ DebugDraw2D.GetGraph("randf").TextSuffix = "utf8 ноль zéro";
+ //DebugDraw2D.GetGraph("fps9").line_position = DebugDraw2DGraph.LINE_TOP
+ DebugDraw2D.GetGraph("fps9").Offset = new Vector2I(0, 0);
+ //DebugDraw2D.GetGraph("fps11").LlinePosition = DebugDraw2DGraph.LINE_BOTTOM
+ DebugDraw2D.GetGraph("fps11").Offset = new Vector2I(16, 0);
+ DebugDraw2D.GetGraph("fps6").Offset = new Vector2I(0, 32);
+ DebugDraw2D.GetGraph("fps").Offset = new Vector2I(16, 72);
+ DebugDraw2D.GetGraph("fps9").Enabled = graph_is_enabled;
+
+ if (!Engine.IsEditorHint())
+ {
+ DebugDraw2D.GetGraph("fps").Corner = DebugDraw2DGraph.GraphPosition.LeftTop;
+ }
+
+ // Just sending random data to the graph
+ DebugDraw2D.GraphUpdateData("randf", (float)random.NextDouble());
+ }
+
+ void _upd_graph_params()
+ {
+ DebugDraw2D.Config.GraphsBaseOffset = graph_offset;
+ foreach (var g in new string[] { "FPS", "fps5", "fps8" })
+ {
+ var graph = DebugDraw2D.GetGraph(g) as DebugDraw2DFPSGraph;
+ if (graph != null)
+ {
+
+ graph.Size = graph_size;
+ graph.TitleSize = graph_title_font_size;
+ graph.TextSize = graph_text_font_size;
+ graph.LineWidth = graph_line_width;
+ graph.TextPrecision = graph_text_precision;
+ graph.BufferSize = graph_buffer_size;
+ if (Engine.IsEditorHint() || g != "FPS")
+ {
+ graph.FrameTimeMode = graph_frame_time_mode;
+ }
+ }
+ }
+ }
+
+ float _get_sin_wave_for_graph()
+ {
+ var mul = Input.IsKeyPressed(Key.End) ? 4 : 2;
+ return (float)Mathf.Sin(Engine.GetFramesDrawn() * 0.5) * mul;
+ }
+
+ void _remove_graphs()
+ {
+ if (!test_fps_graph)
+ DebugDraw2D.RemoveGraph("FPS");
+ DebugDraw2D.RemoveGraph("randf");
+ DebugDraw2D.RemoveGraph("fps");
+ DebugDraw2D.RemoveGraph("fps2");
+ DebugDraw2D.RemoveGraph("Sin Wave!");
+ DebugDraw2D.RemoveGraph("fps5");
+ DebugDraw2D.RemoveGraph("fps6");
+ DebugDraw2D.RemoveGraph("fps7");
+ DebugDraw2D.RemoveGraph("fps8");
+ DebugDraw2D.RemoveGraph("fps9");
+ DebugDraw2D.RemoveGraph("fps10");
+ DebugDraw2D.RemoveGraph("fps11");
+ DebugDraw2D.RemoveGraph("fps12");
+ }
+
+ DebugDraw2DGraph _create_graph(string title, bool is_fps, bool show_title, DebugDraw2DGraph.TextFlags flags, string parent = "", DebugDraw2DGraph.GraphSide parent_side = DebugDraw2DGraph.GraphSide.Bottom, DebugDraw2DGraph.GraphPosition pos = DebugDraw2DGraph.GraphPosition.LeftBottom, Vector2I? size = null, Font font = null)
+ {
+ var graph = DebugDraw2D.GetGraph(title);
+ if (graph == null)
+ {
+ if (is_fps)
+ {
+ graph = DebugDraw2D.CreateFpsGraph(title);
+ }
+ else
+ {
+ graph = DebugDraw2D.CreateGraph(title);
+ }
+
+ if (graph != null)
+ {
+ graph.Size = size ?? new Vector2I(256, 60);
+ graph.BufferSize = 50;
+ graph.Corner = pos;
+ graph.ShowTitle = show_title;
+ graph.ShowTextFlags = flags;
+ graph.CustomFont = font;
+ graph.SetParent(parent, parent_side);
+ }
+ }
+ return graph;
+ }
+}
diff --git a/examples_dd3d/DebugDrawDemoSceneCS.tscn b/examples_dd3d/DebugDrawDemoSceneCS.tscn
new file mode 100644
index 0000000..dfcbabd
--- /dev/null
+++ b/examples_dd3d/DebugDrawDemoSceneCS.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=3 format=3 uid="uid://sxtw8fme7g63"]
+
+[ext_resource type="PackedScene" uid="uid://c3sccy6x0ht5j" path="res://examples_dd3d/DebugDrawDemoScene.tscn" id="2"]
+[ext_resource type="Script" path="res://examples_dd3d/DebugDrawDemoSceneCS.cs" id="2_ipqea"]
+
+[node name="DebugDrawDemoSceneCS" instance=ExtResource("2")]
+script = ExtResource("2_ipqea")
+
+[node name="Settings" parent="." index="22"]
+switch_to_scene = "res://examples_dd3d/DebugDrawDemoScene.tscn"
+
+[node name="Label" parent="Settings/HBox/VBoxContainer" index="1"]
+text = "C# example"
+
+[node name="SwitchLang" parent="Settings/HBox/VBox/SettingsPanel/VBox" index="12"]
+text = "Switch to GDScript"
diff --git a/examples_dd3d/Roboto-Bold.ttf b/examples_dd3d/Roboto-Bold.ttf
new file mode 100644
index 0000000..d3f01ad
Binary files /dev/null and b/examples_dd3d/Roboto-Bold.ttf differ
diff --git a/examples_dd3d/Roboto-Bold.ttf.import b/examples_dd3d/Roboto-Bold.ttf.import
new file mode 100644
index 0000000..488f7b2
--- /dev/null
+++ b/examples_dd3d/Roboto-Bold.ttf.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://erdgllynwqkw"
+path="res://.godot/imported/Roboto-Bold.ttf-3674de3d9ad3ee757cd4b4a89f1e126d.fontdata"
+
+[deps]
+
+source_file="res://examples_dd3d/Roboto-Bold.ttf"
+dest_files=["res://.godot/imported/Roboto-Bold.ttf-3674de3d9ad3ee757cd4b4a89f1e126d.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=[{
+"chars": [],
+"glyphs": [],
+"name": "New Configuration"
+}]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/examples_dd3d/VisualizerAudioBus.tres b/examples_dd3d/VisualizerAudioBus.tres
new file mode 100644
index 0000000..4c7b662
--- /dev/null
+++ b/examples_dd3d/VisualizerAudioBus.tres
@@ -0,0 +1,17 @@
+[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://7sy4h4ibftrk"]
+
+[sub_resource type="AudioEffectSpectrumAnalyzer" id="AudioEffectSpectrumAnalyzer_odciy"]
+resource_name = "SpectrumAnalyzer"
+fft_size = 3
+
+[resource]
+bus/0/mute = true
+bus/0/volume_db = -20.0
+bus/1/name = &"MusicAnalyzer"
+bus/1/solo = false
+bus/1/mute = false
+bus/1/bypass_fx = false
+bus/1/volume_db = 0.0
+bus/1/send = &"Master"
+bus/1/effect/0/effect = SubResource("AudioEffectSpectrumAnalyzer_odciy")
+bus/1/effect/0/enabled = true
diff --git a/examples_dd3d/addon_icon.gd b/examples_dd3d/addon_icon.gd
new file mode 100644
index 0000000..4283024
--- /dev/null
+++ b/examples_dd3d/addon_icon.gd
@@ -0,0 +1,11 @@
+@tool
+extends Node3D
+
+func _process(delta: float) -> void:
+ var a = DebugDraw3D.new_scoped_config().set_thickness(0.015)
+ DebugDraw3D.draw_box_xf($box.global_transform, Color.GREEN)
+ DebugDraw3D.draw_gizmo($gizmo.global_transform)
+ DebugDraw3D.draw_grid_xf($gizmo/grid.global_transform, Vector2i(2,2), DebugDraw3D.empty_color, false)
+ DebugDraw3D.draw_sphere_xf($sphere.global_transform, Color.RED)
+ DebugDraw3D.draw_cylinder($cylinder.global_transform, Color.BLUE)
+ DebugDraw3D.draw_line_hit_offset($"line/1".global_transform.origin, $"line/2".global_transform.origin, true, 0.3, 0.1)
diff --git a/examples_dd3d/addon_icon.tscn b/examples_dd3d/addon_icon.tscn
new file mode 100644
index 0000000..b577312
--- /dev/null
+++ b/examples_dd3d/addon_icon.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=3 format=3 uid="uid://1lhiwf8tgleh"]
+
+[ext_resource type="Script" path="res://examples_dd3d/addon_icon.gd" id="1_bq18y"]
+
+[sub_resource type="Environment" id="1"]
+background_mode = 1
+
+[node name="icon" type="Node3D"]
+script = ExtResource("1_bq18y")
+
+[node name="Camera" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.39732)
+environment = SubResource("1")
+current = true
+
+[node name="box" type="Node3D" parent="."]
+transform = Transform3D(0.316305, 0.0204714, -0.293415, -0.239575, 0.267896, -0.239575, 0.170631, 0.338191, 0.207538, -0.410294, 0.312541, 0.243199)
+
+[node name="gizmo" type="Node3D" parent="."]
+transform = Transform3D(0.707107, 0, -0.707107, -0.294265, 0.909294, -0.294265, 0.642968, 0.416154, 0.642968, 0, 0, 0)
+
+[node name="grid" type="Node3D" parent="gizmo"]
+transform = Transform3D(1, -2.98023e-08, 1.19209e-07, 0, 1, 0, 1.19209e-07, -2.98023e-08, 1, -0.0263093, -0.0170284, -0.0263093)
+
+[node name="sphere" type="Node3D" parent="."]
+transform = Transform3D(0.401341, 0.207831, -0.437109, -0.449118, 0.371584, -0.235691, 0.180418, 0.46267, 0.385639, 0.466197, 0.322665, 0.200436)
+
+[node name="cylinder" type="Node3D" parent="."]
+transform = Transform3D(0.155034, 0.231693, -0.112783, -0.160003, 0.264761, -0.0839674, 0.0232275, 0.277352, 0.174372, -0.0566943, -0.290515, 0.905274)
+
+[node name="line" type="Node3D" parent="."]
+
+[node name="1" type="Node3D" parent="line"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.568458, -0.615948, 0.653444)
+
+[node name="2" type="Node3D" parent="line"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0051975, 0.373791, 0.0974927)
diff --git a/examples_dd3d/demo_camera_movement.gd b/examples_dd3d/demo_camera_movement.gd
new file mode 100644
index 0000000..51f758d
--- /dev/null
+++ b/examples_dd3d/demo_camera_movement.gd
@@ -0,0 +1,60 @@
+extends Camera3D
+
+@export var mouse_sensitivity := 0.25
+@export var camera_speed := 10.0
+@export var camera_speed_fast := 30.0
+
+var btn_clicked := false
+const hPI := PI/2
+var rot_x := 0.0
+var rot_y := 0.0
+
+
+func _ready():
+ reset_input_rotation()
+
+
+func _unhandled_input(event) -> void:
+ if event is InputEventMouseButton:
+ btn_clicked = event.pressed
+
+
+func reset_input_rotation():
+ rot_x = rotation.y
+ rot_y = rotation.x
+
+
+func _input(event) -> void:
+ if btn_clicked:
+ if event is InputEventMouseMotion:
+ if event.button_mask == MOUSE_BUTTON_LEFT:
+ rot_x += -deg_to_rad(event.relative.x * mouse_sensitivity)
+ rot_y += -deg_to_rad(event.relative.y * mouse_sensitivity)
+ rot_y = clamp(rot_y, -hPI, hPI)
+
+ transform.basis = Basis()
+ rotate_object_local(Vector3.UP, rot_x)
+ rotate_object_local(Vector3.RIGHT, rot_y)
+
+
+func get_axis(neg : Array[Key], pos : Array[Key]) -> float:
+ var pressed = func (arr: Array[Key]):
+ var p: float = 0
+ for k in arr:
+ if Input.is_physical_key_pressed(k):
+ p = 1
+ break
+ return p
+
+ return pressed.call(pos) - pressed.call(neg)
+
+
+func _process(delta) -> void:
+ var motion := Vector2(get_axis([KEY_S], [KEY_W]), get_axis([KEY_A], [KEY_D]))
+ var lift := get_axis([KEY_Q, KEY_CTRL], [KEY_E, KEY_SPACE])
+ var speed := camera_speed_fast if Input.is_physical_key_pressed(KEY_SHIFT) else camera_speed
+ motion = motion.limit_length()
+
+ var b := global_transform.basis
+ var v := (-b.z * motion.x) + (b.x * motion.y) + (b.y * lift)
+ global_position += v.limit_length() * speed * delta
diff --git a/examples_dd3d/demo_music_visualizer.gd b/examples_dd3d/demo_music_visualizer.gd
new file mode 100644
index 0000000..d143205
--- /dev/null
+++ b/examples_dd3d/demo_music_visualizer.gd
@@ -0,0 +1,179 @@
+@tool
+extends VBoxContainer
+
+@export_range(1, 128) var bars_count := 32
+var transform: Transform3D:
+ get:
+ return %AudioVisualizer.global_transform
+@export_exp_easing("inout") var motion_smoothing := 0.025
+@export_range(0, 0.5) var bar_thickness := 0.065
+@export_range(0, 10) var bars_separation := 0.325
+@export_exp_easing("inout") var color_offset_speed := 0.4
+@export var colors: Gradient = null
+
+var MusicAnalyzerBus := &"MusicAnalyzer"
+var MasterBus := &"Master"
+var MAX_HZ := 16000.0
+var MIN_HZ := 20.0
+var MIN_DB := 60.0
+var spectrum: AudioEffectSpectrumAnalyzerInstance = null
+
+var smoothed_energy: Array[float] = []
+var color_offset := 0.0
+
+var _on_data_loaded_callback = null
+
+# TODO remove after moving to 4.2
+var is_4_2_and_higher = Engine.get_version_info()["major"] >= 4 && Engine.get_version_info()["minor"] >= 2
+
+
+func _ready():
+ var bus = AudioServer.get_bus_index(MusicAnalyzerBus)
+ if bus == -1:
+ print("'MusicVisualizer' audio bus not found.\nSet 'VisualizerAudioBus.tres' as the default bus to use the audio visualizer.")
+
+ spectrum = AudioServer.get_bus_effect_instance(bus, 0)
+ %MuteMaster.button_pressed = AudioServer.is_bus_mute(AudioServer.get_bus_index(MasterBus))
+ %VolumeSlider.value = db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(MasterBus)))
+
+ if OS.has_feature('web'):
+ motion_smoothing = motion_smoothing * 1.5
+
+ _on_data_loaded_callback = JavaScriptBridge.create_callback(_on_data_loaded)
+ # Retrieve the 'gd_callbacks' object
+ var gdcallbacks: JavaScriptObject = JavaScriptBridge.get_interface("gd_callbacks")
+ # Assign the callbacks
+ gdcallbacks.dataLoaded = _on_data_loaded_callback
+
+
+func _process(_delta):
+ if %MusicPlayer.playing:
+ draw_spectrum()
+
+
+func _pressed():
+ var open_file = func(filepath: String):
+ print("Opening '%s'" % filepath)
+ var file = FileAccess.open(filepath, FileAccess.READ)
+ var data = file.get_buffer(file.get_length())
+ open_stream(filepath.get_extension(), data)
+
+ if is_4_2_and_higher and DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG):
+ # TODO remove call() and get() after moving to 4.2
+ DisplayServer.call("file_dialog_show", "Select audio file", "", "", true, DisplayServer.get("FILE_DIALOG_MODE_OPEN_FILE"), ["*.mp3"],
+ func (status: bool, selected: PackedStringArray, _fileter: int):
+ if status and selected.size():
+ open_file.call(selected[0])
+ )
+ elif OS.has_feature('web'):
+ JavaScriptBridge.eval("loadData()")
+ else:
+ var fd := FileDialog.new()
+ add_child(fd)
+
+ fd.title = "Select audio file"
+ fd.access = FileDialog.ACCESS_FILESYSTEM
+ fd.file_mode = FileDialog.FILE_MODE_OPEN_FILE
+ fd.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)
+ fd.add_filter("*.mp3")
+ fd.popup_centered_ratio(0.8)
+
+ fd.file_selected.connect(func(path: String):
+ open_file.call(path)
+ )
+
+ fd.visibility_changed.connect(func():
+ if not fd.visible:
+ fd.queue_free()
+ )
+
+
+func _on_data_loaded(data: Array) -> void:
+ # Make sure there is something
+ if (data.size() == 0):
+ return
+
+ var file_name: String = data[0]
+ print("Opening '%s'" % file_name)
+
+ var arr: PackedByteArray = JavaScriptBridge.eval("gd_callbacks.dataLoadedResult;")
+ open_stream(file_name.get_extension(), arr)
+
+
+func open_stream(file_ext: String, data: PackedByteArray):
+ var stream: AudioStream = null
+ if file_ext == "mp3":
+ stream = AudioStreamMP3.new()
+ stream.data = data
+
+ if not stream.data:
+ print("Failed to load MP3!")
+ return
+
+ if not stream:
+ print("Failed to load music!")
+ return
+
+ %MusicPlayer.stream = stream
+ %MusicPlayer.bus = MusicAnalyzerBus
+ %MusicPlayer.play()
+
+ # Debugging frequencies
+ for ih in range(1, bars_count + 1):
+ var _hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
+ #print("%.0f hz %.2f" % [_hz, ih / float(bars_count)])
+
+
+func draw_spectrum():
+ var _s1 = DebugDraw3D.scoped_config().set_thickness(bar_thickness).set_center_brightness(0.9)
+ var prev_hz = MIN_HZ
+ smoothed_energy.resize(bars_count)
+
+ var xf := transform
+ var y := xf.basis.y
+ var h := y.length()
+ var x := xf.basis.x
+ var z := xf.basis.z
+ var origin := xf.origin - (x * bars_count + (x * bars_separation) * (bars_count - 1)) * 0.5
+ var sum := 0.0
+
+ for ih in range(1, bars_count + 1):
+ var i := ih - 1
+ var hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
+ var magnitude: float = spectrum.get_magnitude_for_frequency_range(prev_hz, hz, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_AVERAGE).length()
+ var energy: float = clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
+ var e: float = lerp(smoothed_energy[i], energy, clampf(get_process_delta_time() / motion_smoothing if motion_smoothing else 1.0, 0, 1))
+ smoothed_energy[i] = e
+ var height: float = e * h
+ sum += e
+
+ var s := x * bars_separation
+
+ var a := origin + x * i + s * i + (z * 0.5)
+ var b := origin + x * (i + 1) + s * i + (z * -0.5) + xf.basis.y.normalized() * clampf(height, 0.001, h)
+ var c := Color.HOT_PINK
+ if colors:
+ c = colors.sample(wrapf(float(ih) / bars_count + color_offset, 0, 1))
+ c.s = clamp(c.s - smoothed_energy[i] * 0.3, 0, 1.0)
+
+ DebugDraw3D.draw_box_ab(a, b, y, c)
+
+ prev_hz = hz
+
+ color_offset = wrapf(color_offset + sum / smoothed_energy.size() * clampf(get_process_delta_time() / color_offset_speed if color_offset_speed else 1.0, 0, 1), 0, 1)
+
+
+func log10(val: float) -> float:
+ return log(val) / 2.302585
+
+
+func log_freq(pos: float, min_hz: float, max_hz: float) -> float:
+ return pow(10, log10(min_hz) + (log10(max_hz) - log10(min_hz)) * pos)
+
+
+func _on_volume_slider_value_changed(value):
+ AudioServer.set_bus_volume_db(AudioServer.get_bus_index(MasterBus), linear_to_db(value))
+
+
+func _on_mute_master_toggled(toggled_on):
+ AudioServer.set_bus_mute(AudioServer.get_bus_index(MasterBus), toggled_on)
diff --git a/examples_dd3d/demo_settings_panel.gd b/examples_dd3d/demo_settings_panel.gd
new file mode 100644
index 0000000..894996a
--- /dev/null
+++ b/examples_dd3d/demo_settings_panel.gd
@@ -0,0 +1,112 @@
+@tool
+extends Control
+
+@export var switch_to_scene = ""
+var is_ready := false
+
+func _ready():
+ if Engine.is_editor_hint():
+ return
+
+ if ProjectSettings.has_setting("application/config/no_csharp_support"):
+ %SwitchLang.visible = false
+
+ %SwitchLang.disabled = true
+
+ var test := DebugDraw2D.get_graph(&"FPS")
+ if test:
+ %FPSEnabled.button_pressed = test.enabled
+ %FPSMS.button_pressed = test.frame_time_mode
+ %WidthSlider.value = test.size.x
+ %BufferSlider.value = test.buffer_size
+
+ %ThicknessSlider.value = get_parent().debug_thickness
+ %FrustumScaleSlider.value = get_parent().camera_frustum_scale
+ %UpdateInPhysics.text = "Update in physics (%d Ticks) *" % ProjectSettings.get_setting("physics/common/physics_ticks_per_second")
+ %UpdateInPhysics.button_pressed = get_parent().update_in_physics
+
+ %ShowStats.button_pressed = get_parent().text_groups_show_stats
+ %DrawBoxes.button_pressed = get_parent().draw_array_of_boxes
+ %Draw1MBoxes.button_pressed = get_parent().draw_1m_boxes
+
+ if get_tree():
+ await get_tree().create_timer(0.2).timeout
+
+ %SwitchLang.disabled = false
+ is_ready = true
+
+
+func _on_CheckBox_toggled(button_pressed: bool) -> void:
+ if not is_ready: return
+
+ var cfg = DebugDraw2D.get_graph(&"FPS")
+ if cfg:
+ cfg.enabled = button_pressed
+
+
+func _on_FPSMS_toggled(button_pressed: bool) -> void:
+ if not is_ready: return
+
+ var cfg = DebugDraw2D.get_graph(&"FPS")
+ if cfg:
+ cfg.frame_time_mode = button_pressed
+
+
+func _on_Button_pressed() -> void:
+ get_tree().call_deferred("change_scene_to_file", switch_to_scene)
+
+
+func _on_hide_show_panel_pressed():
+ if %SettingsPanel.visible:
+ %SettingsPanel.hide()
+ %HideShowPanelButton.text = "Show panel"
+ else:
+ %SettingsPanel.show()
+ %HideShowPanelButton.text = "Hide panel"
+
+
+func _on_width_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().graph_size = Vector2i(int(value), get_parent().graph_size.y)
+
+
+func _on_buffer_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().graph_buffer_size = int(value)
+
+
+func _on_thickness_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().debug_thickness = value
+
+
+func _on_frustum_scale_slider_value_changed(value):
+ if not is_ready: return
+
+ get_parent().camera_frustum_scale = value
+
+
+func _on_update_in_physics_toggled(toggled_on):
+ get_parent().update_in_physics = toggled_on
+
+
+func _on_show_stats_toggled(toggled_on):
+ get_parent().text_groups_show_stats = toggled_on
+
+
+func _on_draw_boxes_toggled(toggled_on):
+ get_parent().draw_array_of_boxes = toggled_on
+
+ DebugDraw3D.clear_all()
+ get_parent().timer_cubes = 0
+
+
+func _on_draw_1m_boxes_toggled(toggled_on):
+ get_parent().draw_1m_boxes = toggled_on
+
+ if get_parent().draw_array_of_boxes:
+ DebugDraw3D.clear_all()
+ get_parent().timer_cubes = 0
diff --git a/examples_dd3d/demo_web_docs_version_select.gd b/examples_dd3d/demo_web_docs_version_select.gd
new file mode 100644
index 0000000..c63bc49
--- /dev/null
+++ b/examples_dd3d/demo_web_docs_version_select.gd
@@ -0,0 +1,42 @@
+extends HBoxContainer
+
+var _on_versions_loaded_callback = null
+@onready var btn: OptionButton = $OptionButton
+
+func _enter_tree():
+ hide()
+
+
+func _ready():
+ if OS.has_feature('web'):
+ _on_versions_loaded_callback = JavaScriptBridge.create_callback(_on_versions_loaded)
+ var versions_callbacks: JavaScriptObject = JavaScriptBridge.get_interface("versions_callbacks")
+ versions_callbacks.loaded = _on_versions_loaded_callback
+
+ JavaScriptBridge.eval("loadVersions()")
+
+
+func _on_versions_loaded(args: Array) -> void:
+ if (args.size() == 0):
+ return
+
+ var current_version: String = args[0]
+
+ var versions_str: String = JavaScriptBridge.eval("versions_callbacks.versions;")
+ var version_urls_str: String = JavaScriptBridge.eval("versions_callbacks.version_urls;")
+ var versions: PackedStringArray = versions_str.split(";", false)
+ var version_urls: PackedStringArray = version_urls_str.split(";", false)
+
+ if versions:
+ show()
+ btn.clear()
+ btn.item_selected.connect(func(idx):
+ # move to another version
+ JavaScriptBridge.eval("window.location.href = \"%s\"" % version_urls[idx])
+ )
+
+ for i in range(versions.size()):
+ btn.add_item(versions[i], i)
+
+ if versions[i] == current_version:
+ btn.select(i)
diff --git a/export_presets.cfg b/export_presets.cfg
new file mode 100644
index 0000000..5b2d8de
--- /dev/null
+++ b/export_presets.cfg
@@ -0,0 +1,146 @@
+[preset.0]
+
+name="Linux"
+platform="Linux"
+runnable=true
+advanced_options=false
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter="addons/*"
+exclude_filter=""
+export_path="../.build/mj177/linux/apapercut.x86_64"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+script_export_mode=2
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/s3tc_bptc=true
+texture_format/etc2_astc=false
+binary_format/architecture="x86_64"
+ssh_remote_deploy/enabled=true
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="#!/usr/bin/env bash
+export DISPLAY=:0
+unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
+\"{temp_dir}/{exe_name}\" {cmd_args}"
+ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
+kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
+rm -rf \"{temp_dir}\""
+
+[preset.1]
+
+name="Windows Desktop"
+platform="Windows Desktop"
+runnable=true
+advanced_options=false
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter="addons/*"
+exclude_filter=""
+export_path="../.build/mj177/windows/apapercut.exe"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+script_export_mode=2
+
+[preset.1.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/s3tc_bptc=true
+texture_format/etc2_astc=false
+binary_format/architecture="x86_64"
+codesign/enable=false
+codesign/timestamp=true
+codesign/timestamp_server_url=""
+codesign/digest_algorithm=1
+codesign/description=""
+codesign/custom_options=PackedStringArray()
+application/modify_resources=false
+application/icon=""
+application/console_wrapper_icon=""
+application/icon_interpolation=4
+application/file_version=""
+application/product_version=""
+application/company_name=""
+application/product_name=""
+application/file_description=""
+application/copyright=""
+application/trademarks=""
+application/export_angle=0
+application/export_d3d12=0
+application/d3d12_agility_sdk_multiarch=true
+ssh_remote_deploy/enabled=false
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
+$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
+$trigger = New-ScheduledTaskTrigger -Once -At 00:00
+$settings = New-ScheduledTaskSettingsSet
+$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
+Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
+Start-ScheduledTask -TaskName godot_remote_debug
+while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
+Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
+ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
+Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
+Remove-Item -Recurse -Force '{temp_dir}'"
+
+[preset.2]
+
+name="Web"
+platform="Web"
+runnable=true
+advanced_options=false
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter="addons/*"
+exclude_filter=""
+export_path="../.build/mj177/web/index.html"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+script_export_mode=2
+
+[preset.2.options]
+
+custom_template/debug=""
+custom_template/release=""
+variant/extensions_support=true
+variant/thread_support=true
+vram_texture_compression/for_desktop=true
+vram_texture_compression/for_mobile=false
+html/export_icon=true
+html/custom_html_shell=""
+html/head_include=""
+html/canvas_resize_policy=2
+html/focus_canvas_on_start=true
+html/experimental_virtual_keyboard=false
+progressive_web_app/enabled=false
+progressive_web_app/ensure_cross_origin_isolation_headers=true
+progressive_web_app/offline_page=""
+progressive_web_app/display=1
+progressive_web_app/orientation=0
+progressive_web_app/icon_144x144=""
+progressive_web_app/icon_180x180=""
+progressive_web_app/icon_512x512=""
+progressive_web_app/background_color=Color(0, 0, 0, 1)
diff --git a/prefabs/SecretTrigger.tscn b/prefabs/SecretTrigger.tscn
new file mode 100644
index 0000000..d452baf
--- /dev/null
+++ b/prefabs/SecretTrigger.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3 uid="uid://c2h3negcbnqi"]
+
+[ext_resource type="Script" path="res://prefabs/secret_trigger.gd" id="1_1yylx"]
+
+[node name="SecretTrigger" type="Area3D"]
+script = ExtResource("1_1yylx")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
diff --git a/prefabs/death_handler.gd b/prefabs/death_handler.gd
new file mode 100644
index 0000000..830a58f
--- /dev/null
+++ b/prefabs/death_handler.gd
@@ -0,0 +1,20 @@
+extends Node
+class_name DeathHandler
+
+@export var camera: Node3D;
+
+var enabled: bool = false
+var targetDirection: Vector3;
+
+func died(from: Node3D):
+ print(from, ' ', from.global_position)
+ enabled = true
+
+ var direction = (from.global_position - camera.global_position).normalized()
+ var previousForward = -camera.global_basis.z;
+
+ create_tween().tween_method(self.rotateToTarget.bind(direction, previousForward), 0.0, 1.0, 1.0).set_trans(Tween.TRANS_EXPO)
+
+
+func rotateToTarget(weight:float, targetDirection: Vector3, previousForward: Vector3) -> void:
+ camera.look_at(lerp(camera.global_position + previousForward, camera.global_position + targetDirection, weight))
diff --git a/prefabs/delete_after_play.gd b/prefabs/delete_after_play.gd
new file mode 100644
index 0000000..345c2a0
--- /dev/null
+++ b/prefabs/delete_after_play.gd
@@ -0,0 +1,5 @@
+extends AudioStreamPlayer3D
+
+
+func _on_finished() -> void:
+ self.queue_free()
diff --git a/prefabs/movement_player.gd b/prefabs/movement_player.gd
new file mode 100644
index 0000000..fa06665
--- /dev/null
+++ b/prefabs/movement_player.gd
@@ -0,0 +1,25 @@
+extends AudioStreamPlayer3D
+
+@export var threshold: float = 1.5
+@export var targetElement: Node3D
+
+var currentLocation: Vector3
+var distance: float
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ currentLocation = targetElement.global_position
+ distance = threshold
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta: float) -> void:
+
+ var positionDistance = targetElement.global_position.distance_to(currentLocation)
+ currentLocation = targetElement.global_position
+
+ distance = distance - positionDistance
+ if (distance > 0):
+ return
+
+ distance = threshold
+ play()
diff --git a/prefabs/player.tscn b/prefabs/player.tscn
index 730a031..6a40fd4 100644
--- a/prefabs/player.tscn
+++ b/prefabs/player.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=9 format=3 uid="uid://ctu0cdqcefrwg"]
+[gd_scene load_steps=17 format=3 uid="uid://ctu0cdqcefrwg"]
[ext_resource type="Script" path="res://scripts/player/player.gd" id="1_1tqna"]
[ext_resource type="Script" path="res://scripts/player/cameraController.gd" id="1_v2q4i"]
@@ -6,7 +6,15 @@
[ext_resource type="Script" path="res://scripts/player/weapon.gd" id="4_nfpgt"]
[ext_resource type="PackedScene" uid="uid://bnwuklgcmgyw8" path="res://content/muzzleflash/muzzleflash.tscn" id="5_v58ob"]
[ext_resource type="Script" path="res://demo/agents/scripts/health.gd" id="6_ixo02"]
+[ext_resource type="AudioStream" uid="uid://cn33abuyca8so" path="res://audio/gun/assaultrifle.tres" id="6_lf83g"]
[ext_resource type="PackedScene" uid="uid://bwnlcxfwxyj7j" path="res://content/ui/UI.tscn" id="7_qi8fi"]
+[ext_resource type="Script" path="res://prefabs/death_handler.gd" id="8_h34n7"]
+[ext_resource type="Script" path="res://prefabs/movement_player.gd" id="10_cd7pb"]
+[ext_resource type="AudioStream" uid="uid://dq8k15jwlxhw" path="res://audio/footsteps/walk/footsteps_walking.tres" id="10_n5agh"]
+[ext_resource type="AudioStream" uid="uid://c0c1k0gmcd4yb" path="res://audio/footsteps/sprint/footsteps_running.tres" id="12_l38x2"]
+[ext_resource type="AudioStream" uid="uid://ba5iu681ldn1r" path="res://audio/startup/startup.tres" id="13_dc1a4"]
+[ext_resource type="Script" path="res://prefabs/delete_after_play.gd" id="14_i2m1b"]
+[ext_resource type="AudioStream" uid="uid://sisk81utsw72" path="res://audio/playerhit/playerhit.tres" id="15_5lcwc"]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_w4dyg"]
margin = 0.332
@@ -21,9 +29,10 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CylinderShape3D_w4dyg")
[node name="Camera3D" type="Camera3D" parent="CollisionShape3D" node_paths=PackedStringArray("PlayerNode")]
+process_mode = 3
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0, 0.68094, 0)
-keep_aspect = 0
-fov = 66.5
+current = true
+fov = 70.0
script = ExtResource("1_v2q4i")
PlayerNode = NodePath("../..")
@@ -34,70 +43,36 @@ MuzzleFlash = ExtResource("5_v58ob")
Origin = NodePath("LVA4_Armature/Skeleton3D/BoneAttachment3D/Plane/Muzzle")
BulletOrigin = NodePath("..")
+[node name="LVA4_Armature" parent="CollisionShape3D/Camera3D/Weapon" index="0"]
+transform = Transform3D(100, 0, 0, 0, 0, 100, 0, -100, 0, 1.48072e-08, 0, 0.169375)
+
[node name="Skeleton3D" parent="CollisionShape3D/Camera3D/Weapon/LVA4_Armature" index="0"]
-bones/6/rotation = Quaternion(0.697459, -0.0272195, 0.0377435, 0.715112)
-bones/6/scale = Vector3(1.01704, 0.970755, 1.01744)
-bones/9/rotation = Quaternion(0.59753, 0.0883948, -0.0950469, 0.791272)
-bones/9/scale = Vector3(0.895205, 1.24851, 0.895209)
-bones/12/rotation = Quaternion(0.5, 0.5, 0.5, -0.5)
-bones/13/rotation = Quaternion(-0.288794, -0.583705, 0.584114, 0.484456)
-bones/14/rotation = Quaternion(0.847616, 0.123803, -0.0282024, 0.515193)
-bones/15/rotation = Quaternion(-0.281539, -0.0718473, 0.00808318, 0.956822)
-bones/17/rotation = Quaternion(-0.0979801, -0.0112315, 0.138692, 0.985413)
-bones/18/rotation = Quaternion(0.00285996, -0.0771369, -0.0955664, 0.992426)
-bones/19/rotation = Quaternion(-0.014737, 0.0372155, -0.0126211, 0.999119)
-bones/23/rotation = Quaternion(0.0470606, 0.0222304, 0.674114, 0.736791)
-bones/24/rotation = Quaternion(-0.0161525, -0.0078105, 0.43526, 0.900126)
-bones/28/rotation = Quaternion(0.102228, 0.0657092, 0.694722, 0.708938)
-bones/29/rotation = Quaternion(-0.0349312, -0.0131626, -0.00239495, 0.9993)
-bones/33/rotation = Quaternion(0.0718133, 0.0329945, 0.409986, 0.908661)
-bones/37/rotation = Quaternion(-0.0719948, 0.0990852, 0.535014, 0.835918)
-bones/38/rotation = Quaternion(0.0321199, 0.0582583, 0.363318, 0.929287)
-bones/40/rotation = Quaternion(0.0136756, -0.0172532, -0.0023784, 0.999755)
-bones/42/rotation = Quaternion(0.0135645, -0.0568211, -0.00291822, 0.998288)
-bones/44/rotation = Quaternion(0.0136133, -0.0399402, -0.00268841, 0.999106)
-bones/46/rotation = Quaternion(0.8627, 0.184653, -0.263834, -0.389927)
-bones/48/rotation = Quaternion(0.0136754, -0.0172533, -0.00237861, 0.999755)
-bones/50/rotation = Quaternion(-0.0240424, -0.0253808, 0.0230706, 0.999122)
-bones/52/rotation = Quaternion(9.57256e-08, -0.10147, 2.38067e-08, 0.994839)
-bones/54/rotation = Quaternion(0.5, -0.5, -0.5, -0.5)
-bones/55/rotation = Quaternion(-0.00966559, 0.431855, -0.779032, 0.454442)
-bones/56/rotation = Quaternion(0.569151, -0.1247, -0.023923, 0.81237)
-bones/57/rotation = Quaternion(-0.205716, -0.0320915, 0.116515, 0.971121)
-bones/59/position = Vector3(5.26137e-06, 0.00062688, 4.61328e-06)
-bones/59/rotation = Quaternion(-0.0921515, 0.0504243, 0.195935, 0.974974)
-bones/60/rotation = Quaternion(-0.0504229, 0.0584449, -0.605684, 0.791952)
-bones/61/rotation = Quaternion(0.0114703, -0.0383484, -0.608711, 0.792382)
-bones/64/position = Vector3(-1.39906e-05, 0.000607789, 8.67999e-07)
-bones/64/rotation = Quaternion(-0.0560437, 0.00556503, 0.0915036, 0.994211)
-bones/65/rotation = Quaternion(0.0483554, -0.0192523, -0.71875, 0.693318)
-bones/66/rotation = Quaternion(-0.0173623, 0.00452312, -0.601864, 0.798397)
-bones/69/position = Vector3(1.30467e-06, 0.000602322, -2.7101e-06)
-bones/69/rotation = Quaternion(0.0409456, -0.0210825, 0.0696472, 0.996508)
-bones/70/rotation = Quaternion(0.0377608, -0.115509, -0.0908998, 0.988417)
-bones/71/rotation = Quaternion(-0.0354375, -0.0117314, -0.626531, 0.778502)
-bones/74/position = Vector3(-8.65025e-06, 0.000669554, 2.38656e-05)
-bones/74/rotation = Quaternion(0.0945839, -0.108202, 0.236727, 0.960889)
-bones/75/rotation = Quaternion(0.0550313, -0.0567217, -0.0538148, 0.995419)
-bones/76/rotation = Quaternion(-0.0284048, -0.0472237, -0.149045, 0.987294)
-bones/78/position = Vector3(-8.86769e-05, 0.000149522, 3.81398e-05)
-bones/78/rotation = Quaternion(0.293949, 0.638458, 0.159887, 0.69311)
-bones/79/rotation = Quaternion(-0.24679, -0.068873, -0.208296, 0.943909)
-bones/80/rotation = Quaternion(0.0181542, -0.258237, 0.0705999, 0.963327)
-bones/82/rotation = Quaternion(0.013728, -0.00687772, 0.0020478, 0.99988)
-bones/84/rotation = Quaternion(0.0137617, -0.0217986, 0.00184283, 0.999666)
-bones/86/rotation = Quaternion(0.0137452, -0.0144382, 0.0019441, 0.999799)
-bones/88/rotation = Quaternion(0.8627, -0.184653, 0.263834, -0.389927)
-bones/90/rotation = Quaternion(0.0137278, -0.00687766, 0.00204802, 0.99988)
-bones/92/rotation = Quaternion(-0.0245858, 0.00136125, -0.0224883, 0.999444)
-bones/94/rotation = Quaternion(2.7712e-09, 0.0456322, 4.00834e-08, 0.998958)
-bones/96/position = Vector3(0.00148682, 0.00419669, -0.00257247)
-bones/96/rotation = Quaternion(-0.664692, 0.0194188, 0.125885, 0.736179)
-bones/98/position = Vector3(0.000566952, 0.00437002, -0.00361386)
-bones/98/rotation = Quaternion(-0.687624, -0.153843, -0.170745, 0.688732)
+bones/6/rotation = Quaternion(0.699082, -0.0275782, 0.0381791, 0.713489)
+bones/9/rotation = Quaternion(0.598288, 0.0862358, -0.0928841, 0.791194)
+bones/9/scale = Vector3(0.899362, 1.23607, 0.899366)
+bones/13/rotation = Quaternion(-0.283862, -0.59042, 0.587465, 0.475091)
+bones/14/rotation = Quaternion(0.845483, 0.123918, -0.0276912, 0.518687)
+bones/15/rotation = Quaternion(-0.268192, -0.0524338, -0.0136426, 0.961841)
+bones/40/rotation = Quaternion(0.013691, -0.0123893, -0.00231186, 0.999827)
+bones/42/rotation = Quaternion(0.0136324, -0.0408477, -0.00269982, 0.999069)
+bones/44/rotation = Quaternion(0.0136645, -0.0280867, -0.0025261, 0.999509)
+bones/48/rotation = Quaternion(0.0136908, -0.0123894, -0.00231206, 0.999827)
+bones/50/rotation = Quaternion(-0.0241645, -0.0205397, 0.0229569, 0.999233)
+bones/52/rotation = Quaternion(9.57242e-08, -0.10117, 2.37787e-08, 0.994869)
+bones/55/rotation = Quaternion(-0.0158395, 0.439327, -0.774392, 0.455035)
+bones/56/rotation = Quaternion(0.574799, -0.124864, -0.0230532, 0.808383)
+bones/57/rotation = Quaternion(-0.216828, -0.0109878, 0.105707, 0.970408)
+bones/82/rotation = Quaternion(0.0137185, -0.00171057, 0.00211874, 0.999902)
+bones/84/rotation = Quaternion(0.0137406, -0.00472083, 0.00207752, 0.999892)
+bones/86/rotation = Quaternion(0.0137263, -0.0023118, 0.00211055, 0.999901)
+bones/90/rotation = Quaternion(0.0137183, -0.00171051, 0.00211896, 0.999902)
+bones/92/rotation = Quaternion(-0.0244656, 0.00660966, -0.0226152, 0.999423)
+bones/94/rotation = Quaternion(2.80718e-09, 0.0447449, 4.00891e-08, 0.998999)
+bones/96/rotation = Quaternion(-0.663965, 0.0366491, 0.106752, 0.739196)
+bones/98/rotation = Quaternion(-0.691382, -0.135959, -0.188549, 0.684072)
[node name="BoneAttachment3D" parent="CollisionShape3D/Camera3D/Weapon/LVA4_Armature/Skeleton3D" index="1"]
-transform = Transform3D(0.967405, -0.211612, -0.139095, 0.196587, 0.973809, -0.114238, 0.159626, 0.0831701, 0.983668, 0.00147876, 0.00257324, -0.00102415)
+transform = Transform3D(0.974534, -0.206316, -0.0878506, 0.195862, 0.973921, -0.11452, 0.109187, 0.0943966, 0.989529, 0.00141857, 0.0025722, -0.00106618)
[node name="Muzzle" parent="CollisionShape3D/Camera3D/Weapon/LVA4_Armature/Skeleton3D/BoneAttachment3D/Plane" index="0"]
transform = Transform3D(0.00256023, 0.00111408, -0.00109729, 0.000993761, 0.00046617, 0.00279197, 0.00120734, -0.00274618, 2.87907e-05, -0.00118721, 0.00250132, 0.000390777)
@@ -106,13 +81,53 @@ transform = Transform3D(0.00256023, 0.00111408, -0.00109729, 0.000993761, 0.0004
wait_time = 0.3
one_shot = true
+[node name="ShootSound" type="AudioStreamPlayer3D" parent="CollisionShape3D/Camera3D/Weapon"]
+stream = ExtResource("6_lf83g")
+bus = &"Player"
+
[node name="Health" type="Node" parent="."]
script = ExtResource("6_ixo02")
-max_health = 5.0
+max_health = 1.0
[node name="Ui" parent="." instance=ExtResource("7_qi8fi")]
[node name="Enemy Target" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.32602, 0)
+[node name="DeathHandler" type="Node" parent="." node_paths=PackedStringArray("camera")]
+process_mode = 3
+script = ExtResource("8_h34n7")
+camera = NodePath("../CollisionShape3D/Camera3D")
+
+[node name="Walking" type="AudioStreamPlayer3D" parent="." node_paths=PackedStringArray("targetElement")]
+stream = ExtResource("10_n5agh")
+bus = &"Player"
+script = ExtResource("10_cd7pb")
+threshold = 1.45
+targetElement = NodePath("..")
+
+[node name="Sprinting" type="AudioStreamPlayer3D" parent="." node_paths=PackedStringArray("targetElement")]
+process_mode = 4
+stream = ExtResource("12_l38x2")
+bus = &"Player"
+script = ExtResource("10_cd7pb")
+threshold = 3.0
+targetElement = NodePath("..")
+
+[node name="Startup" type="AudioStreamPlayer3D" parent="."]
+process_mode = 3
+stream = ExtResource("13_dc1a4")
+autoplay = true
+bus = &"Player"
+script = ExtResource("14_i2m1b")
+
+[node name="HitSound" type="AudioStreamPlayer" parent="."]
+process_mode = 3
+stream = ExtResource("15_5lcwc")
+volume_db = 2.907
+pitch_scale = 1.04
+bus = &"Player"
+
+[connection signal="finished" from="Startup" to="Startup" method="_on_finished"]
+
[editable path="CollisionShape3D/Camera3D/Weapon"]
diff --git a/prefabs/secret_trigger.gd b/prefabs/secret_trigger.gd
new file mode 100644
index 0000000..da6efee
--- /dev/null
+++ b/prefabs/secret_trigger.gd
@@ -0,0 +1,14 @@
+extends Area3D
+
+@export var SecretHandler: SecretHandler;
+@export var TriggerID: String;
+
+func _ready() -> void:
+ SecretHandler.registerTrigger(TriggerID)
+ self.body_entered.connect(self.player_entered)
+
+func player_entered(body: Node3D) -> void:
+ if body is not Player:
+ return
+
+ SecretHandler.triggered(TriggerID)
diff --git a/project.godot b/project.godot
index 33f7853..29c5c4f 100644
--- a/project.godot
+++ b/project.godot
@@ -11,15 +11,23 @@ config_version=5
[application]
config/name="MJ177"
-run/main_scene="res://TestScene.tscn"
+run/main_scene="res://content/levels/MainMenu.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility")
config/icon="res://icon.svg"
+[autoload]
+
+CyclopsAutoload="*res://addons/cyclops_level_builder/cyclops_global_scene.tscn"
+
[display]
window/size/viewport_width=1920
window/size/viewport_height=1080
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/cyclops_level_builder/plugin.cfg")
+
[input]
player_left={
diff --git a/scripts/enemies/enemy.gd b/scripts/enemies/enemy.gd
index c1044d6..56dcb8e 100644
--- a/scripts/enemies/enemy.gd
+++ b/scripts/enemies/enemy.gd
@@ -8,6 +8,9 @@ const MOVEMENT_SPEED = 1
@onready var enemyAi: EnemyAI = $BTPlayer
@onready var health: Health = $Health
@onready var origin: Node3D = $"enemies/Vert/Shoot Marker"
+@onready var enemyVisual: Node3D = $enemies/Vert
+@onready var shootSound: AudioStreamPlayer3D = $"enemies/Vert/Shoot Marker/ShootSound"
+@onready var deathSound: AudioStreamPlayer = $DeathSound
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
@@ -18,13 +21,19 @@ func _ready() -> void:
health.death.connect(died)
func died() -> void:
+ deathSound.play()
+
+ process_mode = Node.PROCESS_MODE_DISABLED
+
+ await deathSound.finished
+
queue_free()
func updateMovementBlend(delta: float, to: float) -> void:
movementBlend = move_toward(movementBlend, to, delta * 2);
func rotateTo(delta: float, targetDirection: Vector3) -> void:
- self.global_basis.z = lerp(self.global_basis.z, targetDirection, delta * 10);
+ self.global_basis.z = targetDirection;
func _physics_process(delta: float) -> void:
if agent.is_navigation_finished():
@@ -50,11 +59,10 @@ func _physics_process(delta: float) -> void:
self.animationtree['parameters/Movement/blend_position'] = movementBlend
func fireWeapon() -> void:
- var fireDirection = (Player.Instance.enemyTarget.global_position - origin.global_position).normalized()
- fireDirection = fireDirection.rotated(Vector3.UP, randf_range(-.3, .3))
+ shootSound.play()
- DebugDraw3D.draw_arrow(origin.global_position, origin.global_position + Vector3.UP * 1, Color.RED, 0.5, true, 10)
- DebugDraw3D.draw_arrow(origin.global_position, origin.global_position + fireDirection * 10, Color.WHITE, 0.5, true, 10)
+ var fireDirection = (Player.Instance.enemyTarget.global_position - origin.global_position).normalized()
+ fireDirection = fireDirection.rotated(Vector3.UP, randf_range(-0.1, 0.1))
var query = PhysicsRayQueryParameters3D.create(origin.global_position, origin.global_position + fireDirection * 200)
var result = get_world_3d().direct_space_state.intersect_ray(query)
@@ -67,4 +75,4 @@ func fireWeapon() -> void:
if not health:
return
- result.collider.health.take_damage(1, Vector2(0,0))
+ result.collider.health.take_damage(1, Vector2(0,0), self.origin)
diff --git a/scripts/enemies/enemy_ai.gd b/scripts/enemies/enemy_ai.gd
index 3f48c3c..4d45ebb 100644
--- a/scripts/enemies/enemy_ai.gd
+++ b/scripts/enemies/enemy_ai.gd
@@ -8,7 +8,7 @@ const DISTANCE_TO_PLAYER: float = 10.0
@export var character: Enemy
@export_category("Sprite")
-@export var sprite:Sprite3D;
+@export var visual: GeometryInstance3D;
@export var idleTexture: Texture2D;
@export var aggressiveTexture: Texture2D;
@@ -25,12 +25,23 @@ var aggressive = false
func _physics_process(_delta: float) -> void:
self.blackboard.set_var('PlayerDistance', character.global_position.distance_to(Player.Instance.global_position))
self.blackboard.set_var('LookingAngleToPlayer', (-character.global_basis.z).dot((character.global_position - Player.Instance.global_position).normalized()))
+
+ self.blackboard.set_var('PlayerVisible', getPlayerVisible())
+
+func getPlayerVisible() -> bool:
+ var query = PhysicsRayQueryParameters3D.create(Origin.global_position, Player.Instance.global_position);
+ var result = character.get_world_3d().direct_space_state.intersect_ray(query)
+
+ if !result:
+ return false
+
+ return result.collider is Player
func setAggressive(value: bool) -> void:
if not aggressive && value:
agent.target_position = character.global_position;
- sprite.texture = aggressiveTexture if value else idleTexture
+ (visual.material_overlay as StandardMaterial3D).albedo_texture = aggressiveTexture if value else idleTexture
aggressive = value
@@ -44,7 +55,7 @@ func moveToPlayer() -> void:
func Fire() -> void:
self.animationTree['parameters/playback'].travel('Fire')
- var instance = MuzzleFlash.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
+ var instance = MuzzleFlash.instantiate()
Origin.add_child(instance)
character.fireWeapon()
diff --git a/scripts/level_secret_handler.gd b/scripts/level_secret_handler.gd
new file mode 100644
index 0000000..335cfa1
--- /dev/null
+++ b/scripts/level_secret_handler.gd
@@ -0,0 +1,24 @@
+extends Node
+class_name SecretHandler
+
+var totalTriggers: int = 0
+var triggers: Array[String] = []
+
+signal SecretTriggered
+
+func registerTrigger(id: String) -> void:
+ if triggers.count(id) > 0:
+ return
+
+ totalTriggers = totalTriggers + 1
+ triggers.append(id)
+
+func triggered(id: String) -> void:
+ if triggers.count(id) < 1:
+ return
+
+ triggers.erase(id)
+ print("Triggered ", id)
+ SecretTriggered.emit()
+
+ Player.Instance.ui.displaySecret()
diff --git a/scripts/player/cameraController.gd b/scripts/player/cameraController.gd
index 731bd19..9ebb055 100644
--- a/scripts/player/cameraController.gd
+++ b/scripts/player/cameraController.gd
@@ -1,16 +1,25 @@
extends Camera3D
+class_name CameraController
+
@export var PlayerNode: Node3D;
@export var turnSpeed: float = 0.25;
@export var XRotationClamp: Vector2 = Vector2(-90, 90)
var currentRotation: Vector2 = Vector2(0,0);
+var enabled: bool = true
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED;
+func disableInput():
+ enabled = false
+
func _input(event: InputEvent) -> void:
+ if not enabled:
+ return
+
if event is InputEventKey && event.key_label == KEY_ESCAPE:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
diff --git a/scripts/player/player.gd b/scripts/player/player.gd
index 87c8027..e7cdbcf 100644
--- a/scripts/player/player.gd
+++ b/scripts/player/player.gd
@@ -14,6 +14,15 @@ static var Instance: Player;
@onready var collisionShape: CollisionShape3D = $CollisionShape3D
@onready var health: Health = $Health
@onready var enemyTarget: Node3D = $"Enemy Target"
+@onready var deathHandler: DeathHandler = $DeathHandler
+@onready var cameraController: CameraController = $CollisionShape3D/Camera3D
+@onready var ui: UI = $Ui
+@onready var weapon: Node3D = $CollisionShape3D/Camera3D/Weapon
+
+@onready var walkingSound = $Walking
+@onready var sprintingSound = $Sprinting
+@onready var startupSound = $Startup
+@onready var hitSound = $HitSound
const CROUCHED_SPEED: float = 2.5
const SPEED: float = 5.0
@@ -23,13 +32,25 @@ const JUMP_VELOCITY: float = 4.5
var lastDirection = Vector2(0,0)
@export var PlayerState: PlayerStates = PlayerStates.STANDING
+var controls = true
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
func _ready() -> void:
+ weapon.process_mode = Node.PROCESS_MODE_ALWAYS
+
+ get_tree().paused = true
+ ui.setFade(0)
+ get_tree().create_timer(1).timeout.connect(readying)
+ weapon.process_mode = Node.PROCESS_MODE_PAUSABLE
+
Instance = self
health.damaged.connect(self.damaged)
+ health.death.connect(self.onDeath)
+
+func readying():
+ get_tree().paused = false;
func _physics_process(delta: float) -> void:
# Add the gravity.
@@ -53,8 +74,12 @@ func _physics_process(delta: float) -> void:
self.handleCrouching()
if PlayerState == PlayerStates.STANDING && Input.is_action_pressed('player_sprint'):
self.PlayerState = PlayerStates.SPRINTING
+ walkingSound.process_mode = Node.PROCESS_MODE_DISABLED
+ sprintingSound.process_mode = Node.PROCESS_MODE_INHERIT
if PlayerState == PlayerStates.SPRINTING && Input.is_action_just_released('player_sprint'):
self.PlayerState = PlayerStates.STANDING
+ walkingSound.process_mode = Node.PROCESS_MODE_INHERIT
+ sprintingSound.process_mode = Node.PROCESS_MODE_DISABLED
if PlayerState == PlayerStates.SPRINTING:
speed = SPRINT_SPEED
@@ -96,3 +121,17 @@ func handleCrouching():
func damaged():
print("Damage")
+
+func onDeath(from):
+ hitSound.play()
+ deathHandler.died(from)
+ cameraController.disableInput()
+ ui.displayYouDied()
+
+ get_tree().paused = true
+
+ await get_tree().create_timer(2.5).timeout
+ ui.setFade(1)
+ await get_tree().create_timer(1.0).timeout
+
+ get_tree().reload_current_scene()
diff --git a/scripts/player/weapon.gd b/scripts/player/weapon.gd
index fe5aa12..5e48a0e 100644
--- a/scripts/player/weapon.gd
+++ b/scripts/player/weapon.gd
@@ -2,6 +2,7 @@ extends Node3D
@onready var animationtree: AnimationTree = $AnimationPlayer/AnimationTree
@onready var timer: Timer = $CooldownTimer
+@onready var shootSound: AudioStreamPlayer3D = $ShootSound
@export_category("Firing")
@export var MuzzleFlash: PackedScene
@@ -33,25 +34,23 @@ func _input(event: InputEvent) -> void:
self.animationtree.set('parameters/' + travelTarget + '/blend_position', randf_range(0, 2))
self.statemachine.travel(travelTarget)
+ shootSound.play()
fireBullet()
self.timer.start()
func fireBullet() -> void:
- var instance = self.MuzzleFlash.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
+ var instance = self.MuzzleFlash.instantiate()
Origin.add_child(instance)
var query = PhysicsRayQueryParameters3D.create(BulletOrigin.global_position, BulletOrigin.global_position + BulletOrigin.global_basis.z * -200)
var result = get_world_3d().direct_space_state.intersect_ray(query)
if not result:
- print('No result')
return
var health = result.collider.get_node_or_null('Health')
if health:
- print("Dealing Damage")
- result.collider.health.take_damage(1, Vector2(0,0))
+ result.collider.health.take_damage(1, Vector2(0,0), BulletOrigin)
return
- print('No Health Element')
diff --git a/scripts/secret_label.gd b/scripts/secret_label.gd
new file mode 100644
index 0000000..a61ad8e
--- /dev/null
+++ b/scripts/secret_label.gd
@@ -0,0 +1,10 @@
+extends Label3D
+
+@export var secretHandler: SecretHandler;
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ secretHandler.SecretTriggered.connect(self.secretTriggered)
+
+func secretTriggered() -> void:
+ text = 'Secrets found: \n {0} / {1}'.format([secretHandler.totalTriggers - secretHandler.triggers.size(), secretHandler.totalTriggers])