aboutsummaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/voxel-core/assets/classes/voxel.pngbin0 -> 352 bytes
-rw-r--r--addons/voxel-core/assets/classes/voxel.png.import35
-rw-r--r--addons/voxel-core/assets/classes/voxel_core.pngbin0 -> 655 bytes
-rw-r--r--addons/voxel-core/assets/classes/voxel_core.png.import35
-rw-r--r--addons/voxel-core/assets/classes/voxel_mesh.pngbin0 -> 627 bytes
-rw-r--r--addons/voxel-core/assets/classes/voxel_mesh.png.import35
-rw-r--r--addons/voxel-core/assets/classes/voxel_set.pngbin0 -> 512 bytes
-rw-r--r--addons/voxel-core/assets/classes/voxel_set.png.import35
-rw-r--r--addons/voxel-core/assets/classes/voxel_world.pngbin0 -> 859 bytes
-rw-r--r--addons/voxel-core/assets/classes/voxel_world.png.import35
-rw-r--r--addons/voxel-core/assets/controls/about.pngbin0 -> 392 bytes
-rw-r--r--addons/voxel-core/assets/controls/about.png.import35
-rw-r--r--addons/voxel-core/assets/controls/add.pngbin0 -> 387 bytes
-rw-r--r--addons/voxel-core/assets/controls/add.png.import35
-rw-r--r--addons/voxel-core/assets/controls/area.pngbin0 -> 389 bytes
-rw-r--r--addons/voxel-core/assets/controls/area.png.import35
-rw-r--r--addons/voxel-core/assets/controls/autosave.pngbin0 -> 441 bytes
-rw-r--r--addons/voxel-core/assets/controls/autosave.png.import35
-rw-r--r--addons/voxel-core/assets/controls/cancel.pngbin0 -> 450 bytes
-rw-r--r--addons/voxel-core/assets/controls/cancel.png.import35
-rw-r--r--addons/voxel-core/assets/controls/commit.pngbin0 -> 443 bytes
-rw-r--r--addons/voxel-core/assets/controls/commit.png.import35
-rw-r--r--addons/voxel-core/assets/controls/confirm.pngbin0 -> 439 bytes
-rw-r--r--addons/voxel-core/assets/controls/confirm.png.import35
-rw-r--r--addons/voxel-core/assets/controls/cursor.pngbin0 -> 402 bytes
-rw-r--r--addons/voxel-core/assets/controls/cursor.png.import35
-rw-r--r--addons/voxel-core/assets/controls/docs.pngbin0 -> 410 bytes
-rw-r--r--addons/voxel-core/assets/controls/docs.png.import35
-rw-r--r--addons/voxel-core/assets/controls/down.pngbin0 -> 457 bytes
-rw-r--r--addons/voxel-core/assets/controls/down.png.import35
-rw-r--r--addons/voxel-core/assets/controls/duplicate.pngbin0 -> 407 bytes
-rw-r--r--addons/voxel-core/assets/controls/duplicate.png.import35
-rw-r--r--addons/voxel-core/assets/controls/edit.pngbin0 -> 390 bytes
-rw-r--r--addons/voxel-core/assets/controls/edit.png.import35
-rw-r--r--addons/voxel-core/assets/controls/effects.pngbin0 -> 397 bytes
-rw-r--r--addons/voxel-core/assets/controls/effects.png.import35
-rw-r--r--addons/voxel-core/assets/controls/export.pngbin0 -> 402 bytes
-rw-r--r--addons/voxel-core/assets/controls/export.png.import35
-rw-r--r--addons/voxel-core/assets/controls/extrude.pngbin0 -> 434 bytes
-rw-r--r--addons/voxel-core/assets/controls/extrude.png.import35
-rw-r--r--addons/voxel-core/assets/controls/fill.pngbin0 -> 402 bytes
-rw-r--r--addons/voxel-core/assets/controls/fill.png.import35
-rw-r--r--addons/voxel-core/assets/controls/floor.pngbin0 -> 384 bytes
-rw-r--r--addons/voxel-core/assets/controls/floor.png.import35
-rw-r--r--addons/voxel-core/assets/controls/general.pngbin0 -> 405 bytes
-rw-r--r--addons/voxel-core/assets/controls/general.png.import35
-rw-r--r--addons/voxel-core/assets/controls/grid.pngbin0 -> 407 bytes
-rw-r--r--addons/voxel-core/assets/controls/grid.png.import35
-rw-r--r--addons/voxel-core/assets/controls/import.pngbin0 -> 398 bytes
-rw-r--r--addons/voxel-core/assets/controls/import.png.import35
-rw-r--r--addons/voxel-core/assets/controls/individual.pngbin0 -> 402 bytes
-rw-r--r--addons/voxel-core/assets/controls/individual.png.import35
-rw-r--r--addons/voxel-core/assets/controls/info.pngbin0 -> 395 bytes
-rw-r--r--addons/voxel-core/assets/controls/info.png.import35
-rw-r--r--addons/voxel-core/assets/controls/issues.pngbin0 -> 404 bytes
-rw-r--r--addons/voxel-core/assets/controls/issues.png.import35
-rw-r--r--addons/voxel-core/assets/controls/lock.pngbin0 -> 490 bytes
-rw-r--r--addons/voxel-core/assets/controls/lock.png.import35
-rw-r--r--addons/voxel-core/assets/controls/mirrorx.pngbin0 -> 492 bytes
-rw-r--r--addons/voxel-core/assets/controls/mirrorx.png.import35
-rw-r--r--addons/voxel-core/assets/controls/mirrory.pngbin0 -> 467 bytes
-rw-r--r--addons/voxel-core/assets/controls/mirrory.png.import35
-rw-r--r--addons/voxel-core/assets/controls/mirrorz.pngbin0 -> 518 bytes
-rw-r--r--addons/voxel-core/assets/controls/mirrorz.png.import35
-rw-r--r--addons/voxel-core/assets/controls/paint.pngbin0 -> 392 bytes
-rw-r--r--addons/voxel-core/assets/controls/paint.png.import35
-rw-r--r--addons/voxel-core/assets/controls/pan.pngbin0 -> 414 bytes
-rw-r--r--addons/voxel-core/assets/controls/pan.png.import35
-rw-r--r--addons/voxel-core/assets/controls/pick.pngbin0 -> 396 bytes
-rw-r--r--addons/voxel-core/assets/controls/pick.png.import35
-rw-r--r--addons/voxel-core/assets/controls/primary.pngbin0 -> 437 bytes
-rw-r--r--addons/voxel-core/assets/controls/primary.png.import35
-rw-r--r--addons/voxel-core/assets/controls/raw.pngbin0 -> 396 bytes
-rw-r--r--addons/voxel-core/assets/controls/raw.png.import35
-rw-r--r--addons/voxel-core/assets/controls/refresh.pngbin0 -> 400 bytes
-rw-r--r--addons/voxel-core/assets/controls/refresh.png.import35
-rw-r--r--addons/voxel-core/assets/controls/reset.pngbin0 -> 403 bytes
-rw-r--r--addons/voxel-core/assets/controls/reset.png.import35
-rw-r--r--addons/voxel-core/assets/controls/secondary.pngbin0 -> 469 bytes
-rw-r--r--addons/voxel-core/assets/controls/secondary.png.import35
-rw-r--r--addons/voxel-core/assets/controls/select.pngbin0 -> 394 bytes
-rw-r--r--addons/voxel-core/assets/controls/select.png.import35
-rw-r--r--addons/voxel-core/assets/controls/settings.pngbin0 -> 525 bytes
-rw-r--r--addons/voxel-core/assets/controls/settings.png.import35
-rw-r--r--addons/voxel-core/assets/controls/solid.pngbin0 -> 385 bytes
-rw-r--r--addons/voxel-core/assets/controls/solid.png.import35
-rw-r--r--addons/voxel-core/assets/controls/sub.pngbin0 -> 364 bytes
-rw-r--r--addons/voxel-core/assets/controls/sub.png.import35
-rw-r--r--addons/voxel-core/assets/controls/swap.pngbin0 -> 419 bytes
-rw-r--r--addons/voxel-core/assets/controls/swap.png.import35
-rw-r--r--addons/voxel-core/assets/controls/tools.pngbin0 -> 467 bytes
-rw-r--r--addons/voxel-core/assets/controls/tools.png.import35
-rw-r--r--addons/voxel-core/assets/controls/unlock.pngbin0 -> 500 bytes
-rw-r--r--addons/voxel-core/assets/controls/unlock.png.import35
-rw-r--r--addons/voxel-core/assets/controls/up.pngbin0 -> 465 bytes
-rw-r--r--addons/voxel-core/assets/controls/up.png.import35
-rw-r--r--addons/voxel-core/assets/controls/visible.pngbin0 -> 465 bytes
-rw-r--r--addons/voxel-core/assets/controls/visible.png.import35
-rw-r--r--addons/voxel-core/assets/controls/wired.pngbin0 -> 407 bytes
-rw-r--r--addons/voxel-core/assets/controls/wired.png.import35
-rw-r--r--addons/voxel-core/assets/logos/GitHub.pngbin0 -> 281 bytes
-rw-r--r--addons/voxel-core/assets/logos/GitHub.png.import35
-rw-r--r--addons/voxel-core/assets/logos/Godot.pngbin0 -> 286 bytes
-rw-r--r--addons/voxel-core/assets/logos/Godot.png.import35
-rw-r--r--addons/voxel-core/assets/logos/MagicaVoxel.pngbin0 -> 482 bytes
-rw-r--r--addons/voxel-core/assets/logos/MagicaVoxel.png.import35
-rw-r--r--addons/voxel-core/classes/reader.gd21
-rw-r--r--addons/voxel-core/classes/readers/gpl.gd48
-rw-r--r--addons/voxel-core/classes/readers/image.gd40
-rw-r--r--addons/voxel-core/classes/readers/vox.gd86
-rw-r--r--addons/voxel-core/classes/voxel.gd330
-rw-r--r--addons/voxel-core/classes/voxel_mesh.gd124
-rw-r--r--addons/voxel-core/classes/voxel_object.gd501
-rw-r--r--addons/voxel-core/classes/voxel_set.gd234
-rw-r--r--addons/voxel-core/classes/voxel_tool.gd227
-rw-r--r--addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd199
-rw-r--r--addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn12
-rw-r--r--addons/voxel-core/controls/voxel_button/voxel_button.gd110
-rw-r--r--addons/voxel-core/controls/voxel_button/voxel_button.tscn37
-rw-r--r--addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd368
-rw-r--r--addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn81
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd824
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn475
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres4
-rw-r--r--addons/voxel-core/defaults/voxel_set.tres444
-rw-r--r--addons/voxel-core/engine/importers/meshes.gd108
-rw-r--r--addons/voxel-core/engine/importers/voxel_objects.gd112
-rw-r--r--addons/voxel-core/engine/importers/voxel_sets.gd66
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd17
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/area.gd39
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/extrude.gd69
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/individual.gd27
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd29
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/add.gd49
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/fill.gd42
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/pick.gd21
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/sub.gd42
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/swap.gd45
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/voxel_cursor/voxel_cursor.gd112
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/voxel_grid/voxel_grid.gd113
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.gd866
-rw-r--r--addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.tscn1010
-rw-r--r--addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.gd210
-rw-r--r--addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.tscn256
-rw-r--r--addons/voxel-core/plugin.cfg7
-rw-r--r--addons/voxel-core/voxel-core.gd168
146 files changed, 9428 insertions, 0 deletions
diff --git a/addons/voxel-core/assets/classes/voxel.png b/addons/voxel-core/assets/classes/voxel.png
new file mode 100644
index 0000000..820250c
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel.png
Binary files differ
diff --git a/addons/voxel-core/assets/classes/voxel.png.import b/addons/voxel-core/assets/classes/voxel.png.import
new file mode 100644
index 0000000..da4c1a1
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/voxel.png-dd64143ceb6a899a7d27aac508889c4a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/classes/voxel.png"
+dest_files=[ "res://.import/voxel.png-dd64143ceb6a899a7d27aac508889c4a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/classes/voxel_core.png b/addons/voxel-core/assets/classes/voxel_core.png
new file mode 100644
index 0000000..fa4ebc2
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_core.png
Binary files differ
diff --git a/addons/voxel-core/assets/classes/voxel_core.png.import b/addons/voxel-core/assets/classes/voxel_core.png.import
new file mode 100644
index 0000000..5cfba0d
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_core.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/voxel_core.png-a32ca196c37db7145310427f4db62a3a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/classes/voxel_core.png"
+dest_files=[ "res://.import/voxel_core.png-a32ca196c37db7145310427f4db62a3a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/classes/voxel_mesh.png b/addons/voxel-core/assets/classes/voxel_mesh.png
new file mode 100644
index 0000000..092497d
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_mesh.png
Binary files differ
diff --git a/addons/voxel-core/assets/classes/voxel_mesh.png.import b/addons/voxel-core/assets/classes/voxel_mesh.png.import
new file mode 100644
index 0000000..274479d
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_mesh.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/voxel_mesh.png-fdbe099c6e210aa233cb4e3c0a8a12a1.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/classes/voxel_mesh.png"
+dest_files=[ "res://.import/voxel_mesh.png-fdbe099c6e210aa233cb4e3c0a8a12a1.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/classes/voxel_set.png b/addons/voxel-core/assets/classes/voxel_set.png
new file mode 100644
index 0000000..265a832
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_set.png
Binary files differ
diff --git a/addons/voxel-core/assets/classes/voxel_set.png.import b/addons/voxel-core/assets/classes/voxel_set.png.import
new file mode 100644
index 0000000..8fa7b45
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_set.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/voxel_set.png-22b15a472cc87576fbf5d9ffb176d0e3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/classes/voxel_set.png"
+dest_files=[ "res://.import/voxel_set.png-22b15a472cc87576fbf5d9ffb176d0e3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/classes/voxel_world.png b/addons/voxel-core/assets/classes/voxel_world.png
new file mode 100644
index 0000000..3e1c7c8
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_world.png
Binary files differ
diff --git a/addons/voxel-core/assets/classes/voxel_world.png.import b/addons/voxel-core/assets/classes/voxel_world.png.import
new file mode 100644
index 0000000..7cd0a59
--- /dev/null
+++ b/addons/voxel-core/assets/classes/voxel_world.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/voxel_world.png-92700e4046f2e306d3d5d64f2cc531c9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/classes/voxel_world.png"
+dest_files=[ "res://.import/voxel_world.png-92700e4046f2e306d3d5d64f2cc531c9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/about.png b/addons/voxel-core/assets/controls/about.png
new file mode 100644
index 0000000..d1f5330
--- /dev/null
+++ b/addons/voxel-core/assets/controls/about.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/about.png.import b/addons/voxel-core/assets/controls/about.png.import
new file mode 100644
index 0000000..77d2c21
--- /dev/null
+++ b/addons/voxel-core/assets/controls/about.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/about.png-fdcdf3764bf5a8fdadba080dba99a88b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/about.png"
+dest_files=[ "res://.import/about.png-fdcdf3764bf5a8fdadba080dba99a88b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/add.png b/addons/voxel-core/assets/controls/add.png
new file mode 100644
index 0000000..d1879d7
--- /dev/null
+++ b/addons/voxel-core/assets/controls/add.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/add.png.import b/addons/voxel-core/assets/controls/add.png.import
new file mode 100644
index 0000000..201ffaf
--- /dev/null
+++ b/addons/voxel-core/assets/controls/add.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/add.png-a5cac4fdeb395610b18264da2a692b24.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/add.png"
+dest_files=[ "res://.import/add.png-a5cac4fdeb395610b18264da2a692b24.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/area.png b/addons/voxel-core/assets/controls/area.png
new file mode 100644
index 0000000..039a01c
--- /dev/null
+++ b/addons/voxel-core/assets/controls/area.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/area.png.import b/addons/voxel-core/assets/controls/area.png.import
new file mode 100644
index 0000000..5b09214
--- /dev/null
+++ b/addons/voxel-core/assets/controls/area.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/area.png-7f52dbe582835348e059a30546c78d68.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/area.png"
+dest_files=[ "res://.import/area.png-7f52dbe582835348e059a30546c78d68.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/autosave.png b/addons/voxel-core/assets/controls/autosave.png
new file mode 100644
index 0000000..5fba4ce
--- /dev/null
+++ b/addons/voxel-core/assets/controls/autosave.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/autosave.png.import b/addons/voxel-core/assets/controls/autosave.png.import
new file mode 100644
index 0000000..fb4c11c
--- /dev/null
+++ b/addons/voxel-core/assets/controls/autosave.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/autosave.png-86a19041a0dbfb2c147a91f63f9e623b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/autosave.png"
+dest_files=[ "res://.import/autosave.png-86a19041a0dbfb2c147a91f63f9e623b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/cancel.png b/addons/voxel-core/assets/controls/cancel.png
new file mode 100644
index 0000000..4a7dabc
--- /dev/null
+++ b/addons/voxel-core/assets/controls/cancel.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/cancel.png.import b/addons/voxel-core/assets/controls/cancel.png.import
new file mode 100644
index 0000000..a1e49fc
--- /dev/null
+++ b/addons/voxel-core/assets/controls/cancel.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/cancel.png-e31e2dc5faff0d5c62dfa166f5c96eb9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/cancel.png"
+dest_files=[ "res://.import/cancel.png-e31e2dc5faff0d5c62dfa166f5c96eb9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/commit.png b/addons/voxel-core/assets/controls/commit.png
new file mode 100644
index 0000000..034db6b
--- /dev/null
+++ b/addons/voxel-core/assets/controls/commit.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/commit.png.import b/addons/voxel-core/assets/controls/commit.png.import
new file mode 100644
index 0000000..d1ddbe0
--- /dev/null
+++ b/addons/voxel-core/assets/controls/commit.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/commit.png-a216b7fa39a4140fcb40b739de83f977.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/commit.png"
+dest_files=[ "res://.import/commit.png-a216b7fa39a4140fcb40b739de83f977.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/confirm.png b/addons/voxel-core/assets/controls/confirm.png
new file mode 100644
index 0000000..dcd3cec
--- /dev/null
+++ b/addons/voxel-core/assets/controls/confirm.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/confirm.png.import b/addons/voxel-core/assets/controls/confirm.png.import
new file mode 100644
index 0000000..1470e22
--- /dev/null
+++ b/addons/voxel-core/assets/controls/confirm.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/confirm.png-a1dca69286718e53fdb170de2c5dc2a0.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/confirm.png"
+dest_files=[ "res://.import/confirm.png-a1dca69286718e53fdb170de2c5dc2a0.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/cursor.png b/addons/voxel-core/assets/controls/cursor.png
new file mode 100644
index 0000000..1cd544c
--- /dev/null
+++ b/addons/voxel-core/assets/controls/cursor.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/cursor.png.import b/addons/voxel-core/assets/controls/cursor.png.import
new file mode 100644
index 0000000..0c871f5
--- /dev/null
+++ b/addons/voxel-core/assets/controls/cursor.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/cursor.png-4919f183c1246e8f6de0aa43fa57e669.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/cursor.png"
+dest_files=[ "res://.import/cursor.png-4919f183c1246e8f6de0aa43fa57e669.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/docs.png b/addons/voxel-core/assets/controls/docs.png
new file mode 100644
index 0000000..71f7aa0
--- /dev/null
+++ b/addons/voxel-core/assets/controls/docs.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/docs.png.import b/addons/voxel-core/assets/controls/docs.png.import
new file mode 100644
index 0000000..aa4f724
--- /dev/null
+++ b/addons/voxel-core/assets/controls/docs.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/docs.png-1f40ac273e9d9bab073c00e747b08026.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/docs.png"
+dest_files=[ "res://.import/docs.png-1f40ac273e9d9bab073c00e747b08026.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/down.png b/addons/voxel-core/assets/controls/down.png
new file mode 100644
index 0000000..67d3679
--- /dev/null
+++ b/addons/voxel-core/assets/controls/down.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/down.png.import b/addons/voxel-core/assets/controls/down.png.import
new file mode 100644
index 0000000..309ec19
--- /dev/null
+++ b/addons/voxel-core/assets/controls/down.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/down.png-e920e62c852398092f2c7db7a9fb7935.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/down.png"
+dest_files=[ "res://.import/down.png-e920e62c852398092f2c7db7a9fb7935.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/duplicate.png b/addons/voxel-core/assets/controls/duplicate.png
new file mode 100644
index 0000000..8cce084
--- /dev/null
+++ b/addons/voxel-core/assets/controls/duplicate.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/duplicate.png.import b/addons/voxel-core/assets/controls/duplicate.png.import
new file mode 100644
index 0000000..8d3158a
--- /dev/null
+++ b/addons/voxel-core/assets/controls/duplicate.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/duplicate.png-2f73b353b427b078fd0fdb0de9e665e0.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/duplicate.png"
+dest_files=[ "res://.import/duplicate.png-2f73b353b427b078fd0fdb0de9e665e0.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/edit.png b/addons/voxel-core/assets/controls/edit.png
new file mode 100644
index 0000000..743dc87
--- /dev/null
+++ b/addons/voxel-core/assets/controls/edit.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/edit.png.import b/addons/voxel-core/assets/controls/edit.png.import
new file mode 100644
index 0000000..3b5d64b
--- /dev/null
+++ b/addons/voxel-core/assets/controls/edit.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/edit.png-446dcdc24343dea15b1b27696f2bd4de.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/edit.png"
+dest_files=[ "res://.import/edit.png-446dcdc24343dea15b1b27696f2bd4de.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/effects.png b/addons/voxel-core/assets/controls/effects.png
new file mode 100644
index 0000000..8eba484
--- /dev/null
+++ b/addons/voxel-core/assets/controls/effects.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/effects.png.import b/addons/voxel-core/assets/controls/effects.png.import
new file mode 100644
index 0000000..8624282
--- /dev/null
+++ b/addons/voxel-core/assets/controls/effects.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/effects.png-cc3019a7415108bbe2cd6a01b2a05f4d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/effects.png"
+dest_files=[ "res://.import/effects.png-cc3019a7415108bbe2cd6a01b2a05f4d.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/export.png b/addons/voxel-core/assets/controls/export.png
new file mode 100644
index 0000000..24960e1
--- /dev/null
+++ b/addons/voxel-core/assets/controls/export.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/export.png.import b/addons/voxel-core/assets/controls/export.png.import
new file mode 100644
index 0000000..9209f17
--- /dev/null
+++ b/addons/voxel-core/assets/controls/export.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/export.png-392e6dca3adf094ea5780304b67ff91b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/export.png"
+dest_files=[ "res://.import/export.png-392e6dca3adf094ea5780304b67ff91b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/extrude.png b/addons/voxel-core/assets/controls/extrude.png
new file mode 100644
index 0000000..0947356
--- /dev/null
+++ b/addons/voxel-core/assets/controls/extrude.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/extrude.png.import b/addons/voxel-core/assets/controls/extrude.png.import
new file mode 100644
index 0000000..e5ee1f9
--- /dev/null
+++ b/addons/voxel-core/assets/controls/extrude.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/extrude.png-d6f7285b6c44effd6ac3d65b3ed32cd7.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/extrude.png"
+dest_files=[ "res://.import/extrude.png-d6f7285b6c44effd6ac3d65b3ed32cd7.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/fill.png b/addons/voxel-core/assets/controls/fill.png
new file mode 100644
index 0000000..c7254ff
--- /dev/null
+++ b/addons/voxel-core/assets/controls/fill.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/fill.png.import b/addons/voxel-core/assets/controls/fill.png.import
new file mode 100644
index 0000000..2796a06
--- /dev/null
+++ b/addons/voxel-core/assets/controls/fill.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/fill.png-e785c5c2b1ee31f14d7d7d8a474bc3a3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/fill.png"
+dest_files=[ "res://.import/fill.png-e785c5c2b1ee31f14d7d7d8a474bc3a3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/floor.png b/addons/voxel-core/assets/controls/floor.png
new file mode 100644
index 0000000..e47e4b1
--- /dev/null
+++ b/addons/voxel-core/assets/controls/floor.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/floor.png.import b/addons/voxel-core/assets/controls/floor.png.import
new file mode 100644
index 0000000..add2c50
--- /dev/null
+++ b/addons/voxel-core/assets/controls/floor.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/floor.png-76f4d6848e948e1ebc4cf8e9181abea8.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/floor.png"
+dest_files=[ "res://.import/floor.png-76f4d6848e948e1ebc4cf8e9181abea8.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/general.png b/addons/voxel-core/assets/controls/general.png
new file mode 100644
index 0000000..b13fb7d
--- /dev/null
+++ b/addons/voxel-core/assets/controls/general.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/general.png.import b/addons/voxel-core/assets/controls/general.png.import
new file mode 100644
index 0000000..dded8fe
--- /dev/null
+++ b/addons/voxel-core/assets/controls/general.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/general.png-af8f345e532cfc44e2feaf44fbb63c0b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/general.png"
+dest_files=[ "res://.import/general.png-af8f345e532cfc44e2feaf44fbb63c0b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/grid.png b/addons/voxel-core/assets/controls/grid.png
new file mode 100644
index 0000000..0cad214
--- /dev/null
+++ b/addons/voxel-core/assets/controls/grid.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/grid.png.import b/addons/voxel-core/assets/controls/grid.png.import
new file mode 100644
index 0000000..11bfe10
--- /dev/null
+++ b/addons/voxel-core/assets/controls/grid.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/grid.png-56b39c1518415acbf54834e0514757f3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/grid.png"
+dest_files=[ "res://.import/grid.png-56b39c1518415acbf54834e0514757f3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/import.png b/addons/voxel-core/assets/controls/import.png
new file mode 100644
index 0000000..d226ff8
--- /dev/null
+++ b/addons/voxel-core/assets/controls/import.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/import.png.import b/addons/voxel-core/assets/controls/import.png.import
new file mode 100644
index 0000000..0e491ef
--- /dev/null
+++ b/addons/voxel-core/assets/controls/import.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/import.png-377ecc2782d263e943001de94e25ceaf.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/import.png"
+dest_files=[ "res://.import/import.png-377ecc2782d263e943001de94e25ceaf.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/individual.png b/addons/voxel-core/assets/controls/individual.png
new file mode 100644
index 0000000..b564065
--- /dev/null
+++ b/addons/voxel-core/assets/controls/individual.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/individual.png.import b/addons/voxel-core/assets/controls/individual.png.import
new file mode 100644
index 0000000..08e85c0
--- /dev/null
+++ b/addons/voxel-core/assets/controls/individual.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/individual.png-7b992ff172be4dc84a00cfdca92208f1.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/individual.png"
+dest_files=[ "res://.import/individual.png-7b992ff172be4dc84a00cfdca92208f1.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/info.png b/addons/voxel-core/assets/controls/info.png
new file mode 100644
index 0000000..2516917
--- /dev/null
+++ b/addons/voxel-core/assets/controls/info.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/info.png.import b/addons/voxel-core/assets/controls/info.png.import
new file mode 100644
index 0000000..623216f
--- /dev/null
+++ b/addons/voxel-core/assets/controls/info.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/info.png-007b0015b57716d228b0dc0ee0e10fa5.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/info.png"
+dest_files=[ "res://.import/info.png-007b0015b57716d228b0dc0ee0e10fa5.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/issues.png b/addons/voxel-core/assets/controls/issues.png
new file mode 100644
index 0000000..a2b8e8b
--- /dev/null
+++ b/addons/voxel-core/assets/controls/issues.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/issues.png.import b/addons/voxel-core/assets/controls/issues.png.import
new file mode 100644
index 0000000..0409106
--- /dev/null
+++ b/addons/voxel-core/assets/controls/issues.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/issues.png-229cf0a3fbfc313cde302a402d46f7d7.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/issues.png"
+dest_files=[ "res://.import/issues.png-229cf0a3fbfc313cde302a402d46f7d7.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/lock.png b/addons/voxel-core/assets/controls/lock.png
new file mode 100644
index 0000000..4d5e711
--- /dev/null
+++ b/addons/voxel-core/assets/controls/lock.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/lock.png.import b/addons/voxel-core/assets/controls/lock.png.import
new file mode 100644
index 0000000..e8982cb
--- /dev/null
+++ b/addons/voxel-core/assets/controls/lock.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/lock.png-553809ff2d6459b74d8ba546626fed09.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/lock.png"
+dest_files=[ "res://.import/lock.png-553809ff2d6459b74d8ba546626fed09.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/mirrorx.png b/addons/voxel-core/assets/controls/mirrorx.png
new file mode 100644
index 0000000..7517f13
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrorx.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/mirrorx.png.import b/addons/voxel-core/assets/controls/mirrorx.png.import
new file mode 100644
index 0000000..31adef1
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrorx.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/mirrorx.png-572be229c3f5fcf661e2d24477316d4c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/mirrorx.png"
+dest_files=[ "res://.import/mirrorx.png-572be229c3f5fcf661e2d24477316d4c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/mirrory.png b/addons/voxel-core/assets/controls/mirrory.png
new file mode 100644
index 0000000..6251dbb
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrory.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/mirrory.png.import b/addons/voxel-core/assets/controls/mirrory.png.import
new file mode 100644
index 0000000..8026a52
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrory.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/mirrory.png-ab93caae34580c9a6038b5b2f5728acb.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/mirrory.png"
+dest_files=[ "res://.import/mirrory.png-ab93caae34580c9a6038b5b2f5728acb.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/mirrorz.png b/addons/voxel-core/assets/controls/mirrorz.png
new file mode 100644
index 0000000..59901b8
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrorz.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/mirrorz.png.import b/addons/voxel-core/assets/controls/mirrorz.png.import
new file mode 100644
index 0000000..bf34d28
--- /dev/null
+++ b/addons/voxel-core/assets/controls/mirrorz.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/mirrorz.png-4172e70a4a19dc2827474b2090f15fae.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/mirrorz.png"
+dest_files=[ "res://.import/mirrorz.png-4172e70a4a19dc2827474b2090f15fae.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/paint.png b/addons/voxel-core/assets/controls/paint.png
new file mode 100644
index 0000000..2ee39e9
--- /dev/null
+++ b/addons/voxel-core/assets/controls/paint.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/paint.png.import b/addons/voxel-core/assets/controls/paint.png.import
new file mode 100644
index 0000000..e4bb873
--- /dev/null
+++ b/addons/voxel-core/assets/controls/paint.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/paint.png-a266569402af57f2f03cd276413a545e.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/paint.png"
+dest_files=[ "res://.import/paint.png-a266569402af57f2f03cd276413a545e.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/pan.png b/addons/voxel-core/assets/controls/pan.png
new file mode 100644
index 0000000..f900328
--- /dev/null
+++ b/addons/voxel-core/assets/controls/pan.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/pan.png.import b/addons/voxel-core/assets/controls/pan.png.import
new file mode 100644
index 0000000..76f6444
--- /dev/null
+++ b/addons/voxel-core/assets/controls/pan.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/pan.png-e9c7f05716b4d2bcb75e5aab794a49bd.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/pan.png"
+dest_files=[ "res://.import/pan.png-e9c7f05716b4d2bcb75e5aab794a49bd.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/pick.png b/addons/voxel-core/assets/controls/pick.png
new file mode 100644
index 0000000..c59e2ad
--- /dev/null
+++ b/addons/voxel-core/assets/controls/pick.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/pick.png.import b/addons/voxel-core/assets/controls/pick.png.import
new file mode 100644
index 0000000..eeb3514
--- /dev/null
+++ b/addons/voxel-core/assets/controls/pick.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/pick.png-61da8185e78416281715c2ea9bda5039.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/pick.png"
+dest_files=[ "res://.import/pick.png-61da8185e78416281715c2ea9bda5039.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/primary.png b/addons/voxel-core/assets/controls/primary.png
new file mode 100644
index 0000000..15a02a5
--- /dev/null
+++ b/addons/voxel-core/assets/controls/primary.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/primary.png.import b/addons/voxel-core/assets/controls/primary.png.import
new file mode 100644
index 0000000..98b1118
--- /dev/null
+++ b/addons/voxel-core/assets/controls/primary.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/primary.png-00c953116309404e4cc7d272249267d4.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/primary.png"
+dest_files=[ "res://.import/primary.png-00c953116309404e4cc7d272249267d4.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/raw.png b/addons/voxel-core/assets/controls/raw.png
new file mode 100644
index 0000000..50b147f
--- /dev/null
+++ b/addons/voxel-core/assets/controls/raw.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/raw.png.import b/addons/voxel-core/assets/controls/raw.png.import
new file mode 100644
index 0000000..23832fa
--- /dev/null
+++ b/addons/voxel-core/assets/controls/raw.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/raw.png-d72b23a8d292660902297ee381384781.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/raw.png"
+dest_files=[ "res://.import/raw.png-d72b23a8d292660902297ee381384781.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/refresh.png b/addons/voxel-core/assets/controls/refresh.png
new file mode 100644
index 0000000..1138a7c
--- /dev/null
+++ b/addons/voxel-core/assets/controls/refresh.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/refresh.png.import b/addons/voxel-core/assets/controls/refresh.png.import
new file mode 100644
index 0000000..f61ea7e
--- /dev/null
+++ b/addons/voxel-core/assets/controls/refresh.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/refresh.png-34cd6fa9d7b7b01009e35226719e686b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/refresh.png"
+dest_files=[ "res://.import/refresh.png-34cd6fa9d7b7b01009e35226719e686b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/reset.png b/addons/voxel-core/assets/controls/reset.png
new file mode 100644
index 0000000..12ea5c1
--- /dev/null
+++ b/addons/voxel-core/assets/controls/reset.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/reset.png.import b/addons/voxel-core/assets/controls/reset.png.import
new file mode 100644
index 0000000..52559fa
--- /dev/null
+++ b/addons/voxel-core/assets/controls/reset.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/reset.png-87976538570a609a0c55689dc0f8cdfd.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/reset.png"
+dest_files=[ "res://.import/reset.png-87976538570a609a0c55689dc0f8cdfd.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/secondary.png b/addons/voxel-core/assets/controls/secondary.png
new file mode 100644
index 0000000..992b035
--- /dev/null
+++ b/addons/voxel-core/assets/controls/secondary.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/secondary.png.import b/addons/voxel-core/assets/controls/secondary.png.import
new file mode 100644
index 0000000..f1173d0
--- /dev/null
+++ b/addons/voxel-core/assets/controls/secondary.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/secondary.png-2779be8e951900e831059bca1ad87a6c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/secondary.png"
+dest_files=[ "res://.import/secondary.png-2779be8e951900e831059bca1ad87a6c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/select.png b/addons/voxel-core/assets/controls/select.png
new file mode 100644
index 0000000..21a2140
--- /dev/null
+++ b/addons/voxel-core/assets/controls/select.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/select.png.import b/addons/voxel-core/assets/controls/select.png.import
new file mode 100644
index 0000000..9ec4181
--- /dev/null
+++ b/addons/voxel-core/assets/controls/select.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/select.png-2ecbd2be264ebb6af1d5e752108708d6.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/select.png"
+dest_files=[ "res://.import/select.png-2ecbd2be264ebb6af1d5e752108708d6.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/settings.png b/addons/voxel-core/assets/controls/settings.png
new file mode 100644
index 0000000..df73412
--- /dev/null
+++ b/addons/voxel-core/assets/controls/settings.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/settings.png.import b/addons/voxel-core/assets/controls/settings.png.import
new file mode 100644
index 0000000..f272d6c
--- /dev/null
+++ b/addons/voxel-core/assets/controls/settings.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/settings.png-6c842e322bdca795d6a6ca707bc85ea6.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/settings.png"
+dest_files=[ "res://.import/settings.png-6c842e322bdca795d6a6ca707bc85ea6.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/solid.png b/addons/voxel-core/assets/controls/solid.png
new file mode 100644
index 0000000..e131ca0
--- /dev/null
+++ b/addons/voxel-core/assets/controls/solid.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/solid.png.import b/addons/voxel-core/assets/controls/solid.png.import
new file mode 100644
index 0000000..15c7ece
--- /dev/null
+++ b/addons/voxel-core/assets/controls/solid.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/solid.png-374b2adbc9c464a75c6ea2f0dce77290.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/solid.png"
+dest_files=[ "res://.import/solid.png-374b2adbc9c464a75c6ea2f0dce77290.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/sub.png b/addons/voxel-core/assets/controls/sub.png
new file mode 100644
index 0000000..b1c4e2b
--- /dev/null
+++ b/addons/voxel-core/assets/controls/sub.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/sub.png.import b/addons/voxel-core/assets/controls/sub.png.import
new file mode 100644
index 0000000..1853641
--- /dev/null
+++ b/addons/voxel-core/assets/controls/sub.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/sub.png-40eb99ba4394b3faa60807cda60ed1bd.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/sub.png"
+dest_files=[ "res://.import/sub.png-40eb99ba4394b3faa60807cda60ed1bd.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/swap.png b/addons/voxel-core/assets/controls/swap.png
new file mode 100644
index 0000000..5e07710
--- /dev/null
+++ b/addons/voxel-core/assets/controls/swap.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/swap.png.import b/addons/voxel-core/assets/controls/swap.png.import
new file mode 100644
index 0000000..993d3cc
--- /dev/null
+++ b/addons/voxel-core/assets/controls/swap.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/swap.png-7b4fb67b51c967e125a18b8652c326a8.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/swap.png"
+dest_files=[ "res://.import/swap.png-7b4fb67b51c967e125a18b8652c326a8.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/tools.png b/addons/voxel-core/assets/controls/tools.png
new file mode 100644
index 0000000..cbd2366
--- /dev/null
+++ b/addons/voxel-core/assets/controls/tools.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/tools.png.import b/addons/voxel-core/assets/controls/tools.png.import
new file mode 100644
index 0000000..9547a05
--- /dev/null
+++ b/addons/voxel-core/assets/controls/tools.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/tools.png-028847a3d1acfbc48776b44d1bf345c4.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/tools.png"
+dest_files=[ "res://.import/tools.png-028847a3d1acfbc48776b44d1bf345c4.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/unlock.png b/addons/voxel-core/assets/controls/unlock.png
new file mode 100644
index 0000000..f8af9e4
--- /dev/null
+++ b/addons/voxel-core/assets/controls/unlock.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/unlock.png.import b/addons/voxel-core/assets/controls/unlock.png.import
new file mode 100644
index 0000000..3847ded
--- /dev/null
+++ b/addons/voxel-core/assets/controls/unlock.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/unlock.png-ddbd22687d18368c3f3e2116f87f58bc.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/unlock.png"
+dest_files=[ "res://.import/unlock.png-ddbd22687d18368c3f3e2116f87f58bc.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/up.png b/addons/voxel-core/assets/controls/up.png
new file mode 100644
index 0000000..38457c5
--- /dev/null
+++ b/addons/voxel-core/assets/controls/up.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/up.png.import b/addons/voxel-core/assets/controls/up.png.import
new file mode 100644
index 0000000..70252be
--- /dev/null
+++ b/addons/voxel-core/assets/controls/up.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/up.png-872801d8cb60724f5a3cfb7b672d5a3f.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/up.png"
+dest_files=[ "res://.import/up.png-872801d8cb60724f5a3cfb7b672d5a3f.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/visible.png b/addons/voxel-core/assets/controls/visible.png
new file mode 100644
index 0000000..600d413
--- /dev/null
+++ b/addons/voxel-core/assets/controls/visible.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/visible.png.import b/addons/voxel-core/assets/controls/visible.png.import
new file mode 100644
index 0000000..a79b2cc
--- /dev/null
+++ b/addons/voxel-core/assets/controls/visible.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/visible.png-4d489f659b102565c38b3d59c191d870.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/visible.png"
+dest_files=[ "res://.import/visible.png-4d489f659b102565c38b3d59c191d870.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/controls/wired.png b/addons/voxel-core/assets/controls/wired.png
new file mode 100644
index 0000000..2ca80e3
--- /dev/null
+++ b/addons/voxel-core/assets/controls/wired.png
Binary files differ
diff --git a/addons/voxel-core/assets/controls/wired.png.import b/addons/voxel-core/assets/controls/wired.png.import
new file mode 100644
index 0000000..363d104
--- /dev/null
+++ b/addons/voxel-core/assets/controls/wired.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/wired.png-0a3b67460f74146c971e419167b3d087.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/controls/wired.png"
+dest_files=[ "res://.import/wired.png-0a3b67460f74146c971e419167b3d087.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/logos/GitHub.png b/addons/voxel-core/assets/logos/GitHub.png
new file mode 100644
index 0000000..7fdf0e1
--- /dev/null
+++ b/addons/voxel-core/assets/logos/GitHub.png
Binary files differ
diff --git a/addons/voxel-core/assets/logos/GitHub.png.import b/addons/voxel-core/assets/logos/GitHub.png.import
new file mode 100644
index 0000000..9bf7ce7
--- /dev/null
+++ b/addons/voxel-core/assets/logos/GitHub.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/GitHub.png-a7e9b9a4105cbc36b63e16091f3b6653.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/logos/GitHub.png"
+dest_files=[ "res://.import/GitHub.png-a7e9b9a4105cbc36b63e16091f3b6653.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/logos/Godot.png b/addons/voxel-core/assets/logos/Godot.png
new file mode 100644
index 0000000..8eb9374
--- /dev/null
+++ b/addons/voxel-core/assets/logos/Godot.png
Binary files differ
diff --git a/addons/voxel-core/assets/logos/Godot.png.import b/addons/voxel-core/assets/logos/Godot.png.import
new file mode 100644
index 0000000..43e6c36
--- /dev/null
+++ b/addons/voxel-core/assets/logos/Godot.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/Godot.png-af37480e4162bd9534f4000863edfce2.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/logos/Godot.png"
+dest_files=[ "res://.import/Godot.png-af37480e4162bd9534f4000863edfce2.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/assets/logos/MagicaVoxel.png b/addons/voxel-core/assets/logos/MagicaVoxel.png
new file mode 100644
index 0000000..5f36aca
--- /dev/null
+++ b/addons/voxel-core/assets/logos/MagicaVoxel.png
Binary files differ
diff --git a/addons/voxel-core/assets/logos/MagicaVoxel.png.import b/addons/voxel-core/assets/logos/MagicaVoxel.png.import
new file mode 100644
index 0000000..379a5a3
--- /dev/null
+++ b/addons/voxel-core/assets/logos/MagicaVoxel.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/MagicaVoxel.png-30a5149067cfc4e001c354a603b78d60.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/voxel-core/assets/logos/MagicaVoxel.png"
+dest_files=[ "res://.import/MagicaVoxel.png-30a5149067cfc4e001c354a603b78d60.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=false
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+process/normal_map_invert_y=false
+stream=false
+size_limit=0
+detect_3d=false
+svg/scale=1.0
diff --git a/addons/voxel-core/classes/reader.gd b/addons/voxel-core/classes/reader.gd
new file mode 100644
index 0000000..7c17b8a
--- /dev/null
+++ b/addons/voxel-core/classes/reader.gd
@@ -0,0 +1,21 @@
+class_name Reader
+extends Reference
+# Makeshift interface class inhereted by all file readers.
+
+
+
+## Public Methods
+# Calls on appropriate file reader according to file_path's extension.
+# file_path : String : path to file to be read
+# return : Dictionary : read results, contains: { error : int, voxels : Dictionary<Vec3, int>, palette : Dictionary<int, Dictionary<String, Variant> }
+static func read_file(file_path : String) -> Dictionary:
+ var result = { "error": ERR_FILE_UNRECOGNIZED }
+ match file_path.get_extension():
+ "png", "bmp", "dds", "exr", "hdr", "jpg", "jpeg", "tga", "svg", "svgz", "webp":
+ result = ImageReader.read_file(file_path)
+ "vox": result = VoxReader.read_file(file_path)
+ "qb": continue
+ "qbt": continue
+ "vxm": continue
+ "gpl": result = GPLReader.read_file(file_path)
+ return result
diff --git a/addons/voxel-core/classes/readers/gpl.gd b/addons/voxel-core/classes/readers/gpl.gd
new file mode 100644
index 0000000..4a6b943
--- /dev/null
+++ b/addons/voxel-core/classes/readers/gpl.gd
@@ -0,0 +1,48 @@
+class_name GPLReader
+extends Reference
+# GIMP palette file reader
+
+
+
+# Public Methods
+# Reads GPL file, and returns voxel palette
+static func read(gpl_file : File) -> Dictionary:
+ var result := {
+ "error": OK,
+ "voxels": {},
+ "palette": [],
+ }
+
+ if gpl_file.get_line() == "GIMP Palette":
+ while not gpl_file.eof_reached():
+ var line = gpl_file.get_line()
+ if typeof(line) == TYPE_STRING and not line.empty() and (line[0].is_valid_integer() or line[0] == " "):
+ var tokens = line.split("\t")
+ var name = ""
+ var color = tokens[0].split_floats(" ")
+ color = Color(color[0] / 255, color[1] / 255, color[2] / 255)
+ if tokens.size() > 1:
+ name = tokens[1]
+ var end = name.find("(")
+ name = name.substr(0, end)
+
+ if not result["palette"].has(color):
+ var voxel := Voxel.colored(color)
+ result["palette"].append(voxel)
+ if not name.empty():
+ Voxel.set_name(voxel, name.strip_edges())
+ else:
+ result["error"] = ERR_FILE_UNRECOGNIZED
+
+ return result
+
+
+static func read_file(gpl_path : String) -> Dictionary:
+ var result := { "error": OK }
+ var file := File.new()
+ var error = file.open(gpl_path, File.READ)
+ if error == OK:
+ result = read(file)
+ if file.is_open():
+ file.close()
+ return result
diff --git a/addons/voxel-core/classes/readers/image.gd b/addons/voxel-core/classes/readers/image.gd
new file mode 100644
index 0000000..3c19d2f
--- /dev/null
+++ b/addons/voxel-core/classes/readers/image.gd
@@ -0,0 +1,40 @@
+class_name ImageReader, "res://addons/voxel-core/assets/logos/MagicaVoxel.png"
+extends Reference
+# Image file reader
+
+
+
+## Public Methods
+# Reads images pixels, returns voxel content and voxel palette
+static func read(image : Image) -> Dictionary:
+ var result := {
+ "error": OK,
+ "voxels": {},
+ "palette": [],
+ }
+
+ image.lock()
+ for x in range(image.get_width()):
+ for y in range(image.get_height()):
+ if image.get_pixel(x, y).a > 0:
+ var color := image.get_pixel(x, y)
+ color.a = 1.0
+ var index = result["palette"].find(color)
+ if index == -1:
+ index = result["palette"].size()
+ result["palette"].append(color)
+ result["voxels"][Vector3(x, -y, 0).round()] = index
+ image.unlock()
+
+ for index in range(result["palette"].size()):
+ result["palette"][index] = Voxel.colored(result["palette"][index])
+
+ return result
+
+
+static func read_file(image_path : String) -> Dictionary:
+ var image := Image.new()
+ var err = image.load(image_path)
+ if err == OK:
+ return read(image)
+ return { "error": err }
diff --git a/addons/voxel-core/classes/readers/vox.gd b/addons/voxel-core/classes/readers/vox.gd
new file mode 100644
index 0000000..51dfa86
--- /dev/null
+++ b/addons/voxel-core/classes/readers/vox.gd
@@ -0,0 +1,86 @@
+class_name VoxReader, "res://addons/voxel-core/assets/logos/MagicaVoxel.png"
+extends Reference
+# MagicaVoxel file reader
+
+
+
+# Constants
+const magicavoxel_default_palette := [
+ Color("00000000"), Color("ffffffff"), Color("ffccffff"), Color("ff99ffff"), Color("ff66ffff"), Color("ff33ffff"), Color("ff00ffff"), Color("ffffccff"), Color("ffccccff"), Color("ff99ccff"), Color("ff66ccff"), Color("ff33ccff"), Color("ff00ccff"), Color("ffff99ff"), Color("ffcc99ff"), Color("ff9999ff"),
+ Color("ff6699ff"), Color("ff3399ff"), Color("ff0099ff"), Color("ffff66ff"), Color("ffcc66ff"), Color("ff9966ff"), Color("ff6666ff"), Color("ff3366ff"), Color("ff0066ff"), Color("ffff33ff"), Color("ffcc33ff"), Color("ff9933ff"), Color("ff6633ff"), Color("ff3333ff"), Color("ff0033ff"), Color("ffff00ff"),
+ Color("ffcc00ff"), Color("ff9900ff"), Color("ff6600ff"), Color("ff3300ff"), Color("ff0000ff"), Color("ffffffcc"), Color("ffccffcc"), Color("ff99ffcc"), Color("ff66ffcc"), Color("ff33ffcc"), Color("ff00ffcc"), Color("ffffcccc"), Color("ffcccccc"), Color("ff99cccc"), Color("ff66cccc"), Color("ff33cccc"),
+ Color("ff00cccc"), Color("ffff99cc"), Color("ffcc99cc"), Color("ff9999cc"), Color("ff6699cc"), Color("ff3399cc"), Color("ff0099cc"), Color("ffff66cc"), Color("ffcc66cc"), Color("ff9966cc"), Color("ff6666cc"), Color("ff3366cc"), Color("ff0066cc"), Color("ffff33cc"), Color("ffcc33cc"), Color("ff9933cc"),
+ Color("ff6633cc"), Color("ff3333cc"), Color("ff0033cc"), Color("ffff00cc"), Color("ffcc00cc"), Color("ff9900cc"), Color("ff6600cc"), Color("ff3300cc"), Color("ff0000cc"), Color("ffffff99"), Color("ffccff99"), Color("ff99ff99"), Color("ff66ff99"), Color("ff33ff99"), Color("ff00ff99"), Color("ffffcc99"),
+ Color("ffcccc99"), Color("ff99cc99"), Color("ff66cc99"), Color("ff33cc99"), Color("ff00cc99"), Color("ffff9999"), Color("ffcc9999"), Color("ff999999"), Color("ff669999"), Color("ff339999"), Color("ff009999"), Color("ffff6699"), Color("ffcc6699"), Color("ff996699"), Color("ff666699"), Color("ff336699"),
+ Color("ff006699"), Color("ffff3399"), Color("ffcc3399"), Color("ff993399"), Color("ff663399"), Color("ff333399"), Color("ff003399"), Color("ffff0099"), Color("ffcc0099"), Color("ff990099"), Color("ff660099"), Color("ff330099"), Color("ff000099"), Color("ffffff66"), Color("ffccff66"), Color("ff99ff66"),
+ Color("ff66ff66"), Color("ff33ff66"), Color("ff00ff66"), Color("ffffcc66"), Color("ffcccc66"), Color("ff99cc66"), Color("ff66cc66"), Color("ff33cc66"), Color("ff00cc66"), Color("ffff9966"), Color("ffcc9966"), Color("ff999966"), Color("ff669966"), Color("ff339966"), Color("ff009966"), Color("ffff6666"),
+ Color("ffcc6666"), Color("ff996666"), Color("ff666666"), Color("ff336666"), Color("ff006666"), Color("ffff3366"), Color("ffcc3366"), Color("ff993366"), Color("ff663366"), Color("ff333366"), Color("ff003366"), Color("ffff0066"), Color("ffcc0066"), Color("ff990066"), Color("ff660066"), Color("ff330066"),
+ Color("ff000066"), Color("ffffff33"), Color("ffccff33"), Color("ff99ff33"), Color("ff66ff33"), Color("ff33ff33"), Color("ff00ff33"), Color("ffffcc33"), Color("ffcccc33"), Color("ff99cc33"), Color("ff66cc33"), Color("ff33cc33"), Color("ff00cc33"), Color("ffff9933"), Color("ffcc9933"), Color("ff999933"),
+ Color("ff669933"), Color("ff339933"), Color("ff009933"), Color("ffff6633"), Color("ffcc6633"), Color("ff996633"), Color("ff666633"), Color("ff336633"), Color("ff006633"), Color("ffff3333"), Color("ffcc3333"), Color("ff993333"), Color("ff663333"), Color("ff333333"), Color("ff003333"), Color("ffff0033"),
+ Color("ffcc0033"), Color("ff990033"), Color("ff660033"), Color("ff330033"), Color("ff000033"), Color("ffffff00"), Color("ffccff00"), Color("ff99ff00"), Color("ff66ff00"), Color("ff33ff00"), Color("ff00ff00"), Color("ffffcc00"), Color("ffcccc00"), Color("ff99cc00"), Color("ff66cc00"), Color("ff33cc00"),
+ Color("ff00cc00"), Color("ffff9900"), Color("ffcc9900"), Color("ff999900"), Color("ff669900"), Color("ff339900"), Color("ff009900"), Color("ffff6600"), Color("ffcc6600"), Color("ff996600"), Color("ff666600"), Color("ff336600"), Color("ff006600"), Color("ffff3300"), Color("ffcc3300"), Color("ff993300"),
+ Color("ff663300"), Color("ff333300"), Color("ff003300"), Color("ffff0000"), Color("ffcc0000"), Color("ff990000"), Color("ff660000"), Color("ff330000"), Color("ff0000ee"), Color("ff0000dd"), Color("ff0000bb"), Color("ff0000aa"), Color("ff000088"), Color("ff000077"), Color("ff000055"), Color("ff000044"),
+ Color("ff000022"), Color("ff000011"), Color("ff00ee00"), Color("ff00dd00"), Color("ff00bb00"), Color("ff00aa00"), Color("ff008800"), Color("ff007700"), Color("ff005500"), Color("ff004400"), Color("ff002200"), Color("ff001100"), Color("ffee0000"), Color("ffdd0000"), Color("ffbb0000"), Color("ffaa0000"),
+ Color("ff880000"), Color("ff770000"), Color("ff550000"), Color("ff440000"), Color("ff220000"), Color("ff110000"), Color("ffeeeeee"), Color("ffdddddd"), Color("ffbbbbbb"), Color("ffaaaaaa"), Color("ff888888"), Color("ff777777"), Color("ff555555"), Color("ff444444"), Color("ff222222"), Color("ff111111")
+]
+
+
+
+# Public Methods
+# Reads vox file, returns voxel content and voxel palette
+static func read(vox_file : File) -> Dictionary:
+ var result := {
+ "error": OK,
+ "voxels": {},
+ "palette": [],
+ }
+
+ var magic := vox_file.get_buffer(4).get_string_from_ascii()
+ var magic_version := vox_file.get_32()
+ if magic == "VOX " and magic_version == 150:
+ var nodes := {}
+ while vox_file.get_position() < vox_file.get_len():
+ var chunk_name = vox_file.get_buffer(4).get_string_from_ascii()
+ var chunk_size = vox_file.get_32()
+ var chunk_children = vox_file.get_32()
+
+ match chunk_name:
+ "XYZI":
+ for i in range(0, vox_file.get_32()):
+ var x := vox_file.get_8()
+ var z := -vox_file.get_8()
+ var y := vox_file.get_8()
+ result["voxels"][Vector3(
+ x, y, z).floor()] = vox_file.get_8() - 1
+ "RGBA":
+ for i in range(0,256):
+ var color := Color(
+ float(vox_file.get_8() / 255.0),
+ float(vox_file.get_8() / 255.0),
+ float(vox_file.get_8() / 255.0),
+ float(vox_file.get_8() / 255.0))
+ color.a = 1.0
+ result["palette"].append(color)
+ _:
+ vox_file.get_buffer(chunk_size)
+
+ if result["palette"].empty():
+ result["palette"] = magicavoxel_default_palette.duplicate()
+ else:
+ result["error"] = ERR_FILE_UNRECOGNIZED
+
+ for index in range(result["palette"].size()):
+ result["palette"][index] = Voxel.colored(result["palette"][index])
+
+ return result
+
+
+static func read_file(vox_path : String) -> Dictionary:
+ var result := { "error": OK }
+ var vox_file := File.new()
+ var error = vox_file.open(vox_path, File.READ)
+ if error == OK:
+ result = read(vox_file)
+ if vox_file.is_open():
+ vox_file.close()
+ return result
diff --git a/addons/voxel-core/classes/voxel.gd b/addons/voxel-core/classes/voxel.gd
new file mode 100644
index 0000000..f9bc7bb
--- /dev/null
+++ b/addons/voxel-core/classes/voxel.gd
@@ -0,0 +1,330 @@
+tool
+class_name Voxel, "res://addons/voxel-core/assets/classes/voxel.png"
+extends Object
+# Utility class containing various properties and methods to do with voxels.
+#
+# Voxel Schema:
+# Every voxel is a Dictionary, not every Dictionary is a voxel, only by following
+# the voxel scheme indicated below can wide varieties of voxels be produced.
+# Note that voxel dictionaries may have additions, but they must be done in such
+# a way as to respect the original structure so as to avoid conflicts.
+#
+# {
+# name : String ~ Used in VoxelSets, should always be lowercase
+# color : Color ~ Default color used for all voxel faces, if not present fallback is Transparent color
+# colors : Dictionary = { ~ Color used on a per face bases, if not present fallback is voxel color
+# Vector3.UP : Color
+# Vector3.DOWN : Color
+# Vector3.RIGHT : Color
+# Vector3.LEFT : Color
+# Vector3.FORWARD : Color
+# Vector3.BACK : Color
+# }
+# uv : Vector2 ~ Default uv position used for all voxel faces, if not present fallback is (-1.0, -1.0)
+# uvs : Dictionary = { ~ uv position used on a per face bases, if not present fallback to voxel uv
+# Vector3.UP : Vector2
+# Vector3.DOWN : Vector2
+# Vector3.RIGHT : Vector2
+# Vector3.LEFT : Vector2
+# Vector3.FORWARD : Vector2
+# Vector3.BACK : Vector2
+# }
+# metallic : float ~ Metallic value used for all voxel face's material, must be between 0.0 and 1.0 and if not present fallback is 0.0
+# specular : float ~ Specular value used for all voxel faces, must be between 0.0 and 1.0 and if not present fallback is 0.5
+# roughness : float ~ Roughness value used for all voxel faces, must be between 0.0 and 1.0 and if not present fallback is 1.0
+# energy : float ~ Emission energy value used for all voxel faces, must be between 0.0 and 16.0 and if not present fallback is 0.0
+# energy_color : Color ~ Emission color used for all voxel faces, if not present fallback is white
+# material : int ~ ID of the VoxelSet material used for this voxel, if not present fallback is -1
+# }
+#
+
+
+
+## Constants
+# Lists of all voxel faces, and their adjacent directions
+const Faces := {
+ Vector3.RIGHT: [ Vector3.FORWARD, Vector3.BACK, Vector3.DOWN, Vector3.UP ],
+ Vector3.UP: [ Vector3.LEFT, Vector3.RIGHT, Vector3.FORWARD, Vector3.BACK ],
+ Vector3.FORWARD: [ Vector3.LEFT, Vector3.RIGHT, Vector3.DOWN, Vector3.UP ],
+ Vector3.LEFT: [ Vector3.FORWARD, Vector3.BACK, Vector3.DOWN, Vector3.UP ],
+ Vector3.DOWN: [ Vector3.LEFT, Vector3.RIGHT, Vector3.FORWARD, Vector3.BACK ],
+ Vector3.BACK: [ Vector3.LEFT, Vector3.RIGHT, Vector3.DOWN, Vector3.UP ],
+}
+
+# 0.5 means that voxels will have the dimensions of 0.5 x 0.5 x 0.5
+const VoxelWorldSize := 0.5
+
+
+
+## Public Methods
+# Returns Dictionary representation of a colored voxel
+# color : Color : color to set
+# colors : Dictionary<Vector3, Color> : face colors to set
+# return : Dictionary<String, Variant> : Dictionary representing voxel
+static func colored(color : Color, colors := {}) -> Dictionary:
+ var voxel = {}
+ voxel["color"] = color
+ if not colors.empty():
+ voxel["colors"] = colors.duplicate()
+ return voxel
+
+
+# Returns true if voxel has color defined
+static func has_color(voxel : Dictionary) -> bool:
+ return voxel.has("color")
+
+
+# Returns the defined color within given voxel if present, otherwise returns transparent color
+static func get_color(voxel : Dictionary) -> Color:
+ return voxel.get("color", Color.transparent)
+
+
+# Sets the given color to the given voxel
+static func set_color(voxel : Dictionary, color : Color) -> void:
+ voxel["color"] = color
+
+
+# Returns true if voxel has specified color at given face
+static func has_face_color(voxel : Dictionary, face : Vector3) -> bool:
+ return voxel.has("colors") and voxel["colors"].has(face)
+
+
+# Returns the defined color at given face if present, otherwise returns color
+static func get_face_color(voxel : Dictionary, face : Vector3) -> Color:
+ return voxel["colors"].get(face, get_color(voxel)) if voxel.has("colors") else get_color(voxel)
+
+
+# Sets the given color at the given face to the given voxel
+static func set_face_color(voxel : Dictionary, face : Vector3, color : Color) -> void:
+ if not voxel.has("colors"): voxel["colors"] = {}
+ voxel["colors"][face] = color
+
+
+# Removes color at given face from given voxel
+static func remove_face_color(voxel : Dictionary, face : Vector3) -> void:
+ if voxel.has("colors"):
+ voxel["colors"].erase(face)
+ if voxel["colors"].empty():
+ voxel.erase("colors")
+
+
+# Returns Dictionary representation of a uvd voxel
+# uv : Vector2 : uv to set
+# uvs : Dictionary<Vector3, Vector2> : face uv to set
+# color : Color : color to set
+# colors : Dictionary<Vector3, Color> : face colors to set
+# return : Dictionary<String, Variant> : Dictionary representing voxel
+static func uvd(uv : Vector2, uvs := {}, color := Color.white, colors := {}) -> Dictionary:
+ var voxel = colored(color, colors)
+ voxel["uv"] = uv
+ if not uvs.empty():
+ voxel["uvs"] = uvs
+ return voxel
+
+
+# Returns true if voxel has uv defined
+static func has_uv(voxel : Dictionary) -> bool:
+ return voxel.has("uv")
+
+
+# Returns the defined uv within given voxel if present, otherwise returns a negative vector
+static func get_uv(voxel : Dictionary) -> Vector2:
+ return voxel.get("uv", -Vector2.ONE)
+
+
+# Sets the given uv to the given voxel
+static func set_uv(voxel : Dictionary, uv : Vector2) -> void:
+ voxel["uv"] = uv
+
+
+# Removes uv from given voxel
+static func remove_uv(voxel : Dictionary) -> void:
+ voxel.erase("uv")
+
+
+# Returns true if voxel has specified uv at given face
+static func has_face_uv(voxel : Dictionary, face : Vector3) -> bool:
+ return voxel.has("uvs") and voxel["uvs"].has(face)
+
+
+# Returns the defined uv at given face if present, otherwise returns uv
+static func get_face_uv(voxel : Dictionary, face : Vector3) -> Vector2:
+ return voxel["uvs"].get(face, get_uv(voxel)) if voxel.has("uvs") else get_uv(voxel)
+
+
+# Sets the given uv at the given face to the given voxel
+static func set_face_uv(voxel : Dictionary, face : Vector3, uv : Vector2) -> void:
+ if not voxel.has("uvs"):
+ voxel["uvs"] = {}
+ voxel["uvs"][face] = uv
+
+
+# Removes uv at given face from given voxel
+static func remove_face_uv(voxel : Dictionary, face : Vector3) -> void:
+ if voxel.has("uvs"):
+ voxel["uvs"].erase(face)
+ if voxel["uvs"].empty():
+ voxel.erase("uvs")
+
+
+# Returns true if given name is valid
+static func is_valid_name(name : String) -> bool:
+ return not name.empty()
+
+
+# Returns the defined name within given voxel if present, otherwise returns an empty string
+static func get_name(voxel : Dictionary) -> String:
+ return voxel.get("name", "")
+
+
+# Sets the given name to the given voxel
+static func set_name(voxel : Dictionary, name : String) -> void:
+ voxel["name"] = name.to_lower()
+
+
+# Removes name from given voxel
+static func remove_name(voxel : Dictionary) -> void:
+ voxel.erase("name")
+
+
+# Returns the defined metallic within given voxel if present, otherwise returns 0
+static func get_metallic(voxel : Dictionary) -> float:
+ return voxel.get("metallic", 0.0)
+
+
+# Sets the given metallic to the given voxel
+static func set_metallic(voxel : Dictionary, metallic : float) -> void:
+ voxel["metallic"] = metallic
+
+
+# Removes metallic from given voxel
+static func remove_metallic(voxel : Dictionary) -> void:
+ voxel.erase("metallic")
+
+
+# Returns the defined specular within given voxel if present, otherwise returns 0.5
+static func get_specular(voxel : Dictionary) -> float:
+ return voxel.get("specular", 0.5)
+
+
+# Sets the given specular to the given voxel
+static func set_specular(voxel : Dictionary, specular : float) -> void:
+ voxel["specular"] = specular
+
+
+# Removes specular from given voxel
+static func remove_specular(voxel : Dictionary) -> void:
+ voxel.erase("specular")
+
+
+# Returns the defined roughness within given voxel if present, otherwise returns 1
+static func get_roughness(voxel : Dictionary) -> float:
+ return voxel.get("roughness", 1.0)
+
+
+# Sets the given roughness to the given voxel
+static func set_roughness(voxel : Dictionary, roughness : float) -> void:
+ voxel["roughness"] = roughness
+
+
+# Removes roughness from given voxel
+static func remove_roughness(voxel : Dictionary) -> void:
+ voxel.erase("roughness")
+
+
+# Returns the defined energy within given voxel if present, otherwise returns 0
+static func get_energy(voxel : Dictionary) -> float:
+ return voxel.get("energy", 0.0)
+
+
+# Sets the given energy to the given voxel
+static func set_energy(voxel : Dictionary, energy : float) -> void:
+ voxel["energy"] = energy
+
+
+# Removes energy from given voxel
+static func remove_energy(voxel : Dictionary) -> void:
+ voxel.erase("energy")
+
+
+# Returns the defined energy_color within given voxel if present, otherwise returns Color.white
+static func get_energy_color(voxel : Dictionary) -> Color:
+ return voxel.get("energy_color", Color.white)
+
+
+# Sets the given energy_color to the given voxel
+static func set_energy_color(voxel : Dictionary, energy_color : Color) -> void:
+ voxel["energy_color"] = energy_color
+
+
+# Removes energy_color from given voxel
+static func remove_energy_color(voxel : Dictionary) -> void:
+ voxel.erase("energy_color")
+
+
+# Returns the defined material within given voxel if present, otherwise returns -1
+static func get_material(voxel : Dictionary) -> int:
+ return voxel.get("material", -1)
+
+
+# Sets the given material to the given voxel
+static func set_material(voxel : Dictionary, material : int) -> void:
+ voxel["material"] = material
+
+
+# Removes material from given voxel
+static func remove_material(voxel : Dictionary) -> void:
+ voxel.erase("material")
+
+
+# Removes unnecessary properties of given voxel in accordance to Voxel schema
+static func clean(voxel : Dictionary) -> void:
+ if not is_valid_name(get_name(voxel)):
+ remove_name(voxel)
+
+ if get_uv(voxel) == get_uv({}):
+ remove_uv(voxel)
+
+ for face in Faces:
+ if get_face_color(voxel, face) == get_color({}):
+ remove_face_color(voxel, face)
+ if get_face_uv(voxel, face) == get_uv({}):
+ remove_face_uv(voxel, face)
+
+ if get_material(voxel) == get_material({}):
+ remove_material(voxel)
+
+ if get_metallic(voxel) == get_metallic({}):
+ remove_metallic(voxel)
+
+ if get_specular(voxel) == get_specular({}):
+ remove_specular(voxel)
+
+ if get_roughness(voxel) == get_roughness({}):
+ remove_roughness(voxel)
+
+ if get_energy(voxel) == get_energy({}):
+ remove_energy(voxel)
+
+ if get_energy_color(voxel) == get_energy_color({}):
+ remove_energy_color(voxel)
+
+
+# Returns the world position as snapped world position
+static func world_to_snapped(world : Vector3) -> Vector3:
+ return (world / VoxelWorldSize).floor() * VoxelWorldSize
+
+
+# Returns the snapped world position as voxel grid position
+static func snapped_to_grid(snapped : Vector3) -> Vector3:
+ return snapped / VoxelWorldSize
+
+
+# Returns world position as voxel grid position
+static func world_to_grid(world : Vector3) -> Vector3:
+ return snapped_to_grid(world_to_snapped(world))
+
+
+# Returns voxel grid position as snapped world position
+static func grid_to_snapped(grid : Vector3) -> Vector3:
+ return grid * VoxelWorldSize
diff --git a/addons/voxel-core/classes/voxel_mesh.gd b/addons/voxel-core/classes/voxel_mesh.gd
new file mode 100644
index 0000000..0191deb
--- /dev/null
+++ b/addons/voxel-core/classes/voxel_mesh.gd
@@ -0,0 +1,124 @@
+tool
+class_name VoxelMesh, "res://addons/voxel-core/assets/classes/voxel_mesh.png"
+extends "res://addons/voxel-core/classes/voxel_object.gd"
+# The most basic voxel visualization object, for a moderate amount of voxels.
+
+
+
+## Private Variables
+# Used voxels, Dictionary<Vector3, int>
+var _voxels := {}
+
+
+
+## Built-In Virtual Methods
+func _get(property : String):
+ if property == "VOXELS":
+ return _voxels
+
+
+func _set(property : String, value):
+ if property == "VOXELS":
+ _voxels = value
+ return true
+ return false
+
+
+func _get_property_list():
+ var properties = []
+
+ properties.append({
+ "name": "VOXELS",
+ "type": TYPE_DICTIONARY,
+ "hint": PROPERTY_HINT_NONE,
+ "usage": PROPERTY_USAGE_STORAGE,
+ })
+
+ return properties
+
+
+
+## Public Methods
+func empty() -> bool:
+ return _voxels.empty()
+
+
+func set_voxel(grid : Vector3, voxel : int) -> void:
+ _voxels[grid] = voxel
+
+
+func set_voxels(voxels : Dictionary) -> void:
+ erase_voxels()
+ _voxels = voxels
+
+
+func get_voxel_id(grid : Vector3) -> int:
+ return _voxels.get(grid, -1)
+
+
+func get_voxels() -> Array:
+ return _voxels.keys()
+
+
+func erase_voxel(grid : Vector3) -> void:
+ _voxels.erase(grid)
+
+
+func erase_voxels() -> void:
+ _voxels.clear()
+
+
+func update_mesh() -> void:
+ if not _voxels.empty():
+ var vt := VoxelTool.new()
+ var materials := {}
+ if is_instance_valid(mesh) and mesh is ArrayMesh:
+ for index in get_surface_material_count():
+ var material := get_surface_material(index)
+ if is_instance_valid(material):
+ materials[mesh.surface_get_name(index)] = material
+
+ match MeshModes.NAIVE if edit_hint > 0 else mesh_mode:
+ MeshModes.GREEDY:
+ mesh = greed_volume(_voxels.keys(), vt)
+ _:
+ mesh = naive_volume(_voxels.keys(), vt)
+
+ for material_name in materials:
+ var material_index = mesh.surface_find_by_name(material_name)
+ if material_index > -1:
+ set_surface_material(material_index, materials[material_name])
+ else:
+ mesh = null
+ .update_mesh()
+
+
+func update_static_body() -> void:
+ var staticBody = get_node_or_null("StaticBody")
+
+ if (edit_hint >= 2 or static_body) and is_instance_valid(mesh):
+ if not is_instance_valid(staticBody):
+ staticBody = StaticBody.new()
+ staticBody.set_name("StaticBody")
+ add_child(staticBody)
+
+ var collisionShape
+ if staticBody.has_node("CollisionShape"):
+ collisionShape = staticBody.get_node("CollisionShape")
+ else:
+ collisionShape = CollisionShape.new()
+ collisionShape.set_name("CollisionShape")
+ staticBody.add_child(collisionShape)
+ collisionShape.shape = mesh.create_trimesh_shape()
+
+ if static_body and not staticBody.owner:
+ staticBody.set_owner(get_tree().get_edited_scene_root())
+ elif not static_body and staticBody.owner:
+ staticBody.set_owner(null)
+ if static_body and not collisionShape.owner:
+ collisionShape.set_owner(get_tree().get_edited_scene_root())
+ elif not static_body and staticBody.owner:
+ collisionShape.set_owner(null)
+ elif is_instance_valid(staticBody):
+ remove_child(staticBody)
+ staticBody.queue_free()
diff --git a/addons/voxel-core/classes/voxel_object.gd b/addons/voxel-core/classes/voxel_object.gd
new file mode 100644
index 0000000..8d12633
--- /dev/null
+++ b/addons/voxel-core/classes/voxel_object.gd
@@ -0,0 +1,501 @@
+tool
+extends MeshInstance
+# Makeshift interface class inhereted by all voxel visualization objects.
+
+
+
+## Signals
+# Emitted when VoxelSet is changed
+signal set_voxel_set(voxel_set)
+
+
+
+## Enums
+# Defines the modes in which Mesh can be constructed
+enum MeshModes {
+ # Naive meshing, simple culling of voxel faces; http://web.archive.org/web/20200428085802/https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
+ NAIVE,
+ # Greedy meshing, culls and merges similar voxel faces; http://web.archive.org/web/20201112011204/https://www.gedge.ca/dev/2014/08/17/greedy-voxel-meshing
+ GREEDY,
+ # Marching Cubes meshing, https://en.wikipedia.org/wiki/Marching_cubes
+ #MARCHING_CUBES,
+ # Transvoxel meshing, http://web.archive.org/web/20201112033736/http://transvoxel.org/
+ #TRANSVOXEL,
+}
+
+
+
+## Exported Variables
+# The meshing mode by which Mesh is generated
+export(MeshModes) var mesh_mode := MeshModes.NAIVE setget set_mesh_mode
+
+# Flag indicating that UV Mapping should be applied when generating meshes if applicable
+export var uv_map := false setget set_uv_map
+
+# Flag indicating the persitant attachment and maintenance of a StaticBody
+export var static_body := false setget set_static_body
+
+# The VoxelSet for this VoxelObject
+export(Resource) var voxel_set = null setget set_voxel_set
+
+
+
+## Public Variables
+# Flag indicating that edits to voxel data will be frequent
+# NOTE: When true will only allow naive meshing
+var edit_hint := 0 setget set_edit_hint
+
+
+
+# Public Methods
+# Sets the EditHint flag, calls update_mesh if needed and not told otherwise
+func set_edit_hint(value : int, update := is_inside_tree()) -> void:
+ edit_hint = value
+
+ if update:
+ update_mesh()
+
+
+# Sets the mesh_mode, calls update_mesh if needed and not told otherwise
+func set_mesh_mode(value : int, update := is_inside_tree()) -> void:
+ mesh_mode = value
+
+ if update:
+ update_mesh()
+
+
+# Sets the uv_map, calls update_mesh if needed and not told otherwise
+func set_uv_map(value : bool, update := is_inside_tree()) -> void:
+ uv_map = value
+
+ if update:
+ update_mesh()
+
+
+# Sets static_body, calls update_static_body if needed and not told otherwise
+func set_static_body(value : bool, update := is_inside_tree()) -> void:
+ static_body = value
+
+ if update:
+ update_static_body()
+
+
+# Sets voxel_set, calls update_mesh if needed and not told otherwise
+func set_voxel_set(value : Resource, update := is_inside_tree()) -> void:
+ if not (typeof(value) == TYPE_NIL or value is VoxelSet):
+ printerr("Invalid Resource given expected VoxelSet")
+ return
+
+ if is_instance_valid(voxel_set):
+ if voxel_set.is_connected("requested_refresh", self, "update_mesh"):
+ voxel_set.disconnect("requested_refresh", self, "update_mesh")
+
+ voxel_set = value
+ if is_instance_valid(voxel_set):
+ if not voxel_set.is_connected("requested_refresh", self, "update_mesh"):
+ voxel_set.connect("requested_refresh", self, "update_mesh")
+
+ if update:
+ update_mesh()
+ emit_signal("set_voxel_set", voxel_set)
+
+
+# Return true if no voxels are present
+func empty() -> bool:
+ return true
+
+
+# Sets given voxel id at the given grid position
+func set_voxel(grid : Vector3, voxel_id : int) -> void:
+ pass
+
+
+# Replace current voxel data with given voxel data
+# voxels : Dictionary<Vector3, int> : voxels to set
+func set_voxels(voxels : Dictionary) -> void:
+ erase_voxels()
+ for grid in voxels:
+ set_voxel(grid, voxels[grid])
+
+
+# Returns voxel id at given grid position if present; otherwise returns -1
+func get_voxel_id(grid : Vector3) -> int:
+ return -1
+
+
+# Returns voxel Dictionary representing voxel id at given grid position
+func get_voxel(grid : Vector3) -> Dictionary:
+ return voxel_set.get_voxel(get_voxel_id(grid))
+
+
+# Returns Array of all voxel grid positions
+# return : Array<Vector3> : Array of Vector3 each represents a grid position of a voxel
+func get_voxels() -> Array:
+ return []
+
+
+# Erase voxel id at given grid position
+func erase_voxel(grid : Vector3) -> void:
+ pass
+
+
+# Erase all voxels
+func erase_voxels() -> void:
+ for grid in get_voxels():
+ erase_voxel(grid)
+
+
+# Returns 3D axis-aligned bounding box
+# volume : Array<Vector3> : Array of grid positions from which to calculate bounds
+# return : Dictionary : bounding box, contains: { position : Vector3, size: Vector3 }
+func get_box(volume := get_voxels()) -> Dictionary:
+ var box := { "position": Vector3.ZERO, "size": Vector3.ZERO }
+
+ if not volume.empty():
+ box["position"] = Vector3.INF
+ box["size"] = -Vector3.INF
+
+ for voxel_grid in volume:
+ if voxel_grid.x < box["position"].x:
+ box["position"].x = voxel_grid.x
+ if voxel_grid.y < box["position"].y:
+ box["position"].y = voxel_grid.y
+ if voxel_grid.z < box["position"].z:
+ box["position"].z = voxel_grid.z
+
+ if voxel_grid.x > box["size"].x:
+ box["size"].x = voxel_grid.x
+ if voxel_grid.y > box["size"].y:
+ box["size"].y = voxel_grid.y
+ if voxel_grid.z > box["size"].z:
+ box["size"].z = voxel_grid.z
+
+ box["size"] = (box["size"] - box["position"]).abs() + Vector3.ONE
+
+ return box
+
+
+# Moves voxels in given volume by given translation
+# translation : Vector3 : translation to move voxels by
+# volume : Array<Vector3> : Array of grid positions representing voxels to move
+func move(translation := Vector3(), volume := get_voxels()) -> void:
+ var translated := {}
+ for voxel_grid in volume:
+ translated[voxel_grid + translation] = get_voxel_id(voxel_grid)
+ erase_voxel(voxel_grid)
+ for voxel_grid in translated:
+ set_voxel(voxel_grid, translated[voxel_grid])
+
+
+# Centers voxels in given volume with respect to axis origin with the given alignment
+# alignment : Vector3 : Alignment to center voxels by
+# volume : Array<Vector3> : Array of grid positions representing voxels to center
+func center(alignment := Vector3(0.5, 0.5, 0.5), volume := get_voxels()) -> void:
+ move(vec_to_center(alignment, volume), volume)
+
+
+# Flips voxels in given volume over set axis
+func flip(x : bool, y : bool, z : bool, volume := get_voxels()) -> void:
+ var flipped := {}
+ for voxel_grid in volume:
+ flipped[Vector3(
+ (voxel_grid.x + (1 if z else 0)) * (-1 if z else 1),
+ (voxel_grid.y + (1 if y else 0)) * (-1 if y else 1),
+ (voxel_grid.z + (1 if x else 0)) * (-1 if x else 1))] = get_voxel_id(voxel_grid)
+ erase_voxel(voxel_grid)
+ for voxel_grid in flipped:
+ set_voxel(voxel_grid, flipped[voxel_grid])
+
+
+# Returns the translation necessary to center given volume by
+# alignment : Vector3 : Alignment to center voxels by
+# volume : Array<Vector3> : Array of grid positions representing voxels to center
+# return : Vector3 : Translation necessary to center
+func vec_to_center(alignment := Vector3(0.5, 0.5, 0.5), volume := get_voxels()) -> Vector3:
+ var box := get_box(volume)
+ alignment = Vector3.ONE - Vector3(
+ clamp(alignment.x, 0.0, 1.0),
+ clamp(alignment.y, 0.0, 1.0),
+ clamp(alignment.z, 0.0, 1.0))
+ return -box["position"] - (box["size"] * alignment).floor()
+
+# A Fast Voxel Traversal Algorithm for Ray Tracing, by John Amanatides
+# Algorithm paper: https://web.archive.org/web/20201108160724/http://www.cse.chalmers.se/edu/year/2010/course/TDA361/grid.pdf
+# from : Vector3 : World position from which to start raycast
+# direction : Vector3 : Direction of raycast
+# max_distance : int : Maximum distance of ray cast
+# stop : FuncRef : Calls on function, that receives "hit" and returns bool, as raycast is projected, if it returns true raycast is returned
+# return : Dictionary<String, Vector3> : If voxel is "hit", returns Dictionary with grid position and face normal; else empty
+func intersect_ray(
+ from : Vector3,
+ direction : Vector3,
+ max_distance := 64,
+ stop : FuncRef = null) -> Dictionary:
+ var hit := {
+ "normal": Vector3(),
+ }
+ var grid := Voxel.world_to_grid(from)
+ var step := Vector3(
+ 1 if direction.x > 0 else -1,
+ 1 if direction.y > 0 else -1,
+ 1 if direction.z > 0 else -1)
+ var t_delta := direction.inverse().abs()
+ var dist := from.distance_to(Voxel.world_to_snapped(from))
+ var t_max := t_delta * dist
+ var step_index := -1
+
+ var t = 0.0
+ var valid := false
+ while t < max_distance:
+ hit["position"] = grid
+ hit["normal"].x = -step.x if step_index == 0 else 0
+ hit["normal"].y = -step.y if step_index == 1 else 0
+ hit["normal"].z = -step.z if step_index == 2 else 0
+ if get_voxel_id(grid) > -1 or (is_instance_valid(stop) and stop.call_func(hit)):
+ valid = true
+ break
+
+ match t_max.min_axis():
+ Vector3.AXIS_X:
+ grid.x += step.x
+ t = t_max.x
+ t_max.x += t_delta.x
+ step_index = 0
+ Vector3.AXIS_Y:
+ grid.y += step.y
+ t = t_max.y
+ t_max.y += t_delta.y
+ step_index = 1
+ Vector3.AXIS_Z:
+ grid.z += step.z
+ t = t_max.z
+ t_max.z += t_delta.z
+ step_index = 2
+ if not valid:
+ hit.clear()
+ return hit
+
+
+# Returns Array of all voxel grid positions connected to given target
+# target : Vector3 : Grid position at which to start flood select
+# selected : Array : Array to add selected voxel grid positions to
+# return : Array<Vector3> : Array of all voxel grid positions connected to given target
+func select_flood(target : Vector3, selected := []) -> Array:
+ selected.append(get_voxel_id(target))
+
+ for direction in Voxel.Faces:
+ var next = target + direction
+ if get_voxel_id(next) == get_voxel_id(selected[0]):
+ if not selected.has(next):
+ select_flood(next, selected)
+
+ return selected
+
+
+# Returns Array of all voxel grid positions connected to given target that aren't obstructed at the given face normal
+# target : Vector3 : Grid position at which to start flood select
+# face_normal : Vector3 : Normal of face to check for obstruction
+# selected : Array : Array to add selected voxel grid positions to
+# return : Array<Vector3> : Array of all voxel grid positions connected to given target
+func select_face(target : Vector3, face_normal : Vector3, selected := []) -> Array:
+ selected.append(target)
+
+ for direction in Voxel.Faces[face_normal]:
+ var next = target + direction
+ if get_voxel_id(next) > -1:
+ if get_voxel_id(next + face_normal) == -1:
+ if not selected.has(next):
+ select_face(next, face_normal, selected)
+
+ return selected
+
+
+# Returns Array of all voxel grid positions connected to given target that are similar and aren't obstructed at the given face normal
+# target : Vector3 : Grid position at which to start flood select
+# face_normal : Vector3 : Normal of face to check for obstruction
+# selected : Array : Array to add selected voxel grid positions to
+# return : Array<Vector3> : Array of all voxel grid positions connected to given target
+func select_face_similar(target : Vector3, face_normal : Vector3, selected := []) -> Array:
+ selected.append(target)
+
+ for direction in Voxel.Faces[face_normal]:
+ var next = target + direction
+ if get_voxel_id(next) == get_voxel_id(selected[0]):
+ if get_voxel_id(next + face_normal) == -1:
+ if not selected.has(next):
+ select_face_similar(next, face_normal, selected)
+
+ return selected
+
+
+# Loads and sets voxels and replaces VoxelSet with given file
+# NOTE: Reference Reader.gd for valid file imports
+# source_file : String : Path to file to be loaded
+# new_voxel_set : bool : If true new VoxelSet is created, else overwrite current one
+# return int : int : Error code
+func load_file(source_file : String, new_voxel_set := true) -> int:
+ var read := Reader.read_file(source_file)
+ var error : int = read.get("error", FAILED)
+ if error == OK:
+ if new_voxel_set or not is_instance_valid(voxel_set):
+ set_voxel_set(VoxelSet.new(), false)
+ voxel_set.set_voxels(read["palette"])
+
+ set_voxels(read["voxels"])
+ return error
+
+
+# Makes a naive mesh out of volume of voxels given
+# volume : Array<Vector3> : Array of grid positions representing volume of voxels from which to buid ArrayMesh
+# vt : VoxelTool : VoxelTool with which ArrayMesh will be built
+# return : ArrayMesh : Naive voxel mesh
+func naive_volume(volume : Array, vt := VoxelTool.new()) -> ArrayMesh:
+ if not is_instance_valid(voxel_set):
+ return null
+
+ vt.begin(voxel_set, uv_map)
+
+ for position in volume:
+ for direction in Voxel.Faces:
+ if get_voxel_id(position + direction) == -1:
+ vt.add_face(get_voxel(position), direction, position)
+
+ return vt.commit()
+
+
+# Greedy meshing
+# volume : Array<Vector3> : Array of grid positions representing volume of voxels from which to buid ArrayMesh
+# vt : VoxelTool : VoxelTool with which ArrayMesh will be built
+# return : ArrayMesh : Greedy voxel mesh
+func greed_volume(volume : Array, vt := VoxelTool.new()) -> ArrayMesh:
+ if not is_instance_valid(voxel_set):
+ return null
+
+ vt.begin(voxel_set, uv_map)
+
+ var faces = Voxel.Faces.duplicate()
+ for face in faces:
+ faces[face] = []
+ for position in volume:
+ if get_voxel_id(position + face) == -1:
+ faces[face].append(position)
+
+ for face in faces:
+ while not faces[face].empty():
+ var bottom_right : Vector3 = faces[face].pop_front()
+ var bottom_left : Vector3 = bottom_right
+ var top_right : Vector3 = bottom_right
+ var top_left : Vector3 = bottom_right
+ var voxel : Dictionary = get_voxel(bottom_right)
+
+
+ if not uv_map or Voxel.get_face_uv(voxel, face) == -Vector2.ONE:
+ var width := 1
+
+ while true:
+ var index = faces[face].find(top_right + Voxel.Faces[face][1])
+ if index > -1:
+ var _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ width += 1
+ faces[face].remove(index)
+ top_right += Voxel.Faces[face][1]
+ bottom_right += Voxel.Faces[face][1]
+ else:
+ break
+ else:
+ break
+
+ while true:
+ var index = faces[face].find(top_left + Voxel.Faces[face][0])
+ if index > -1:
+ var _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ width += 1
+ faces[face].remove(index)
+ top_left += Voxel.Faces[face][0]
+ bottom_left += Voxel.Faces[face][0]
+ else:
+ break
+ else:
+ break
+
+ while true:
+ var used := []
+ var current := top_right
+ var index = faces[face].find(current + Voxel.Faces[face][3])
+ if index > -1:
+ var _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ current += Voxel.Faces[face][3]
+ used.append(current)
+ while true:
+ index = faces[face].find(current + Voxel.Faces[face][0])
+ if index > -1:
+ _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ current += Voxel.Faces[face][0]
+ used.append(current)
+ else:
+ break
+ else:
+ break
+ if used.size() == width:
+ top_right += Voxel.Faces[face][3]
+ top_left += Voxel.Faces[face][3]
+ for use in used:
+ faces[face].erase(use)
+ else:
+ break
+ else:
+ break
+ else:
+ break
+
+ while true:
+ var used := []
+ var current := bottom_right
+ var index = faces[face].find(current + Voxel.Faces[face][2])
+ if index > -1:
+ var _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ current += Voxel.Faces[face][2]
+ used.append(current)
+ while true:
+ index = faces[face].find(current + Voxel.Faces[face][0])
+ if index > -1:
+ _voxel = get_voxel(faces[face][index])
+ if Voxel.get_face_color(_voxel, face) == Voxel.get_face_color(voxel, face) and (not uv_map or Voxel.get_face_uv(_voxel, face) == -Vector2.ONE):
+ current += Voxel.Faces[face][0]
+ used.append(current)
+ else:
+ break
+ else:
+ break
+ if used.size() == width:
+ bottom_right += Voxel.Faces[face][2]
+ bottom_left += Voxel.Faces[face][2]
+ for use in used:
+ faces[face].erase(use)
+ else:
+ break
+ else:
+ break
+ else:
+ break
+
+ vt.add_face(voxel,face,
+ bottom_right, bottom_left, top_right, top_left)
+
+ return vt.commit()
+
+
+# Updates Mesh and calls on save and update_static_body if needed
+# save : bool : Save voxels on update
+func update_mesh() -> void:
+ update_static_body()
+
+
+# Sets and updates StaticMesh if demanded
+func update_static_body() -> void:
+ pass
diff --git a/addons/voxel-core/classes/voxel_set.gd b/addons/voxel-core/classes/voxel_set.gd
new file mode 100644
index 0000000..d0bccac
--- /dev/null
+++ b/addons/voxel-core/classes/voxel_set.gd
@@ -0,0 +1,234 @@
+tool
+class_name VoxelSet, "res://addons/voxel-core/assets/classes/voxel_set.png"
+extends Resource
+# Library of Voxels used by VoxelObjects.
+
+
+
+## Signals
+# Emitted on request_refresh
+signal requested_refresh
+
+
+
+## Exported Variables
+# Size of each tile in tiles in pixels
+export var tile_size := Vector2(32.0, 32.0) setget set_tile_size
+
+# Texture used for tiles / uv mapping
+export var tiles : Texture = null setget set_tiles
+
+# Materials used by voxels
+export(Array, Material) var materials := [
+ SpatialMaterial.new(),
+] setget set_materials
+
+
+
+## Private Variables
+# Voxels stored by their id
+var _voxels := []
+
+# Flag indicating whether _uv_scale, tile_size and tiles texture is set
+var _uv_ready := false
+
+# World UV Scale, calculated on request_refresh
+var _uv_scale := Vector2.ONE
+
+
+
+## Built-In Virtual Methods
+func _get(property : String):
+ if property == "VOXELS":
+ return _voxels
+
+
+func _set(property : String, value) -> bool:
+ if property == "VOXELS":
+ if typeof(value) == TYPE_DICTIONARY:
+ for key in value:
+ var voxel : Dictionary = value[key]
+ if voxel.has("vsn"):
+ Voxel.set_name(voxel, voxel["vsn"])
+ voxel.erase("vsn")
+ _voxels.append(value[key])
+ else:
+ _voxels = value
+ return true
+ return false
+
+
+func _get_property_list():
+ var properties = []
+
+ properties.append({
+ "name": "VOXELS",
+ "type": TYPE_ARRAY,
+ "hint": PROPERTY_HINT_NONE,
+ "usage": PROPERTY_USAGE_STORAGE,
+ })
+
+ return properties
+
+
+
+## Public Methods
+# Sets tile_size, calls on request_refresh by default
+func set_tile_size(value : Vector2, refresh := true) -> void:
+ tile_size = Vector2(
+ floor(clamp(value.x, 1, 256)),
+ floor(clamp(value.y, 1, 256)))
+
+ if refresh:
+ request_refresh()
+
+
+# Sets tiles, calls on request_refresh by default
+func set_tiles(value : Texture, refresh := true) -> void:
+ tiles = value
+
+ if refresh:
+ request_refresh()
+
+
+# Sets materials used by voxels in VoxelSet
+func set_materials(values : Array, refresh := true) -> void:
+ for index in range(values.size()):
+ var material = values[index]
+ if is_instance_valid(material) and not (material is SpatialMaterial or material is ShaderMaterial):
+ printerr("VoxelSet : Expected Spatial or Shader material got " + str(material))
+ values[index] = null
+
+ if values.empty():
+ materials.resize(1)
+ else:
+ materials = values
+ property_list_changed_notify()
+
+ if refresh:
+ request_refresh()
+
+
+# Returns VoxelSet material with id if present, otherwise returns null
+func get_material(id : int) -> Material:
+ return materials[id] if id > -1 and id < materials.size() else null
+
+
+# Returns number of voxels in VoxelSet
+func size() -> int:
+ return _voxels.size()
+
+
+# Returns true if VoxelSet has no voxels
+func empty() -> bool:
+ return _voxels.empty()
+
+
+# Returns true if VoxelSet has voxel with given id
+func has_id(id : int) -> bool:
+ return id > -1 and id < _voxels.size()
+
+
+# Returns true if VoxelSet has everything necessary for uv mapping
+func uv_ready() -> bool:
+ return _uv_ready
+
+
+# Returns the uv scale
+func uv_scale() -> Vector2:
+ return _uv_scale
+
+
+# Returns true if given id is valid
+static func is_valid_id(id : int) -> bool:
+ return id > -1
+
+
+# Returns a list of all the voxel ids
+# returns : Array<int> : contained voxel ids
+func get_ids() -> Array:
+ return range(_voxels.size())
+
+
+# Returns name associated with the given id, returns a empty string if id isn't found
+func id_to_name(id : int) -> String:
+ return Voxel.get_name(get_voxel(id))
+
+
+# Returns the id of the voxel with the given name, returns -1 if not found
+func name_to_id(name : String) -> int:
+ name = name.to_lower()
+ for id in get_ids():
+ if id_to_name(id) == name:
+ return id
+ return -1
+
+
+# Appends voxel to end of VoxelSet
+func add_voxel(voxel : Dictionary) -> void:
+ _voxels.append(voxel)
+
+
+# Sets the voxel at given id
+func set_voxel(id : int, voxel : Dictionary) -> void:
+ if not has_id(id):
+ printerr("VoxelSet : given id `" + str(id) + "` is out of range")
+ return
+
+ _voxels[id] = voxel
+
+
+# Insert voxel with given id
+func insert_voxel(id : int, voxel : Dictionary) -> void:
+ if id < -1 and id > _voxels.size():
+ printerr("VoxelSet : given id `" + str(id) + "` is out of range")
+ return
+
+ _voxels.insert(id, voxel)
+
+
+# Replaces all _voxels
+func set_voxels(voxels : Array) -> void:
+ _voxels = voxels
+
+
+# Gets voxel Dictionary by their id, returns an empty Dictionary if not found
+func get_voxel(id : int) -> Dictionary:
+ return _voxels[id] if has_id(id) else {}
+
+
+# Erase voxel from VoxelSet
+func erase_voxel(id : int) -> void:
+ _voxels.remove(id)
+
+
+# Erases all voxels in VoxelSet
+func erase_voxels() -> void:
+ _voxels.clear()
+
+
+# Should be called when noticable changes have been committed to voxels.
+# Emits requested_refresh, calculates _uv_scale and updates _uv_ready
+func request_refresh() -> void:
+ _uv_ready = is_instance_valid(tiles)
+ if _uv_ready:
+ _uv_scale = Vector2.ONE / (tiles.get_size() / tile_size)
+ else:
+ _uv_scale = Vector2.ONE
+ emit_signal("requested_refresh")
+
+
+# Loads file's content as voxels
+# NOTE: Reference Reader.gd for valid file imports
+# source_file : String : Path to file to be loaded
+# return int : int : Error code
+func load_file(source_file : String, append := false) -> int:
+ var read := Reader.read_file(source_file)
+ var error : int = read.get("error", FAILED)
+ if error == OK:
+ if append:
+ for voxel in read["palette"]:
+ add_voxel(voxel)
+ else:
+ set_voxels(read["palette"])
+ return error
diff --git a/addons/voxel-core/classes/voxel_tool.gd b/addons/voxel-core/classes/voxel_tool.gd
new file mode 100644
index 0000000..bba14aa
--- /dev/null
+++ b/addons/voxel-core/classes/voxel_tool.gd
@@ -0,0 +1,227 @@
+tool
+class_name VoxelTool
+extends Reference
+# Used to construct a Mesh with provided VoxelSet
+# and by specifying voxel faces individually.
+
+
+
+class Surface:
+ ## Public Variables
+ # Index of the last vertex in Mesh being constructed
+ var index : int
+
+ var material : SpatialMaterial
+
+ # SurfaceTool used to construct Mesh
+ var surface_tool : SurfaceTool
+
+
+
+ ## Built-In Virtual Methods
+ func _init() -> void:
+ index = 0
+ surface_tool = SurfaceTool.new()
+ surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
+
+
+
+## Private Variables
+# Flag indicating whether uv mapping should be applied to constructed mesh
+var _uv_voxels := false
+
+# Contains Surfaces being constructed
+var _surfaces := {}
+
+# VoxelSet used when constructing Mesh, is set on begin
+var _voxel_set : VoxelSet = null
+
+
+
+## Public Methods
+# Called before constructing mesh, takes the VoxelSet with which Mesh will be constructed
+func begin(voxel_set : VoxelSet = null, uv_voxels := false) -> void:
+ clear()
+ _uv_voxels = uv_voxels and voxel_set.uv_ready()
+ _voxel_set = voxel_set
+
+
+# Clear all information
+func clear() -> void:
+ _uv_voxels = false
+ _surfaces.clear()
+ _voxel_set = null
+
+
+# Returns a constructed ArrayMesh
+func commit() -> ArrayMesh:
+ var mesh := ArrayMesh.new()
+ for surface_id in _surfaces:
+ var surface : Surface = _surfaces[surface_id]
+ var submesh = surface.surface_tool.commit_to_arrays()
+ mesh.add_surface_from_arrays(
+ Mesh.PRIMITIVE_TRIANGLES,
+ submesh)
+ mesh.surface_set_name(mesh.get_surface_count() - 1, surface_id)
+ mesh.surface_set_material(mesh.get_surface_count() - 1, surface.material)
+ clear()
+ return mesh
+
+
+# Adds a voxel face to Mesh with given vertex positions and voxel data
+# voxel : Dictioanry<String, Variant> : voxel data to use
+# face : Vector3 : face of voxel to generate
+# bottom_right : Vector3 : grid position of bottom right vertex pertaining to face
+# bottom_left : Vector3 : grid position of bottom left vertex pertaining to face, if not given botttom right is used
+# top_right : Vector3 : grid position of top right vertex pertaining to face, if not given botttom right is used
+# top_left : Vector3 : grid position of top left vertex pertaining to face, if not given botttom right is used
+func add_face(
+ voxel : Dictionary,
+ face : Vector3,
+ bottom_right : Vector3,
+ bottom_left := Vector3.INF,
+ top_right := Vector3.INF,
+ top_left := Vector3.INF) -> void:
+ bottom_right = bottom_right
+ if bottom_left == Vector3.INF: bottom_left = bottom_right
+ if top_right == Vector3.INF: top_right = bottom_right
+ if top_left == Vector3.INF: top_left = bottom_right
+
+ var color := Voxel.get_face_color(voxel, face)
+ var uv := Voxel.get_face_uv(voxel, face) if _uv_voxels else -Vector2.ONE
+ var uv_surface := uv != -Vector2.ONE
+
+ var material := Voxel.get_material(voxel)
+
+ var metal := Voxel.get_metallic(voxel)
+ var specular := Voxel.get_specular(voxel)
+ var rough := Voxel.get_roughness(voxel)
+ var energy := Voxel.get_energy(voxel)
+ var energy_color := Voxel.get_energy_color(voxel)
+
+ var surface_id := str(material) if material > -1 else (str(metal) + "," + str(specular) + "," + str(rough) + "," + str(energy) + "," + str(energy_color))
+ if uv_surface:
+ surface_id += "_uv"
+ var surface : Surface = _surfaces.get(surface_id)
+ if not is_instance_valid(surface):
+ surface = Surface.new()
+
+ surface.material = _voxel_set.get_material(material) if is_instance_valid(_voxel_set) else null
+ if is_instance_valid(surface.material):
+ surface.material = surface.material.duplicate()
+ else:
+ surface.material = SpatialMaterial.new()
+
+ surface.material.metallic = metal
+ surface.material.metallic_specular = specular
+ surface.material.roughness = rough
+ if energy > 0.0:
+ surface.material.emission_enabled = true
+ surface.material.emission = energy_color
+ surface.material.emission_energy = energy
+
+ if surface.material is SpatialMaterial:
+ surface.material.vertex_color_use_as_albedo = true
+ if uv_surface:
+ surface.material.albedo_texture = _voxel_set.tiles
+
+ _surfaces[surface_id] = surface
+
+ surface.surface_tool.add_normal(face)
+ surface.surface_tool.add_color(color)
+
+ match face:
+ Vector3.RIGHT:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.RIGHT + Vector3.UP) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left + Vector3.RIGHT) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.ONE) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.RIGHT + Vector3.BACK) * Voxel.VoxelWorldSize)
+ Vector3.LEFT:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.UP) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.UP + Vector3.BACK) * Voxel.VoxelWorldSize)
+ Vector3.UP:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.UP + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left + Vector3.UP) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.ONE) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.RIGHT + Vector3.UP) * Voxel.VoxelWorldSize)
+ Vector3.DOWN:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.RIGHT + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.RIGHT) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left) * Voxel.VoxelWorldSize)
+ Vector3.FORWARD:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.RIGHT) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.RIGHT + Vector3.UP) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.UP) * Voxel.VoxelWorldSize)
+ Vector3.BACK:
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.RIGHT) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_right + Vector3.ONE) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.ONE) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_right + Vector3.RIGHT + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((top_left + Vector3.UP + Vector3.BACK) * Voxel.VoxelWorldSize)
+ if uv_surface:
+ surface.surface_tool.add_uv((uv + Vector2.DOWN) * _voxel_set.uv_scale())
+ surface.surface_tool.add_vertex((bottom_left + Vector3.BACK) * Voxel.VoxelWorldSize)
+
+ surface.index += 4
+ surface.surface_tool.add_index(surface.index - 4)
+ surface.surface_tool.add_index(surface.index - 3)
+ surface.surface_tool.add_index(surface.index - 2)
+ surface.surface_tool.add_index(surface.index - 3)
+ surface.surface_tool.add_index(surface.index - 1)
+ surface.surface_tool.add_index(surface.index - 2)
+
+
+# Adds all the faces of a voxel to Mesh at given position and with voxel data
+# voxel : Dictioanry<String, Variant> : voxel data to use
+# grid : Vector3 : voxel grid position of voxel
+func add_faces(voxel : Dictionary, grid : Vector3) -> void:
+ for face in Voxel.Faces:
+ add_face(voxel, face, grid)
diff --git a/addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd b/addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd
new file mode 100644
index 0000000..2bea836
--- /dev/null
+++ b/addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd
@@ -0,0 +1,199 @@
+tool
+extends TextureRect
+# Shows tiles of VoxelSet and allows for the selection of Tile(s)
+
+
+
+## Signals
+# Emitted when a uv position has been selected
+signal selected_uv(uv)
+# Emitted when a uv position has been unselected
+signal unselected_uv(uv)
+
+
+
+## Exported Variables
+# Maximum number of uv positions that can be selected at any one time
+export(int, -1, 256) var selection_max := 0 setget set_selection_max
+
+# Color to applyed to border of hovered uv position(s)
+export var hovered_color := Color(1, 1, 1, 0.6) setget set_hovered_color
+
+# Color to applyed to border of selected uv position(s)
+export var selected_color := Color.white setget set_selection_color
+
+# Color to applyed to border of invalid uv position(s)
+export var invalid_color := Color.red setget set_invalid_color
+
+# VoxelSet being used
+export(Resource) var voxel_set = null setget set_voxel_set
+
+
+
+## Private Variables
+# Stores last uv position hovered
+var _last_uv_hovered := -Vector2.ONE
+
+# Selected uv positions
+var _selections := []
+
+
+
+## Built-In Virtual Methods
+func _gui_input(event : InputEvent):
+ if event is InputEventMouse:
+ _last_uv_hovered = world_to_uv(event.position)
+ if selection_max != 0 and event is InputEventMouseButton:
+ if is_valid_uv(_last_uv_hovered) and event.button_index == BUTTON_LEFT and not event.is_pressed():
+ if _selections.has(_last_uv_hovered):
+ unselect(_last_uv_hovered)
+ else:
+ select(_last_uv_hovered)
+ update()
+
+
+func _draw():
+ if is_instance_valid(voxel_set) and voxel_set.uv_ready():
+ texture = voxel_set.tiles
+ if selection_max != 0:
+ for selection in _selections:
+ draw_rect(Rect2(
+ selection * voxel_set.tile_size,
+ voxel_set.tile_size),
+ selected_color, false, 3)
+
+ if _last_uv_hovered == -Vector2.ONE:
+ hint_tooltip = ""
+ else:
+ hint_tooltip = str(_last_uv_hovered)
+ draw_rect(Rect2(
+ _last_uv_hovered * voxel_set.tile_size,
+ voxel_set.tile_size),
+ hovered_color if is_valid_uv(_last_uv_hovered) else invalid_color,
+ false, 3)
+
+
+
+## Public Methods
+# Sets selection_max, shrinks _selections to new maximum if needed and calls on update by default
+func set_selection_max(value : int, update := true) -> void:
+ selection_max = clamp(value, -1, 256)
+ unselect_shrink()
+ if update:
+ self.update()
+
+
+# Sets hovered_color, and calls on update by default
+func set_hovered_color(value : Color, update := true) -> void:
+ hovered_color = value
+ if update:
+ self.update()
+
+
+# Sets selected_color, and calls on update by default
+func set_selection_color(value : Color, update := true) -> void:
+ selected_color = value
+ if update:
+ self.update()
+
+
+# Sets invalid_color, and calls on update by default
+func set_invalid_color(value : Color, update := true) -> void:
+ invalid_color = value
+ if update:
+ self.update()
+
+
+# Sets voxel_set, calls update_mesh if needed and not told otherwise
+func set_voxel_set(value : Resource, update := true) -> void:
+ if not (typeof(value) == TYPE_NIL or value is VoxelSet):
+ printerr("Invalid Resource given expected VoxelSet")
+ return
+
+ if is_instance_valid(voxel_set):
+ if voxel_set.is_connected("requested_refresh", self, "update"):
+ voxel_set.disconnect("requested_refresh", self, "update")
+
+ voxel_set = value
+ if is_instance_valid(voxel_set):
+ if not voxel_set.is_connected("requested_refresh", self, "update"):
+ voxel_set.connect("requested_refresh", self, "update")
+
+ if update:
+ self.update()
+
+
+# Returns true if uv is selected
+func has_selected(uv : Vector2) -> bool:
+ return _selections.has(uv)
+
+
+# Returns uv selected at given index
+func get_selected(index : int) -> Vector2:
+ return _selections[index]
+
+
+# Returns array of selected faces
+func get_selections() -> Array:
+ return _selections.duplicate()
+
+
+# Returns number of faces selected
+func get_selected_size() -> int:
+ return _selections.size()
+
+
+# Returns world position as uv position
+func world_to_uv(world : Vector2) -> Vector2:
+ return (world / voxel_set.tile_size).floor() if is_instance_valid(voxel_set) and voxel_set.uv_ready() else -Vector2.ONE
+
+
+# Returns true if uv position is valid
+func is_valid_uv(uv : Vector2) -> bool:
+ if is_instance_valid(voxel_set) and voxel_set.uv_ready():
+ var bounds = voxel_set.tiles.get_size() / voxel_set.tile_size
+ return uv.x >= 0 and uv.y >= 0 and uv.x < bounds.x and uv.y < bounds.y
+ return false
+
+
+# Returns true if world position is valid uv position
+func is_valid_world(world : Vector2) -> bool:
+ return is_valid_uv(world_to_uv(world))
+
+
+# Selects given uv position, and emits selected_uv
+func select(uv : Vector2, emit := true) -> void:
+ # TODO UV within bounds
+ if selection_max != 0:
+ unselect_shrink(selection_max - 1)
+ _selections.append(uv)
+ if emit:
+ emit_signal("selected_uv", uv)
+
+
+# Unselects given uv position, and emits unselected_uv
+func unselect(uv : Vector2, emit := true) -> void:
+ if _selections.has(uv):
+ _selections.erase(uv)
+ if emit:
+ emit_signal("unselected_uv", uv)
+
+
+# Unselects all uv position
+func unselect_all() -> void:
+ while not _selections.empty():
+ unselect(_selections.back())
+
+
+# Unselects all uv position until given size is met
+func unselect_shrink(size := selection_max, emit := true) -> void:
+ if size >= 0:
+ while _selections.size() > size:
+ unselect(_selections.back(), emit)
+
+
+
+## Private Methods
+func _on_TilesViewer_mouse_exited():
+ _last_uv_hovered = -Vector2.ONE
+ update()
diff --git a/addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn b/addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn
new file mode 100644
index 0000000..a72e06b
--- /dev/null
+++ b/addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd" type="Script" id=1]
+
+[node name="TilesViewer" type="TextureRect"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+[connection signal="mouse_exited" from="." to="." method="_on_TilesViewer_mouse_exited"]
diff --git a/addons/voxel-core/controls/voxel_button/voxel_button.gd b/addons/voxel-core/controls/voxel_button/voxel_button.gd
new file mode 100644
index 0000000..f45724c
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_button/voxel_button.gd
@@ -0,0 +1,110 @@
+tool
+extends Button
+# Button representing a voxel's face
+
+
+
+## Exported Variables
+# Color of voxel
+export var voxel_color := Color.black setget set_voxel_color
+
+# Texture of voxel
+export var voxel_texture : Texture = null setget set_voxel_texture
+
+# ID of voxel to represented
+export(int, -1, 100000) var voxel_id := -1 setget set_voxel_id
+
+# Voxel's face to represent
+export var voxel_face := Vector3.ZERO setget set_voxel_face
+
+# VoxelSet being used
+export(Resource) var voxel_set = null setget set_voxel_set
+
+
+
+## Built-In Virtual Methods
+func _ready():
+ set_voxel_color(voxel_color)
+ set_voxel_texture(voxel_texture)
+
+
+
+## Public Methods
+# Sets voxel_color
+func set_voxel_color(value : Color) -> void:
+ voxel_color = value
+
+ $VoxelColor.color = voxel_color
+ property_list_changed_notify()
+
+
+# Sets voxel_texture
+func set_voxel_texture(value : Texture) -> void:
+ voxel_texture = value
+
+ $VoxelColor/VoxelTexture.texture = voxel_texture
+ property_list_changed_notify()
+
+
+# Sets voxel_id, and calls on update_view by default
+func set_voxel_id(value : int, update := true) -> void:
+ if value < -1:
+ return
+
+ voxel_id = value
+
+ if update:
+ update_view()
+
+
+# Sets voxel_face, and calls on update_view by default
+func set_voxel_face(value : Vector3, update := true) -> void:
+ voxel_face = value
+ if update:
+ update_view()
+
+
+# Sets voxel_set, and calls on update_view by default
+func set_voxel_set(value : Resource, update := true) -> void:
+ if not (typeof(value) == TYPE_NIL or value is VoxelSet):
+ printerr("Invalid Resource given expected VoxelSet")
+ return
+
+ voxel_set = value
+ if update:
+ update_view()
+
+
+# Quick setup of voxel_set, voxel_id and voxel_face; calls on update_view
+func setup(voxel_set : VoxelSet, voxel_id : int, voxel_face := Vector3.ZERO) -> void:
+ set_voxel_set(voxel_set, false)
+ set_voxel_id(voxel_id, false)
+ set_voxel_face(voxel_face, false)
+ update_view()
+
+
+# Sets up the voxel to visualize the face of the voxel id given
+func update_view() -> void:
+ if typeof(voxel_set) == TYPE_NIL:
+ return
+
+ var voxel : Dictionary = voxel_set.get_voxel(voxel_id)
+
+ hint_tooltip = str(voxel_id)
+ var name = voxel_set.id_to_name(voxel_id)
+ if not name.empty():
+ hint_tooltip += "|" + name
+
+ set_voxel_color(Voxel.get_face_color(voxel, voxel_face))
+
+ if not typeof(voxel_set.tiles) == TYPE_NIL:
+ var uv := Voxel.get_face_uv(voxel, voxel_face)
+ if uv == -Vector2.ONE:
+ set_voxel_texture(null)
+ else:
+ var img_texture := ImageTexture.new()
+ img_texture.create_from_image(
+ voxel_set.tiles.get_data().get_rect(Rect2(
+ Vector2.ONE * uv * voxel_set.tile_size,
+ Vector2.ONE * voxel_set.tile_size)))
+ set_voxel_texture(img_texture)
diff --git a/addons/voxel-core/controls/voxel_button/voxel_button.tscn b/addons/voxel-core/controls/voxel_button/voxel_button.tscn
new file mode 100644
index 0000000..d37b44d
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_button/voxel_button.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/voxel_button/voxel_button.gd" type="Script" id=1]
+
+[node name="VoxelButton" type="Button"]
+anchor_right = 0.03125
+anchor_bottom = 0.0533333
+rect_min_size = Vector2( 32, 32 )
+mouse_default_cursor_shape = 2
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="VoxelColor" type="ColorRect" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 4.0
+margin_top = 4.0
+margin_right = -4.0
+margin_bottom = -4.0
+focus_mode = 2
+mouse_filter = 2
+color = Color( 0, 0, 0, 1 )
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="VoxelTexture" type="TextureRect" parent="VoxelColor"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 2
+expand = true
+stretch_mode = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
diff --git a/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd
new file mode 100644
index 0000000..20d2ae3
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd
@@ -0,0 +1,368 @@
+tool
+extends Control
+# Listing of voxels in VoxelSet, with the ability to search, select and edit voxels.
+
+
+
+## Signals
+# Emitted when voxel has been selected
+signal selected_voxel(voxel_id)
+# Emitted when voxel has been unselected
+signal unselected_voxel(voxel_id)
+
+
+
+## Constants
+const VoxelButton := preload("res://addons/voxel-core/controls/voxel_button/voxel_button.tscn")
+
+
+
+## Exported Variables
+# Search being done
+export var search := "" setget set_search
+
+# Flag indicating whether edits are allowed
+export var allow_edit := false setget set_edit_mode
+
+# Number of uv positions that can be selected at any one time
+export(int, -1, 256) var selection_max := 0 setget set_selection_max
+
+# Flag indicating whether Hints is visible
+export var show_hints := false setget set_show_hints
+
+# VoxelSet being used
+export(Resource) var voxel_set = null setget set_voxel_set
+
+
+
+## Public Variables
+# UndoRedo used to commit operations
+var undo_redo : UndoRedo
+
+
+
+## Private Variables
+# Selected voxel ids
+var _selections := []
+
+
+
+## OnReady Variables
+onready var Search := get_node("VBoxContainer/Search")
+
+onready var Voxels := get_node("VBoxContainer/ScrollContainer/Voxels")
+
+onready var Hints := get_node("VBoxContainer/Hints")
+
+onready var Hint := get_node("VBoxContainer/Hints/Hint")
+
+onready var ContextMenu := get_node("ContextMenu")
+
+
+
+## Built-In Virtual Methods
+func _ready():
+ set_show_hints(show_hints)
+ set_voxel_set(voxel_set)
+
+ if not is_instance_valid(undo_redo):
+ undo_redo = UndoRedo.new()
+
+
+
+## Public Methods
+# Sets search, and calls on update_view by default
+func set_search(value : String, update := true) -> void:
+ search = value
+
+ if is_instance_valid(Search):
+ Search.text = search
+ if update:
+ update_view()
+
+
+# Sets allow_edit
+func set_edit_mode(value : bool, update := true) -> void:
+ allow_edit = value
+
+
+# Sets selection_max, and shrinks _selections to new maximum if needed
+func set_selection_max(value : int) -> void:
+ selection_max = clamp(value, -1, 256)
+ unselect_shrink()
+
+
+# Setter for show_hints
+func set_show_hints(value := show_hints) -> void:
+ show_hints = value
+
+ if is_instance_valid(Hints):
+ Hints.visible = show_hints and (allow_edit or selection_max)
+ if show_hints:
+ Hint.text = ""
+ if allow_edit:
+ Hint.text += "right click : context menu"
+ if selection_max == -1 or selection_max > 1:
+ Hint.text += ", ctrl + left click : multiple select / unselect"
+
+
+# Setter for voxel_set
+func set_voxel_set(value : Resource, update := true) -> void:
+ if not (typeof(value) == TYPE_NIL or value is VoxelSet):
+ printerr("Invalid Resource given expected VoxelSet")
+ return
+
+ if is_instance_valid(voxel_set):
+ if voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.disconnect("requested_refresh", self, "update_view")
+
+ voxel_set = value
+ if is_instance_valid(voxel_set):
+ if not voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.connect("requested_refresh", self, "update_view", [true])
+ elif is_instance_valid(Voxels):
+ for child in Voxels.get_children():
+ Voxels.remove_child(child)
+ child.queue_free()
+
+ if update:
+ update_view(true)
+
+
+# Returns true if voxel set id is selected
+func has_selected(voxel_id : int) -> bool:
+ return _selections.has(voxel_id)
+
+
+func get_selected(index : int) -> int:
+ return _selections[index]
+
+
+func get_selections() -> Array:
+ return _selections.duplicate()
+
+
+func get_selected_size() -> int:
+ return _selections.size()
+
+
+# Returns VoxelButton with given voxel_id if found, else returns null
+func get_voxel_button(voxel_id : int):
+ return Voxels.find_node(str(voxel_id), false, false) if Voxels else null
+
+
+# Selects voxel with given voxel_id if found, and emits selected_voxel
+func select(voxel_id : int, emit := true) -> void:
+ if selection_max == 0:
+ return
+
+ var voxel_button = get_voxel_button(voxel_id)
+ if not is_instance_valid(voxel_button):
+ return
+
+ unselect_shrink(selection_max - 1, emit)
+
+ voxel_button.pressed = true
+ _selections.append(voxel_id)
+ if emit:
+ emit_signal("selected_voxel", voxel_id)
+
+
+# Unselects voxel with given voxel_id if found, and emits unselected_voxel
+func unselect(voxel_id : int, emit := true) -> void:
+ var index := _selections.find(voxel_id)
+ if index == -1:
+ return
+
+ _selections.remove(index)
+ var voxel_button = get_voxel_button(voxel_id)
+ if is_instance_valid(voxel_button):
+ voxel_button.pressed = false
+ if emit:
+ emit_signal("unselected_voxel", voxel_id)
+
+
+# Unselects all selected voxel ids
+func unselect_all(emit := true) -> void:
+ while not _selections.empty():
+ unselect(_selections[-1], emit)
+
+
+# Unselects all voxels ids until given size is met
+func unselect_shrink(size := selection_max, emit := true) -> void:
+ if size >= 0:
+ while _selections.size() > size:
+ unselect(_selections[-1], emit)
+
+
+# Updates the listing of voxels
+# redraw : bool : if true will repopulate listing with new Voxel Buttons
+func update_view(redraw := false) -> void:
+ if is_instance_valid(Voxels) and is_instance_valid(voxel_set):
+ if redraw:
+ for child in Voxels.get_children():
+ Voxels.remove_child(child)
+ child.queue_free()
+
+ for id in voxel_set.get_ids():
+ var voxel_button := VoxelButton.instance()
+ voxel_button.name = str(id)
+ voxel_button.set_voxel_id(id, false)
+ voxel_button.set_voxel_set(voxel_set, false)
+ voxel_button.update_view()
+ voxel_button.toggle_mode = true
+ voxel_button.pressed = _selections.has(id)
+ voxel_button.mouse_filter = Control.MOUSE_FILTER_PASS
+ voxel_button.connect("pressed", self, "_on_VoxelButton_pressed", [voxel_button])
+ Voxels.add_child(voxel_button)
+
+ var keys := search.split(",", false)
+ for id in voxel_set.get_ids():
+ var show = true
+ for key in keys:
+ if (key.is_valid_integer() and id == key.to_int()) or voxel_set.id_to_name(id).find(key) > -1:
+ show = true
+ break
+ show = false
+
+ if not show:
+ unselect(id)
+ get_voxel_button(id).visible = show
+ call_deferred("correct")
+
+
+# Corrects the columns of listing to fit as many voxels horizonataly
+func correct() -> void:
+ if is_instance_valid(Voxels):
+ Voxels.columns = int(floor(rect_size.x / 36))
+
+
+
+## Private Methods
+func _on_Voxels_gui_input(event):
+ if allow_edit and event is InputEventMouseButton and event.button_index == BUTTON_RIGHT:
+ ContextMenu.clear()
+ if _selections.size() > 1:
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/cancel.png"),
+ "Deselect voxels", 3)
+ ContextMenu.add_separator()
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/add.png"),
+ "Add voxel", 0)
+ if _selections.size() == 1:
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/duplicate.png"),
+ "Duplicate voxel", 1)
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/sub.png"),
+ "Remove voxel", 2)
+ elif _selections.size() > 1:
+ ContextMenu.add_separator()
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/duplicate.png"),
+ "Duplicate voxels", 4)
+ ContextMenu.add_icon_item(
+ preload("res://addons/voxel-core/assets/controls/sub.png"),
+ "Remove voxels", 5)
+ ContextMenu.set_as_minsize()
+
+ ContextMenu.popup(Rect2(event.global_position, ContextMenu.rect_size))
+
+
+func _on_VoxelButton_pressed(voxel_button) -> void:
+ if selection_max != 0:
+ if voxel_button.pressed:
+ if not Input.is_key_pressed(KEY_CONTROL):
+ unselect_all()
+ select(voxel_button.voxel_id)
+ else:
+ if _selections.has(voxel_button.voxel_id):
+ unselect_all()
+ select(voxel_button.voxel_id)
+ else:
+ unselect(voxel_button.voxel_id)
+ else: voxel_button.pressed = false
+
+
+func _on_ContextMenu_id_pressed(_id : int):
+ match _id:
+ 0:
+ var id = voxel_set.size()
+ undo_redo.create_action("VoxelSetViewer : Add voxel")
+ undo_redo.add_do_method(voxel_set, "add_voxel", Voxel.colored(Color.white))
+ undo_redo.add_undo_method(voxel_set, "erase_voxel", id)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ unselect_all()
+ select(id)
+ 1:
+ var id = voxel_set.size()
+ undo_redo.create_action("VoxelSetViewer : Duplicate voxel")
+ undo_redo.add_do_method(
+ voxel_set,
+ "add_voxel",
+ voxel_set.get_voxel(_selections[0]).duplicate(true))
+ undo_redo.add_undo_method(voxel_set, "erase_voxel", id)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ unselect_all()
+ select(id)
+ 2:
+ undo_redo.create_action("VoxelSetViewer : Remove voxel")
+ undo_redo.add_do_method(voxel_set, "erase_voxel", _selections[0])
+ undo_redo.add_undo_method(
+ voxel_set,
+ "insert_voxel",
+ _selections[0],
+ voxel_set.get_voxel(_selections[0]))
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ unselect(_selections[0])
+ 3: unselect_all()
+ 4:
+ undo_redo.create_action("VoxelSetViewer : Duplicate voxels")
+ var id = voxel_set.size()
+ var ids = []
+ for selection in range(_selections.size()):
+ ids.append(id + selection)
+ undo_redo.add_do_method(
+ voxel_set,
+ "add_voxel",
+ voxel_set.get_voxel(_selections[selection]).duplicate(true))
+ undo_redo.add_undo_method(
+ voxel_set,
+ "erase_voxel",
+ id + _selections.size() - selection - 1)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ unselect_all()
+ for _id in ids:
+ select(_id)
+ 5:
+ undo_redo.create_action("VoxelSetViewer : Remove voxels")
+ var selections := _selections.duplicate()
+ selections.sort()
+ for index in range(selections.size()):
+ undo_redo.add_do_method(
+ voxel_set,
+ "erase_voxel",
+ selections[selections.size() - index - 1])
+ undo_redo.add_undo_method(
+ voxel_set,
+ "insert_voxel",
+ selections[index],
+ voxel_set.get_voxel(selections[index]))
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ unselect_all()
+
+
+func _on_Search_text_changed(new_text):
+ search = new_text
+ update_view()
diff --git a/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn
new file mode 100644
index 0000000..5bf8caf
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn
@@ -0,0 +1,81 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd" type="Script" id=2]
+[ext_resource path="res://addons/voxel-core/assets/controls/duplicate.png" type="Texture" id=3]
+[ext_resource path="res://addons/voxel-core/assets/controls/sub.png" type="Texture" id=4]
+[ext_resource path="res://addons/voxel-core/assets/controls/add.png" type="Texture" id=5]
+[ext_resource path="res://addons/voxel-core/assets/controls/cancel.png" type="Texture" id=6]
+
+[node name="VoxelSetViewer" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Search" type="LineEdit" parent="VBoxContainer"]
+margin_right = 1024.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+clear_button_enabled = true
+placeholder_text = "Search by ID or Name..."
+caret_blink = true
+caret_blink_speed = 0.5
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
+margin_top = 28.0
+margin_right = 1024.0
+margin_bottom = 600.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+
+[node name="Voxels" type="GridContainer" parent="VBoxContainer/ScrollContainer"]
+margin_right = 1024.0
+margin_bottom = 572.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+custom_constants/hseparation = 4
+columns = 28
+
+[node name="Hints" type="VBoxContainer" parent="VBoxContainer"]
+visible = false
+margin_top = 582.0
+margin_right = 1024.0
+margin_bottom = 600.0
+size_flags_horizontal = 3
+custom_constants/separation = 0
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer/Hints"]
+margin_right = 1024.0
+margin_bottom = 4.0
+
+[node name="Hint" type="Label" parent="VBoxContainer/Hints"]
+margin_top = 4.0
+margin_right = 1024.0
+margin_bottom = 18.0
+size_flags_horizontal = 3
+size_flags_vertical = 7
+align = 2
+valign = 1
+autowrap = true
+
+[node name="ContextMenu" type="PopupMenu" parent="."]
+margin_right = 146.0
+margin_bottom = 154.0
+items = [ "Add voxel", ExtResource( 5 ), 0, false, false, 0, 0, null, "", false, "Duplicate voxel", ExtResource( 3 ), 0, false, false, 2, 0, null, "", false, "Remove voxel", ExtResource( 4 ), 0, false, false, 2, 0, null, "", false, "", null, 0, false, false, 3, 0, null, "", true, "Deselect", ExtResource( 6 ), 0, false, false, 4, 0, null, "", false, "Duplicate voxels", ExtResource( 3 ), 0, false, false, 5, 0, null, "", false, "Remove voxels", ExtResource( 4 ), 0, false, false, 6, 0, null, "", false ]
+__meta__ = {
+"_edit_use_anchors_": false
+}
+[connection signal="resized" from="." to="." method="correct"]
+[connection signal="text_changed" from="VBoxContainer/Search" to="." method="_on_Search_text_changed"]
+[connection signal="gui_input" from="VBoxContainer/ScrollContainer/Voxels" to="." method="_on_Voxels_gui_input"]
+[connection signal="id_pressed" from="ContextMenu" to="." method="_on_ContextMenu_id_pressed"]
diff --git a/addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd b/addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd
new file mode 100644
index 0000000..1c3f881
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd
@@ -0,0 +1,824 @@
+tool
+extends Control
+# 2D / 3D preview of a voxel, that allows for selection and editing of faces.
+
+
+
+## Signals
+# Emitted when a voxel face has been selected
+signal selected_face(face)
+# Emitted when a voxel face has been unselected
+signal unselected_face(face)
+
+
+
+## Enums
+# View modes available
+enum ViewModes { VIEW_2D, VIEW_3D }
+
+
+
+## Constants
+# Default environment used
+var DefaultEnv := preload("res://addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres")
+
+
+
+## Exported Variables
+# Number of uv positions that can be selected at any one time
+export(int, 0, 6) var selection_max := 0 setget set_selection_max
+
+# Flag indicating whether edits are allowed
+export var allow_edit := false setget set_allow_edit
+
+# Current view being shown
+export(ViewModes) var view_mode := ViewModes.VIEW_3D setget set_view_mode
+
+# View sensitivity for the 3D view
+export(int, 0, 100) var camera_sensitivity := 8
+
+# ID of voxel to represented
+export var voxel_id : int setget set_voxel_id
+
+# VoxelSet beings used
+export(Resource) var voxel_set = null setget set_voxel_set
+
+# Environment used in 3D view
+export(Environment) var environment := DefaultEnv setget set_environment
+
+
+
+## Public Variables
+# UndoRedo used to commit operations
+var undo_redo : UndoRedo
+
+
+
+## Private Variables
+# Selected voxel ids
+var _selections := []
+
+# VoxelTool used for Mesh generation
+var _voxel_tool := VoxelTool.new()
+
+# Internal flag used to know whether user is dragging in 3D view
+var _is_dragging := false
+
+# Internal flag used to know the last face the user hovered
+var _last_hovered_face := Vector3.ZERO
+
+# Internal value used to revert to old versions of voxel data
+var _unedited_voxel := {}
+
+# Internal flag used to indicate the operation being committed
+var _editing_action := -1
+
+# Internal flag used to indicate the face being edited
+var _editing_face := Vector3.ZERO
+
+# Internal flag used to indicate whether multiple faces are being edited
+var _editing_multiple := false
+
+
+
+## OnReady Variables
+onready var View2D := get_node("View2D")
+
+onready var View3D := get_node("View3D")
+
+onready var ViewPort := get_node("View3D/Viewport")
+
+onready var CameraPivot := get_node("View3D/Viewport/CameraPivot")
+
+onready var CameraRef := get_node("View3D/Viewport/CameraPivot/Camera")
+
+onready var VoxelPreview := get_node("View3D/Viewport/VoxelPreview")
+
+onready var Select := get_node("View3D/Viewport/Select")
+
+onready var ViewModeRef := get_node("ToolBar/ViewMode")
+
+onready var ViewerHint := get_node("ToolBar/Hint")
+
+onready var ContextMenu := get_node("ContextMenu")
+
+onready var ColorMenu := get_node("ColorMenu")
+
+onready var VoxelColor := get_node("ColorMenu/VBoxContainer/VoxelColor")
+
+onready var TextureMenu := get_node("TextureMenu")
+
+onready var VoxelTexture := get_node("TextureMenu/VBoxContainer/ScrollContainer/VoxelTexture")
+
+onready var MaterialMenu := get_node("MaterialMenu")
+
+onready var MaterialRef := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer6/Material")
+
+onready var Metallic := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer/Metallic")
+
+onready var Specular := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer2/Specular")
+
+onready var Roughness := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer3/Roughness")
+
+onready var Energy := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer4/Energy")
+
+onready var EnergyColor := get_node("MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer5/EnergyColor")
+
+onready var EnvironmentMenu := get_node("EnvironmentMenu")
+
+
+
+## Built-In Virtual Methods
+func _ready():
+ set_view_mode(view_mode)
+ set_voxel_set(voxel_set)
+ load_environment()
+
+ if not is_instance_valid(undo_redo):
+ undo_redo = UndoRedo.new()
+
+
+
+## Public Methods
+# Saves the used environment path
+func save_environment() -> void:
+ if environment == DefaultEnv:
+ var dir := Directory.new()
+ if dir.file_exists("res://addons/voxel-core/controls/voxel_viewer/config.var"):
+ dir.remove("res://addons/voxel-core/controls/voxel_viewer/config.var")
+ elif is_instance_valid(environment):
+ var file := File.new()
+ var opened = file.open(
+ "res://addons/voxel-core/controls/voxel_viewer/config.var",
+ File.WRITE)
+ if opened == OK:
+ file.store_string(environment.resource_path)
+ if file.is_open():
+ file.close()
+
+
+# Loads and sets the environment file
+func load_environment() -> void:
+ var loaded := false
+ var file := File.new()
+ var opened = file.open(
+ "res://addons/voxel-core/controls/voxel_viewer/config.var",
+ File.READ)
+ if opened == OK:
+ var environment_path = file.get_as_text()
+ if file.file_exists(environment_path):
+ var _environment := load(environment_path)
+ if _environment is Environment:
+ set_environment(_environment)
+ loaded = true
+
+ if not loaded:
+ set_environment(DefaultEnv)
+
+ if file.is_open():
+ file.close()
+
+
+# Resets the environment to default
+func reset_environment() -> void:
+ set_environment(DefaultEnv)
+ save_environment()
+
+
+func set_selection_max(value : int, update := true) -> void:
+ selection_max = clamp(value, 0, 6)
+ unselect_shrink()
+ if update:
+ self.update()
+
+
+# Sets allow_edit
+func set_allow_edit(value : bool) -> void:
+ allow_edit = value
+
+
+# Sets view_mode
+func set_view_mode(value : int) -> void:
+ _last_hovered_face = Vector3.ZERO
+ view_mode = int(clamp(value, 0, ViewModes.size()))
+
+ if is_instance_valid(ViewModeRef):
+ ViewModeRef.selected = view_mode
+ if is_instance_valid(View2D):
+ View2D.visible = view_mode == ViewModes.VIEW_2D
+ if is_instance_valid(View3D):
+ View3D.visible = view_mode == ViewModes.VIEW_3D
+
+
+# Sets voxel_id, calls on update_view by defalut
+func set_voxel_id(value : int, update := true) -> void:
+ voxel_id = value
+ if update:
+ update_view()
+
+
+# Returns true if face is selected
+func has_selected(face : Vector3) -> bool:
+ return _selections.has(face)
+
+
+# Returns face selected at given index
+func get_selected(index : int) -> Vector3:
+ return _selections[index]
+
+
+# Returns array of selected faces
+func get_selections() -> Array:
+ return _selections.duplicate()
+
+
+# Returns number of faces selected
+func get_selected_size() -> int:
+ return _selections.size()
+
+
+# Sets voxel_set, and calls on update by default
+func set_voxel_set(value : Resource, update := true) -> void:
+ if not (typeof(value) == TYPE_NIL or value is VoxelSet):
+ printerr("Invalid Resource given expected VoxelSet")
+ return
+
+ if is_instance_valid(voxel_set):
+ if voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.disconnect("requested_refresh", self, "update_view")
+
+ voxel_set = value
+ if is_instance_valid(voxel_set):
+ if not voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.connect("requested_refresh", self, "update_view")
+ if is_instance_valid(VoxelTexture):
+ VoxelTexture.voxel_set = voxel_set
+
+ if update:
+ update_view()
+
+
+func set_environment(value : Environment) -> void:
+ environment = value
+ if is_instance_valid(ViewPort):
+ ViewPort.transparent_bg = environment == DefaultEnv
+ ViewPort.world.environment = environment
+
+
+# Return normal associated with given name
+func string_to_face(string : String) -> Vector3:
+ string = string.to_upper()
+ var normal := Vector3.ZERO
+ match string:
+ "RIGHT":
+ normal = Vector3.RIGHT
+ "LEFT":
+ normal = Vector3.LEFT
+ "TOP":
+ normal = Vector3.UP
+ "BOTTOM":
+ normal = Vector3.DOWN
+ "FRONT":
+ normal = Vector3.FORWARD
+ "BACK":
+ normal = Vector3.BACK
+ return normal
+
+# Return name associated with given face
+func face_to_string(face : Vector3) -> String:
+ var string := ""
+ match face:
+ Vector3.RIGHT:
+ string = "RIGHT"
+ Vector3.LEFT:
+ string = "LEFT"
+ Vector3.UP:
+ string = "TOP"
+ Vector3.DOWN:
+ string = "BOTTOM"
+ Vector3.FORWARD:
+ string = "FRONT"
+ Vector3.BACK:
+ string = "BACK"
+ return string
+
+
+# Quick setup of voxel_set, voxel_id; calls on update_view and update_hint
+func setup(voxel_set : VoxelSet, voxel_set_id : int) -> void:
+ set_voxel_set(voxel_set, false)
+ set_voxel_id(voxel_set_id, false)
+ update_view()
+ update_hint()
+
+
+# Returns the voxel data of current voxel, returns a empty Dictionary if not set
+func get_viewing_voxel() -> Dictionary:
+ return voxel_set.get_voxel(voxel_id) if is_instance_valid(voxel_set) else {}
+
+
+# Returns the VoxelButton associated with face normal
+func get_voxle_button(face_normal : Vector3):
+ return View2D.find_node(face_to_string(face_normal).capitalize())
+
+
+# Selects given face, and emits selected_face
+func select(face : Vector3, emit := true) -> void:
+ if selection_max != 0:
+ unselect_shrink(selection_max - 1)
+ _selections.append(face)
+ var voxel_button = get_voxle_button(face)
+ if is_instance_valid(voxel_button):
+ voxel_button.pressed = true
+ if emit:
+ emit_signal("selected_face", face)
+
+
+# Unselects given face, and emits unselected_face
+func unselect(face : Vector3, emit := true) -> void:
+ if _selections.has(face):
+ _selections.erase(face)
+ var voxel_button = get_voxle_button(face)
+ if is_instance_valid(voxel_button):
+ voxel_button.pressed = false
+ if emit:
+ emit_signal("unselected_face", face)
+
+
+# Unselects all the faces
+func unselect_all() -> void:
+ while not _selections.empty():
+ unselect(_selections.back())
+
+
+# Unselects all faces until given size is met
+func unselect_shrink(size := selection_max, emit := true) -> void:
+ if size >= 0:
+ while _selections.size() > size:
+ unselect(_selections.back(), emit)
+
+
+# Updates the hint message
+func update_hint() -> void:
+ if is_instance_valid(ViewerHint):
+ ViewerHint.text = ""
+
+ if not _selections.empty():
+ for i in range(len(_selections)):
+ if i > 0:
+ ViewerHint.text += ", "
+ ViewerHint.text += face_to_string(_selections[i]).to_upper()
+
+ if _last_hovered_face != Vector3.ZERO:
+ if not ViewerHint.text.empty():
+ ViewerHint.text += " | "
+ ViewerHint.text += face_to_string(_last_hovered_face).to_upper()
+
+
+# Updates the view
+func update_view() -> void:
+ if not is_instance_valid(voxel_set):
+ return
+
+ if is_instance_valid(View2D):
+ for voxel_button in View2D.get_children():
+ voxel_button.setup(voxel_set, voxel_id, string_to_face(voxel_button.name))
+ voxel_button.hint_tooltip = voxel_button.name
+
+ if is_instance_valid(VoxelPreview):
+ _voxel_tool.begin(voxel_set, true)
+ for face in Voxel.Faces:
+ _voxel_tool.add_face(
+ get_viewing_voxel(),
+ face, -Vector3.ONE / 2)
+ VoxelPreview.mesh = _voxel_tool.commit()
+
+ _voxel_tool.begin(voxel_set, true)
+ for selection in _selections:
+ _voxel_tool.add_face(
+ Voxel.colored(Color(0, 0, 0, 0.75)),
+ selection, -Vector3.ONE / 2)
+ Select.mesh = _voxel_tool.commit()
+
+
+# Shows the context menu and options
+func show_context_menu(global_position : Vector2, face := _last_hovered_face) -> void:
+ if not is_instance_valid(ContextMenu):
+ return
+ ContextMenu.clear()
+ if _last_hovered_face == Vector3.ZERO:
+ ContextMenu.add_item("Change environment", 14)
+ if environment != DefaultEnv:
+ ContextMenu.add_item("Reset environment", 15)
+ elif allow_edit:
+ _editing_face = face
+ _editing_multiple = false
+ var selected_hovered := _selections.has(_editing_face)
+ if is_instance_valid(voxel_set):
+ var voxel := get_viewing_voxel()
+
+ if _selections.size() < 6:
+ ContextMenu.add_item("Select all", 13)
+ if _selections.size() > 0:
+ ContextMenu.add_item("Unselect all", 11)
+
+ if _selections.size() == 0 or not selected_hovered:
+ ContextMenu.add_separator()
+ ContextMenu.add_item("Color side", 0)
+ if Voxel.has_face_color(voxel, _editing_face):
+ ContextMenu.add_item("Remove side color", 1)
+
+ if voxel_set.uv_ready():
+ ContextMenu.add_item("Texture side", 2)
+ if Voxel.has_face_uv(voxel, _editing_face):
+ ContextMenu.add_item("Remove side uv", 3)
+
+ if selected_hovered and _selections.size() >= 1:
+ ContextMenu.add_separator()
+ ContextMenu.add_item("Color side(s)", 7)
+ if Voxel.has_face_color(voxel, _editing_face):
+ ContextMenu.add_item("Remove side color(s)", 8)
+
+ if voxel_set.uv_ready():
+ ContextMenu.add_item("Texture side(s)", 9)
+ if Voxel.has_face_uv(voxel, _editing_face):
+ ContextMenu.add_item("Remove side uv(s)", 10)
+
+ ContextMenu.add_separator()
+ ContextMenu.add_item("Color voxel", 4)
+
+ ContextMenu.add_item("Modify material", 12)
+
+ if voxel_set.uv_ready():
+ ContextMenu.add_item("Texture voxel", 5)
+ if Voxel.has_uv(voxel):
+ ContextMenu.add_item("Remove voxel uv", 6)
+ ContextMenu.set_as_minsize()
+
+ ContextMenu.popup(Rect2(
+ global_position,
+ ContextMenu.rect_size))
+
+
+# Shows the color menu centered with given color
+func show_color_menu(color : Color) -> void:
+ if is_instance_valid(ColorMenu):
+ VoxelColor.color = color
+ ColorMenu.popup_centered()
+
+
+# Closes the color menu
+func hide_color_menu() -> void:
+ if is_instance_valid(ColorMenu):
+ ColorMenu.hide()
+ update_view()
+
+
+# Shows the texture menu centered with given color
+func show_texture_menu(uv : Vector2) -> void:
+ if is_instance_valid(TextureMenu):
+ VoxelTexture.unselect_all()
+ VoxelTexture.select(uv)
+ TextureMenu.popup_centered()
+
+
+# Closes the texture menu
+func hide_texture_menu() -> void:
+ if is_instance_valid(TextureMenu):
+ TextureMenu.hide()
+ update_view()
+
+
+# Shows the material menu with given voxel data
+func show_material_menu(voxel := get_viewing_voxel()) -> void:
+ if is_instance_valid(MaterialMenu):
+ MaterialRef.value = Voxel.get_material(voxel)
+ MaterialRef.max_value = voxel_set.materials.size() - 1
+ Metallic.value = Voxel.get_metallic(voxel)
+ Specular.value = Voxel.get_specular(voxel)
+ Roughness.value = Voxel.get_roughness(voxel)
+ Energy.value = Voxel.get_energy(voxel)
+ EnergyColor.color = Voxel.get_energy_color(voxel)
+ MaterialMenu.popup_centered()
+
+
+# Closes the material menu
+func hide_material_menu() -> void:
+ if is_instance_valid(MaterialMenu):
+ MaterialMenu.hide()
+ update_view()
+
+
+func show_environment_change_menu() -> void:
+ EnvironmentMenu.popup_centered()
+
+func hide_environment_change_menu() -> void:
+ EnvironmentMenu.hide()
+
+
+
+## Private Methods
+func _set_last_hovered_face(face : Vector3):
+ _last_hovered_face = face
+
+
+func _voxel_backup() -> void:
+ _unedited_voxel = get_viewing_voxel().duplicate(true)
+
+
+func _voxel_restore() -> void:
+ voxel_set.set_voxel(voxel_id, _unedited_voxel)
+
+
+func _on_Face_gui_input(event : InputEvent, normal : Vector3) -> void:
+ _last_hovered_face = normal
+ if event is InputEventMouseButton and event.pressed:
+ if event.button_index == BUTTON_LEFT:
+ if selection_max > 0:
+ if _selections.has(normal):
+ unselect(normal)
+ else:
+ select(normal)
+ accept_event()
+ else:
+ get_voxle_button(normal).pressed = false
+ elif event.button_index == BUTTON_RIGHT:
+ if allow_edit:
+ show_context_menu(event.global_position, _last_hovered_face)
+ update_hint()
+
+
+func _on_View3D_gui_input(event : InputEvent) -> void:
+ if event is InputEventMouse:
+ var from = CameraRef.project_ray_origin(event.position)
+ var to = from + CameraRef.project_ray_normal(event.position) * 1000
+ var hit = CameraRef.get_world().direct_space_state.intersect_ray(from, to)
+ if hit.empty():
+ _last_hovered_face = Vector3.ZERO
+ else:
+ hit["normal"] = hit["normal"].round()
+ _last_hovered_face = hit["normal"]
+
+ if event is InputEventMouseMotion:
+ if _is_dragging:
+ var motion = event.relative.normalized()
+ CameraPivot.rotation_degrees.x += -motion.y * camera_sensitivity
+ CameraPivot.rotation_degrees.y += -motion.x * camera_sensitivity
+ elif event is InputEventMouseButton:
+ if event.button_index == BUTTON_LEFT:
+ if event.doubleclick:
+ if not hit.empty() and selection_max > 0:
+ if _selections.has(hit["normal"]):
+ unselect(hit["normal"])
+ else:
+ select(hit["normal"])
+ elif event.is_pressed():
+ _is_dragging = true
+ else:
+ _is_dragging = false
+ elif event.button_index == BUTTON_RIGHT and not event.is_pressed():
+ show_context_menu(event.global_position, _last_hovered_face)
+
+ if _is_dragging:
+ View3D.set_default_cursor_shape(Control.CURSOR_MOVE)
+ elif hit:
+ View3D.set_default_cursor_shape(Control.CURSOR_POINTING_HAND)
+ else:
+ View3D.set_default_cursor_shape(Control.CURSOR_ARROW)
+ update_hint()
+ update_view()
+
+
+func _on_ContextMenu_id_pressed(id : int):
+ _editing_action = id
+ _editing_multiple = false
+ _voxel_backup()
+ match id:
+ 0: # Color editing face
+ show_color_menu(Voxel.get_face_color(get_viewing_voxel(), _editing_face))
+ 1: # Remove editing face color
+ var voxel = get_viewing_voxel()
+ Voxel.remove_face_color(voxel, _editing_face)
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Remove voxel face color")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 2: # Texture editing face
+ show_texture_menu(Voxel.get_face_uv(get_viewing_voxel(), _editing_face))
+ 3: # Remove editing face uv
+ var voxel := get_viewing_voxel()
+ Voxel.remove_face_uv(voxel, _editing_face)
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Remove voxel face uv")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 7: # Color selected faces
+ _editing_multiple = true
+ show_color_menu(Voxel.get_face_color(get_viewing_voxel(), _editing_face))
+ 8: # Remove selected faces color
+ _editing_multiple = true
+ var voxel = get_viewing_voxel()
+ for selection in _selections:
+ Voxel.remove_face_color(voxel, selection)
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Remove voxel face(s) color")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 9: # Texture selected face
+ _editing_multiple = true
+ show_texture_menu(Voxel.get_face_uv(get_viewing_voxel(), _editing_face))
+ 10: # Remove selected face uv
+ _editing_multiple = true
+ var voxel := get_viewing_voxel()
+ for selection in _selections:
+ Voxel.remove_face_uv(voxel, selection)
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Remove voxel face(s) uv")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 4: # Set voxel color
+ show_color_menu(Voxel.get_color(get_viewing_voxel()))
+ 5: # Set voxel uv
+ show_texture_menu(Voxel.get_uv(get_viewing_voxel()))
+ 6: # Remove voxel uv
+ var voxel = voxel_set.get_voxel(voxel_id)
+ Voxel.remove_uv(voxel)
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Remove voxel uv")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 13: # Select all
+ unselect_all()
+ for face in Voxel.Faces:
+ select(face)
+ 11: # Unselect all
+ unselect_all()
+ 12: # Modify material
+ show_material_menu()
+ 14:
+ show_environment_change_menu()
+ 15:
+ reset_environment()
+
+
+func _on_ColorPicker_color_changed(color : Color):
+ match _editing_action:
+ 0, 7:
+ for selection in (_selections if _editing_multiple else [_editing_face]):
+ Voxel.set_face_color(get_viewing_voxel(), selection, color)
+ 4: Voxel.set_color(get_viewing_voxel(), color)
+ update_view()
+
+
+func _on_ColorMenu_Cancel_pressed():
+ _voxel_restore()
+
+ hide_color_menu()
+
+
+func _on_ColorMenu_Confirm_pressed():
+ match _editing_action:
+ 0, 7:
+ var voxel = get_viewing_voxel()
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Set voxel face(s) color")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 4:
+ var voxel = get_viewing_voxel()
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Set voxel color")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ hide_color_menu()
+
+
+func _on_VoxelTexture_selected_uv(uv : Vector2):
+ match _editing_action:
+ 2, 9:
+ for selection in (_selections if _editing_multiple else [_editing_face]):
+ Voxel.set_face_uv(get_viewing_voxel(), selection, uv)
+ 5: Voxel.set_uv(get_viewing_voxel(), uv)
+ update_view()
+
+
+func _on_TextureMenu_Cancel_pressed():
+ _voxel_restore()
+
+ hide_texture_menu()
+
+
+func _on_TextureMenu_Confirm_pressed():
+ match _editing_action:
+ 2, 9:
+ var voxel = get_viewing_voxel()
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Set voxel face(s) uv")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ 5:
+ var voxel = get_viewing_voxel()
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Set voxel uv")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ hide_texture_menu()
+
+
+func _on_Metallic_value_changed(metallic : float):
+ Voxel.set_metallic(get_viewing_voxel(), metallic)
+ update_view()
+
+
+func _on_Specular_value_changed(specular : float):
+ Voxel.set_specular(get_viewing_voxel(), specular)
+ update_view()
+
+
+func _on_Roughness_value_changed(roughness : float):
+ Voxel.set_roughness(get_viewing_voxel(), roughness)
+ update_view()
+
+
+func _on_Energy_value_changed(emergy : float):
+ Voxel.set_energy(get_viewing_voxel(), emergy)
+ update_view()
+
+
+func _on_EnergyColor_changed(color : Color):
+ Voxel.set_energy_color(get_viewing_voxel(), color)
+ update_view()
+
+
+func _on_Material_value_changed(value : int):
+ Metallic.editable = value == -1
+ Specular.editable = value == -1
+ Roughness.editable = value == -1
+ Energy.editable = value == -1
+ EnergyColor.disabled = value > -1
+
+ Voxel.set_material(get_viewing_voxel(), value)
+ update_view()
+
+
+func _on_MaterialMenu_Cancel_pressed():
+ _voxel_restore()
+
+ hide_material_menu()
+
+
+func _on_MaterialMenu_Confirm_pressed():
+ var voxel = get_viewing_voxel()
+ Voxel.clean(voxel)
+
+ undo_redo.create_action("VoxelViewer : Set voxel material")
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, _unedited_voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+
+ hide_material_menu()
+
+
+func _on_EnvironmentMenu_file_selected(path):
+ var _environment = load(path)
+ if _environment is Environment:
+ set_environment(_environment)
+ property_list_changed_notify()
+ save_environment()
diff --git a/addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn b/addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn
new file mode 100644
index 0000000..5ed3914
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn
@@ -0,0 +1,475 @@
+[gd_scene load_steps=9 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd" type="Script" id=1]
+[ext_resource path="res://addons/voxel-core/controls/voxel_button/voxel_button.tscn" type="PackedScene" id=2]
+[ext_resource path="res://addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn" type="PackedScene" id=3]
+[ext_resource path="res://addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres" type="Environment" id=4]
+
+[sub_resource type="World" id=1]
+environment = ExtResource( 4 )
+
+[sub_resource type="CubeMesh" id=2]
+size = Vector3( 0.5, 0.5, 0.5 )
+
+[sub_resource type="BoxShape" id=3]
+extents = Vector3( 0.25, 0.25, 0.25 )
+
+[sub_resource type="SpatialMaterial" id=4]
+flags_transparent = true
+params_grow = true
+params_grow_amount = 0.001
+albedo_color = Color( 0, 0.25, 1, 0.607843 )
+
+[node name="VoxelViewer" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+environment = ExtResource( 4 )
+
+[node name="View2D" type="Control" parent="."]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+rect_clip_content = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Left" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.176758
+anchor_top = 0.366667
+anchor_right = 0.34082
+anchor_bottom = 0.646667
+toggle_mode = true
+
+[node name="Front" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.34082
+anchor_top = 0.366667
+anchor_right = 0.504883
+anchor_bottom = 0.646667
+toggle_mode = true
+
+[node name="Right" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.504883
+anchor_top = 0.366666
+anchor_right = 0.668945
+anchor_bottom = 0.646666
+margin_left = -1.0
+margin_top = 0.000335693
+margin_right = -1.0
+margin_bottom = 0.000335693
+toggle_mode = true
+
+[node name="Back" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.668945
+anchor_top = 0.366667
+anchor_right = 0.833008
+anchor_bottom = 0.646667
+margin_left = -0.999939
+margin_top = 1.52588e-05
+margin_right = -0.999939
+toggle_mode = true
+
+[node name="Top" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.34082
+anchor_top = 0.0866667
+anchor_right = 0.504883
+anchor_bottom = 0.366666
+toggle_mode = true
+
+[node name="Bottom" parent="View2D" instance=ExtResource( 2 )]
+anchor_left = 0.34082
+anchor_top = 0.646667
+anchor_right = 0.504883
+anchor_bottom = 0.926667
+toggle_mode = true
+
+[node name="View3D" type="ViewportContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+stretch = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Viewport" type="Viewport" parent="View3D"]
+size = Vector2( 1024, 600 )
+own_world = true
+world = SubResource( 1 )
+transparent_bg = true
+handle_input_locally = false
+render_target_update_mode = 3
+physics_object_picking = true
+
+[node name="CameraPivot" type="Spatial" parent="View3D/Viewport"]
+
+[node name="Camera" type="Camera" parent="View3D/Viewport/CameraPivot"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1 )
+current = true
+
+[node name="OmniLight" type="OmniLight" parent="View3D/Viewport/CameraPivot"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0.7 )
+omni_range = 1.1122
+
+[node name="VoxelPreview" type="MeshInstance" parent="View3D/Viewport"]
+mesh = SubResource( 2 )
+material/0 = null
+
+[node name="VoxelStaticBody" type="StaticBody" parent="View3D/Viewport/VoxelPreview"]
+
+[node name="CollisionShape" type="CollisionShape" parent="View3D/Viewport/VoxelPreview/VoxelStaticBody"]
+shape = SubResource( 3 )
+
+[node name="Select" type="MeshInstance" parent="View3D/Viewport"]
+material_override = SubResource( 4 )
+mesh = SubResource( 2 )
+material/0 = null
+
+[node name="ToolBar" type="HBoxContainer" parent="."]
+margin_left = 12.0
+margin_top = 12.0
+margin_right = 244.0
+margin_bottom = 32.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ViewMode" type="OptionButton" parent="ToolBar"]
+margin_right = 64.0
+margin_bottom = 20.0
+rect_min_size = Vector2( 64, 20 )
+size_flags_horizontal = 0
+text = "3D"
+align = 1
+items = [ "2D", null, false, 0, null, "3D", null, false, 1, null ]
+selected = 1
+
+[node name="Hint" type="Label" parent="ToolBar"]
+margin_left = 68.0
+margin_right = 232.0
+margin_bottom = 20.0
+rect_min_size = Vector2( 164, 20 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ContextMenu" type="PopupMenu" parent="."]
+anchor_right = 0.0878906
+anchor_bottom = 0.206667
+items = [ "Color side", null, 0, false, false, 0, 0, null, "", false, "Remove side's color", null, 0, false, false, 1, 0, null, "", false, "Texture side", null, 0, false, false, 2, 0, null, "", false, "Remove side's texture", null, 0, false, false, 3, 0, null, "", false, "", null, 0, false, false, -1, 0, null, "", true, "Color voxel", null, 0, false, false, 4, 0, null, "", false, "Texture voxel", null, 0, false, false, 5, 0, null, "", false, "Remove voxel's texture", null, 0, false, false, 6, 0, null, "", false ]
+__meta__ = {
+"_edit_use_anchors_": true
+}
+
+[node name="ColorMenu" type="PopupDialog" parent="."]
+margin_right = 324.0
+margin_bottom = 502.0
+rect_min_size = Vector2( 340, 525 )
+popup_exclusive = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="ColorMenu"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 8.0
+margin_top = 8.0
+margin_right = -8.00003
+margin_bottom = -8.00003
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VoxelColor" type="ColorPicker" parent="ColorMenu/VBoxContainer"]
+margin_left = 60.0
+margin_top = 60.0
+margin_right = 384.0
+margin_bottom = 545.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+edit_alpha = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="ColorMenu/VBoxContainer"]
+margin_top = 489.0
+margin_right = 324.0
+margin_bottom = 509.0
+
+[node name="Cancel" type="Button" parent="ColorMenu/VBoxContainer/HBoxContainer"]
+margin_right = 160.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Cancel"
+
+[node name="Confirm" type="Button" parent="ColorMenu/VBoxContainer/HBoxContainer"]
+margin_left = 164.0
+margin_right = 324.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Confirm"
+
+[node name="TextureMenu" type="PopupDialog" parent="."]
+margin_right = 324.0
+margin_bottom = 260.0
+rect_min_size = Vector2( 324, 260 )
+popup_exclusive = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="TextureMenu"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 8.0
+margin_top = 8.0
+margin_right = -8.00003
+margin_bottom = -8.00003
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ScrollContainer" type="ScrollContainer" parent="TextureMenu/VBoxContainer"]
+margin_right = 307.0
+margin_bottom = 219.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VoxelTexture" parent="TextureMenu/VBoxContainer/ScrollContainer" instance=ExtResource( 3 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 307.0
+margin_bottom = 219.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+selection_max = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="TextureMenu/VBoxContainer"]
+margin_top = 223.0
+margin_right = 307.0
+margin_bottom = 243.0
+
+[node name="Cancel" type="Button" parent="TextureMenu/VBoxContainer/HBoxContainer"]
+margin_right = 151.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Cancel"
+
+[node name="Confirm" type="Button" parent="TextureMenu/VBoxContainer/HBoxContainer"]
+margin_left = 155.0
+margin_right = 307.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Confirm"
+
+[node name="MaterialMenu" type="PopupDialog" parent="."]
+margin_right = 250.0
+margin_bottom = 325.0
+rect_min_size = Vector2( 250, 325 )
+size_flags_horizontal = 0
+size_flags_vertical = 0
+popup_exclusive = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MaterialMenu"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 8.0
+margin_top = 8.0
+margin_right = -8.00003
+margin_bottom = -8.00003
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MaterialMenu/VBoxContainer"]
+margin_right = 233.0
+margin_bottom = 284.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+custom_constants/separation = 16
+alignment = 1
+
+[node name="HBoxContainer6" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 22.0
+margin_right = 233.0
+margin_bottom = 46.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer6"]
+margin_top = 5.0
+margin_right = 56.0
+margin_bottom = 19.0
+text = "Material:"
+
+[node name="Material" type="SpinBox" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer6"]
+margin_left = 159.0
+margin_right = 233.0
+margin_bottom = 24.0
+size_flags_horizontal = 10
+min_value = -1.0
+rounded = true
+
+[node name="HSeparator" type="HSeparator" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 62.0
+margin_right = 233.0
+margin_bottom = 66.0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 82.0
+margin_right = 233.0
+margin_bottom = 106.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer"]
+margin_top = 5.0
+margin_right = 55.0
+margin_bottom = 19.0
+text = "Metallic:"
+
+[node name="Metallic" type="SpinBox" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 159.0
+margin_right = 233.0
+margin_bottom = 24.0
+size_flags_horizontal = 10
+max_value = 1.0
+step = 0.01
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 122.0
+margin_right = 233.0
+margin_bottom = 146.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer2"]
+margin_top = 5.0
+margin_right = 58.0
+margin_bottom = 19.0
+text = "Specular:"
+
+[node name="Specular" type="SpinBox" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer2"]
+margin_left = 159.0
+margin_right = 233.0
+margin_bottom = 24.0
+size_flags_horizontal = 10
+max_value = 1.0
+step = 0.01
+
+[node name="HBoxContainer3" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 162.0
+margin_right = 233.0
+margin_bottom = 186.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer3"]
+margin_top = 5.0
+margin_right = 73.0
+margin_bottom = 19.0
+text = "Roughness:"
+
+[node name="Roughness" type="SpinBox" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer3"]
+margin_left = 159.0
+margin_right = 233.0
+margin_bottom = 24.0
+size_flags_horizontal = 10
+max_value = 1.0
+step = 0.01
+
+[node name="HBoxContainer4" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 202.0
+margin_right = 233.0
+margin_bottom = 226.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer4"]
+margin_top = 5.0
+margin_right = 46.0
+margin_bottom = 19.0
+text = "Energy:"
+
+[node name="Energy" type="SpinBox" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer4"]
+margin_left = 159.0
+margin_right = 233.0
+margin_bottom = 24.0
+size_flags_horizontal = 10
+max_value = 16.0
+step = 0.1
+
+[node name="HBoxContainer5" type="HBoxContainer" parent="MaterialMenu/VBoxContainer/VBoxContainer"]
+margin_top = 242.0
+margin_right = 233.0
+margin_bottom = 262.0
+
+[node name="Label" type="Label" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer5"]
+margin_top = 3.0
+margin_right = 83.0
+margin_bottom = 17.0
+text = "Energy Color:"
+
+[node name="EnergyColor" type="ColorPickerButton" parent="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer5"]
+margin_left = 156.0
+margin_right = 233.0
+margin_bottom = 20.0
+rect_min_size = Vector2( 77, 20 )
+size_flags_horizontal = 10
+edit_alpha = false
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MaterialMenu/VBoxContainer"]
+margin_top = 288.0
+margin_right = 233.0
+margin_bottom = 308.0
+
+[node name="Cancel" type="Button" parent="MaterialMenu/VBoxContainer/HBoxContainer"]
+margin_right = 114.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Cancel"
+
+[node name="Confirm" type="Button" parent="MaterialMenu/VBoxContainer/HBoxContainer"]
+margin_left = 118.0
+margin_right = 233.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Confirm"
+
+[node name="EnvironmentMenu" type="FileDialog" parent="."]
+margin_right = 315.0
+margin_bottom = 349.0
+rect_min_size = Vector2( 200, 350 )
+window_title = "Open a File"
+mode = 0
+filters = PoolStringArray( "*.tres; Environment files" )
+[connection signal="gui_input" from="View2D/Left" to="." method="_on_Face_gui_input" binds= [ Vector3( -1, 0, 0 ) ]]
+[connection signal="mouse_exited" from="View2D/Left" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View2D/Front" to="." method="_on_Face_gui_input" binds= [ Vector3( 0, 0, -1 ) ]]
+[connection signal="mouse_exited" from="View2D/Front" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View2D/Right" to="." method="_on_Face_gui_input" binds= [ Vector3( 1, 0, 0 ) ]]
+[connection signal="mouse_exited" from="View2D/Right" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View2D/Back" to="." method="_on_Face_gui_input" binds= [ Vector3( 0, 0, 1 ) ]]
+[connection signal="mouse_exited" from="View2D/Back" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View2D/Top" to="." method="_on_Face_gui_input" binds= [ Vector3( 0, 1, 0 ) ]]
+[connection signal="mouse_exited" from="View2D/Top" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View2D/Bottom" to="." method="_on_Face_gui_input" binds= [ Vector3( 0, -1, 0 ) ]]
+[connection signal="mouse_exited" from="View2D/Bottom" to="." method="_set_last_hovered_face" binds= [ Vector3( 0, 0, 0 ) ]]
+[connection signal="gui_input" from="View3D" to="." method="_on_View3D_gui_input"]
+[connection signal="item_selected" from="ToolBar/ViewMode" to="." method="set_view_mode"]
+[connection signal="id_pressed" from="ContextMenu" to="." method="_on_ContextMenu_id_pressed"]
+[connection signal="color_changed" from="ColorMenu/VBoxContainer/VoxelColor" to="." method="_on_ColorPicker_color_changed"]
+[connection signal="pressed" from="ColorMenu/VBoxContainer/HBoxContainer/Cancel" to="." method="_on_ColorMenu_Cancel_pressed"]
+[connection signal="pressed" from="ColorMenu/VBoxContainer/HBoxContainer/Confirm" to="." method="_on_ColorMenu_Confirm_pressed"]
+[connection signal="selected_uv" from="TextureMenu/VBoxContainer/ScrollContainer/VoxelTexture" to="." method="_on_VoxelTexture_selected_uv"]
+[connection signal="pressed" from="TextureMenu/VBoxContainer/HBoxContainer/Cancel" to="." method="_on_TextureMenu_Cancel_pressed"]
+[connection signal="pressed" from="TextureMenu/VBoxContainer/HBoxContainer/Confirm" to="." method="_on_TextureMenu_Confirm_pressed"]
+[connection signal="value_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer6/Material" to="." method="_on_Material_value_changed"]
+[connection signal="value_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer/Metallic" to="." method="_on_Metallic_value_changed"]
+[connection signal="value_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer2/Specular" to="." method="_on_Specular_value_changed"]
+[connection signal="value_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer3/Roughness" to="." method="_on_Roughness_value_changed"]
+[connection signal="value_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer4/Energy" to="." method="_on_Energy_value_changed"]
+[connection signal="color_changed" from="MaterialMenu/VBoxContainer/VBoxContainer/HBoxContainer5/EnergyColor" to="." method="_on_EnergyColor_changed"]
+[connection signal="pressed" from="MaterialMenu/VBoxContainer/HBoxContainer/Cancel" to="." method="_on_MaterialMenu_Cancel_pressed"]
+[connection signal="pressed" from="MaterialMenu/VBoxContainer/HBoxContainer/Confirm" to="." method="_on_MaterialMenu_Confirm_pressed"]
+[connection signal="file_selected" from="EnvironmentMenu" to="." method="_on_EnvironmentMenu_file_selected"]
diff --git a/addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres b/addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres
new file mode 100644
index 0000000..ed6528c
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres
@@ -0,0 +1,4 @@
+[gd_resource type="Environment" format=2]
+
+[resource]
+ambient_light_color = Color( 1, 1, 1, 1 )
diff --git a/addons/voxel-core/defaults/voxel_set.tres b/addons/voxel-core/defaults/voxel_set.tres
new file mode 100644
index 0000000..c893160
--- /dev/null
+++ b/addons/voxel-core/defaults/voxel_set.tres
@@ -0,0 +1,444 @@
+[gd_resource type="Resource" load_steps=3 format=2]
+
+[ext_resource path="res://addons/voxel-core/classes/voxel_set.gd" type="Script" id=1]
+
+
+[sub_resource type="SpatialMaterial" id=1]
+
+[resource]
+script = ExtResource( 1 )
+tile_size = Vector2( 32, 32 )
+materials = [ SubResource( 1 ) ]
+VOXELS = [ {
+"color": Color( 0.94, 0.97, 1, 1 ),
+"name": "aliceblue"
+}, {
+"color": Color( 0.98, 0.92, 0.84, 1 ),
+"name": "antiquewhite"
+}, {
+"color": Color( 0, 1, 1, 1 ),
+"name": "aqua"
+}, {
+"color": Color( 0.5, 1, 0.83, 1 ),
+"name": "aquamarine"
+}, {
+"color": Color( 0.94, 1, 1, 1 ),
+"name": "azure"
+}, {
+"color": Color( 0.96, 0.96, 0.86, 1 ),
+"name": "beige"
+}, {
+"color": Color( 1, 0.89, 0.77, 1 ),
+"name": "bisque"
+}, {
+"color": Color( 0, 0, 0, 1 ),
+"name": "black"
+}, {
+"color": Color( 1, 0.92, 0.8, 1 ),
+"name": "blanchedalmond"
+}, {
+"color": Color( 0, 0, 1, 1 ),
+"name": "blue"
+}, {
+"color": Color( 0.54, 0.17, 0.89, 1 ),
+"name": "blueviolet"
+}, {
+"color": Color( 0.65, 0.16, 0.16, 1 ),
+"name": "brown"
+}, {
+"color": Color( 0.87, 0.72, 0.53, 1 ),
+"name": "burlywood"
+}, {
+"color": Color( 0.37, 0.62, 0.63, 1 ),
+"name": "cadetblue"
+}, {
+"color": Color( 0.5, 1, 0, 1 ),
+"name": "chartreuse"
+}, {
+"color": Color( 0.82, 0.41, 0.12, 1 ),
+"name": "chocolate"
+}, {
+"color": Color( 1, 0.5, 0.31, 1 ),
+"name": "coral"
+}, {
+"color": Color( 0.39, 0.58, 0.93, 1 ),
+"name": "cornflower"
+}, {
+"color": Color( 1, 0.97, 0.86, 1 ),
+"name": "cornsilk"
+}, {
+"color": Color( 0.86, 0.08, 0.24, 1 ),
+"name": "crimson"
+}, {
+"color": Color( 0, 1, 1, 1 ),
+"name": "cyan"
+}, {
+"color": Color( 0, 0, 0.55, 1 ),
+"name": "darkblue"
+}, {
+"color": Color( 0, 0.55, 0.55, 1 ),
+"name": "darkcyan"
+}, {
+"color": Color( 0.72, 0.53, 0.04, 1 ),
+"name": "darkgoldenrod"
+}, {
+"color": Color( 0.66, 0.66, 0.66, 1 ),
+"name": "darkgray"
+}, {
+"color": Color( 0, 0.39, 0, 1 ),
+"name": "darkgreen"
+}, {
+"color": Color( 0.74, 0.72, 0.42, 1 ),
+"name": "darkkhaki"
+}, {
+"color": Color( 0.55, 0, 0.55, 1 ),
+"name": "darkmagenta"
+}, {
+"color": Color( 0.33, 0.42, 0.18, 1 ),
+"name": "darkolivegreen"
+}, {
+"color": Color( 1, 0.55, 0, 1 ),
+"name": "darkorange"
+}, {
+"color": Color( 0.6, 0.2, 0.8, 1 ),
+"name": "darkorchid"
+}, {
+"color": Color( 0.55, 0, 0, 1 ),
+"name": "darkred"
+}, {
+"color": Color( 0.91, 0.59, 0.48, 1 ),
+"name": "darksalmon"
+}, {
+"color": Color( 0.56, 0.74, 0.56, 1 ),
+"name": "darkseagreen"
+}, {
+"color": Color( 0.28, 0.24, 0.55, 1 ),
+"name": "darkslateblue"
+}, {
+"color": Color( 0.18, 0.31, 0.31, 1 ),
+"name": "darkslategray"
+}, {
+"color": Color( 0, 0.81, 0.82, 1 ),
+"name": "darkturquoise"
+}, {
+"color": Color( 0.58, 0, 0.83, 1 ),
+"name": "darkviolet"
+}, {
+"color": Color( 1, 0.08, 0.58, 1 ),
+"name": "deeppink"
+}, {
+"color": Color( 0, 0.75, 1, 1 ),
+"name": "deepskyblue"
+}, {
+"color": Color( 0.41, 0.41, 0.41, 1 ),
+"name": "dimgray"
+}, {
+"color": Color( 0.12, 0.56, 1, 1 ),
+"name": "dodgerblue"
+}, {
+"color": Color( 0.7, 0.13, 0.13, 1 ),
+"name": "firebrick"
+}, {
+"color": Color( 1, 0.98, 0.94, 1 ),
+"name": "floralwhite"
+}, {
+"color": Color( 0.13, 0.55, 0.13, 1 ),
+"name": "forestgreen"
+}, {
+"color": Color( 1, 0, 1, 1 ),
+"name": "fuchsia"
+}, {
+"color": Color( 0.86, 0.86, 0.86, 1 ),
+"name": "gainsboro"
+}, {
+"color": Color( 0.97, 0.97, 1, 1 ),
+"name": "ghostwhite"
+}, {
+"color": Color( 1, 0.84, 0, 1 ),
+"name": "gold"
+}, {
+"color": Color( 0.85, 0.65, 0.13, 1 ),
+"name": "goldenrod"
+}, {
+"color": Color( 0.75, 0.75, 0.75, 1 ),
+"name": "gray"
+}, {
+"color": Color( 0, 1, 0, 1 ),
+"name": "green"
+}, {
+"color": Color( 0.68, 1, 0.18, 1 ),
+"name": "greenyellow"
+}, {
+"color": Color( 0.94, 1, 0.94, 1 ),
+"name": "honeydew"
+}, {
+"color": Color( 1, 0.41, 0.71, 1 ),
+"name": "hotpink"
+}, {
+"color": Color( 0.8, 0.36, 0.36, 1 ),
+"name": "indianred"
+}, {
+"color": Color( 0.29, 0, 0.51, 1 ),
+"name": "indigo"
+}, {
+"color": Color( 1, 1, 0.94, 1 ),
+"name": "ivory"
+}, {
+"color": Color( 0.94, 0.9, 0.55, 1 ),
+"name": "khaki"
+}, {
+"color": Color( 0.9, 0.9, 0.98, 1 ),
+"name": "lavender"
+}, {
+"color": Color( 1, 0.94, 0.96, 1 ),
+"name": "lavenderblush"
+}, {
+"color": Color( 0.49, 0.99, 0, 1 ),
+"name": "lawngreen"
+}, {
+"color": Color( 1, 0.98, 0.8, 1 ),
+"name": "lemonchiffon"
+}, {
+"color": Color( 0.68, 0.85, 0.9, 1 ),
+"name": "lightblue"
+}, {
+"color": Color( 0.94, 0.5, 0.5, 1 ),
+"name": "lightcoral"
+}, {
+"color": Color( 0.88, 1, 1, 1 ),
+"name": "lightcyan"
+}, {
+"color": Color( 0.98, 0.98, 0.82, 1 ),
+"name": "lightgoldenrod"
+}, {
+"color": Color( 0.83, 0.83, 0.83, 1 ),
+"name": "lightgray"
+}, {
+"color": Color( 0.56, 0.93, 0.56, 1 ),
+"name": "lightgreen"
+}, {
+"color": Color( 1, 0.71, 0.76, 1 ),
+"name": "lightpink"
+}, {
+"color": Color( 1, 0.63, 0.48, 1 ),
+"name": "lightsalmon"
+}, {
+"color": Color( 0.13, 0.7, 0.67, 1 ),
+"name": "lightseagreen"
+}, {
+"color": Color( 0.53, 0.81, 0.98, 1 ),
+"name": "lightskyblue"
+}, {
+"color": Color( 0.47, 0.53, 0.6, 1 ),
+"name": "lightslategray"
+}, {
+"color": Color( 0.69, 0.77, 0.87, 1 ),
+"name": "lightsteelblue"
+}, {
+"color": Color( 1, 1, 0.88, 1 ),
+"name": "lightyellow"
+}, {
+"color": Color( 0, 1, 0, 1 ),
+"name": "lime"
+}, {
+"color": Color( 0.2, 0.8, 0.2, 1 ),
+"name": "limegreen"
+}, {
+"color": Color( 0.98, 0.94, 0.9, 1 ),
+"name": "linen"
+}, {
+"color": Color( 1, 0, 1, 1 ),
+"name": "magenta"
+}, {
+"color": Color( 0.69, 0.19, 0.38, 1 ),
+"name": "maroon"
+}, {
+"color": Color( 0.4, 0.8, 0.67, 1 ),
+"name": "mediumaquamarine"
+}, {
+"color": Color( 0, 0, 0.8, 1 ),
+"name": "mediumblue"
+}, {
+"color": Color( 0.73, 0.33, 0.83, 1 ),
+"name": "mediumorchid"
+}, {
+"color": Color( 0.58, 0.44, 0.86, 1 ),
+"name": "mediumpurple"
+}, {
+"color": Color( 0.24, 0.7, 0.44, 1 ),
+"name": "mediumseagreen"
+}, {
+"color": Color( 0.48, 0.41, 0.93, 1 ),
+"name": "mediumslateblue"
+}, {
+"color": Color( 0, 0.98, 0.6, 1 ),
+"name": "mediumspringgreen"
+}, {
+"color": Color( 0.28, 0.82, 0.8, 1 ),
+"name": "mediumturquoise"
+}, {
+"color": Color( 0.78, 0.08, 0.52, 1 ),
+"name": "mediumvioletred"
+}, {
+"color": Color( 0.1, 0.1, 0.44, 1 ),
+"name": "midnightblue"
+}, {
+"color": Color( 0.96, 1, 0.98, 1 ),
+"name": "mintcream"
+}, {
+"color": Color( 1, 0.89, 0.88, 1 ),
+"name": "mistyrose"
+}, {
+"color": Color( 1, 0.89, 0.71, 1 ),
+"name": "moccasin"
+}, {
+"color": Color( 1, 0.87, 0.68, 1 ),
+"name": "navajowhite"
+}, {
+"color": Color( 0, 0, 0.5, 1 ),
+"name": "navyblue"
+}, {
+"color": Color( 0.99, 0.96, 0.9, 1 ),
+"name": "oldlace"
+}, {
+"color": Color( 0.5, 0.5, 0, 1 ),
+"name": "olive"
+}, {
+"color": Color( 0.42, 0.56, 0.14, 1 ),
+"name": "olivedrab"
+}, {
+"color": Color( 1, 0.65, 0, 1 ),
+"name": "orange"
+}, {
+"color": Color( 1, 0.27, 0, 1 ),
+"name": "orangered"
+}, {
+"color": Color( 0.85, 0.44, 0.84, 1 ),
+"name": "orchid"
+}, {
+"color": Color( 0.93, 0.91, 0.67, 1 ),
+"name": "palegoldenrod"
+}, {
+"color": Color( 0.6, 0.98, 0.6, 1 ),
+"name": "palegreen"
+}, {
+"color": Color( 0.69, 0.93, 0.93, 1 ),
+"name": "paleturquoise"
+}, {
+"color": Color( 0.86, 0.44, 0.58, 1 ),
+"name": "palevioletred"
+}, {
+"color": Color( 1, 0.94, 0.84, 1 ),
+"name": "papayawhip"
+}, {
+"color": Color( 1, 0.85, 0.73, 1 ),
+"name": "peachpuff"
+}, {
+"color": Color( 0.8, 0.52, 0.25, 1 ),
+"name": "peru"
+}, {
+"color": Color( 1, 0.75, 0.8, 1 ),
+"name": "pink"
+}, {
+"color": Color( 0.87, 0.63, 0.87, 1 ),
+"name": "plum"
+}, {
+"color": Color( 0.69, 0.88, 0.9, 1 ),
+"name": "powderblue"
+}, {
+"color": Color( 0.63, 0.13, 0.94, 1 ),
+"name": "purple"
+}, {
+"color": Color( 0.4, 0.2, 0.6, 1 ),
+"name": "rebeccapurple"
+}, {
+"color": Color( 1, 0, 0, 1 ),
+"name": "red"
+}, {
+"color": Color( 0.74, 0.56, 0.56, 1 ),
+"name": "rosybrown"
+}, {
+"color": Color( 0.25, 0.41, 0.88, 1 ),
+"name": "royalblue"
+}, {
+"color": Color( 0.55, 0.27, 0.07, 1 ),
+"name": "saddlebrown"
+}, {
+"color": Color( 0.98, 0.5, 0.45, 1 ),
+"name": "salmon"
+}, {
+"color": Color( 0.18, 0.55, 0.34, 1 ),
+"name": "sandybrown"
+}, {
+"color": Color( 1, 0.96, 0.93, 1 ),
+"name": "seashell"
+}, {
+"color": Color( 0.63, 0.32, 0.18, 1 ),
+"name": "sienna"
+}, {
+"color": Color( 0.75, 0.75, 0.75, 1 ),
+"name": "silver"
+}, {
+"color": Color( 0.53, 0.81, 0.92, 1 ),
+"name": "skyblue"
+}, {
+"color": Color( 0.42, 0.35, 0.8, 1 ),
+"name": "slateblue"
+}, {
+"color": Color( 0.44, 0.5, 0.56, 1 ),
+"name": "slategray"
+}, {
+"color": Color( 1, 0.98, 0.98, 1 ),
+"name": "snow"
+}, {
+"color": Color( 0, 1, 0.5, 1 ),
+"name": "springgreen"
+}, {
+"color": Color( 0.27, 0.51, 0.71, 1 ),
+"name": "steelblue"
+}, {
+"color": Color( 0.82, 0.71, 0.55, 1 ),
+"name": "tan"
+}, {
+"color": Color( 0, 0.5, 0.5, 1 ),
+"name": "teal"
+}, {
+"color": Color( 0.85, 0.75, 0.85, 1 ),
+"name": "thistle"
+}, {
+"color": Color( 1, 0.39, 0.28, 1 ),
+"name": "tomato"
+}, {
+"color": Color( 0.25, 0.88, 0.82, 1 ),
+"name": "turquoise"
+}, {
+"color": Color( 0.93, 0.51, 0.93, 1 ),
+"name": "violet"
+}, {
+"color": Color( 0.5, 0.5, 0.5, 1 ),
+"name": "webgray"
+}, {
+"color": Color( 0, 0.5, 0, 1 ),
+"name": "webgreen"
+}, {
+"color": Color( 0.5, 0, 0, 1 ),
+"name": "webmaroon"
+}, {
+"color": Color( 0.5, 0, 0.5, 1 ),
+"name": "webpurple"
+}, {
+"color": Color( 0.96, 0.87, 0.7, 1 ),
+"name": "wheat"
+}, {
+"color": Color( 1, 1, 1, 1 ),
+"name": "white"
+}, {
+"color": Color( 0.96, 0.96, 0.96, 1 ),
+"name": "whitesmoke"
+}, {
+"color": Color( 1, 1, 0, 1 ),
+"name": "yellow"
+}, {
+"color": Color( 0.6, 0.8, 0.2, 1 ),
+"name": "yellowgreen"
+} ]
diff --git a/addons/voxel-core/engine/importers/meshes.gd b/addons/voxel-core/engine/importers/meshes.gd
new file mode 100644
index 0000000..8b2dbbd
--- /dev/null
+++ b/addons/voxel-core/engine/importers/meshes.gd
@@ -0,0 +1,108 @@
+tool
+extends EditorImportPlugin
+# Import files as static Mesh Resource, not to be confused with VoxelObjects
+
+
+
+## Enums
+enum Presets { DEFAULT }
+
+
+
+## Built-In Virtual Methods
+func get_visible_name() -> String:
+ return "MeshOfVoxels"
+
+
+func get_importer_name() -> String:
+ return "VoxelCore.MeshOfVoxels"
+
+
+func get_recognized_extensions() -> Array:
+ return [
+ "png", "jpg",
+ "vox",
+ #"qb",
+ #"qbt",
+ #"vxm",
+ ]
+
+
+func get_resource_type() -> String:
+ return "Mesh"
+
+
+func get_save_extension() -> String:
+ return "mesh"
+
+
+func get_preset_count() -> int:
+ return Presets.size()
+
+
+func get_preset_name(preset : int) -> String:
+ match preset:
+ Presets.DEFAULT:
+ return "Default"
+ _:
+ return "Unknown"
+
+
+func get_import_options(preset : int) -> Array:
+ var preset_options = [
+ {
+ "name": "mesh_mode",
+ "default_value": VoxelMesh.MeshModes.GREEDY,
+ "property_hint": PROPERTY_HINT_ENUM,
+ "hint_string": PoolStringArray(VoxelMesh.MeshModes.keys()).join(","),
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ {
+ "name": "center",
+ "default_value": 0,
+ "property_hint": PROPERTY_HINT_ENUM,
+ "hint_string": "NONE,CENTER,CENTER_ABOVE_AXIS",
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ ]
+
+ match preset:
+ Presets.DEFAULT:
+ pass
+
+ return preset_options
+
+
+func get_option_visibility(option : String, options : Dictionary) -> bool:
+ return true
+
+
+func import(source_file : String, save_path : String, options : Dictionary, r_platform_variants : Array, r_gen_files : Array) -> int:
+ var read := Reader.read_file(source_file)
+ var error = read.get("error", FAILED)
+ if error == OK:
+ var voxel_mesh = VoxelMesh.new()
+ voxel_mesh.voxel_set = VoxelSet.new()
+
+ voxel_mesh.set_mesh_mode(options.get("mesh_mode", VoxelMesh.MeshModes.GREEDY))
+ voxel_mesh.voxel_set.set_voxels(read["palette"])
+ for voxel_position in read["voxels"]:
+ voxel_mesh.set_voxel(voxel_position, read["voxels"][voxel_position])
+
+ var center = options.get("center", 0)
+ if center > 0:
+ match center:
+ 1:
+ center = Vector3(0.5, 0.5, 0.5)
+ 2:
+ center = Vector3(0.5, 1.0, 0.5)
+ voxel_mesh.center(center)
+
+ voxel_mesh.update_mesh()
+
+ error = ResourceSaver.save(
+ '%s.%s' % [save_path, get_save_extension()],
+ voxel_mesh.mesh)
+
+ voxel_mesh.free()
+ return error
diff --git a/addons/voxel-core/engine/importers/voxel_objects.gd b/addons/voxel-core/engine/importers/voxel_objects.gd
new file mode 100644
index 0000000..e1f2f48
--- /dev/null
+++ b/addons/voxel-core/engine/importers/voxel_objects.gd
@@ -0,0 +1,112 @@
+tool
+extends EditorImportPlugin
+# Import files as VoxelObjects
+
+
+## Enums
+enum Presets { DEFAULT }
+
+
+
+## Built-In Virtual Methods
+func get_visible_name() -> String:
+ return "VoxelObject"
+
+
+func get_importer_name() -> String:
+ return "VoxelCore.VoxelObject"
+
+
+func get_recognized_extensions() -> Array:
+ return [
+ "png", "jpg",
+ "vox",
+ #"qb",
+ #"qbt",
+ #"vxm",
+ ]
+
+
+func get_resource_type() -> String:
+ return "PackedScene"
+
+
+func get_save_extension() -> String:
+ return "tscn"
+
+
+func get_preset_count() -> int:
+ return Presets.size()
+
+
+func get_preset_name(preset : int) -> String:
+ match preset:
+ Presets.DEFAULT:
+ return "Default"
+ _:
+ return "Unknown"
+
+
+func get_import_options(preset : int) -> Array:
+ var preset_options = [
+ {
+ "name": "name",
+ "default_value": "",
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ {
+ "name": "voxel_object",
+ "default_value": 0,
+ "property_hint": PROPERTY_HINT_ENUM,
+ "hint_string": "DETECT,VOXEL_MESH",
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ {
+ "name": "mesh_mode",
+ "default_value": VoxelMesh.MeshModes.NAIVE,
+ "property_hint": PROPERTY_HINT_ENUM,
+ "hint_string": PoolStringArray(VoxelMesh.MeshModes.keys()).join(","),
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ {
+ "name": "center",
+ "default_value": 0,
+ "property_hint": PROPERTY_HINT_ENUM,
+ "hint_string": "NONE,CENTER,CENTER_ABOVE_AXIS",
+ "usage": PROPERTY_USAGE_EDITOR,
+ },
+ ]
+
+ return preset_options
+
+
+func get_option_visibility(option : String, options : Dictionary) -> bool:
+ return true
+
+
+func import(source_file : String, save_path : String, options : Dictionary, r_platform_variants : Array, r_gen_files : Array) -> int:
+ var voxel_object
+ match options.get("voxel_object", 0):
+ _: voxel_object = VoxelMesh.new()
+ var error = voxel_object.load_file(source_file)
+ if error == OK:
+ voxel_object.set_name(source_file.get_file().replace("." + source_file.get_extension(), "") if options["name"].empty() else options["name"])
+ voxel_object.set_mesh_mode(options.get("mesh_mode", VoxelMesh.MeshModes.NAIVE))
+
+ var center = options.get("center", 0)
+ if center > 0:
+ match center:
+ 1:
+ center = Vector3(0.5, 0.5, 0.5)
+ 2:
+ center = Vector3(0.5, 1.0, 0.5)
+ voxel_object.center(center)
+
+ voxel_object.update_mesh()
+
+ var scene = PackedScene.new()
+ error = scene.pack(voxel_object)
+ if error == OK:
+ error = ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], scene)
+ voxel_object.free()
+ return error
diff --git a/addons/voxel-core/engine/importers/voxel_sets.gd b/addons/voxel-core/engine/importers/voxel_sets.gd
new file mode 100644
index 0000000..fc560b4
--- /dev/null
+++ b/addons/voxel-core/engine/importers/voxel_sets.gd
@@ -0,0 +1,66 @@
+tool
+extends EditorImportPlugin
+
+
+
+## Enums
+enum Presets { DEFAULT }
+
+
+
+## Built-In Virtual Methods
+func get_visible_name() -> String:
+ return "VoxelSet"
+
+
+func get_importer_name() -> String:
+ return "VoxelCore.VoxelSet"
+
+
+func get_recognized_extensions() -> Array:
+ return [
+ "png", "jpg",
+ "vox",
+ "gpl",
+ ]
+
+
+func get_resource_type() -> String:
+ return "Resource"
+
+
+func get_save_extension() -> String:
+ return "tres"
+
+
+func get_preset_count() -> int:
+ return Presets.size()
+
+
+func get_preset_name(preset : int) -> String:
+ match preset:
+ Presets.DEFAULT:
+ return "Default"
+ _:
+ return "Unknown"
+
+
+func get_import_options(preset : int) -> Array:
+ var preset_options = [
+
+ ]
+
+ return preset_options
+
+
+func get_option_visibility(option : String, options : Dictionary) -> bool:
+ return true
+
+
+func import(source_file : String, save_path : String, options : Dictionary, r_platform_variants : Array, r_gen_files : Array) -> int:
+ var voxel_set := VoxelSet.new()
+ var error = voxel_set.load_file(source_file)
+ if error == OK:
+ voxel_set.request_refresh()
+ error = ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], voxel_set)
+ return error
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd
new file mode 100644
index 0000000..ec6973d
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd
@@ -0,0 +1,17 @@
+tool
+extends Reference
+
+
+
+## Public Variables
+var name := ""
+
+
+
+## Public Methods
+# Handles VoxelObjectEditor selection(s)
+# editor : VoxelObjectEditor : refrence to VoxelObjectEditor
+# event : InputEventMouse : event to be handled
+# prev_hit : Dictionary : previous raycast result
+func select(editor, event : InputEventMouse, prev_hit : Dictionary) -> bool:
+ return false
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/area.gd b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/area.gd
new file mode 100644
index 0000000..97044ad
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/area.gd
@@ -0,0 +1,39 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd"
+
+
+
+## Private Variables
+var _selection := []
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "area"
+
+
+
+## Public Methods
+func select(editor, event : InputEventMouse, prev_hit : Dictionary) -> bool:
+ editor.set_cursors_visibility(true)
+
+ if event is InputEventMouseButton and not editor.last_hit.empty():
+ if event.pressed:
+ _selection.clear()
+ _selection.append(editor.get_selection())
+ _selection.append(editor.get_selection())
+ else:
+ if not _selection.empty():
+ editor.work_tool()
+ _selection.clear()
+ elif event is InputEventMouseMotion:
+ if not (editor.last_hit.get("position") == prev_hit.get("position") and editor.last_hit.get("normal") == prev_hit.get("normal")):
+ if not editor.last_hit.empty():
+ if _selection.empty():
+ editor.set_cursors_selections([editor.get_selection()])
+ else:
+ _selection[1] = editor.get_selection()
+ editor.set_cursors_selections([_selection])
+
+ return true
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/extrude.gd b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/extrude.gd
new file mode 100644
index 0000000..70cef5f
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/extrude.gd
@@ -0,0 +1,69 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd"
+
+
+
+## Private Variables
+# Face area being selected
+var _face := []
+
+# Flag indicating if currently extruding
+var _extruding := false
+
+# How much extrusion is being applied currently
+var _extrude_amount := 1
+
+# Normal of extrusion
+var _extrude_normal := Vector3.ZERO
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "extrude"
+
+
+## Public Methods
+func select(editor, event : InputEventMouse, prev_hit : Dictionary) -> bool:
+ editor.set_cursors_visibility(true)
+
+ if event is InputEventMouseButton:
+ if event.is_pressed():
+ if not _face.empty():
+ _extruding = true
+ _extrude_normal = editor.last_hit["normal"]
+ else:
+ if _extruding:
+ editor.work_tool()
+ _face.clear()
+ _extrude_amount = 1
+ _extrude_normal = Vector3.ZERO
+ _extruding = false
+ editor.set_cursors_selections(_face)
+ elif event is InputEventMouseMotion:
+ if _extruding:
+ var extrude := []
+ _extrude_amount = clamp(_extrude_amount + clamp(event.relative.normalized().x, -1, 1), 1, 100)
+ var extrude_direction := 1 if editor.get_tool_normal() > 0 else -1
+ for e in range(_extrude_amount):
+ for position in _face:
+ extrude.append(position + _extrude_normal * extrude_direction * e)
+ editor.set_cursors_selections(extrude)
+ else:
+ if not editor.last_hit.empty():
+ if editor.voxel_object.get_voxel_id(editor.last_hit["position"]) > -1:
+ if editor.last_hit.empty() or not (editor.last_hit.get("position") == prev_hit.get("position") and editor.last_hit.get("normal") == prev_hit.get("normal")):
+ if Input.is_key_pressed(KEY_SHIFT):
+ _face = editor.voxel_object.select_face_similar(editor.last_hit["position"], editor.last_hit["normal"])
+ else:
+ _face = editor.voxel_object.select_face(editor.last_hit["position"], editor.last_hit["normal"])
+ if editor.get_tool_normal() > 0:
+ for i in range(_face.size()):
+ _face[i] += editor.last_hit["normal"]
+ else:
+ _face.clear()
+ else:
+ _face.clear()
+ editor.set_cursors_selections(_face)
+
+ return true
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/individual.gd b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/individual.gd
new file mode 100644
index 0000000..7b45702
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/individual.gd
@@ -0,0 +1,27 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selection.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "individual"
+
+
+
+## Public Methods
+func select(editor, event : InputEventMouse, prev_hit : Dictionary) -> bool:
+ editor.set_cursors_visibility(true)
+
+ if (event is InputEventMouseButton and not event.pressed) and not editor.last_hit.empty():
+ editor.work_tool()
+ elif Input.is_mouse_button_pressed(BUTTON_LEFT) and Input.is_key_pressed(KEY_SHIFT):
+ editor.work_tool()
+
+ if not (editor.last_hit.get("position") == prev_hit.get("position") and editor.last_hit.get("normal") == prev_hit.get("normal")):
+ if editor.last_hit.empty():
+ editor.set_cursors_selections([])
+ else:
+ editor.set_cursors_selections([editor.get_selection()])
+
+ return true
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd
new file mode 100644
index 0000000..48b8667
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd
@@ -0,0 +1,29 @@
+tool
+extends Reference
+
+
+
+## Public Variables
+# Name of this tool
+var name := ""
+
+# Offset applied to VoxelObjectEditor selection when using this tool
+var tool_normal := 0
+
+# Types of selection modes
+var selection_modes := PoolStringArray([
+ "individual",
+ "area",
+ "extrude",
+])
+
+# A 1 in each coordinate means all selection mirrors are applicable using this tool
+var mirror_modes := Vector3.ONE
+
+
+
+## Public Methods
+# Applies tool
+# editor : VoxelObjectEditor : refrence to VoxelObjectEditor
+func work(editor) -> void:
+ pass
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/add.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/add.gd
new file mode 100644
index 0000000..da7e832
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/add.gd
@@ -0,0 +1,49 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "add"
+ tool_normal = 1
+
+
+
+## Public Methods
+# Sets voxel id at given grid position in given VoxelObject and commits it to provided UndoRedo
+func add(voxel_object, position : Vector3, voxel : int, undo_redo : UndoRedo) -> void:
+ undo_redo.add_do_method(voxel_object, "set_voxel", position, voxel)
+ voxel = voxel_object.get_voxel_id(position)
+ if voxel == -1:
+ undo_redo.add_undo_method(voxel_object, "erase_voxel", position)
+ else:
+ undo_redo.add_undo_method(voxel_object, "set_voxel", position, voxel)
+
+
+func work(editor) -> void:
+ var voxel = editor.get_palette()
+ if voxel == -1:
+ return
+
+ editor.undo_redo.create_action("VoxelObjectEditor : Add voxel")
+ for cursor_selection in editor.get_selections():
+ for selection in cursor_selection:
+ match typeof(selection):
+ TYPE_VECTOR3:
+ add(editor.voxel_object, selection, voxel, editor.undo_redo)
+ TYPE_ARRAY:
+ var origin := Vector3(
+ selection[0 if selection[0].x < selection[1].x else 1].x,
+ selection[0 if selection[0].y < selection[1].y else 1].y,
+ selection[0 if selection[0].z < selection[1].z else 1].z)
+ var dimensions : Vector3 = (selection[0] - selection[1]).abs()
+ for x in range(origin.x, origin.x + dimensions.x + 1):
+ for y in range(origin.y, origin.y + dimensions.y + 1):
+ for z in range(origin.z, origin.z + dimensions.z + 1):
+ add(
+ editor.voxel_object,
+ Vector3(x, y, z), voxel, editor.undo_redo)
+ editor.undo_redo.add_do_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.add_undo_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.commit_action()
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/fill.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/fill.gd
new file mode 100644
index 0000000..9336e78
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/fill.gd
@@ -0,0 +1,42 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "fill"
+ selection_modes = PoolStringArray([
+ "individual"
+ ])
+
+
+
+## Public Methods
+# Replaces all matching connected voxels in VoxelObject starting at given position and commits it to provided UndoRedo
+func fill(voxel_object, position : Vector3, target : int, replacement : int, undo_redo : UndoRedo, filled := []) -> void:
+ var voxel = voxel_object.get_voxel_id(position)
+ if voxel > -1 and voxel == target and not filled.has(position):
+ filled.append(position)
+ undo_redo.add_do_method(voxel_object, 'set_voxel', position, replacement)
+ undo_redo.add_undo_method(voxel_object, 'set_voxel', position, target)
+ for face in Voxel.Faces:
+ fill(
+ voxel_object, position + face, target, replacement,
+ undo_redo, filled)
+
+
+func work(editor) -> void:
+ editor.undo_redo.create_action("VoxelObjectEditor : Fill voxel(s)")
+ for selection in editor.get_selections():
+ for position in selection:
+ var target = editor.voxel_object.get_voxel_id(position)
+ if target == -1:
+ continue
+
+ fill(
+ editor.voxel_object, position, target, editor.get_palette(),
+ editor.undo_redo)
+ editor.undo_redo.add_do_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.add_undo_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.commit_action()
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/pick.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/pick.gd
new file mode 100644
index 0000000..34f8732
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/pick.gd
@@ -0,0 +1,21 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "pick"
+ selection_modes = PoolStringArray([
+ "individual"
+ ])
+ mirror_modes = Vector3.ZERO
+
+
+
+## Public Methods
+func work(editor) -> void:
+ var voxel = editor.voxel_object.get_voxel_id(editor.get_selection())
+ if voxel > -1:
+ editor.VoxelSetViewer.unselect_all()
+ editor.VoxelSetViewer.select(voxel)
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/sub.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/sub.gd
new file mode 100644
index 0000000..d778583
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/sub.gd
@@ -0,0 +1,42 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "sub"
+
+
+
+## Public Methods
+# Removes voxel id at given grid position in given VoxelObject and commits it to provided UndoRedo
+func sub(voxel_object, position : Vector3, undo_redo : UndoRedo) -> void:
+ var voxel = voxel_object.get_voxel_id(position)
+ if voxel > -1:
+ undo_redo.add_do_method(voxel_object, 'erase_voxel', position)
+ undo_redo.add_undo_method(voxel_object, 'set_voxel', position, voxel)
+
+
+func work(editor) -> void:
+ editor.undo_redo.create_action("VoxelObjectEditor : Sub voxel")
+ for cursor_selection in editor.get_selections():
+ for selection in cursor_selection:
+ match typeof(selection):
+ TYPE_VECTOR3:
+ sub(editor.voxel_object, selection, editor.undo_redo)
+ TYPE_ARRAY:
+ var origin := Vector3(
+ selection[0 if selection[0].x < selection[1].x else 1].x,
+ selection[0 if selection[0].y < selection[1].y else 1].y,
+ selection[0 if selection[0].z < selection[1].z else 1].z)
+ var dimensions : Vector3 = (selection[0] - selection[1]).abs()
+ for x in range(origin.x, origin.x + dimensions.x + 1):
+ for y in range(origin.y, origin.y + dimensions.y + 1):
+ for z in range(origin.z, origin.z + dimensions.z + 1):
+ sub(
+ editor.voxel_object, Vector3(x, y, z),
+ editor.undo_redo)
+ editor.undo_redo.add_do_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.add_undo_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.commit_action()
diff --git a/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/swap.gd b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/swap.gd
new file mode 100644
index 0000000..5b7b92f
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/swap.gd
@@ -0,0 +1,45 @@
+tool
+extends "res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tool.gd"
+
+
+
+## Built-In Virtual Methods
+func _init():
+ name = "swap"
+
+
+## Public Methods
+# Swaps voxel at given grid position in given VoxelObject with given id and commits it to provided UndoRedo
+func swap(voxel_object, position : Vector3, voxel, undo_redo : UndoRedo) -> void:
+ var prev_voxel = voxel_object.get_voxel_id(position)
+ if prev_voxel > -1:
+ undo_redo.add_do_method(voxel_object, 'set_voxel', position, voxel)
+ undo_redo.add_undo_method(voxel_object, 'set_voxel', position, prev_voxel)
+
+
+func work(editor) -> void:
+ var voxel = editor.get_palette()
+ if voxel == -1:
+ return
+
+ editor.undo_redo.create_action("VoxelObjectEditor : Swap voxel")
+ for cursor_selection in editor.get_selections():
+ for selection in cursor_selection:
+ match typeof(selection):
+ TYPE_VECTOR3:
+ swap(editor.voxel_object, selection, voxel, editor.undo_redo)
+ TYPE_ARRAY:
+ var origin := Vector3(
+ selection[0 if selection[0].x < selection[1].x else 1].x,
+ selection[0 if selection[0].y < selection[1].y else 1].y,
+ selection[0 if selection[0].z < selection[1].z else 1].z)
+ var dimensions : Vector3 = (selection[0] - selection[1]).abs()
+ for x in range(origin.x, origin.x + dimensions.x + 1):
+ for y in range(origin.y, origin.y + dimensions.y + 1):
+ for z in range(origin.z, origin.z + dimensions.z + 1):
+ swap(
+ editor.voxel_object, Vector3(x, y, z),
+ voxel, editor.undo_redo)
+ editor.undo_redo.add_do_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.add_undo_method(editor.voxel_object, "update_mesh")
+ editor.undo_redo.commit_action()
diff --git a/addons/voxel-core/engine/voxel_object_editor/voxel_cursor/voxel_cursor.gd b/addons/voxel-core/engine/voxel_object_editor/voxel_cursor/voxel_cursor.gd
new file mode 100644
index 0000000..178a976
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/voxel_cursor/voxel_cursor.gd
@@ -0,0 +1,112 @@
+tool
+extends MeshInstance
+# Mesh used to highlight voxel grid selections
+
+
+
+## Exported Variables
+# Highlight color
+export var color := Color(1, 1, 1, 0.75) setget set_color
+
+
+
+## Public Variables
+# Grid positions and areas to highlight
+# A Vector3 highlights a single grid position
+# [Vector3, Vector3] highlights the area between two grid positions
+# Many type of selections can be mixed and selected at a time
+var selections := [] setget set_selections
+
+
+
+## Private Variables
+# VoxelTool used to construct mesh
+var _voxel_tool := VoxelTool.new()
+
+
+
+## Built-In Virtual Methods
+func _init():
+ setup()
+func _ready() -> void:
+ setup()
+
+
+
+## Public Methods
+func set_color(value : Color) -> void:
+ color = value
+ if is_instance_valid(material_override):
+ material_override.albedo_color = color
+
+
+func set_selections(value : Array, update := true) -> void:
+ selections = value
+
+ if update:
+ self.update()
+
+
+# Setup the material if not already done
+func setup() -> void:
+ if not is_instance_valid(material_override):
+ material_override = SpatialMaterial.new()
+ material_override.flags_transparent = true
+ material_override.flags_unshaded = true
+ material_override.params_grow = true
+ material_override.params_grow_amount = 0.001
+ material_override.albedo_color = color
+ update()
+
+
+# Update highlighted position(s) / area(s)
+func update() -> void:
+ if not selections.empty():
+ _voxel_tool.begin()
+ var voxel := Voxel.colored(color)
+ for selection in selections:
+ match typeof(selection):
+ TYPE_VECTOR3:
+ for face in Voxel.Faces:
+ if not selections.has(selection + face):
+ _voxel_tool.add_face(voxel, face, selection)
+ TYPE_ARRAY:
+ var origin := Vector3(
+ selection[0 if selection[0].x < selection[1].x else 1].x,
+ selection[0 if selection[0].y < selection[1].y else 1].y,
+ selection[0 if selection[0].z < selection[1].z else 1].z)
+ var dimensions : Vector3 = (selection[0] - selection[1]).abs()
+
+ _voxel_tool.add_face(voxel, Vector3.RIGHT,
+ Vector3(origin.x + dimensions.x, origin.y, origin.z + dimensions.z),
+ Vector3(origin.x + dimensions.x, origin.y, origin.z),
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z + dimensions.z),
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z))
+ _voxel_tool.add_face(voxel, Vector3.LEFT,
+ Vector3(origin.x, origin.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y, origin.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z))
+ _voxel_tool.add_face(voxel, Vector3.UP,
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z),
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z + dimensions.z))
+ _voxel_tool.add_face(voxel, Vector3.DOWN,
+ Vector3(origin.x + dimensions.x, origin.y, origin.z),
+ Vector3(origin.x, origin.y, origin.z),
+ Vector3(origin.x + dimensions.x, origin.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y, origin.z + dimensions.z))
+ _voxel_tool.add_face(voxel, Vector3.BACK,
+ Vector3(origin.x + dimensions.x, origin.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y, origin.z + dimensions.z),
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z + dimensions.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z + dimensions.z))
+ _voxel_tool.add_face(voxel, Vector3.FORWARD,
+ Vector3(origin.x + dimensions.x, origin.y, origin.z),
+ Vector3(origin.x, origin.y, origin.z),
+ Vector3(origin.x + dimensions.x, origin.y + dimensions.y, origin.z),
+ Vector3(origin.x, origin.y + dimensions.y, origin.z))
+ mesh = _voxel_tool.commit()
+ else:
+ mesh = null
diff --git a/addons/voxel-core/engine/voxel_object_editor/voxel_grid/voxel_grid.gd b/addons/voxel-core/engine/voxel_object_editor/voxel_grid/voxel_grid.gd
new file mode 100644
index 0000000..5ed975e
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/voxel_grid/voxel_grid.gd
@@ -0,0 +1,113 @@
+tool
+extends MeshInstance
+# Grid Mesh used by VoxelObjectEditor
+
+
+
+## Enums
+enum GridModes { SOLID, WIRED }
+
+
+
+## Exported Variables
+export var disabled := false setget set_disabled
+
+export(Color, RGB) var color := Color.white setget set_modulate
+
+export(GridModes) var grid_mode := GridModes.WIRED setget set_grid_mode
+
+export var grid_size := Vector3(16, 16, 16) setget set_grid_size
+
+
+
+## Private Variables
+var _surface_tool := SurfaceTool.new()
+
+
+
+## Built-In Virtual Methods
+func _init():
+ setup()
+
+
+func _ready() -> void:
+ setup()
+
+
+
+## Public Methods
+func set_disabled(value : bool) -> void:
+ disabled = value
+ visible = not disabled
+ find_node("CollisionShape", true, false).disabled = disabled
+
+
+func set_modulate(value : Color) -> void:
+ color = value
+
+ material_override.albedo_color = color
+
+
+func set_grid_mode(value : int, update := true) -> void:
+ grid_mode = value
+
+ if update:
+ self.update()
+
+
+func set_grid_size(value : Vector3, update := true) -> void:
+ grid_size = Vector3(
+ clamp(value.x, 1, 100),
+ clamp(value.y, 1, 100),
+ clamp(value.z, 1, 100))
+
+ if update:
+ self.update()
+
+
+func setup() -> void:
+ if not is_instance_valid(material_override):
+ material_override = SpatialMaterial.new()
+ material_override.albedo_color = color
+ material_override.set_cull_mode(2)
+ update()
+
+
+func update() -> void:
+ match grid_mode:
+ GridModes.SOLID:
+ mesh = PlaneMesh.new()
+ scale = Vector3(
+ grid_size.x * Voxel.VoxelWorldSize,
+ 1,
+ grid_size.z * Voxel.VoxelWorldSize)
+ GridModes.WIRED:
+ scale = Vector3.ONE
+ _surface_tool.begin(Mesh.PRIMITIVE_LINES)
+
+ var x : int = -grid_size.x
+ while x <= grid_size.x:
+ _surface_tool.add_normal(Vector3.UP)
+ _surface_tool.add_vertex(
+ Voxel.grid_to_snapped(Vector3(x, 0, -abs(grid_size.z))))
+ _surface_tool.add_vertex(
+ Voxel.grid_to_snapped(Vector3(x, 0, abs(grid_size.z))))
+
+ x += 1
+
+ var z : int = -grid_size.z
+ while z <= grid_size.z:
+ _surface_tool.add_normal(Vector3.UP)
+ _surface_tool.add_vertex(
+ Voxel.grid_to_snapped(Vector3(-abs(grid_size.x), 0, z)))
+ _surface_tool.add_vertex(
+ Voxel.grid_to_snapped(Vector3(abs(grid_size.x), 0, z)))
+
+ z += 1
+
+ mesh = _surface_tool.commit()
+ for child in get_children():
+ remove_child(child)
+ child.queue_free()
+ create_convex_collision()
+ set_disabled(disabled)
diff --git a/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.gd b/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.gd
new file mode 100644
index 0000000..a2a03b4
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.gd
@@ -0,0 +1,866 @@
+tool
+extends Control
+
+
+
+## Signals
+# Emited when editing state changed
+signal editing(state)
+
+# Emited when editor needs to be closed
+signal close
+
+
+
+## Enums
+# The possible palettes
+enum Palettes { PRIMARY, SECONDARY }
+
+
+
+## Constants
+const VoxelGrid := preload("res://addons/voxel-core/engine/voxel_object_editor/voxel_grid/voxel_grid.gd")
+
+const VoxelCursor := preload("res://addons/voxel-core/engine/voxel_object_editor/voxel_cursor/voxel_cursor.gd")
+
+const VoxelObject := preload("res://addons/voxel-core/classes/voxel_object.gd")
+
+# Default editor config
+const ConfigDefault := {
+ "cursor.visible": true,
+ "cursor.dynamic": true,
+ "cursor.voxel_raycasting": false,
+ "cursor.color": Color.white,
+ "grid.visible": true,
+ "grid.mode": VoxelGrid.GridModes.WIRED,
+ "grid.color": Color.white,
+ "grid.constant": true,
+ "grid.size": Vector2(16, 16),
+}
+
+
+
+## Public Variables
+# UndoRedo used to commit operations
+var undo_redo : UndoRedo
+
+# Last registered raycast hit
+var last_hit := {}
+
+# Editor's current config
+var config := {}
+
+# Reference to VoxelObject being edited
+var voxel_object : VoxelObject setget start_editing
+
+
+
+## Private Variables
+# Refrence to editor's grid
+var _grid := VoxelGrid.new()
+
+# Colletions of loaded editor selection modes
+var _selection_modes := [
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/individual.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/area.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_selection/editor_selections/extrude.gd").new(),
+]
+
+# Refrence to editor's voxel cursors
+var _cursors := {
+ Vector3(0, 0, 0): VoxelCursor.new(),
+ Vector3(1, 0, 0): VoxelCursor.new(),
+ Vector3(1, 1, 0): VoxelCursor.new(),
+ Vector3(1, 1, 1): VoxelCursor.new(),
+ Vector3(0, 1, 1): VoxelCursor.new(),
+ Vector3(0, 1, 0): VoxelCursor.new(),
+ Vector3(0, 0, 1): VoxelCursor.new(),
+ Vector3(1, 0, 1): VoxelCursor.new(),
+}
+
+# Collection of loaded editor tools
+var _tools := [
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/add.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/sub.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/swap.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/fill.gd").new(),
+ preload("res://addons/voxel-core/engine/voxel_object_editor/editor_tool/editor_tools/pick.gd").new(),
+]
+
+# Voxel id of each palette
+var _palette := [ -1, -1 ]
+
+# Path to file going to imported
+var import_file_path := ""
+
+
+
+## OnReady Variables
+onready var Editing := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer/Editing")
+
+onready var Options := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options")
+
+onready var Tool := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/Tool")
+
+onready var Palette := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/Palette")
+
+onready var SelectionMode := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/SelectionMode")
+
+onready var MirrorX := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorX")
+
+onready var MirrorY := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorY")
+
+onready var MirrorZ := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorZ")
+
+onready var ColorChooser := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/ColorChooser")
+
+onready var ColorPicked := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/ColorChooser/ColorPicked")
+
+onready var VoxelSetViewer := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VoxelSetViewer")
+
+onready var Notice := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer/Notice")
+
+onready var MoveX := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move/X")
+
+onready var MoveY := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move/Y")
+
+onready var MoveZ := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move/Z")
+
+onready var CenterX := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center/X")
+
+onready var CenterY := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center/Y")
+
+onready var CenterZ := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center/Z")
+
+onready var ImportMenu := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportFile")
+
+onready var ImportHow := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow")
+
+onready var Settings := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings")
+
+onready var CursorVisible := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/CursorVisible")
+
+onready var CursorDynamic := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/CursorDynamic")
+
+onready var VoxelRaycasting := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/VoxelRaycasting")
+
+onready var CursorColor := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/HBoxContainer/CursorColor")
+
+onready var GridVisible := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridVisible")
+
+onready var GridConstant := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridConstant")
+
+onready var GridMode := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridMode")
+
+onready var GridColor := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/HBoxContainer2/GridColor")
+
+onready var GridSizeX := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size/X")
+
+onready var GridSizeZ := get_node("VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size/Z")
+
+onready var ColorMenu := get_node("ColorMenu")
+
+onready var ColorMenuColor := get_node("ColorMenu/VBoxContainer/Color")
+
+onready var ColorMenuAdd := get_node("ColorMenu/VBoxContainer/HBoxContainer/Add")
+
+
+
+## Built-In Virtual Methods
+func _ready():
+ if not is_instance_valid(undo_redo):
+ undo_redo = UndoRedo.new()
+
+ update_tools()
+ update_palette()
+ update_selections()
+ update_mirrors()
+ update_settings()
+ update_grid_mode()
+
+ load_config()
+
+
+func _exit_tree():
+ if is_instance_valid(_grid):
+ _grid.queue_free()
+ for cursor in _cursors:
+ if is_instance_valid(_cursors[cursor]):
+ _cursors[cursor].queue_free()
+
+
+
+## Public Methods
+func is_editing() -> bool:
+ return Editing.pressed
+
+
+# Updates the drop down ui menu in editor with all loaded tools
+func update_tools(tools := _tools) -> void:
+ Tool.clear()
+ for tool_index in range(tools.size()):
+ Tool.add_icon_item(
+ load("res://addons/voxel-core/assets/controls/" + tools[tool_index].name.to_lower() + ".png"),
+ tools[tool_index].name.capitalize(),
+ tool_index)
+
+
+# Updates the drop down ui menu in editor with palettes
+func update_palette(palettes := Palettes.keys()) -> void:
+ Palette.clear()
+ for palette in palettes:
+ Palette.add_icon_item(
+ load("res://addons/voxel-core/assets/controls/" + palette.to_lower() + ".png"),
+ palette.capitalize(),
+ Palettes[palette.to_upper()])
+
+
+# Updates the drop down ui menu in editor with all loaded selection modes
+func update_selections(selection_modes := [
+ "individual",
+ "area",
+ "extrude",
+ ]) -> void:
+ var prev = SelectionMode.get_selected_id()
+ SelectionMode.clear()
+ for select_mode in range(_selection_modes.size()):
+ if selection_modes.find(_selection_modes[select_mode].name.to_lower()) > -1:
+ SelectionMode.add_icon_item(
+ load("res://addons/voxel-core/assets/controls/" + _selection_modes[select_mode].name.to_lower() + ".png"),
+ _selection_modes[select_mode].name.capitalize(), select_mode)
+ if SelectionMode.get_item_index(prev) > -1:
+ SelectionMode.select(SelectionMode.get_item_index(prev))
+ else:
+ _on_SelectionMode_selected(SelectionMode.get_selected_id())
+
+
+# Disables and enables possible selection mirror(s) possible with current tool
+func update_mirrors(mirror := Vector3.ONE) -> void:
+ MirrorX.visible = mirror.x == 1
+ MirrorX.pressed = MirrorX.pressed if mirror.x == 1 else false
+ MirrorY.visible = mirror.y == 1
+ MirrorY.pressed = MirrorY.pressed if mirror.y == 1 else false
+ MirrorZ.visible = mirror.z == 1
+ MirrorZ.pressed = MirrorZ.pressed if mirror.z == 1 else false
+ set_cursors_visibility()
+
+
+# Updates the setting tabs
+func update_settings() -> void:
+ for tab in range(Settings.get_tab_count()):
+ var name : String = Settings.get_tab_title(tab)
+ Settings.set_tab_icon(tab,
+ load("res://addons/voxel-core/assets/controls/" + name.to_lower() + ".png"))
+
+
+# Sets the cursor visibility
+func set_cursor_visible(visible : bool) -> void:
+ config["cursor.visible"] = visible
+ CursorVisible.pressed = visible
+ if not visible:
+ set_cursors_visibility(visible)
+ save_config()
+
+
+# Sets the cursor's dynamic option
+# Enabling would change cursor color based on selected palette
+func set_cursor_dynamic(dynamic : bool) -> void:
+ config["cursor.dynamic"] = dynamic
+ CursorDynamic.pressed = dynamic
+ CursorColor.disabled = dynamic
+ if dynamic:
+ var color = ColorPicked.color
+ color.a = 0.75
+ set_cursor_color(color)
+ else:
+ set_cursor_color(config["cursor.color"])
+ save_config()
+
+
+# Sets the cursor color
+func set_cursor_color(color : Color) -> void:
+ config["cursor.color"] = color
+ CursorColor.color = color
+ for cursor in _cursors.values():
+ cursor.color = color
+ save_config()
+
+
+# Returns true if grid should be visible
+func is_grid_disabled() -> bool:
+ return not Editing.pressed or (Editing.pressed and not GridVisible.pressed or (GridVisible.pressed and (not GridConstant.pressed and (is_instance_valid(voxel_object) and not voxel_object.empty()))))
+
+
+# Sets the grid visibility
+func set_grid_visible(visible : bool) -> void:
+ config["grid.visible"] = visible
+ GridVisible.pressed = visible
+ _grid.disabled = is_grid_disabled()
+ save_config()
+
+
+# Sets the grid constant option
+# Disabling would mean grid would not be visible once voxels are present
+func set_grid_constant(constant : bool) -> void:
+ config["grid.constant"] = constant
+ GridConstant.pressed = constant
+ _grid.disabled = is_grid_disabled()
+ save_config()
+
+
+# Updates the grid ui options
+func update_grid_mode() -> void:
+ GridMode.clear()
+ for mode_index in range(VoxelGrid.GridModes.size()):
+ GridMode.add_icon_item(
+ load("res://addons/voxel-core/assets/controls/" + VoxelGrid.GridModes.keys()[mode_index].to_lower() + ".png"),
+ VoxelGrid.GridModes.keys()[mode_index].capitalize(), mode_index)
+
+
+# Sets the grid mode
+func set_grid_mode(mode : int) -> void:
+ config["grid.mode"] = mode
+ GridMode.selected = mode
+ _grid.grid_mode = mode
+ save_config()
+
+
+# Sets the grid color
+func set_grid_color(color : Color) -> void:
+ config["grid.color"] = color
+ GridColor.color = color
+ _grid.color = color
+ save_config()
+
+
+# Sets the grid size
+func set_grid_size(size : Vector2) -> void:
+ config["grid.size"] = size
+ GridSizeX.value = size.x
+ GridSizeZ.value = size.y
+ _grid.grid_size = Vector3(size.x, 0, size.y)
+ save_config()
+
+
+# Gets the user selected mirror(s)
+func get_mirrors() -> Array:
+ var mirrors := []
+
+ if MirrorX.pressed:
+ mirrors.append(Vector3(1, 0, 0))
+ if MirrorZ.pressed:
+ mirrors.append(Vector3(1, 0, 1))
+ if MirrorY.pressed:
+ mirrors.append(Vector3(0, 1, 0))
+ if MirrorX.pressed:
+ mirrors.append(Vector3(1, 1, 0))
+ if MirrorZ.pressed:
+ mirrors.append(Vector3(0, 1, 1))
+ if MirrorX.pressed && MirrorZ.pressed:
+ mirrors.append(Vector3(1, 1, 1))
+ if MirrorZ.pressed:
+ mirrors.append(Vector3(0, 0, 1))
+
+ return mirrors
+
+
+# Gets the user's current voxel grid selection
+func get_selection() -> Vector3:
+ return Vector3.INF if last_hit.empty() else (last_hit["position"] + last_hit["normal"] * _tools[Tool.get_selected_id()].tool_normal)
+
+
+# Gets each of the user's voxel grid selection with mirror applied
+func get_selections() -> Array:
+ var selections := [_cursors[Vector3.ZERO].selections]
+ for mirror in get_mirrors():
+ selections.append(_cursors[mirror].selections)
+ return selections
+
+
+# Sets the cursor's visiblity
+func set_cursors_visibility(visible := Editing.pressed) -> void:
+ _cursors[Vector3.ZERO].visible = visible and CursorVisible.pressed
+ var mirrors := get_mirrors()
+ for cursor in _cursors:
+ if not cursor == Vector3.ZERO:
+ _cursors[cursor].visible = _cursors[Vector3.ZERO].visible and mirrors.has(cursor)
+
+
+# Sets the cursors selection
+func set_cursors_selections(
+ selections := [last_hit["position"] + last_hit["normal"] * _tools[Tool.get_selected_id()].tool_normal] if not last_hit.empty() else []
+ ) -> void:
+ _cursors[Vector3.ZERO].selections = selections
+ var mirrors := get_mirrors()
+ for mirror in mirrors:
+ _cursors[mirror].selections = mirror_positions(selections, mirror)
+
+
+# Updats the cursor visuals
+func update_cursors() -> void:
+ var mirrors := get_mirrors()
+ for cursor in _cursors:
+ if not cursor == Vector3.ZERO:
+ _cursors[cursor].visible = _cursors[Vector3.ZERO].visible and mirrors.has(cursor)
+ if mirrors.has(cursor):
+ _cursors[cursor].selections = mirror_positions(
+ _cursors[Vector3.ZERO].selections,
+ cursor
+ )
+
+
+# Sets the palette
+func set_palette(palette : int, voxel_id : int) -> void:
+ _palette[palette] = voxel_id
+ if palette == Palette.get_selected_id():
+ ColorPicked.color = Voxel.get_color(get_rpalette())
+ if CursorDynamic.pressed:
+ var color = ColorPicked.color
+ color.a = 0.5
+ set_cursor_color(color)
+ if not VoxelSetViewer.has_selected(voxel_id):
+ VoxelSetViewer.select(voxel_id)
+
+
+# Returns the voxel id of palette
+func get_palette(palette : int = Palette.get_selected_id()) -> int:
+ return _palette[palette]
+
+
+# Returns the voxel dictionary of palette
+func get_rpalette(palette : int = get_palette()) -> Dictionary:
+ return voxel_object.voxel_set.get_voxel(palette)
+
+
+# Setter for voxel raycasting, saves to config
+func set_voxel_raycasting(value : bool) -> void:
+ VoxelRaycasting.pressed = value
+ config["cursor.voxel_raycasting"] = value
+ _update_editing_hint()
+ save_config()
+
+
+# Saves the current editor config to file
+func save_config() -> void:
+ var file := File.new()
+ var opened = file.open(
+ "res://addons/voxel-core/engine/voxel_object_editor/config.var",
+ File.WRITE)
+ if opened == OK:
+ file.store_var(config)
+ if file.is_open():
+ file.close()
+
+
+# Loads and sets the config file
+func load_config() -> void:
+ var loaded := false
+ var config_file := File.new()
+ var opened = config_file.open(
+ "res://addons/voxel-core/engine/voxel_object_editor/config.var",
+ File.READ)
+ if opened == OK and not config_file.eof_reached():
+ var config_file_data = config_file.get_var()
+ if typeof(config_file_data) == TYPE_DICTIONARY:
+ config = config_file_data
+ loaded = true
+
+ if not loaded:
+ config = ConfigDefault.duplicate()
+
+ if config_file.is_open():
+ config_file.close()
+
+ if config.has("cursor.visible"):
+ set_cursor_visible(config["cursor.visible"])
+ if config.has("cursor.dynamic"):
+ set_cursor_dynamic(config["cursor.dynamic"])
+
+ if config.has("cursor.voxel_raycasting"):
+ set_voxel_raycasting(config["cursor.voxel_raycasting"])
+
+ if config.has("cursor.color"):
+ set_cursor_color(config["cursor.color"])
+
+ if config.has("grid.visible"):
+ set_grid_visible(config["grid.visible"])
+ if config.has("grid.mode"):
+ set_grid_mode(config["grid.mode"])
+ if config.has("grid.color"):
+ set_grid_color(config["grid.color"])
+ if config.has("grid.constant"):
+ set_grid_constant(config["grid.constant"])
+ if config.has("grid.size"):
+ set_grid_size(config["grid.size"])
+
+
+# Sets the current editor config to default
+func reset_config() -> void:
+ config = ConfigDefault.duplicate()
+ save_config()
+ load_config()
+
+
+# Attempts to raycast for the VoxelObject
+func raycast_for(camera : Camera, screen_position : Vector2, target : Node) -> Dictionary:
+ var hit := {}
+ var from := camera.project_ray_origin(screen_position)
+ var direction := camera.project_ray_normal(screen_position)
+
+ if VoxelRaycasting.pressed:
+ hit = voxel_object.intersect_ray(from, direction, 64, funcref(self, "_raycast_stop"))
+ else:
+ var exclude := []
+ var to := from + direction * 1000
+ while true:
+ hit = camera.get_world().direct_space_state.intersect_ray(from, to, exclude)
+ if not hit.empty():
+ if target.is_a_parent_of(hit.collider):
+ if _grid.is_a_parent_of(hit.collider):
+ hit["normal"] = Vector3.ZERO
+ hit["position"] = Voxel.world_to_grid(
+ voxel_object.to_local(
+ hit.position + -hit.normal * (Voxel.VoxelWorldSize / 2)))
+ hit["normal"] = hit["normal"].round()
+ break
+ else:
+ exclude.append(hit.collider)
+ else:
+ break
+
+ return hit
+
+
+# Returns given grid position mirrored in accordance to mirror
+func mirror_position(position : Vector3, mirror : Vector3) -> Vector3:
+ match mirror:
+ Vector3(1, 0, 0):
+ return Vector3(position.x, position.y, (position.z + 1) * -1)
+ Vector3(1, 0, 1):
+ return Vector3((position.x + 1) * -1, position.y, (position.z + 1) * -1)
+ Vector3(0, 1, 0):
+ return Vector3(position.x, (position.y + 1) * -1, position.z)
+ Vector3(1, 1, 0):
+ return Vector3(position.x, (position.y + 1) * -1, (position.z + 1) * -1)
+ Vector3(0, 1, 1):
+ return Vector3((position.x + 1) * -1, (position.y + 1) * -1, position.z)
+ Vector3(1, 1, 1):
+ return Vector3((position.x + 1) * -1, (position.y + 1) * -1, (position.z + 1) * -1)
+ Vector3(0, 0, 1):
+ return Vector3((position.x + 1) * -1, position.y, position.z)
+ return position
+
+
+# Mirrors and returns given grid positions in accordance to mirror
+func mirror_positions(positions : Array, mirror : Vector3) -> Array:
+ var mirrored := []
+ for position in positions:
+ match typeof(position):
+ TYPE_VECTOR3:
+ mirrored.append(mirror_position(position, mirror))
+ TYPE_ARRAY:
+ var mirroring := []
+ for index in range(position.size()):
+ mirroring.append(mirror_position(position[index], mirror))
+ mirrored.append(mirroring)
+ return mirrored
+
+
+# Disconnects the previous VoxelSet and connects the given VoxelSet
+func setup_voxel_set(voxel_set : VoxelSet) -> void:
+ VoxelSetViewer.voxel_set = voxel_set
+ VoxelSetViewer.unselect_all()
+ if is_instance_valid(voxel_set) and not voxel_set.empty():
+ var first_voxel_id = voxel_set.get_ids()[0]
+ _palette.clear()
+ _palette.append(first_voxel_id)
+ _palette.append(first_voxel_id)
+ VoxelSetViewer.select(first_voxel_id)
+
+ Editing.pressed = false
+ Editing.disabled = not is_instance_valid(voxel_set)
+ Options.visible = is_instance_valid(voxel_set)
+ Notice.visible = not is_instance_valid(voxel_set)
+
+
+# Attach editor components to current voxelobject
+func attach_editor_components() -> void:
+ detach_editor_components()
+ voxel_object.add_child(_grid)
+ for cursor in _cursors.values():
+ voxel_object.add_child(cursor)
+
+
+# Detach editor components from their parents
+func detach_editor_components() -> void:
+ if is_instance_valid(_grid.get_parent()):
+ _grid.get_parent().remove_child(_grid)
+ for cursor in _cursors:
+ cursor = _cursors[cursor]
+ if is_instance_valid(cursor.get_parent()):
+ cursor.get_parent().remove_child(cursor)
+
+
+# Disconnect previous edited VoxelObject and starts editing the new one
+func start_editing(new_voxel_object : VoxelObject) -> void:
+ if new_voxel_object == voxel_object:
+ return
+
+ stop_editing()
+
+ voxel_object = new_voxel_object
+
+ setup_voxel_set(voxel_object.voxel_set)
+ voxel_object.connect("set_voxel_set", self, "setup_voxel_set")
+ voxel_object.connect("tree_exiting", self, "stop_editing", [true])
+
+
+# Disconnect currently edited VoxelObject
+func stop_editing(close := false) -> void:
+ if is_instance_valid(voxel_object):
+ voxel_object.edit_hint = 0
+
+ detach_editor_components()
+
+ voxel_object.disconnect("set_voxel_set", self, "setup_voxel_set")
+ voxel_object.disconnect("tree_exiting", self, "stop_editing")
+
+ Editing.pressed = false
+ voxel_object = null
+
+ if close:
+ emit_signal("close")
+
+
+func get_tool_normal() -> int:
+ return _tools[Tool.get_selected_id()].tool_normal
+
+# Applies current tool
+func work_tool() -> void:
+ _tools[Tool.get_selected_id()].work(self)
+
+
+# Handles editor input
+func handle_input(camera : Camera, event : InputEvent) -> bool:
+ if is_instance_valid(voxel_object):
+ if event is InputEventMouse:
+ var prev_hit = last_hit
+ last_hit = raycast_for(camera, event.position, voxel_object)
+ if Editing.pressed:
+ if event.button_mask & ~BUTTON_MASK_LEFT > 0 or (event is InputEventMouseButton and not event.button_index == BUTTON_LEFT):
+ set_cursors_visibility(false)
+ return false
+
+ var handle_result = _selection_modes[SelectionMode.get_selected_id()].select(
+ self,
+ event,
+ prev_hit)
+
+ if not GridConstant.pressed:
+ _grid.disabled = is_grid_disabled()
+
+ return handle_result
+ return false
+
+
+# Shows color menu centered
+func show_color_menu(color := ColorPicked.color) -> void:
+ ColorMenuColor.color = color
+ ColorMenu.popup_centered()
+
+
+# Hide color menu
+func hide_color_menu() -> void:
+ ColorMenu.hide()
+
+
+# Shows import menu centered
+func show_import_menu() -> void:
+ ImportMenu.popup_centered()
+
+
+# Hides import menu
+func hide_import_menu() -> void:
+ ImportMenu.hide()
+
+
+
+## Private Methods
+func _raycast_stop(hit : Dictionary) -> bool:
+ if not is_grid_disabled() and (hit["position"].y == -1 or hit["position"].y == 0):
+ hit["normal"] = Vector3.ZERO
+ return true
+ return false
+
+
+func _update_editing_hint() -> void:
+ if is_instance_valid(voxel_object):
+ var flag = 0
+ if Editing.pressed:
+ if VoxelRaycasting.pressed:
+ flag = 1
+ else:
+ flag = 2
+ voxel_object.edit_hint = flag
+
+
+func _on_Editing_toggled(editing : bool):
+ _update_editing_hint()
+
+ _grid.disabled = is_grid_disabled()
+ set_cursors_visibility(editing)
+ if editing:
+ attach_editor_components()
+ set_cursors_selections()
+
+ emit_signal("editing", editing)
+
+
+func _on_ColorMenu_Add_pressed():
+ var voxel_id = voxel_object.voxel_set.size()
+ undo_redo.create_action("VoxelObjectEditor : Add voxel to used VoxeSet")
+ undo_redo.add_do_method(voxel_object.voxel_set, "add_voxel", Voxel.colored(ColorMenuColor.color))
+ undo_redo.add_undo_method(voxel_object.voxel_set, "erase_voxel", voxel_id)
+ undo_redo.add_do_method(voxel_object.voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_object.voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ VoxelSetViewer.select(voxel_id)
+ hide_color_menu()
+
+
+func _on_Tool_selected(id : int):
+ update_mirrors(_tools[id].mirror_modes)
+ update_selections(_tools[id].selection_modes)
+
+
+func _on_Palette_selected(id : int) -> void:
+ set_palette(Palette.get_selected_id(), _palette[Palette.get_selected_id()])
+
+
+func _on_SelectionMode_selected(id : int):
+ set_cursors_selections()
+
+
+func _on_VoxelSetViewer_selected(voxel_id : int) -> void:
+ set_palette(Palette.get_selected_id(), voxel_id)
+
+
+func _on_NewVoxelSet_pressed():
+ voxel_object.voxel_set = VoxelSet.new()
+ voxel_object.property_list_changed_notify()
+
+
+func _on_Translate_Apply_pressed():
+ var translation := Vector3(MoveX.value, MoveY.value, MoveZ.value)
+ undo_redo.create_action("VoxelObjectEditor : Moved voxels")
+ undo_redo.add_do_method(voxel_object, "move", translation)
+ undo_redo.add_undo_method(voxel_object, "move", -translation)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_Center_Apply_pressed():
+ var translation = voxel_object.vec_to_center(Vector3(
+ CenterX.value,
+ CenterY.value,
+ CenterZ.value))
+ undo_redo.create_action("VoxelObjectEditor : Center voxels")
+ undo_redo.add_do_method(voxel_object, "move", translation)
+ undo_redo.add_undo_method(voxel_object, "move", -translation)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_Flip_X_pressed():
+ undo_redo.create_action("VoxelObjectEditor : X flip voxels")
+ undo_redo.add_do_method(voxel_object, "flip", true, false, false)
+ undo_redo.add_undo_method(voxel_object, "flip", true, false, false)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_Flip_Y_pressed():
+ undo_redo.create_action("VoxelObjectEditor : Y flip voxels")
+ undo_redo.add_do_method(voxel_object, "flip", false, true, false)
+ undo_redo.add_undo_method(voxel_object, "flip", false, true, false)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_Flip_Z_pressed():
+ undo_redo.create_action("VoxelObjectEditor : Z flip voxels")
+ undo_redo.add_do_method(voxel_object, "flip", false, false, true)
+ undo_redo.add_undo_method(voxel_object, "flip", false, false, true)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_Clear_pressed():
+ var voxels = {}
+ for voxel in voxel_object.get_voxels():
+ voxels[voxel] = voxel_object.get_voxel_id(voxel)
+ undo_redo.create_action("VoxelObjectEditor : Clear voxels")
+ undo_redo.add_do_method(voxel_object, "erase_voxels")
+ undo_redo.add_undo_method(voxel_object, "set_voxels", voxels)
+ undo_redo.add_do_method(voxel_object, "update_mesh")
+ undo_redo.add_undo_method(voxel_object, "update_mesh")
+ undo_redo.commit_action()
+
+
+func _on_ImportFile_file_selected(path : String):
+ import_file_path = path
+ if is_instance_valid(voxel_object.voxel_set):
+ ImportHow.popup_centered()
+ else:
+ _on_Import_New_pressed()
+
+
+func _on_Import_Overwrite_pressed():
+ var result := voxel_object.load_file(import_file_path, false)
+ if result == OK:
+ voxel_object.voxel_set.request_refresh()
+ else:
+ printerr(result)
+ ImportHow.hide()
+
+
+func _on_Import_New_pressed():
+ var result := voxel_object.load_file(import_file_path, true)
+ if result == OK:
+ voxel_object.voxel_set.request_refresh()
+ else:
+ printerr(result)
+ ImportHow.hide()
+
+
+func _on_Import_Cancel_pressed():
+ ImportHow.hide()
+
+
+func _on_Docs_pressed():
+ OS.shell_open("https://github.com/ClarkThyLord/voxel-core/wiki")
+
+
+func _on_Issues_pressed():
+ OS.shell_open("https://github.com/ClarkThyLord/voxel-core/issues")
+
+
+func _on_GitHub_pressed():
+ OS.shell_open("https://github.com/ClarkThyLord/Voxel-Core")
+
+
+func _on_Update_pressed():
+ voxel_object.update_mesh()
+
+
+func _on_Grid_Size_X_value_changed(value : int):
+ set_grid_size(Vector2(value, _grid.grid_size.z))
+
+
+func _on_Grid_Size_Z_value_changed(value : int):
+ set_grid_size(Vector2(_grid.grid_size.x, value))
diff --git a/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.tscn b/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.tscn
new file mode 100644
index 0000000..a6d2263
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.tscn
@@ -0,0 +1,1010 @@
+[gd_scene load_steps=46 format=2]
+
+[ext_resource path="res://addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.gd" type="Script" id=1]
+[ext_resource path="res://addons/voxel-core/assets/controls/grid.png" type="Texture" id=2]
+[ext_resource path="res://addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn" type="PackedScene" id=3]
+[ext_resource path="res://addons/voxel-core/assets/classes/voxel_core.png" type="Texture" id=4]
+[ext_resource path="res://addons/voxel-core/assets/controls/mirrorz.png" type="Texture" id=5]
+[ext_resource path="res://addons/voxel-core/assets/controls/mirrory.png" type="Texture" id=6]
+[ext_resource path="res://addons/voxel-core/assets/controls/mirrorx.png" type="Texture" id=7]
+[ext_resource path="res://addons/voxel-core/assets/controls/edit.png" type="Texture" id=8]
+[ext_resource path="res://addons/voxel-core/assets/controls/general.png" type="Texture" id=9]
+[ext_resource path="res://addons/voxel-core/assets/controls/wired.png" type="Texture" id=10]
+[ext_resource path="res://addons/voxel-core/assets/controls/cursor.png" type="Texture" id=11]
+[ext_resource path="res://addons/voxel-core/assets/controls/solid.png" type="Texture" id=12]
+[ext_resource path="res://addons/voxel-core/assets/controls/pick.png" type="Texture" id=13]
+[ext_resource path="res://addons/voxel-core/assets/controls/secondary.png" type="Texture" id=14]
+[ext_resource path="res://addons/voxel-core/assets/controls/swap.png" type="Texture" id=15]
+[ext_resource path="res://addons/voxel-core/assets/controls/export.png" type="Texture" id=16]
+[ext_resource path="res://addons/voxel-core/assets/controls/import.png" type="Texture" id=17]
+[ext_resource path="res://addons/voxel-core/assets/controls/fill.png" type="Texture" id=18]
+[ext_resource path="res://addons/voxel-core/assets/controls/sub.png" type="Texture" id=19]
+[ext_resource path="res://addons/voxel-core/assets/controls/add.png" type="Texture" id=20]
+[ext_resource path="res://addons/voxel-core/assets/controls/primary.png" type="Texture" id=21]
+[ext_resource path="res://addons/voxel-core/assets/controls/individual.png" type="Texture" id=22]
+[ext_resource path="res://addons/voxel-core/assets/controls/area.png" type="Texture" id=23]
+[ext_resource path="res://addons/voxel-core/assets/controls/extrude.png" type="Texture" id=24]
+[ext_resource path="res://addons/voxel-core/assets/controls/docs.png" type="Texture" id=31]
+[ext_resource path="res://addons/voxel-core/assets/logos/GitHub.png" type="Texture" id=32]
+[ext_resource path="res://addons/voxel-core/assets/controls/issues.png" type="Texture" id=33]
+[ext_resource path="res://addons/voxel-core/assets/controls/reset.png" type="Texture" id=34]
+[ext_resource path="res://addons/voxel-core/assets/controls/refresh.png" type="Texture" id=35]
+
+[sub_resource type="InputEventKey" id=1]
+scancode = 32
+
+[sub_resource type="ShortCut" id=2]
+shortcut = SubResource( 1 )
+
+[sub_resource type="InputEventKey" id=3]
+scancode = 49
+
+[sub_resource type="ShortCut" id=4]
+shortcut = SubResource( 3 )
+
+[sub_resource type="InputEventKey" id=5]
+scancode = 50
+
+[sub_resource type="ShortCut" id=6]
+shortcut = SubResource( 5 )
+
+[sub_resource type="InputEventKey" id=7]
+scancode = 51
+
+[sub_resource type="ShortCut" id=8]
+shortcut = SubResource( 7 )
+
+[sub_resource type="InputEventKey" id=9]
+shift = true
+scancode = 49
+
+[sub_resource type="ShortCut" id=10]
+shortcut = SubResource( 9 )
+
+[sub_resource type="InputEventKey" id=11]
+shift = true
+scancode = 50
+
+[sub_resource type="ShortCut" id=12]
+shortcut = SubResource( 11 )
+
+[sub_resource type="InputEventKey" id=13]
+shift = true
+scancode = 51
+
+[sub_resource type="ShortCut" id=14]
+shortcut = SubResource( 13 )
+
+[sub_resource type="InputEventKey" id=15]
+scancode = 52
+
+[sub_resource type="ShortCut" id=16]
+shortcut = SubResource( 15 )
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+rect_min_size = Vector2( 0, 200 )
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VoxelObjectEditor" type="ScrollContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+rect_min_size = Vector2( 0, 200 )
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+scroll_vertical_enabled = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor"]
+margin_right = 1024.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer"]
+margin_right = 332.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer"]
+margin_right = 332.0
+margin_bottom = 24.0
+
+[node name="ToolButton" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_right = 109.0
+margin_bottom = 24.0
+mouse_filter = 2
+text = "VoxelObject"
+icon = ExtResource( 4 )
+
+[node name="HSeparator" type="HSeparator" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 113.0
+margin_right = 175.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+
+[node name="Editing" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 179.0
+margin_right = 270.0
+margin_bottom = 24.0
+hint_tooltip = "Enable editing"
+shortcut = SubResource( 2 )
+text = "Editing"
+icon = ExtResource( 8 )
+flat = true
+
+[node name="Update" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 274.0
+margin_right = 332.0
+margin_bottom = 24.0
+hint_tooltip = "Update voxel content"
+text = "Update"
+
+[node name="Options" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer"]
+margin_top = 28.0
+margin_right = 332.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options"]
+margin_right = 332.0
+margin_bottom = 74.0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer"]
+margin_right = 332.0
+margin_bottom = 22.0
+
+[node name="Tool" type="OptionButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer"]
+margin_right = 107.0
+margin_bottom = 22.0
+hint_tooltip = "Tools"
+size_flags_horizontal = 3
+shortcut = SubResource( 4 )
+text = "Add"
+icon = ExtResource( 20 )
+items = [ "Add", ExtResource( 20 ), false, 0, null, "Sub", ExtResource( 19 ), false, 1, null, "Swap", ExtResource( 15 ), false, 2, null, "Fill", ExtResource( 18 ), false, 3, null, "Pick", ExtResource( 13 ), false, 4, null ]
+selected = 0
+
+[node name="Palette" type="OptionButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer"]
+margin_left = 111.0
+margin_right = 218.0
+margin_bottom = 22.0
+hint_tooltip = "Palettes"
+size_flags_horizontal = 3
+shortcut = SubResource( 6 )
+text = "Primary"
+icon = ExtResource( 21 )
+items = [ "Primary", ExtResource( 21 ), false, 0, null, "Secondary", ExtResource( 14 ), false, 1, null ]
+selected = 0
+
+[node name="SelectionMode" type="OptionButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer"]
+margin_left = 222.0
+margin_right = 332.0
+margin_bottom = 22.0
+hint_tooltip = "Selection modes"
+size_flags_horizontal = 3
+shortcut = SubResource( 8 )
+text = "Individual"
+icon = ExtResource( 22 )
+items = [ "Individual", ExtResource( 22 ), false, 0, null, "Area", ExtResource( 23 ), false, 1, null, "Extrude", ExtResource( 24 ), false, 2, null ]
+selected = 0
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer"]
+margin_top = 26.0
+margin_right = 332.0
+margin_bottom = 50.0
+alignment = 1
+
+[node name="MirrorX" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2"]
+margin_right = 108.0
+margin_bottom = 24.0
+hint_tooltip = "Mirror operations over x-axis"
+size_flags_horizontal = 3
+shortcut = SubResource( 10 )
+text = "Mirror X"
+icon = ExtResource( 6 )
+
+[node name="MirrorY" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2"]
+margin_left = 112.0
+margin_right = 220.0
+margin_bottom = 24.0
+hint_tooltip = "Mirror operations over y-axis"
+size_flags_horizontal = 3
+shortcut = SubResource( 12 )
+text = "Mirror Y"
+icon = ExtResource( 7 )
+
+[node name="MirrorZ" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2"]
+margin_left = 224.0
+margin_right = 332.0
+margin_bottom = 24.0
+hint_tooltip = "Mirror operations over z-axis"
+size_flags_horizontal = 3
+shortcut = SubResource( 14 )
+text = "Mirror Z"
+icon = ExtResource( 5 )
+
+[node name="ColorChooser" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer"]
+margin_top = 54.0
+margin_right = 332.0
+margin_bottom = 74.0
+hint_tooltip = "Add voxel to VoxelSet"
+size_flags_horizontal = 3
+shortcut = SubResource( 16 )
+
+[node name="ColorPicked" type="ColorRect" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/ColorChooser"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 4.0
+margin_top = 4.0
+margin_right = -4.0
+margin_bottom = -4.0
+mouse_filter = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_edit_lock_": true,
+"_edit_use_anchors_": false
+}
+
+[node name="VoxelSetViewer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options" instance=ExtResource( 3 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_top = 78.0
+margin_right = 332.0
+margin_bottom = 172.0
+size_flags_vertical = 3
+selection_max = 1
+
+[node name="Notice" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer"]
+visible = false
+margin_left = 115.0
+margin_top = 122.0
+margin_right = 216.0
+margin_bottom = 194.0
+size_flags_horizontal = 6
+size_flags_vertical = 6
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Notice"]
+margin_right = 101.0
+margin_bottom = 48.0
+text = "No set VoxelSet
+set / load one
+or"
+align = 1
+
+[node name="NewVoxelSet" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer/Notice"]
+margin_top = 52.0
+margin_right = 101.0
+margin_bottom = 72.0
+text = "New VoxelSet"
+
+[node name="VSeparator" type="VSeparator" parent="VoxelObjectEditor/HBoxContainer"]
+margin_left = 336.0
+margin_right = 340.0
+margin_bottom = 200.0
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer"]
+margin_left = 344.0
+margin_right = 679.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2"]
+margin_right = 335.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+scroll_horizontal_enabled = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer"]
+margin_right = 335.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="EditHeader" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_right = 335.0
+margin_bottom = 22.0
+
+[node name="ToolButton" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/EditHeader"]
+margin_right = 36.0
+margin_bottom = 22.0
+text = "Edit"
+
+[node name="HSeparator" type="HSeparator" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/EditHeader"]
+margin_left = 40.0
+margin_right = 335.0
+margin_bottom = 22.0
+size_flags_horizontal = 3
+
+[node name="Move" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 26.0
+margin_right = 335.0
+margin_bottom = 50.0
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_top = 5.0
+margin_right = 12.0
+margin_bottom = 19.0
+text = "X:"
+
+[node name="X" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 16.0
+margin_right = 90.0
+margin_bottom = 24.0
+hint_tooltip = "X translation"
+rounded = true
+allow_greater = true
+allow_lesser = true
+
+[node name="Label2" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 94.0
+margin_top = 5.0
+margin_right = 105.0
+margin_bottom = 19.0
+text = "Y:"
+
+[node name="Y" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 109.0
+margin_right = 183.0
+margin_bottom = 24.0
+hint_tooltip = "Y translation"
+rounded = true
+allow_greater = true
+allow_lesser = true
+
+[node name="Label3" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 187.0
+margin_top = 5.0
+margin_right = 199.0
+margin_bottom = 19.0
+text = "Z:"
+
+[node name="Z" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 203.0
+margin_right = 277.0
+margin_bottom = 24.0
+hint_tooltip = "Z translation"
+rounded = true
+allow_greater = true
+allow_lesser = true
+
+[node name="Apply" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move"]
+margin_left = 281.0
+margin_right = 335.0
+margin_bottom = 24.0
+hint_tooltip = "Move all voxels by translation"
+size_flags_horizontal = 3
+text = "Move"
+
+[node name="Center" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 54.0
+margin_right = 335.0
+margin_bottom = 78.0
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_top = 5.0
+margin_right = 12.0
+margin_bottom = 19.0
+text = "X:"
+
+[node name="X" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 16.0
+margin_right = 90.0
+margin_bottom = 24.0
+hint_tooltip = "X-axis alignment
+1.0, RIGHT of axis
+0.0, LEFT of axis"
+max_value = 1.0
+step = 0.1
+value = 0.5
+
+[node name="Label2" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 94.0
+margin_top = 5.0
+margin_right = 105.0
+margin_bottom = 19.0
+text = "Y:"
+
+[node name="Y" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 109.0
+margin_right = 183.0
+margin_bottom = 24.0
+hint_tooltip = "Y-axis alignment
+1.0, UP of axis
+0.0, DOWN of axis"
+max_value = 1.0
+step = 0.1
+value = 0.5
+
+[node name="Label3" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 187.0
+margin_top = 5.0
+margin_right = 199.0
+margin_bottom = 19.0
+text = "Z:"
+
+[node name="Z" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 203.0
+margin_right = 277.0
+margin_bottom = 24.0
+hint_tooltip = "Z-axis alignment
+1.0, FORWARD of axis
+0.0, BACK of axis"
+max_value = 1.0
+step = 0.1
+value = 0.5
+
+[node name="Apply" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center"]
+margin_left = 281.0
+margin_right = 335.0
+margin_bottom = 24.0
+hint_tooltip = "Center all voxels with alignment"
+size_flags_horizontal = 3
+text = "Center"
+
+[node name="Flip" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 82.0
+margin_right = 335.0
+margin_bottom = 102.0
+
+[node name="X" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip"]
+margin_right = 109.0
+margin_bottom = 20.0
+hint_tooltip = "Flip voxels over x-axis"
+size_flags_horizontal = 3
+text = "Flip X"
+
+[node name="Y" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip"]
+margin_left = 113.0
+margin_right = 222.0
+margin_bottom = 20.0
+hint_tooltip = "Flip voxels over y-axis"
+size_flags_horizontal = 3
+text = "Flip Y"
+
+[node name="Z" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip"]
+margin_left = 226.0
+margin_right = 335.0
+margin_bottom = 20.0
+hint_tooltip = "Flip voxels over z-axis"
+size_flags_horizontal = 3
+text = "Flip Z"
+
+[node name="Clear" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 106.0
+margin_right = 335.0
+margin_bottom = 126.0
+hint_tooltip = "Removes all voxels"
+text = "Clear Voxels"
+
+[node name="FileHeader" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 130.0
+margin_right = 335.0
+margin_bottom = 152.0
+
+[node name="ToolButton" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/FileHeader"]
+margin_right = 35.0
+margin_bottom = 22.0
+text = "File"
+
+[node name="HSeparator" type="HSeparator" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/FileHeader"]
+margin_left = 39.0
+margin_right = 335.0
+margin_bottom = 22.0
+size_flags_horizontal = 3
+
+[node name="File" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer"]
+margin_top = 156.0
+margin_right = 335.0
+margin_bottom = 178.0
+
+[node name="Export" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File"]
+visible = false
+margin_right = 173.0
+margin_bottom = 22.0
+size_flags_horizontal = 3
+text = "Export"
+icon = ExtResource( 16 )
+
+[node name="Import" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File"]
+margin_right = 335.0
+margin_bottom = 22.0
+hint_tooltip = "Import voxels from file"
+size_flags_horizontal = 3
+text = "Import"
+icon = ExtResource( 17 )
+
+[node name="ImportFile" type="FileDialog" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import"]
+margin_right = 400.0
+margin_bottom = 300.0
+rect_min_size = Vector2( 400, 300 )
+window_title = "Open a File"
+resizable = true
+mode = 0
+filters = PoolStringArray( "*.vox ; VOX Files", "*.png, *.bmp, *.dds, *.exr, *.hdr, *.jpg, *.jpeg, *.tga, *.svg, *.svgz, *.webp; Image Files" )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ImportHow" type="PopupDialog" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import"]
+margin_right = 322.0
+margin_bottom = 97.0
+rect_min_size = Vector2( 325, 100 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer"]
+margin_right = 325.0
+margin_bottom = 48.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+text = "Overwrite or make new VoxelSet?"
+align = 1
+valign = 1
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer"]
+margin_top = 52.0
+margin_right = 325.0
+margin_bottom = 100.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+alignment = 1
+
+[node name="Overwrite" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 15.0
+margin_top = 14.0
+margin_right = 149.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Overwrite VoxelSet"
+
+[node name="New" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 153.0
+margin_top = 14.0
+margin_right = 252.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "New VoxelSet"
+
+[node name="Cancel" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 256.0
+margin_top = 14.0
+margin_right = 310.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Cancel"
+
+[node name="VSeparator2" type="VSeparator" parent="VoxelObjectEditor/HBoxContainer"]
+margin_left = 683.0
+margin_right = 687.0
+margin_bottom = 200.0
+
+[node name="VBoxContainer3" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer"]
+margin_left = 691.0
+margin_right = 1024.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Settings" type="TabContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3"]
+margin_right = 333.0
+margin_bottom = 200.0
+size_flags_vertical = 3
+tab_align = 0
+
+[node name="General" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 4.0
+margin_top = 34.0
+margin_right = -4.0
+margin_bottom = -4.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_tab_icon": ExtResource( 9 )
+}
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+scroll_horizontal_enabled = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer"]
+margin_right = 325.0
+margin_bottom = 136.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+alignment = 1
+
+[node name="Docs" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2"]
+margin_left = 52.0
+margin_top = 56.0
+margin_right = 115.0
+margin_bottom = 80.0
+mouse_default_cursor_shape = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Docs"
+icon = ExtResource( 31 )
+align = 0
+
+[node name="Issues" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2"]
+margin_left = 119.0
+margin_top = 56.0
+margin_right = 192.0
+margin_bottom = 80.0
+mouse_default_cursor_shape = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Issues"
+icon = ExtResource( 33 )
+align = 0
+
+[node name="GitHub" type="ToolButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2"]
+margin_left = 196.0
+margin_top = 56.0
+margin_right = 272.0
+margin_bottom = 80.0
+mouse_default_cursor_shape = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "GitHub"
+icon = ExtResource( 32 )
+align = 0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer"]
+margin_top = 140.0
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+
+[node name="SettingsRefresh" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer"]
+margin_right = 160.0
+margin_bottom = 22.0
+hint_tooltip = "Reload config file"
+size_flags_horizontal = 3
+size_flags_vertical = 0
+text = "Refresh Settings"
+icon = ExtResource( 35 )
+align = 0
+
+[node name="SettingsReset2" type="Button" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer"]
+margin_left = 164.0
+margin_right = 325.0
+margin_bottom = 22.0
+size_flags_horizontal = 3
+size_flags_vertical = 0
+text = "Reset Settings"
+icon = ExtResource( 34 )
+align = 0
+
+[node name="Cursor" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 4.0
+margin_top = 34.0
+margin_right = -4.0
+margin_bottom = -4.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_tab_icon": ExtResource( 11 )
+}
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+scroll_horizontal_enabled = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VoxelRaycasting" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer"]
+margin_right = 325.0
+margin_bottom = 24.0
+hint_tooltip = "Might significantly improve performance when editing Voxel Object
+!!! Warning Experimental !!!"
+text = "Voxel Raycasting"
+
+[node name="CursorVisible" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer"]
+margin_top = 28.0
+margin_right = 325.0
+margin_bottom = 52.0
+pressed = true
+text = "Visible"
+
+[node name="CursorDynamic" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer"]
+margin_top = 56.0
+margin_right = 325.0
+margin_bottom = 80.0
+hint_tooltip = "Cursor changes color based on selected palette"
+pressed = true
+text = "Dynamic Color"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer"]
+margin_top = 84.0
+margin_right = 325.0
+margin_bottom = 104.0
+
+[node name="CursorColor" type="ColorPickerButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/HBoxContainer"]
+margin_right = 32.0
+margin_bottom = 20.0
+rect_min_size = Vector2( 32, 0 )
+disabled = true
+color = Color( 1, 1, 1, 0.75 )
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/HBoxContainer"]
+margin_left = 36.0
+margin_top = 3.0
+margin_right = 114.0
+margin_bottom = 17.0
+text = "Cursor Color"
+
+[node name="Grid" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings"]
+visible = false
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 4.0
+margin_top = 34.0
+margin_right = -4.0
+margin_bottom = -4.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_tab_icon": ExtResource( 2 )
+}
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+scroll_horizontal_enabled = false
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer"]
+margin_right = 325.0
+margin_bottom = 162.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="GridVisible" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer"]
+margin_right = 325.0
+margin_bottom = 24.0
+pressed = true
+text = "Visible"
+
+[node name="GridConstant" type="CheckBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer"]
+margin_top = 28.0
+margin_right = 325.0
+margin_bottom = 52.0
+hint_tooltip = "If disabled grid is only visible while VoxelObject is empty"
+text = "Constant"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer"]
+margin_top = 56.0
+margin_right = 325.0
+margin_bottom = 76.0
+
+[node name="GridColor" type="ColorPickerButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/HBoxContainer2"]
+margin_right = 32.0
+margin_bottom = 20.0
+rect_min_size = Vector2( 32, 0 )
+color = Color( 1, 1, 1, 1 )
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/HBoxContainer2"]
+margin_left = 36.0
+margin_top = 3.0
+margin_right = 100.0
+margin_bottom = 17.0
+text = "Grid Color"
+
+[node name="GridMode" type="OptionButton" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer"]
+margin_top = 80.0
+margin_right = 325.0
+margin_bottom = 102.0
+text = "Wired"
+icon = ExtResource( 10 )
+items = [ "Solid", ExtResource( 12 ), false, 0, null, "Wired", ExtResource( 10 ), false, 1, null ]
+selected = 1
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer"]
+margin_top = 106.0
+margin_right = 325.0
+margin_bottom = 148.0
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer"]
+margin_right = 325.0
+margin_bottom = 14.0
+text = "Grid Size"
+
+[node name="Size" type="HBoxContainer" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer"]
+margin_top = 18.0
+margin_right = 325.0
+margin_bottom = 42.0
+alignment = 1
+
+[node name="Label" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size"]
+margin_top = 5.0
+margin_right = 12.0
+margin_bottom = 19.0
+text = "X:"
+
+[node name="X" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size"]
+margin_left = 16.0
+margin_right = 160.0
+margin_bottom = 24.0
+hint_tooltip = "X-axis alignment
+1.0, RIGHT of axis
+0.0, LEFT of axis"
+size_flags_horizontal = 3
+min_value = 1.0
+value = 16.0
+rounded = true
+allow_greater = true
+
+[node name="Label3" type="Label" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size"]
+margin_left = 164.0
+margin_top = 5.0
+margin_right = 176.0
+margin_bottom = 19.0
+text = "Z:"
+
+[node name="Z" type="SpinBox" parent="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size"]
+margin_left = 180.0
+margin_right = 325.0
+margin_bottom = 24.0
+hint_tooltip = "X-axis alignment
+1.0, RIGHT of axis
+0.0, LEFT of axis"
+size_flags_horizontal = 3
+min_value = 1.0
+value = 16.0
+rounded = true
+allow_greater = true
+
+[node name="ColorMenu" type="WindowDialog" parent="."]
+margin_right = 325.0
+margin_bottom = 545.0
+window_title = "Color Picker"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="ColorMenu"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 5.0
+margin_top = 5.0
+margin_right = -5.0
+margin_bottom = -5.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Color" type="ColorPicker" parent="ColorMenu/VBoxContainer"]
+margin_left = 20.0
+margin_top = 20.0
+margin_right = 335.0
+margin_bottom = 527.0
+size_flags_vertical = 3
+edit_alpha = false
+
+[node name="VSplitContainer" type="VSplitContainer" parent="ColorMenu/VBoxContainer"]
+margin_top = 511.0
+margin_right = 315.0
+margin_bottom = 511.0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="ColorMenu/VBoxContainer"]
+margin_top = 515.0
+margin_right = 315.0
+margin_bottom = 535.0
+
+[node name="Add" type="Button" parent="ColorMenu/VBoxContainer/HBoxContainer"]
+margin_right = 155.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Add To VoxelSet"
+
+[node name="Cancel" type="Button" parent="ColorMenu/VBoxContainer/HBoxContainer"]
+margin_left = 159.0
+margin_right = 315.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+text = "Cancel"
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer/Editing" to="." method="_on_Editing_toggled"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/HBoxContainer/Update" to="." method="_on_Update_pressed"]
+[connection signal="item_selected" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/Tool" to="." method="_on_Tool_selected"]
+[connection signal="item_selected" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/Palette" to="." method="_on_Palette_selected"]
+[connection signal="item_selected" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer/SelectionMode" to="." method="_on_SelectionMode_selected"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorX" to="." method="update_cursors"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorY" to="." method="update_cursors"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/HBoxContainer2/MirrorZ" to="." method="update_cursors"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VBoxContainer/ColorChooser" to="." method="show_color_menu"]
+[connection signal="selected_voxel" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Options/VoxelSetViewer" to="." method="_on_VoxelSetViewer_selected"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer/Notice/NewVoxelSet" to="." method="_on_NewVoxelSet_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Move/Apply" to="." method="_on_Translate_Apply_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Center/Apply" to="." method="_on_Center_Apply_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip/X" to="." method="_on_Flip_X_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip/Y" to="." method="_on_Flip_Y_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Flip/Z" to="." method="_on_Flip_Z_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/Clear" to="." method="_on_Clear_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import" to="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportFile" method="popup_centered"]
+[connection signal="file_selected" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportFile" to="." method="_on_ImportFile_file_selected"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer/Overwrite" to="." method="_on_Import_Overwrite_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer/New" to="." method="_on_Import_New_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer2/ScrollContainer/VBoxContainer/File/Import/ImportHow/VBoxContainer/HBoxContainer/Cancel" to="." method="_on_Import_Cancel_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2/Docs" to="." method="_on_Docs_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2/Issues" to="." method="_on_Issues_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer2/GitHub" to="." method="_on_GitHub_pressed"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer/SettingsRefresh" to="." method="load_config"]
+[connection signal="pressed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/General/ScrollContainer/VBoxContainer/HBoxContainer/SettingsReset2" to="." method="reset_config"]
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/VoxelRaycasting" to="." method="set_voxel_raycasting"]
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/CursorVisible" to="." method="set_cursor_visible"]
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/CursorDynamic" to="." method="set_cursor_dynamic"]
+[connection signal="color_changed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Cursor/ScrollContainer/VBoxContainer/HBoxContainer/CursorColor" to="." method="set_cursor_color"]
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridVisible" to="." method="set_grid_visible"]
+[connection signal="toggled" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridConstant" to="." method="set_grid_constant"]
+[connection signal="color_changed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/HBoxContainer2/GridColor" to="." method="set_grid_color"]
+[connection signal="item_selected" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/GridMode" to="." method="set_grid_mode"]
+[connection signal="value_changed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size/X" to="." method="_on_Grid_Size_X_value_changed"]
+[connection signal="value_changed" from="VoxelObjectEditor/HBoxContainer/VBoxContainer3/Settings/Grid/ScrollContainer/VBoxContainer/VBoxContainer/Size/Z" to="." method="_on_Grid_Size_Z_value_changed"]
+[connection signal="pressed" from="ColorMenu/VBoxContainer/HBoxContainer/Add" to="." method="_on_ColorMenu_Add_pressed"]
+[connection signal="pressed" from="ColorMenu/VBoxContainer/HBoxContainer/Cancel" to="ColorMenu" method="hide"]
diff --git a/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.gd b/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.gd
new file mode 100644
index 0000000..53acba2
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.gd
@@ -0,0 +1,210 @@
+tool
+extends ScrollContainer
+
+
+
+## Signals
+# Emited when editor needs closing
+signal close
+
+
+
+## Exported Variables
+export(Resource) var voxel_set = null setget set_voxel_set
+
+
+
+## Public Variables
+var undo_redo : UndoRedo
+
+
+
+## Private Variables
+var import_file_path := ""
+
+
+
+## OnReady Variables
+onready var ImportMenu := get_node("HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportFile")
+
+onready var ImportHow := get_node("HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow")
+
+onready var VoxelSetInfo := get_node("HBoxContainer/VBoxContainer/VoxelSetInfo")
+
+onready var VoxelInfo := get_node("HBoxContainer/VBoxContainer/VoxelInfo")
+
+onready var VoxelID := get_node("HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer/VoxelID")
+
+onready var VoxelName := get_node("HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer/VoxelName")
+
+onready var VoxelData := get_node("HBoxContainer/VBoxContainer/VoxelInfo/VoxelData")
+
+onready var VoxelSetViewer := get_node("HBoxContainer/VBoxContainer2/VoxelSetViewer")
+
+onready var VoxelInspector := get_node("HBoxContainer/VoxelInspector")
+
+onready var VoxelViewer := get_node("HBoxContainer/VoxelInspector/VoxelViewer")
+
+
+
+## Built-In Virtual Methods
+func _ready():
+ set_voxel_set(voxel_set)
+
+ if not is_instance_valid(undo_redo):
+ undo_redo = UndoRedo.new()
+ VoxelSetViewer.undo_redo = undo_redo
+ VoxelViewer.undo_redo = undo_redo
+
+
+
+## Public Methods
+func set_voxel_set(value : Resource, update := true) -> void:
+ if not typeof(voxel_set) == TYPE_NIL and not voxel_set is VoxelSet:
+ printerr("VoxelSetEditor : Invalid Resource given expected VoxelSet")
+ return
+
+ if is_instance_valid(voxel_set):
+ if voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.disconnect("requested_refresh", self, "update_view")
+
+ voxel_set = value
+ if is_instance_valid(voxel_set):
+ if not voxel_set.is_connected("requested_refresh", self, "update_view"):
+ voxel_set.connect("requested_refresh", self, "update_view")
+ if is_instance_valid(VoxelSetViewer):
+ VoxelSetViewer.voxel_set = voxel_set
+
+ if update:
+ update_view()
+
+
+func update_view() -> void:
+ if is_instance_valid(voxel_set):
+ if is_instance_valid(VoxelSetInfo):
+ VoxelSetInfo.text = "Voxels:\t\t" + str(voxel_set.size())
+ VoxelSetInfo.text += "\nUV Ready:\t" + str(voxel_set.uv_ready())
+
+ if is_instance_valid(VoxelSetViewer):
+ var editing_single : bool = VoxelSetViewer.get_selected_size() == 1
+ VoxelSetInfo.size_flags_vertical = Container.SIZE_FILL if editing_single else Container.SIZE_EXPAND_FILL
+ VoxelInfo.visible = editing_single
+ VoxelInspector.visible = editing_single
+
+ if editing_single:
+ var id = VoxelSetViewer.get_selected(0)
+
+ VoxelID.text = str(id)
+ VoxelName.text = voxel_set.id_to_name(id)
+ VoxelData.text = var2str(voxel_set.get_voxel(id))
+
+ VoxelViewer.setup(voxel_set, id)
+ else:
+ if not is_instance_valid(VoxelSetInfo):
+ return
+ VoxelSetInfo.text = ""
+
+
+# Show import menu centered
+func show_import_menu() -> void:
+ ImportMenu.popup_centered()
+
+
+# Hide import menu
+func hide_import_menu() -> void:
+ ImportMenu.hide()
+
+
+# Show import how centered
+func show_import_how():
+ ImportHow.popup_centered()
+
+
+# Hide import how
+func hide_import_how():
+ ImportHow.hide()
+
+
+
+## Private Methods
+func _on_Refresh_pressed():
+ voxel_set.request_refresh()
+
+
+func _on_Import_file_selected(path):
+ import_file_path = path
+ show_import_how()
+
+
+func _on_Import_Append_pressed():
+ var result = voxel_set.load_file(import_file_path, true)
+ if result == OK:
+ voxel_set.request_refresh()
+ else:
+ printerr(result)
+ hide_import_how()
+
+
+func _on_Import_Replace_pressed():
+ var result = voxel_set.load_file(import_file_path, false)
+ if result == OK:
+ voxel_set.request_refresh()
+ else:
+ printerr(result)
+ hide_import_how()
+
+
+func _on_Close_pressed():
+ emit_signal("close")
+
+
+func _on_VoxelID_text_entered(new_id):
+ if not new_id.is_valid_integer():
+ return
+ new_id = new_id.to_int()
+ if new_id == VoxelSetViewer.get_selected(0):
+ return
+ elif new_id <= -1 or new_id >= voxel_set.size():
+ return
+
+ var id = VoxelSetViewer.get_selected(0)
+ var voxel = voxel_set.get_voxel(id)
+ undo_redo.create_action("VoxelSetEditor : Set voxel id")
+ undo_redo.add_do_method(voxel_set, "erase_voxel", id)
+ undo_redo.add_undo_method(voxel_set, "insert_voxel", id, voxel)
+ undo_redo.add_do_method(voxel_set, "insert_voxel", new_id, voxel)
+ undo_redo.add_undo_method(voxel_set, "erase_voxel", new_id)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+ VoxelSetViewer.unselect_all()
+ VoxelSetViewer.select(new_id)
+
+
+func _on_VoxelName_text_entered(new_name : String):
+ var voxel_id = VoxelSetViewer.get_selected(0)
+ var voxel = voxel_set.get_voxel(voxel_id)
+ if new_name == Voxel.get_name(voxel):
+ return
+
+ var _voxel = voxel.duplicate(true)
+ if new_name.empty():
+ undo_redo.create_action("VoxelSetEditor : Remove voxel name")
+ Voxel.remove_name(_voxel)
+ else:
+ undo_redo.create_action("VoxelSetEditor : Set voxel name")
+ Voxel.set_name(_voxel, new_name)
+ Voxel.clean(_voxel)
+ undo_redo.add_do_method(voxel_set, "set_voxel", voxel_id, _voxel)
+ undo_redo.add_undo_method(voxel_set, "set_voxel", voxel_id, voxel)
+ undo_redo.add_do_method(voxel_set, "request_refresh")
+ undo_redo.add_undo_method(voxel_set, "request_refresh")
+ undo_redo.commit_action()
+
+
+func _on_VoxelSetViewer_selected(voxel_id : int):
+ update_view()
+
+
+func _on_VoxelSetViewer_unselected(voxel_id : int):
+ update_view()
diff --git a/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.tscn b/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.tscn
new file mode 100644
index 0000000..b70f995
--- /dev/null
+++ b/addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.tscn
@@ -0,0 +1,256 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn" type="PackedScene" id=2]
+[ext_resource path="res://addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.gd" type="Script" id=3]
+[ext_resource path="res://addons/voxel-core/assets/classes/voxel_set.png" type="Texture" id=4]
+
+[node name="VoxelSetEditor" type="ScrollContainer"]
+anchor_right = 1.0
+margin_bottom = 200.0
+rect_min_size = Vector2( 0, 200 )
+follow_focus = true
+scroll_vertical_enabled = false
+script = ExtResource( 3 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+margin_right = 1024.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
+margin_right = 300.0
+margin_bottom = 200.0
+rect_min_size = Vector2( 300, 0 )
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
+margin_right = 300.0
+margin_bottom = 20.0
+
+[node name="TextureRect" type="TextureRect" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_right = 16.0
+margin_bottom = 20.0
+size_flags_vertical = 3
+texture = ExtResource( 4 )
+stretch_mode = 4
+
+[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 20.0
+margin_top = 3.0
+margin_right = 75.0
+margin_bottom = 17.0
+text = "VoxelSet"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/HBoxContainer"]
+margin_left = 79.0
+margin_right = 300.0
+margin_bottom = 20.0
+size_flags_horizontal = 3
+alignment = 2
+
+[node name="Import" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
+margin_left = 50.0
+margin_right = 106.0
+margin_bottom = 20.0
+hint_tooltip = "Import voxels from file"
+text = "Import"
+
+[node name="ImportFile" type="FileDialog" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import"]
+margin_right = 315.0
+margin_bottom = 130.0
+rect_min_size = Vector2( 400, 300 )
+window_title = "Open a File"
+resizable = true
+mode = 0
+filters = PoolStringArray( "*.vox ; VOX Files", "*.png, *.bmp, *.dds, *.exr, *.hdr, *.jpg, *.jpeg, *.tga, *.svg, *.svgz, *.webp; Image Files", "*.gpl; GIMP Palette" )
+
+[node name="ImportHow" type="PopupDialog" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import"]
+margin_right = 40.0
+margin_bottom = 40.0
+rect_min_size = Vector2( 325, 100 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer"]
+margin_top = 17.0
+margin_right = 325.0
+margin_bottom = 31.0
+size_flags_horizontal = 3
+size_flags_vertical = 6
+text = "How to import voxels to VoxelSet?"
+align = 1
+valign = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer"]
+margin_top = 52.0
+margin_right = 325.0
+margin_bottom = 100.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+alignment = 1
+
+[node name="Append" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 70.0
+margin_top = 14.0
+margin_right = 131.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Append"
+
+[node name="Replace" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 135.0
+margin_top = 14.0
+margin_right = 197.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Replace"
+
+[node name="Cancel" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer"]
+margin_left = 201.0
+margin_top = 14.0
+margin_right = 255.0
+margin_bottom = 34.0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Cancel"
+
+[node name="Refresh" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
+margin_left = 110.0
+margin_right = 170.0
+margin_bottom = 20.0
+hint_tooltip = "Request refresh from all connected objects"
+text = "Refresh"
+
+[node name="Close" type="Button" parent="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
+margin_left = 174.0
+margin_right = 221.0
+margin_bottom = 20.0
+hint_tooltip = "Close the VoxelSetEditor"
+text = "Close"
+
+[node name="VoxelSetInfo" type="RichTextLabel" parent="HBoxContainer/VBoxContainer"]
+margin_top = 24.0
+margin_right = 300.0
+margin_bottom = 200.0
+rect_min_size = Vector2( 0, 50 )
+size_flags_vertical = 3
+
+[node name="VoxelInfo" type="VBoxContainer" parent="HBoxContainer/VBoxContainer"]
+visible = false
+margin_top = 114.0
+margin_right = 300.0
+margin_bottom = 200.0
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer/VoxelInfo"]
+margin_right = 300.0
+margin_bottom = 24.0
+
+[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer"]
+margin_top = 5.0
+margin_right = 19.0
+margin_bottom = 19.0
+text = "ID:"
+
+[node name="VoxelID" type="LineEdit" parent="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer"]
+margin_left = 23.0
+margin_right = 81.0
+margin_bottom = 24.0
+hint_tooltip = "Enter to change id"
+
+[node name="Label2" type="Label" parent="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer"]
+margin_left = 85.0
+margin_top = 5.0
+margin_right = 127.0
+margin_bottom = 19.0
+text = "Name:"
+
+[node name="VoxelName" type="LineEdit" parent="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer"]
+margin_left = 131.0
+margin_right = 300.0
+margin_bottom = 24.0
+hint_tooltip = "Enter to change name"
+size_flags_horizontal = 3
+placeholder_text = "Enter name..."
+
+[node name="VoxelData" type="RichTextLabel" parent="HBoxContainer/VBoxContainer/VoxelInfo"]
+margin_top = 28.0
+margin_right = 300.0
+margin_bottom = 86.0
+size_flags_vertical = 3
+
+[node name="HSplitContainer" type="VSeparator" parent="HBoxContainer"]
+margin_left = 304.0
+margin_right = 308.0
+margin_bottom = 200.0
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="HBoxContainer"]
+margin_left = 312.0
+margin_right = 1024.0
+margin_bottom = 200.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VoxelSetViewer" parent="HBoxContainer/VBoxContainer2" instance=ExtResource( 1 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_right = 712.0
+margin_bottom = 200.0
+size_flags_horizontal = 7
+size_flags_vertical = 3
+search = ""
+allow_edit = true
+selection_max = -1
+show_hints = true
+voxel_set = null
+
+[node name="VoxelInspector" type="HBoxContainer" parent="HBoxContainer"]
+visible = false
+margin_left = 684.0
+margin_right = 1024.0
+margin_bottom = 200.0
+
+[node name="HSplitContainer2" type="VSeparator" parent="HBoxContainer/VoxelInspector"]
+margin_right = 4.0
+margin_bottom = 200.0
+
+[node name="VoxelViewer" parent="HBoxContainer/VoxelInspector" instance=ExtResource( 2 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_left = 8.0
+margin_right = 340.0
+margin_bottom = 200.0
+rect_min_size = Vector2( 332, 200 )
+size_flags_horizontal = 0
+size_flags_vertical = 4
+selection_max = 6
+allow_edit = true
+view_mode = 1
+camera_sensitivity = 8
+voxel_id = 0
+voxel_set = null
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import" to="." method="show_import_menu"]
+[connection signal="file_selected" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportFile" to="." method="_on_Import_file_selected"]
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer/Append" to="." method="_on_Import_Append_pressed"]
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer/Replace" to="." method="_on_Import_Replace_pressed"]
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Import/ImportHow/VBoxContainer/HBoxContainer/Cancel" to="." method="hide_import_how"]
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Refresh" to="." method="_on_Refresh_pressed"]
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer/Close" to="." method="_on_Close_pressed"]
+[connection signal="text_entered" from="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer/VoxelID" to="." method="_on_VoxelID_text_entered"]
+[connection signal="text_entered" from="HBoxContainer/VBoxContainer/VoxelInfo/HBoxContainer/VoxelName" to="." method="_on_VoxelName_text_entered"]
+[connection signal="selected_voxel" from="HBoxContainer/VBoxContainer2/VoxelSetViewer" to="." method="_on_VoxelSetViewer_selected"]
+[connection signal="unselected_voxel" from="HBoxContainer/VBoxContainer2/VoxelSetViewer" to="." method="_on_VoxelSetViewer_unselected"]
diff --git a/addons/voxel-core/plugin.cfg b/addons/voxel-core/plugin.cfg
new file mode 100644
index 0000000..191b7b3
--- /dev/null
+++ b/addons/voxel-core/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Voxel-Core"
+description="Voxel plugin for the Godot game engine!"
+author="ClarkThyLord"
+version="3.0.0"
+script="voxel-core.gd"
diff --git a/addons/voxel-core/voxel-core.gd b/addons/voxel-core/voxel-core.gd
new file mode 100644
index 0000000..204771a
--- /dev/null
+++ b/addons/voxel-core/voxel-core.gd
@@ -0,0 +1,168 @@
+tool
+extends EditorPlugin
+
+
+
+## Enums
+enum VoxelCore {
+ OTHER = -1,
+ VOXEL_SET,
+ VOXEL_OBJECT,
+ VOXEL_MESH,
+}
+
+
+
+## Constants
+const VoxelObject := preload("res://addons/voxel-core/classes/voxel_object.gd")
+
+const VoxelSetEditor := preload("res://addons/voxel-core/engine/voxel_set_editor/voxel_set_editor.tscn")
+
+const VoxelObjectEditor := preload("res://addons/voxel-core/engine/voxel_object_editor/voxel_object_editor.tscn")
+
+
+
+## Private Variables
+var _current_main_scene : String
+
+var _handling_voxel_set : VoxelSet
+
+var _handling_voxel_object : VoxelObject
+
+var voxel_set_editor
+
+var voxel_object_editor
+
+var Meshes := preload("res://addons/voxel-core/engine/importers/meshes.gd").new()
+
+var VoxelObjects := preload("res://addons/voxel-core/engine/importers/voxel_objects.gd").new()
+
+var VoxelSets := preload("res://addons/voxel-core/engine/importers/voxel_sets.gd").new()
+
+
+
+## Built-In Virtual Methods
+func _enter_tree():
+ add_import_plugin(Meshes)
+ add_import_plugin(VoxelObjects)
+ add_import_plugin(VoxelSets)
+
+ connect("scene_closed", self, "_on_scene_closed")
+ connect("main_screen_changed", self, "_on_main_screen_changed")
+
+ print("Voxel-Core is active...")
+
+
+func _exit_tree():
+ remove_import_plugin(Meshes)
+ remove_import_plugin(VoxelObjects)
+ remove_import_plugin(VoxelSets)
+
+ close_voxel_set_editor()
+ close_voxel_object_editor()
+
+ print("Voxel-Core is inactive!")
+
+
+func handles(object : Object) -> bool:
+
+ if _current_main_scene != "3D":
+ close_voxel_object_editor()
+ if is_instance_valid(_handling_voxel_object):
+ var selections := get_editor_interface().get_selection().get_selected_nodes()
+ if selections.size() != 1 or not selections.has(_handling_voxel_object):
+ _handling_voxel_object = null
+ close_voxel_object_editor()
+
+ return true if typeof_voxel_core(object) > VoxelCore.OTHER else false
+
+
+func edit(object):
+ match typeof_voxel_core(object):
+ VoxelCore.VOXEL_SET:
+ _handling_voxel_set = object
+ if not is_instance_valid(voxel_object_editor) or not voxel_object_editor.is_editing():
+ show_voxel_set_editor(object)
+ return true
+ VoxelCore.VOXEL_OBJECT:
+ _handling_voxel_object = object
+ if _current_main_scene == "3D":
+ show_voxel_object_editor(object)
+ return true
+
+
+func forward_spatial_gui_input(camera : Camera, event : InputEvent) -> bool:
+ return voxel_object_editor.handle_input(camera, event) if is_instance_valid(voxel_object_editor) else false
+
+
+
+## Public Methods
+static func typeof_voxel_core(object : Object) -> int:
+ var type_of = VoxelCore.OTHER
+
+ if object is VoxelSet:
+ type_of = VoxelCore.VOXEL_SET
+ elif object is VoxelObject:
+ type_of = VoxelCore.VOXEL_OBJECT
+ elif object is VoxelMesh:
+ type_of = VoxelCore.VOXEL_MESH
+
+ return type_of
+
+
+func show_voxel_set_editor(voxel_set : VoxelSet) -> void:
+ if not is_instance_valid(voxel_set_editor):
+ voxel_set_editor = VoxelSetEditor.instance()
+ voxel_set_editor.undo_redo = get_undo_redo()
+ voxel_set_editor.connect("close", self, "close_voxel_set_editor")
+ add_control_to_bottom_panel(voxel_set_editor, "VoxelSet")
+ voxel_set_editor.voxel_set = voxel_set
+ make_bottom_panel_item_visible(voxel_set_editor)
+
+
+func close_voxel_set_editor() -> void:
+ if is_instance_valid(voxel_set_editor):
+ remove_control_from_bottom_panel(voxel_set_editor)
+ voxel_set_editor.queue_free()
+ voxel_set_editor = null
+
+
+func show_voxel_object_editor(voxel_object : VoxelObject) -> void:
+ if not is_instance_valid(voxel_object_editor):
+ voxel_object_editor = VoxelObjectEditor.instance()
+ voxel_object_editor.undo_redo = get_undo_redo()
+ voxel_object_editor.connect("editing", self, "_on_voxel_object_editor_editing_toggled")
+ voxel_object_editor.connect("close", self, "close_voxel_object_editor")
+ add_control_to_bottom_panel(voxel_object_editor, "VoxelObject")
+ voxel_object_editor.start_editing(voxel_object)
+ make_bottom_panel_item_visible(voxel_object_editor)
+
+
+func close_voxel_object_editor() -> void:
+ if is_instance_valid(voxel_object_editor):
+ voxel_object_editor.stop_editing()
+ remove_control_from_bottom_panel(voxel_object_editor)
+ voxel_object_editor.queue_free()
+ voxel_object_editor = null
+
+
+
+## Private Methods
+func _on_main_screen_changed(screen_name : String) -> void:
+ _current_main_scene = screen_name
+ if screen_name == "3D" and is_instance_valid(_handling_voxel_object):
+ show_voxel_object_editor(_handling_voxel_object)
+ else:
+ close_voxel_object_editor()
+
+
+func _on_scene_closed(filepath : String) -> void:
+ close_voxel_object_editor()
+
+
+func _on_voxel_object_editor_editing_toggled(toggled : bool) -> void:
+ if is_instance_valid(voxel_object_editor) and voxel_object_editor.voxel_object == _handling_voxel_object:
+ if toggled:
+ get_editor_interface().get_selection().clear()
+ else:
+ get_editor_interface().get_selection().add_node(_handling_voxel_object)