aboutsummaryrefslogtreecommitdiff
path: root/addons/voxel-core/controls
diff options
context:
space:
mode:
authorjacopograndi <jacopo.grandi@outlook.it>2022-01-15 16:29:02 +0100
committerjacopograndi <jacopo.grandi@outlook.it>2022-01-15 16:29:02 +0100
commitc9c5b5d7c2a238310ce7bde336f76d2d1b6f9f29 (patch)
treec6e935fea995752a0b307e56892e8422fb734c0e /addons/voxel-core/controls
parent98f356e68b759bf84300290315d4ada09c41f79b (diff)
moved shapes to loading area & fixed asset paths
Diffstat (limited to 'addons/voxel-core/controls')
-rw-r--r--addons/voxel-core/controls/tiles_viewer/tiles_viewer.gd199
-rw-r--r--addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn13
-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.tscn82
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer.gd824
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn476
-rw-r--r--addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres4
9 files changed, 2113 insertions, 0 deletions
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..7daf1f9
--- /dev/null
+++ b/addons/voxel-core/controls/tiles_viewer/tiles_viewer.tscn
@@ -0,0 +1,13 @@
+[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..edafa32
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn
@@ -0,0 +1,82 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://addons/voxel-core/assets/controls/cancel.png" type="Texture" id=1]
+[ext_resource path="res://addons/voxel-core/assets/controls/sub.png" type="Texture" id=2]
+[ext_resource path="res://addons/voxel-core/assets/controls/add.png" type="Texture" id=3]
+[ext_resource path="res://addons/voxel-core/assets/controls/duplicate.png" type="Texture" id=4]
+[ext_resource path="res://addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd" type="Script" id=5]
+
+[node name="VoxelSetViewer" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 5 )
+__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( 3 ), 0, false, false, 0, 0, null, "", false, "Duplicate voxel", ExtResource( 4 ), 0, false, false, 2, 0, null, "", false, "Remove voxel", ExtResource( 2 ), 0, false, false, 2, 0, null, "", false, "", null, 0, false, false, 3, 0, null, "", true, "Deselect", ExtResource( 1 ), 0, false, false, 4, 0, null, "", false, "Duplicate voxels", ExtResource( 4 ), 0, false, false, 5, 0, null, "", false, "Remove voxels", ExtResource( 2 ), 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..c24d738
--- /dev/null
+++ b/addons/voxel-core/controls/voxel_viewer/voxel_viewer.tscn
@@ -0,0 +1,476 @@
+[gd_scene load_steps=9 format=2]
+
+[ext_resource path="res://addons/voxel-core/controls/voxel_button/voxel_button.tscn" type="PackedScene" id=1]
+[ext_resource path="res://addons/voxel-core/controls/voxel_viewer/voxel_viewer_env.tres" type="Environment" 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.gd" type="Script" id=4]
+
+[sub_resource type="World" id=1]
+environment = ExtResource( 2 )
+
+[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( 4 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+environment = ExtResource( 2 )
+
+[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( 1 )]
+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( 1 )]
+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( 1 )]
+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( 1 )]
+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( 1 )]
+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( 1 )]
+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 )