first commit
This commit is contained in:
6
addons/zylann.hterrain/tools/texture_editor/display_alpha.gdshader
Executable file
6
addons/zylann.hterrain/tools/texture_editor/display_alpha.gdshader
Executable file
@@ -0,0 +1,6 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
void fragment() {
|
||||
float a = texture(TEXTURE, UV).a;
|
||||
COLOR = vec4(a, a, a, 1.0);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://6bb66fu1fw8n
|
||||
9
addons/zylann.hterrain/tools/texture_editor/display_alpha_material.tres
Executable file
9
addons/zylann.hterrain/tools/texture_editor/display_alpha_material.tres
Executable file
@@ -0,0 +1,9 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/zylann.hterrain/tools/texture_editor/display_alpha.gdshader" type="Shader" id=1]
|
||||
|
||||
[resource]
|
||||
|
||||
render_priority = 0
|
||||
shader = ExtResource( 1 )
|
||||
|
||||
9
addons/zylann.hterrain/tools/texture_editor/display_alpha_slice.gdshader
Executable file
9
addons/zylann.hterrain/tools/texture_editor/display_alpha_slice.gdshader
Executable file
@@ -0,0 +1,9 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2DArray u_texture_array;
|
||||
uniform float u_index;
|
||||
|
||||
void fragment() {
|
||||
vec4 col = texture(u_texture_array, vec3(UV.x, UV.y, u_index));
|
||||
COLOR = vec4(col.a, col.a, col.a, 1.0);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://btcvsvc4f2nhc
|
||||
7
addons/zylann.hterrain/tools/texture_editor/display_color.gdshader
Executable file
7
addons/zylann.hterrain/tools/texture_editor/display_color.gdshader
Executable file
@@ -0,0 +1,7 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
void fragment() {
|
||||
// TODO Have an option to "undo" sRGB, for funzies?
|
||||
vec4 col = texture(TEXTURE, UV);
|
||||
COLOR = vec4(col.rgb, 1.0);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1wwjr82d2o37
|
||||
6
addons/zylann.hterrain/tools/texture_editor/display_color_material.tres
Executable file
6
addons/zylann.hterrain/tools/texture_editor/display_color_material.tres
Executable file
@@ -0,0 +1,6 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/zylann.hterrain/tools/texture_editor/display_color.gdshader" type="Shader" id=1]
|
||||
|
||||
[resource]
|
||||
shader = ExtResource( 1 )
|
||||
9
addons/zylann.hterrain/tools/texture_editor/display_color_slice.gdshader
Executable file
9
addons/zylann.hterrain/tools/texture_editor/display_color_slice.gdshader
Executable file
@@ -0,0 +1,9 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2DArray u_texture_array;
|
||||
uniform float u_index;
|
||||
|
||||
void fragment() {
|
||||
vec4 col = texture(u_texture_array, vec3(UV.x, UV.y, u_index));
|
||||
COLOR = vec4(col.rgb, 1.0);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://npob3uj5dgkg
|
||||
28
addons/zylann.hterrain/tools/texture_editor/display_normal.gdshader
Executable file
28
addons/zylann.hterrain/tools/texture_editor/display_normal.gdshader
Executable file
@@ -0,0 +1,28 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform float u_strength = 1.0;
|
||||
uniform bool u_flip_y = false;
|
||||
|
||||
vec3 unpack_normal(vec4 rgba) {
|
||||
vec3 n = rgba.xzy * 2.0 - vec3(1.0);
|
||||
// Had to negate Z because it comes from Y in the normal map,
|
||||
// and OpenGL-style normal maps are Y-up.
|
||||
n.z *= -1.0;
|
||||
return n;
|
||||
}
|
||||
|
||||
vec3 pack_normal(vec3 n) {
|
||||
n.z *= -1.0;
|
||||
return 0.5 * (n.xzy + vec3(1.0));
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec4 col = texture(TEXTURE, UV);
|
||||
vec3 n = unpack_normal(col);
|
||||
n = normalize(mix(n, vec3(-n.x, n.y, -n.z), 0.5 - 0.5 * u_strength));
|
||||
if (u_flip_y) {
|
||||
n.z = -n.z;
|
||||
}
|
||||
col.rgb = pack_normal(n);
|
||||
COLOR = vec4(col.rgb, 1.0);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://s7qinobxy30j
|
||||
41
addons/zylann.hterrain/tools/texture_editor/flow_container.gd
Executable file
41
addons/zylann.hterrain/tools/texture_editor/flow_container.gd
Executable file
@@ -0,0 +1,41 @@
|
||||
@tool
|
||||
extends Container
|
||||
|
||||
const SEPARATION = 2
|
||||
|
||||
|
||||
func _notification(what: int):
|
||||
if what == NOTIFICATION_SORT_CHILDREN:
|
||||
_sort_children2()
|
||||
|
||||
|
||||
# TODO Function with ugly name to workaround a Godot 3.1 issue
|
||||
# See https://github.com/godotengine/godot/pull/38396
|
||||
func _sort_children2():
|
||||
var max_x := size.x - SEPARATION
|
||||
var pos := Vector2(SEPARATION, SEPARATION)
|
||||
var line_height := 0
|
||||
|
||||
for i in get_child_count():
|
||||
var child_node = get_child(i)
|
||||
if not child_node is Control:
|
||||
continue
|
||||
var child := child_node as Control
|
||||
|
||||
var rect := child.get_rect()
|
||||
|
||||
if rect.size.y > line_height:
|
||||
line_height = rect.size.y
|
||||
|
||||
if pos.x + rect.size.x > max_x:
|
||||
pos.x = SEPARATION
|
||||
pos.y += line_height + SEPARATION
|
||||
line_height = rect.size.y
|
||||
|
||||
rect.position = pos
|
||||
fit_child_in_rect(child, rect)
|
||||
|
||||
pos.x += rect.size.x + SEPARATION
|
||||
|
||||
custom_minimum_size.y = pos.y + line_height
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://d2cf2epylmuo6
|
||||
@@ -0,0 +1,60 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
|
||||
#const HT_EmptyTexture = preload("../../icons/empty.png")
|
||||
const EMPTY_TEXTURE_PATH = "res://addons/zylann.hterrain/tools/icons/empty.png"
|
||||
|
||||
signal load_pressed
|
||||
signal clear_pressed
|
||||
|
||||
|
||||
@onready var _label : Label = $Label
|
||||
@onready var _texture_rect : TextureRect = $TextureRect
|
||||
|
||||
@onready var _buttons = [
|
||||
$LoadButton,
|
||||
$ClearButton
|
||||
]
|
||||
|
||||
var _material : Material
|
||||
var _is_empty := true
|
||||
|
||||
|
||||
func set_label(text: String):
|
||||
_label.text = text
|
||||
|
||||
|
||||
func set_texture(tex: Texture):
|
||||
if tex == null:
|
||||
_texture_rect.texture = load(EMPTY_TEXTURE_PATH)
|
||||
_texture_rect.material = null
|
||||
_is_empty = true
|
||||
else:
|
||||
_texture_rect.texture = tex
|
||||
_texture_rect.material = _material
|
||||
_is_empty = false
|
||||
|
||||
|
||||
func set_texture_tooltip(msg: String):
|
||||
_texture_rect.tooltip_text = msg
|
||||
|
||||
|
||||
func _on_LoadButton_pressed():
|
||||
load_pressed.emit()
|
||||
|
||||
|
||||
func _on_ClearButton_pressed():
|
||||
clear_pressed.emit()
|
||||
|
||||
|
||||
func set_material(mat: Material):
|
||||
_material = mat
|
||||
if not _is_empty:
|
||||
_texture_rect.material = _material
|
||||
|
||||
|
||||
func set_enabled(enabled: bool):
|
||||
for b in _buttons:
|
||||
b.disabled = not enabled
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://de8wkty1iyg08
|
||||
@@ -0,0 +1,29 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dqgaomu3tr1ym"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://de8wkty1iyg08" path="res://addons/zylann.hterrain/tools/texture_editor/set_editor/source_file_item_editor.gd" id="2"]
|
||||
|
||||
[node name="SourceFileItem" type="VBoxContainer"]
|
||||
offset_right = 128.0
|
||||
offset_bottom = 194.0
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Albedo"
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
custom_minimum_size = Vector2(128, 128)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="LoadButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Load..."
|
||||
|
||||
[node name="ClearButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
text = "Clear"
|
||||
|
||||
[connection signal="pressed" from="LoadButton" to="." method="_on_LoadButton_pressed"]
|
||||
[connection signal="pressed" from="ClearButton" to="." method="_on_ClearButton_pressed"]
|
||||
529
addons/zylann.hterrain/tools/texture_editor/set_editor/texture_set_editor.gd
Executable file
529
addons/zylann.hterrain/tools/texture_editor/set_editor/texture_set_editor.gd
Executable file
@@ -0,0 +1,529 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
const HTerrainTextureSet = preload("../../../hterrain_texture_set.gd")
|
||||
const HT_EditorUtil = preload("../../util/editor_util.gd")
|
||||
const HT_Util = preload("../../../util/util.gd")
|
||||
const HT_Logger = preload("../../../util/logger.gd")
|
||||
|
||||
const HT_ColorShader = preload("../display_color.gdshader")
|
||||
const HT_ColorSliceShader = preload("../display_color_slice.gdshader")
|
||||
const HT_AlphaShader = preload("../display_alpha.gdshader")
|
||||
const HT_AlphaSliceShader = preload("../display_alpha_slice.gdshader")
|
||||
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
|
||||
#const HT_EmptyTexture = preload("../../icons/empty.png")
|
||||
const EMPTY_TEXTURE_PATH = "res://addons/zylann.hterrain/tools/icons/empty.png"
|
||||
|
||||
signal import_selected
|
||||
|
||||
@onready var _slots_list : ItemList = $VB/HS/VB/SlotsList
|
||||
@onready var _albedo_preview : TextureRect = $VB/HS/VB2/GC/AlbedoPreview
|
||||
@onready var _bump_preview : TextureRect = $VB/HS/VB2/GC/BumpPreview
|
||||
@onready var _normal_preview : TextureRect = $VB/HS/VB2/GC/NormalPreview
|
||||
@onready var _roughness_preview : TextureRect = $VB/HS/VB2/GC/RoughnessPreview
|
||||
@onready var _load_albedo_button : Button = $VB/HS/VB2/GC/LoadAlbedo
|
||||
@onready var _load_normal_button : Button = $VB/HS/VB2/GC/LoadNormal
|
||||
@onready var _clear_albedo_button : Button = $VB/HS/VB2/GC/ClearAlbedo
|
||||
@onready var _clear_normal_button : Button = $VB/HS/VB2/GC/ClearNormal
|
||||
@onready var _mode_selector : OptionButton = $VB/HS/VB2/GC2/ModeSelector
|
||||
@onready var _add_slot_button : Button = $VB/HS/VB/HB/AddSlot
|
||||
@onready var _remove_slot_button : Button = $VB/HS/VB/HB/RemoveSlot
|
||||
|
||||
var _texture_set : HTerrainTextureSet
|
||||
var _undo_redo_manager : EditorUndoRedoManager
|
||||
|
||||
var _mode_confirmation_dialog : ConfirmationDialog
|
||||
var _delete_slot_confirmation_dialog : ConfirmationDialog
|
||||
var _load_texture_dialog : ConfirmationDialog
|
||||
var _load_texture_array_dialog : ConfirmationDialog
|
||||
var _load_texture_type := -1
|
||||
|
||||
var _logger = HT_Logger.get_for(self)
|
||||
|
||||
|
||||
func _init():
|
||||
get_ok_button().hide()
|
||||
|
||||
|
||||
func _ready():
|
||||
if HT_Util.is_in_edited_scene(self):
|
||||
return
|
||||
for id in HTerrainTextureSet.MODE_COUNT:
|
||||
var mode_name = HTerrainTextureSet.get_import_mode_name(id)
|
||||
_mode_selector.add_item(mode_name, id)
|
||||
|
||||
|
||||
func setup_dialogs(parent: Node):
|
||||
var d = HT_EditorUtil.create_open_texture_dialog()
|
||||
d.file_selected.connect(_on_LoadTextureDialog_file_selected)
|
||||
_load_texture_dialog = d
|
||||
add_child(d)
|
||||
|
||||
d = HT_EditorUtil.create_open_texture_array_dialog()
|
||||
d.file_selected.connect(_on_LoadTextureArrayDialog_file_selected)
|
||||
_load_texture_array_dialog = d
|
||||
add_child(d)
|
||||
|
||||
d = ConfirmationDialog.new()
|
||||
d.confirmed.connect(_on_ModeConfirmationDialog_confirmed)
|
||||
# This is ridiculous.
|
||||
# See https://github.com/godotengine/godot/issues/17460
|
||||
# d.connect("modal_closed", self, "_on_ModeConfirmationDialog_cancelled")
|
||||
# d.get_close_button().connect("pressed", self, "_on_ModeConfirmationDialog_cancelled")
|
||||
# d.get_cancel().connect("pressed", self, "_on_ModeConfirmationDialog_cancelled")
|
||||
_mode_confirmation_dialog = d
|
||||
add_child(d)
|
||||
|
||||
|
||||
func _notification(what: int):
|
||||
if HT_Util.is_in_edited_scene(self):
|
||||
return
|
||||
|
||||
if what == NOTIFICATION_EXIT_TREE:
|
||||
# Have to check for null in all of them,
|
||||
# because otherwise it breaks in the scene editor...
|
||||
if _load_texture_dialog != null:
|
||||
_load_texture_dialog.queue_free()
|
||||
if _load_texture_array_dialog != null:
|
||||
_load_texture_array_dialog.queue_free()
|
||||
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
if not visible:
|
||||
# Cleanup referenced resources
|
||||
set_texture_set(null)
|
||||
|
||||
|
||||
func set_undo_redo(ur: EditorUndoRedoManager):
|
||||
_undo_redo_manager = ur
|
||||
|
||||
|
||||
func set_texture_set(texture_set: HTerrainTextureSet):
|
||||
if _texture_set == texture_set:
|
||||
return
|
||||
|
||||
if _texture_set != null:
|
||||
_texture_set.changed.disconnect(_on_texture_set_changed)
|
||||
|
||||
_texture_set = texture_set
|
||||
|
||||
if _texture_set != null:
|
||||
_texture_set.changed.connect(_on_texture_set_changed)
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _on_texture_set_changed():
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _update_ui_from_data():
|
||||
var prev_selected_items = _slots_list.get_selected_items()
|
||||
|
||||
_slots_list.clear()
|
||||
|
||||
var slots_count := _texture_set.get_slots_count()
|
||||
for slot_index in slots_count:
|
||||
_slots_list.add_item("Texture {0}".format([slot_index]))
|
||||
|
||||
_set_selected_id(_mode_selector, _texture_set.get_mode())
|
||||
|
||||
if _slots_list.get_item_count() > 0:
|
||||
if len(prev_selected_items) > 0:
|
||||
var i : int = prev_selected_items[0]
|
||||
if i >= _slots_list.get_item_count():
|
||||
i = _slots_list.get_item_count() - 1
|
||||
_select_slot(i)
|
||||
else:
|
||||
_select_slot(0)
|
||||
else:
|
||||
_clear_previews()
|
||||
|
||||
var max_slots := HTerrainTextureSet.get_max_slots_for_mode(_texture_set.get_mode())
|
||||
_add_slot_button.disabled = slots_count >= max_slots
|
||||
_remove_slot_button.disabled = slots_count == 0
|
||||
|
||||
var buttons := [
|
||||
_load_albedo_button,
|
||||
_load_normal_button,
|
||||
_clear_albedo_button,
|
||||
_clear_normal_button
|
||||
]
|
||||
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
_add_slot_button.visible = true
|
||||
_remove_slot_button.visible = true
|
||||
_load_albedo_button.text = "Load..."
|
||||
_load_normal_button.text = "Load..."
|
||||
|
||||
for b in buttons:
|
||||
b.disabled = (slots_count == 0)
|
||||
|
||||
else:
|
||||
_add_slot_button.visible = false
|
||||
_remove_slot_button.visible = false
|
||||
_load_albedo_button.text = "Load Array..."
|
||||
_load_normal_button.text = "Load Array..."
|
||||
|
||||
for b in buttons:
|
||||
b.disabled = false
|
||||
|
||||
|
||||
static func _set_selected_id(ob: OptionButton, id: int):
|
||||
for i in ob.get_item_count():
|
||||
if ob.get_item_id(i) == id:
|
||||
ob.selected = i
|
||||
break
|
||||
|
||||
|
||||
func select_slot(slot_index: int):
|
||||
var count = _texture_set.get_slots_count()
|
||||
if count > 0:
|
||||
if slot_index >= count:
|
||||
slot_index = count - 1
|
||||
_select_slot(slot_index)
|
||||
|
||||
|
||||
func _clear_previews():
|
||||
var empty_texture : Texture2D = load(EMPTY_TEXTURE_PATH)
|
||||
if empty_texture == null:
|
||||
_logger.error(str("Failed to load empty texture ", EMPTY_TEXTURE_PATH))
|
||||
|
||||
_albedo_preview.texture = empty_texture
|
||||
_bump_preview.texture = empty_texture
|
||||
_normal_preview.texture = empty_texture
|
||||
_roughness_preview.texture = empty_texture
|
||||
|
||||
_albedo_preview.tooltip_text = _get_resource_path_or_empty(null)
|
||||
_bump_preview.tooltip_text = _get_resource_path_or_empty(null)
|
||||
_normal_preview.tooltip_text = _get_resource_path_or_empty(null)
|
||||
_roughness_preview.tooltip_text = _get_resource_path_or_empty(null)
|
||||
|
||||
|
||||
func _select_slot(slot_index: int):
|
||||
assert(slot_index >= 0)
|
||||
assert(slot_index < _texture_set.get_slots_count())
|
||||
|
||||
var empty_texture : Texture2D = load(EMPTY_TEXTURE_PATH)
|
||||
if empty_texture == null:
|
||||
_logger.error(str("Failed to load empty texture ", EMPTY_TEXTURE_PATH))
|
||||
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
var albedo_tex := \
|
||||
_texture_set.get_texture(slot_index, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
var normal_tex := \
|
||||
_texture_set.get_texture(slot_index, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
|
||||
|
||||
_albedo_preview.texture = albedo_tex if albedo_tex != null else empty_texture
|
||||
_bump_preview.texture = albedo_tex if albedo_tex != null else empty_texture
|
||||
_normal_preview.texture = normal_tex if normal_tex != null else empty_texture
|
||||
_roughness_preview.texture = normal_tex if normal_tex != null else empty_texture
|
||||
|
||||
_albedo_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
|
||||
_bump_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
|
||||
_normal_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
|
||||
_roughness_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
|
||||
|
||||
_albedo_preview.material.shader = HT_ColorShader
|
||||
_bump_preview.material.shader = HT_AlphaShader
|
||||
_normal_preview.material.shader = HT_ColorShader
|
||||
_roughness_preview.material.shader = HT_AlphaShader
|
||||
|
||||
_albedo_preview.material.set_shader_parameter("u_texture_array", null)
|
||||
_bump_preview.material.set_shader_parameter("u_texture_array", null)
|
||||
_normal_preview.material.set_shader_parameter("u_texture_array", null)
|
||||
_roughness_preview.material.set_shader_parameter("u_texture_array", null)
|
||||
|
||||
else:
|
||||
var albedo_tex := _texture_set.get_texture_array(HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
var normal_tex := _texture_set.get_texture_array(HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
|
||||
|
||||
_albedo_preview.texture = empty_texture
|
||||
_bump_preview.texture = empty_texture
|
||||
_normal_preview.texture = empty_texture
|
||||
_roughness_preview.texture = empty_texture
|
||||
|
||||
_albedo_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
|
||||
_bump_preview.tooltip_text = _get_resource_path_or_empty(albedo_tex)
|
||||
_normal_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
|
||||
_roughness_preview.tooltip_text = _get_resource_path_or_empty(normal_tex)
|
||||
|
||||
_albedo_preview.material.shader = HT_ColorSliceShader
|
||||
_bump_preview.material.shader = HT_AlphaSliceShader
|
||||
_normal_preview.material.shader = \
|
||||
HT_ColorSliceShader if normal_tex != null else HT_ColorShader
|
||||
_roughness_preview.material.shader = \
|
||||
HT_AlphaSliceShader if normal_tex != null else HT_AlphaShader
|
||||
|
||||
_albedo_preview.material.set_shader_parameter("u_texture_array", albedo_tex)
|
||||
_bump_preview.material.set_shader_parameter("u_texture_array", albedo_tex)
|
||||
_normal_preview.material.set_shader_parameter("u_texture_array", normal_tex)
|
||||
_roughness_preview.material.set_shader_parameter("u_texture_array", normal_tex)
|
||||
|
||||
_albedo_preview.material.set_shader_parameter("u_index", slot_index)
|
||||
_bump_preview.material.set_shader_parameter("u_index", slot_index)
|
||||
_normal_preview.material.set_shader_parameter("u_index", slot_index)
|
||||
_roughness_preview.material.set_shader_parameter("u_index", slot_index)
|
||||
|
||||
_slots_list.select(slot_index)
|
||||
|
||||
|
||||
static func _get_resource_path_or_empty(res: Resource) -> String:
|
||||
if res != null:
|
||||
return res.resource_path
|
||||
return "<empty>"
|
||||
|
||||
|
||||
func _on_ImportButton_pressed():
|
||||
import_selected.emit()
|
||||
|
||||
|
||||
func _on_CloseButton_pressed():
|
||||
hide()
|
||||
|
||||
|
||||
func _get_undo_redo_for_texture_set() -> UndoRedo:
|
||||
return _undo_redo_manager.get_history_undo_redo(
|
||||
_undo_redo_manager.get_object_history_id(_texture_set))
|
||||
|
||||
|
||||
func _on_AddSlot_pressed():
|
||||
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
|
||||
var slot_index = _texture_set.get_slots_count()
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
ur.create_action("HTerrainTextureSet: add slot")
|
||||
ur.add_do_method(_texture_set.insert_slot.bind(-1))
|
||||
ur.add_undo_method(_texture_set.remove_slot.bind(slot_index))
|
||||
ur.commit_action()
|
||||
|
||||
|
||||
func _on_RemoveSlot_pressed():
|
||||
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
|
||||
|
||||
var slot_index = _slots_list.get_selected_items()[0]
|
||||
var textures = []
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
textures.append(_texture_set.get_texture(slot_index, type))
|
||||
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
|
||||
ur.create_action("HTerrainTextureSet: remove slot")
|
||||
|
||||
ur.add_do_method(_texture_set.remove_slot.bind(slot_index))
|
||||
|
||||
ur.add_undo_method(_texture_set.insert_slot.bind(slot_index))
|
||||
for type in len(textures):
|
||||
var texture = textures[type]
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if texture == null:
|
||||
ur.add_undo_method(_texture_set.set_texture_null.bind(slot_index, type))
|
||||
else:
|
||||
ur.add_undo_method(_texture_set.set_texture.bind(slot_index, type, texture))
|
||||
|
||||
ur.commit_action()
|
||||
|
||||
|
||||
func _on_SlotsList_item_selected(index: int):
|
||||
_select_slot(index)
|
||||
|
||||
|
||||
func _open_load_texture_dialog(type: int):
|
||||
_load_texture_type = type
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
_load_texture_dialog.popup_centered_ratio()
|
||||
else:
|
||||
_load_texture_array_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _on_LoadAlbedo_pressed():
|
||||
_open_load_texture_dialog(HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
|
||||
|
||||
func _on_LoadNormal_pressed():
|
||||
_open_load_texture_dialog(HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
|
||||
|
||||
|
||||
func _set_texture_action(slot_index: int, texture: Texture, type: int):
|
||||
var prev_texture = _texture_set.get_texture(slot_index, type)
|
||||
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
|
||||
ur.create_action("HTerrainTextureSet: load texture")
|
||||
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if texture == null:
|
||||
ur.add_do_method(_texture_set.set_texture_null.bind(slot_index, type))
|
||||
else:
|
||||
ur.add_do_method(_texture_set.set_texture.bind(slot_index, type, texture))
|
||||
ur.add_do_method(self._select_slot.bind(slot_index))
|
||||
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if prev_texture == null:
|
||||
ur.add_undo_method(_texture_set.set_texture_null.bind(slot_index, type))
|
||||
else:
|
||||
ur.add_undo_method(_texture_set.set_texture.bind(slot_index, type, prev_texture))
|
||||
ur.add_undo_method(self._select_slot.bind(slot_index))
|
||||
|
||||
ur.commit_action()
|
||||
|
||||
|
||||
func _set_texture_array_action(slot_index: int, texture_array: TextureLayered, type: int):
|
||||
var prev_texture_array = _texture_set.get_texture_array(type)
|
||||
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
|
||||
ur.create_action("HTerrainTextureSet: load texture array")
|
||||
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if texture_array == null:
|
||||
ur.add_do_method(_texture_set.set_texture_array_null.bind(type))
|
||||
# Can't select a slot after this because there won't be any after the array is removed
|
||||
else:
|
||||
ur.add_do_method(_texture_set.set_texture_array.bind(type, texture_array))
|
||||
ur.add_do_method(self._select_slot.bind(slot_index))
|
||||
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if prev_texture_array == null:
|
||||
ur.add_undo_method(_texture_set.set_texture_array_null.bind(type))
|
||||
# Can't select a slot after this because there won't be any after the array is removed
|
||||
else:
|
||||
ur.add_undo_method(_texture_set.set_texture_array.bind(type, prev_texture_array))
|
||||
ur.add_undo_method(self._select_slot.bind(slot_index))
|
||||
|
||||
ur.commit_action()
|
||||
|
||||
|
||||
func _on_LoadTextureDialog_file_selected(fpath: String):
|
||||
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES)
|
||||
var texture = load(fpath)
|
||||
assert(texture != null)
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
_set_texture_action(slot_index, texture, _load_texture_type)
|
||||
|
||||
|
||||
func _on_LoadTextureArrayDialog_file_selected(fpath: String):
|
||||
assert(_texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURE_ARRAYS)
|
||||
var texture_array = load(fpath)
|
||||
assert(texture_array != null)
|
||||
# It's possible no slot exists at the moment,
|
||||
# because there could be no texture array already set.
|
||||
# The number of slots in the new array might also be different.
|
||||
# So in this case we'll default to selecting the first slot.
|
||||
var slot_index := 0
|
||||
_set_texture_array_action(slot_index, texture_array, _load_texture_type)
|
||||
|
||||
|
||||
func _on_ClearAlbedo_pressed():
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
_set_texture_action(slot_index, null, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
else:
|
||||
_set_texture_array_action(slot_index, null, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
|
||||
|
||||
func _on_ClearNormal_pressed():
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
_set_texture_action(slot_index, null, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
|
||||
else:
|
||||
_set_texture_array_action(slot_index, null, HTerrainTextureSet.TYPE_NORMAL_ROUGHNESS)
|
||||
|
||||
|
||||
func _on_ModeSelector_item_selected(index: int):
|
||||
var id = _mode_selector.get_selected_id()
|
||||
if id == _texture_set.get_mode():
|
||||
return
|
||||
|
||||
# Early-cancel the change in OptionButton, so we won't need to rely on
|
||||
# the (inexistent) cancel signal from ConfirmationDialog
|
||||
_set_selected_id(_mode_selector, _texture_set.get_mode())
|
||||
|
||||
if not _texture_set.has_any_textures():
|
||||
_switch_mode_action()
|
||||
|
||||
else:
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
_mode_confirmation_dialog.window_title = "Switch to TextureArrays"
|
||||
_mode_confirmation_dialog.dialog_text = \
|
||||
"This will unload all textures currently setup. Do you want to continue?"
|
||||
_mode_confirmation_dialog.popup_centered()
|
||||
|
||||
else:
|
||||
_mode_confirmation_dialog.window_title = "Switch to Textures"
|
||||
_mode_confirmation_dialog.dialog_text = \
|
||||
"This will unload all textures currently setup. Do you want to continue?"
|
||||
_mode_confirmation_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_ModeConfirmationDialog_confirmed():
|
||||
_switch_mode_action()
|
||||
|
||||
|
||||
func _switch_mode_action():
|
||||
var mode := _texture_set.get_mode()
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
|
||||
if mode == HTerrainTextureSet.MODE_TEXTURES:
|
||||
ur.create_action("HTerrainTextureSet: switch to TextureArrays")
|
||||
ur.add_do_method(_texture_set.set_mode.bind(HTerrainTextureSet.MODE_TEXTURE_ARRAYS))
|
||||
backup_for_undo(_texture_set, ur)
|
||||
|
||||
else:
|
||||
ur.create_action("HTerrainTextureSet: switch to Textures")
|
||||
ur.add_do_method(_texture_set.set_mode.bind(HTerrainTextureSet.MODE_TEXTURES))
|
||||
backup_for_undo(_texture_set, ur)
|
||||
|
||||
ur.commit_action()
|
||||
|
||||
|
||||
static func backup_for_undo(texture_set: HTerrainTextureSet, ur: UndoRedo):
|
||||
var mode := texture_set.get_mode()
|
||||
|
||||
ur.add_undo_method(texture_set.clear)
|
||||
ur.add_undo_method(texture_set.set_mode.bind(mode))
|
||||
|
||||
if mode == HTerrainTextureSet.MODE_TEXTURES:
|
||||
# Backup slots
|
||||
var slot_count := texture_set.get_slots_count()
|
||||
var type_textures := []
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
var textures := []
|
||||
for slot_index in slot_count:
|
||||
textures.append(texture_set.get_texture(slot_index, type))
|
||||
type_textures.append(textures)
|
||||
|
||||
for type in len(type_textures):
|
||||
var textures = type_textures[type]
|
||||
for slot_index in len(textures):
|
||||
ur.add_undo_method(texture_set.insert_slot.bind(slot_index))
|
||||
var texture = textures[slot_index]
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if texture == null:
|
||||
ur.add_undo_method(texture_set.set_texture_null.bind(slot_index, type))
|
||||
else:
|
||||
ur.add_undo_method(texture_set.set_texture.bind(slot_index, type, texture))
|
||||
|
||||
else:
|
||||
# Backup slots
|
||||
var type_textures := []
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
type_textures.append(texture_set.get_texture_array(type))
|
||||
|
||||
for type in len(type_textures):
|
||||
var texture_array = type_textures[type]
|
||||
# TODO This branch only exists because of a flaw in UndoRedo
|
||||
# See https://github.com/godotengine/godot/issues/36895
|
||||
if texture_array == null:
|
||||
ur.add_undo_method(texture_set.set_texture_array_null.bind(type))
|
||||
else:
|
||||
ur.add_undo_method(texture_set.set_texture_array.bind(type, texture_array))
|
||||
|
||||
|
||||
#func _on_ModeConfirmationDialog_cancelled():
|
||||
# print("Cancelled")
|
||||
# _set_selected_id(_mode_selector, _texture_set.get_mode())
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfjw5kcur7ns6
|
||||
@@ -0,0 +1,194 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://c0e7ifnoygvr6"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cfjw5kcur7ns6" path="res://addons/zylann.hterrain/tools/texture_editor/set_editor/texture_set_editor.gd" id="1"]
|
||||
[ext_resource type="Shader" uid="uid://6bb66fu1fw8n" path="res://addons/zylann.hterrain/tools/texture_editor/display_alpha.gdshader" id="2"]
|
||||
[ext_resource type="Shader" uid="uid://b1wwjr82d2o37" path="res://addons/zylann.hterrain/tools/texture_editor/display_color.gdshader" id="3"]
|
||||
[ext_resource type="PackedScene" path="res://addons/zylann.hterrain/tools/util/dialog_fitter.tscn" id="5"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="1"]
|
||||
shader = ExtResource("3")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="2"]
|
||||
shader = ExtResource("2")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="3"]
|
||||
shader = ExtResource("3")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="4"]
|
||||
shader = ExtResource("2")
|
||||
|
||||
[node name="TextureSetEditor" type="AcceptDialog"]
|
||||
title = "TextureSet Editor"
|
||||
size = Vector2i(666, 341)
|
||||
min_size = Vector2i(652, 320)
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -18.0
|
||||
|
||||
[node name="HS" type="HSplitContainer" parent="VB"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="VB/HS"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SlotsList" type="ItemList" parent="VB/HS/VB"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="VB/HS/VB"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddSlot" type="Button" parent="VB/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "+"
|
||||
|
||||
[node name="Control" type="Control" parent="VB/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RemoveSlot" type="Button" parent="VB/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "-"
|
||||
|
||||
[node name="VB2" type="VBoxContainer" parent="VB/HS"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GC" type="GridContainer" parent="VB/HS/VB2"]
|
||||
layout_mode = 2
|
||||
columns = 4
|
||||
|
||||
[node name="AlbedoLabel" type="Label" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Albedo"
|
||||
|
||||
[node name="AlbedoExtraLabel" type="Label" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "+ alpha bump"
|
||||
|
||||
[node name="NormalLabel" type="Label" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Normal"
|
||||
|
||||
[node name="NormalExtraLabel" type="Label" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "+ alpha roughness"
|
||||
|
||||
[node name="AlbedoPreview" type="TextureRect" parent="VB/HS/VB2/GC"]
|
||||
material = SubResource("1")
|
||||
custom_minimum_size = Vector2(128, 128)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="BumpPreview" type="TextureRect" parent="VB/HS/VB2/GC"]
|
||||
material = SubResource("2")
|
||||
custom_minimum_size = Vector2(128, 128)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="NormalPreview" type="TextureRect" parent="VB/HS/VB2/GC"]
|
||||
material = SubResource("3")
|
||||
custom_minimum_size = Vector2(128, 128)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="RoughnessPreview" type="TextureRect" parent="VB/HS/VB2/GC"]
|
||||
material = SubResource("4")
|
||||
custom_minimum_size = Vector2(128, 128)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="LoadAlbedo" type="Button" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Load..."
|
||||
|
||||
[node name="Spacer" type="Control" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LoadNormal" type="Button" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Load..."
|
||||
|
||||
[node name="Spacer2" type="Control" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ClearAlbedo" type="Button" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Clear"
|
||||
|
||||
[node name="Spacer3" type="Control" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ClearNormal" type="Button" parent="VB/HS/VB2/GC"]
|
||||
layout_mode = 2
|
||||
text = "Clear"
|
||||
|
||||
[node name="HSeparator" type="Control" parent="VB/HS/VB2"]
|
||||
custom_minimum_size = Vector2(0, 4)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GC2" type="HBoxContainer" parent="VB/HS/VB2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VB/HS/VB2/GC2"]
|
||||
layout_mode = 2
|
||||
text = "Mode"
|
||||
|
||||
[node name="ModeSelector" type="OptionButton" parent="VB/HS/VB2/GC2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Spacer" type="Control" parent="VB/HS/VB2/GC2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Spacer" type="Control" parent="VB"]
|
||||
custom_minimum_size = Vector2(0, 4)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="VB"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ImportButton" type="Button" parent="VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "Import..."
|
||||
|
||||
[node name="CloseButton" type="Button" parent="VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="VB"]
|
||||
custom_minimum_size = Vector2(0, 2)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DialogFitter" parent="." instance=ExtResource("5")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 658.0
|
||||
offset_bottom = 323.0
|
||||
|
||||
[connection signal="item_selected" from="VB/HS/VB/SlotsList" to="." method="_on_SlotsList_item_selected"]
|
||||
[connection signal="pressed" from="VB/HS/VB/HB/AddSlot" to="." method="_on_AddSlot_pressed"]
|
||||
[connection signal="pressed" from="VB/HS/VB/HB/RemoveSlot" to="." method="_on_RemoveSlot_pressed"]
|
||||
[connection signal="pressed" from="VB/HS/VB2/GC/LoadAlbedo" to="." method="_on_LoadAlbedo_pressed"]
|
||||
[connection signal="pressed" from="VB/HS/VB2/GC/LoadNormal" to="." method="_on_LoadNormal_pressed"]
|
||||
[connection signal="pressed" from="VB/HS/VB2/GC/ClearAlbedo" to="." method="_on_ClearAlbedo_pressed"]
|
||||
[connection signal="pressed" from="VB/HS/VB2/GC/ClearNormal" to="." method="_on_ClearNormal_pressed"]
|
||||
[connection signal="item_selected" from="VB/HS/VB2/GC2/ModeSelector" to="." method="_on_ModeSelector_item_selected"]
|
||||
[connection signal="pressed" from="VB/HB/ImportButton" to="." method="_on_ImportButton_pressed"]
|
||||
[connection signal="pressed" from="VB/HB/CloseButton" to="." method="_on_CloseButton_pressed"]
|
||||
@@ -0,0 +1,920 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
const HTerrainTextureSet = preload("../../../hterrain_texture_set.gd")
|
||||
const HT_Logger = preload("../../../util/logger.gd")
|
||||
const HT_EditorUtil = preload("../../util/editor_util.gd")
|
||||
const HT_Errors = preload("../../../util/errors.gd")
|
||||
const HT_TextureSetEditor = preload("./texture_set_editor.gd")
|
||||
const HT_Result = preload("../../util/result.gd")
|
||||
const HT_Util = preload("../../../util/util.gd")
|
||||
const HT_PackedTextureUtil = preload("../../packed_textures/packed_texture_util.gd")
|
||||
const ResourceImporterTexture_Unexposed = preload("../../util/resource_importer_texture.gd")
|
||||
const ResourceImporterTextureLayered_Unexposed = preload(
|
||||
"../../util/resource_importer_texture_layered.gd")
|
||||
|
||||
const HT_NormalMapPreviewShader = preload("../display_normal.gdshader")
|
||||
|
||||
const COMPRESS_RAW = 0
|
||||
const COMPRESS_LOSSLESS = 1
|
||||
const COMPRESS_LOSSY = 1
|
||||
const COMPRESS_VRAM = 2
|
||||
const COMPRESS_COUNT = 3
|
||||
|
||||
const _compress_names = ["Raw", "Lossless", "Lossy", "VRAM"]
|
||||
|
||||
# Indexed by HTerrainTextureSet.SRC_TYPE_* constants
|
||||
const _smart_pick_file_keywords = [
|
||||
["albedo", "color", "col", "diffuse"],
|
||||
["bump", "height", "depth", "displacement", "disp"],
|
||||
["normal", "norm", "nrm"],
|
||||
["roughness", "rough", "rgh"]
|
||||
]
|
||||
|
||||
signal import_finished
|
||||
|
||||
@onready var _texture_editors = [
|
||||
$Import/HS/VB2/HB/Albedo,
|
||||
$Import/HS/VB2/HB/Bump,
|
||||
$Import/HS/VB2/HB/Normal,
|
||||
$Import/HS/VB2/HB/Roughness
|
||||
]
|
||||
|
||||
@onready var _slots_list : ItemList = $Import/HS/VB/SlotsList
|
||||
|
||||
# TODO Some shortcuts to import options were disabled in the GUI because of Godot issues.
|
||||
# If users want to customize that, they need to do it on the files directly.
|
||||
#
|
||||
# There is no script API in Godot to choose the import settings of a generated file.
|
||||
# They always start with the defaults, and the only implemented case is for the import dock.
|
||||
# It appeared possible to reverse-engineer and write a .import file as done in HTerrainData,
|
||||
# however when I tried this with custom importers, Godot stopped importing after scan(),
|
||||
# and the resources could not load. However, selecting them each and clicking "Reimport"
|
||||
# did import them fine. Unfortunately, this short-circuits the workflow.
|
||||
# Since I have no idea what's going on with this reverse-engineering, I had to drop those options.
|
||||
# Godot needs an API to import specific files and choose settings before the first import.
|
||||
#
|
||||
# Godot 4: now we'll really need it, let's enable and we'll see if it works
|
||||
# when we can test the workflow...
|
||||
const _WRITE_IMPORT_FILES = true
|
||||
|
||||
@onready var _import_mode_selector : OptionButton = $Import/GC/ImportModeSelector
|
||||
@onready var _compression_selector : OptionButton = $Import/GC/CompressionSelector
|
||||
@onready var _resolution_spinbox : SpinBox = $Import/GC/ResolutionSpinBox
|
||||
@onready var _mipmaps_checkbox : CheckBox = $Import/GC/MipmapsCheckbox
|
||||
@onready var _add_slot_button : Button = $Import/HS/VB/HB/AddSlotButton
|
||||
@onready var _remove_slot_button : Button = $Import/HS/VB/HB/RemoveSlotButton
|
||||
@onready var _import_directory_line_edit : LineEdit = $Import/HB2/ImportDirectoryLineEdit
|
||||
@onready var _normalmap_flip_checkbox : CheckBox = $Import/HS/VB2/HB/Normal/NormalMapFlipY
|
||||
|
||||
var _texture_set : HTerrainTextureSet
|
||||
var _undo_redo_manager : EditorUndoRedoManager
|
||||
var _logger = HT_Logger.get_for(self)
|
||||
|
||||
# This is normally an `EditorFileDialog`. I can't type-hint this one properly,
|
||||
# because when I test this UI in isolation, I can't use `EditorFileDialog`.
|
||||
var _load_texture_dialog : ConfirmationDialog
|
||||
var _load_texture_type : int = -1
|
||||
var _error_popup : AcceptDialog
|
||||
var _info_popup : AcceptDialog
|
||||
var _delete_confirmation_popup : ConfirmationDialog
|
||||
var _open_dir_dialog : ConfirmationDialog
|
||||
var _editor_file_system : EditorFileSystem
|
||||
var _normalmap_material : ShaderMaterial
|
||||
|
||||
var _import_mode := HTerrainTextureSet.MODE_TEXTURES
|
||||
|
||||
class HT_TextureSetImportEditorSlot:
|
||||
# Array of strings.
|
||||
# Can be either path to images, hexadecimal colors starting with #, or empty string for "null".
|
||||
var texture_paths := []
|
||||
var flip_normalmap_y := false
|
||||
|
||||
func _init():
|
||||
for i in HTerrainTextureSet.SRC_TYPE_COUNT:
|
||||
texture_paths.append("")
|
||||
|
||||
# Array of HT_TextureSetImportEditorSlot
|
||||
var _slots_data := []
|
||||
|
||||
var _import_settings := {
|
||||
"mipmaps": true,
|
||||
"compression": COMPRESS_VRAM,
|
||||
"resolution": 512
|
||||
}
|
||||
|
||||
|
||||
func _init():
|
||||
get_ok_button().hide()
|
||||
|
||||
# Default data
|
||||
_slots_data.clear()
|
||||
for i in 4:
|
||||
_slots_data.append(HT_TextureSetImportEditorSlot.new())
|
||||
|
||||
|
||||
func _ready():
|
||||
if HT_Util.is_in_edited_scene(self):
|
||||
return
|
||||
|
||||
for src_type in len(_texture_editors):
|
||||
var ed = _texture_editors[src_type]
|
||||
var typename = HTerrainTextureSet.get_source_texture_type_name(src_type)
|
||||
ed.set_label(typename.capitalize())
|
||||
ed.load_pressed.connect(_on_texture_load_pressed.bind(src_type))
|
||||
ed.clear_pressed.connect(_on_texture_clear_pressed.bind(src_type))
|
||||
|
||||
for import_mode in HTerrainTextureSet.MODE_COUNT:
|
||||
var n = HTerrainTextureSet.get_import_mode_name(import_mode)
|
||||
_import_mode_selector.add_item(n, import_mode)
|
||||
|
||||
for compress_mode in COMPRESS_COUNT:
|
||||
var n = _compress_names[compress_mode]
|
||||
_compression_selector.add_item(n, compress_mode)
|
||||
|
||||
_normalmap_material = ShaderMaterial.new()
|
||||
_normalmap_material.shader = HT_NormalMapPreviewShader
|
||||
_texture_editors[HTerrainTextureSet.SRC_TYPE_NORMAL].set_material(_normalmap_material)
|
||||
|
||||
|
||||
func setup_dialogs(parent: Node):
|
||||
var d = HT_EditorUtil.create_open_image_dialog()
|
||||
d.file_selected.connect(_on_LoadTextureDialog_file_selected)
|
||||
_load_texture_dialog = d
|
||||
add_child(d)
|
||||
|
||||
d = AcceptDialog.new()
|
||||
d.title = "Import error"
|
||||
_error_popup = d
|
||||
add_child(_error_popup)
|
||||
|
||||
d = AcceptDialog.new()
|
||||
d.title = "Info"
|
||||
_info_popup = d
|
||||
add_child(_info_popup)
|
||||
|
||||
d = ConfirmationDialog.new()
|
||||
d.confirmed.connect(_on_delete_confirmation_popup_confirmed)
|
||||
_delete_confirmation_popup = d
|
||||
add_child(_delete_confirmation_popup)
|
||||
|
||||
d = HT_EditorUtil.create_open_dir_dialog()
|
||||
d.title = "Choose import directory"
|
||||
d.dir_selected.connect(_on_OpenDirDialog_dir_selected)
|
||||
_open_dir_dialog = d
|
||||
add_child(_open_dir_dialog)
|
||||
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _notification(what: int):
|
||||
if what == NOTIFICATION_EXIT_TREE:
|
||||
# Have to check for null in all of them,
|
||||
# because otherwise it breaks in the scene editor...
|
||||
if _load_texture_dialog != null:
|
||||
_load_texture_dialog.queue_free()
|
||||
if _error_popup != null:
|
||||
_error_popup.queue_free()
|
||||
if _delete_confirmation_popup != null:
|
||||
_delete_confirmation_popup.queue_free()
|
||||
if _open_dir_dialog != null:
|
||||
_open_dir_dialog.queue_free()
|
||||
if _info_popup != null:
|
||||
_info_popup.queue_free()
|
||||
|
||||
|
||||
# TODO Is it still necessary for an import tab?
|
||||
func set_undo_redo(ur: EditorUndoRedoManager):
|
||||
_undo_redo_manager = ur
|
||||
|
||||
|
||||
func set_editor_file_system(efs: EditorFileSystem):
|
||||
_editor_file_system = efs
|
||||
|
||||
|
||||
func set_texture_set(texture_set: HTerrainTextureSet):
|
||||
if _texture_set == texture_set:
|
||||
# TODO What if the set was actually modified since?
|
||||
return
|
||||
_texture_set = texture_set
|
||||
|
||||
_slots_data.clear()
|
||||
|
||||
if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
|
||||
var slots_count = _texture_set.get_slots_count()
|
||||
|
||||
for slot_index in slots_count:
|
||||
var slot := HT_TextureSetImportEditorSlot.new()
|
||||
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
var texture = _texture_set.get_texture(slot_index, type)
|
||||
|
||||
if texture == null or texture.resource_path == "":
|
||||
continue
|
||||
|
||||
if not texture.resource_path.ends_with(".packed_tex"):
|
||||
continue
|
||||
|
||||
var import_data := _parse_json_file(texture.resource_path)
|
||||
if import_data.is_empty() or not import_data.has("src"):
|
||||
continue
|
||||
|
||||
var src_types = HTerrainTextureSet.get_src_types_from_type(type)
|
||||
|
||||
var src_data = import_data["src"]
|
||||
if src_data.has("rgb"):
|
||||
slot.texture_paths[src_types[0]] = src_data["rgb"]
|
||||
if src_data.has("a"):
|
||||
slot.texture_paths[src_types[1]] = src_data["a"]
|
||||
|
||||
_slots_data.append(slot)
|
||||
|
||||
else:
|
||||
var slots_count := _texture_set.get_slots_count()
|
||||
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
var texture_array := _texture_set.get_texture_array(type)
|
||||
|
||||
if texture_array == null or texture_array.resource_path == "":
|
||||
continue
|
||||
|
||||
if not texture_array.resource_path.ends_with(".packed_texarr"):
|
||||
continue
|
||||
|
||||
var import_data := _parse_json_file(texture_array.resource_path)
|
||||
if import_data.is_empty() or not import_data.has("layers"):
|
||||
continue
|
||||
|
||||
var layers_data = import_data["layers"]
|
||||
|
||||
for slot_index in len(layers_data):
|
||||
var src_data = layers_data[slot_index]
|
||||
|
||||
var src_types = HTerrainTextureSet.get_src_types_from_type(type)
|
||||
|
||||
while slot_index >= len(_slots_data):
|
||||
var slot = HT_TextureSetImportEditorSlot.new()
|
||||
_slots_data.append(slot)
|
||||
|
||||
var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
|
||||
|
||||
if src_data.has("rgb"):
|
||||
slot.texture_paths[src_types[0]] = src_data["rgb"]
|
||||
if src_data.has("a"):
|
||||
slot.texture_paths[src_types[1]] = src_data["a"]
|
||||
|
||||
# TODO If the set doesn't have a file, use terrain path by default?
|
||||
if texture_set.resource_path != "":
|
||||
var dir = texture_set.resource_path.get_base_dir()
|
||||
_import_directory_line_edit.text = dir
|
||||
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _parse_json_file(fpath: String) -> Dictionary:
|
||||
var f := FileAccess.open(fpath, FileAccess.READ)
|
||||
if f == null:
|
||||
var err := FileAccess.get_open_error()
|
||||
_logger.error("Could not load {0}: {1}".format([fpath, HT_Errors.get_message(err)]))
|
||||
return {}
|
||||
|
||||
var json_text := f.get_as_text()
|
||||
var json := JSON.new()
|
||||
var json_err := json.parse(json_text)
|
||||
if json_err != OK:
|
||||
_logger.error("Failed to parse {0}: {1}".format([fpath, json.get_error_message()]))
|
||||
return {}
|
||||
|
||||
return json.data
|
||||
|
||||
|
||||
func _update_ui_from_data():
|
||||
var prev_selected_items := _slots_list.get_selected_items()
|
||||
|
||||
_slots_list.clear()
|
||||
|
||||
for slot_index in len(_slots_data):
|
||||
_slots_list.add_item("Texture {0}".format([slot_index]))
|
||||
|
||||
_resolution_spinbox.value = _import_settings.resolution
|
||||
_mipmaps_checkbox.button_pressed = _import_settings.mipmaps
|
||||
_set_selected_id(_compression_selector, _import_settings.compression)
|
||||
_set_selected_id(_import_mode_selector, _import_mode)
|
||||
|
||||
var has_slots : bool = _slots_list.get_item_count() > 0
|
||||
|
||||
for ed in _texture_editors:
|
||||
ed.set_enabled(has_slots)
|
||||
_normalmap_flip_checkbox.disabled = not has_slots
|
||||
|
||||
if has_slots:
|
||||
if len(prev_selected_items) > 0:
|
||||
var i : int = prev_selected_items[0]
|
||||
if i >= _slots_list.get_item_count():
|
||||
i = _slots_list.get_item_count() - 1
|
||||
_select_slot(i)
|
||||
else:
|
||||
_select_slot(0)
|
||||
else:
|
||||
for type in HTerrainTextureSet.SRC_TYPE_COUNT:
|
||||
_set_ui_slot_texture_from_path("", type)
|
||||
|
||||
var max_slots := HTerrainTextureSet.get_max_slots_for_mode(_import_mode)
|
||||
_add_slot_button.disabled = (len(_slots_data) >= max_slots)
|
||||
_remove_slot_button.disabled = (len(_slots_data) == 0)
|
||||
|
||||
|
||||
static func _set_selected_id(ob: OptionButton, id: int):
|
||||
for i in ob.get_item_count():
|
||||
if ob.get_item_id(i) == id:
|
||||
ob.selected = i
|
||||
break
|
||||
|
||||
|
||||
func _select_slot(slot_index: int):
|
||||
assert(slot_index >= 0)
|
||||
assert(slot_index < len(_slots_data))
|
||||
var slot = _slots_data[slot_index]
|
||||
|
||||
for type in HTerrainTextureSet.SRC_TYPE_COUNT:
|
||||
var im_path : String = slot.texture_paths[type]
|
||||
_set_ui_slot_texture_from_path(im_path, type)
|
||||
|
||||
_slots_list.select(slot_index)
|
||||
|
||||
_normalmap_flip_checkbox.button_pressed = slot.flip_normalmap_y
|
||||
_normalmap_material.set_shader_parameter("u_flip_y", slot.flip_normalmap_y)
|
||||
|
||||
|
||||
func _set_ui_slot_texture_from_path(im_path: String, type: int):
|
||||
var ed = _texture_editors[type]
|
||||
|
||||
if im_path == "":
|
||||
ed.set_texture(null)
|
||||
ed.set_texture_tooltip("<empty>")
|
||||
return
|
||||
|
||||
var im : Image
|
||||
|
||||
if im_path.begins_with("#") and im_path.find(".") == -1:
|
||||
# The path is actually a preset for a uniform color.
|
||||
# This is a feature of packed texture descriptor files.
|
||||
# Make a small placeholder image.
|
||||
var color := Color(im_path)
|
||||
im = Image.create(4, 4, false, Image.FORMAT_RGBA8)
|
||||
im.fill(color)
|
||||
|
||||
else:
|
||||
# Regular path
|
||||
im = Image.new()
|
||||
var err := im.load(im_path)
|
||||
if err != OK:
|
||||
_logger.error(str("Unable to load image from ", im_path))
|
||||
# TODO Different icon for images that can't load?
|
||||
ed.set_texture(null)
|
||||
ed.set_texture_tooltip("<empty>")
|
||||
return
|
||||
|
||||
var tex := ImageTexture.create_from_image(im)
|
||||
ed.set_texture(tex)
|
||||
ed.set_texture_tooltip(im_path)
|
||||
|
||||
|
||||
func _set_source_image(fpath: String, type: int):
|
||||
_set_ui_slot_texture_from_path(fpath, type)
|
||||
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
#var prev_path = _texture_set.get_source_image_path(slot_index, type)
|
||||
|
||||
var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
|
||||
slot.texture_paths[type] = fpath
|
||||
|
||||
|
||||
func _set_import_property(key: String, value):
|
||||
var prev_value = _import_settings[key]
|
||||
# This is needed, notably because CheckBox emits a signal too when we set it from code...
|
||||
if prev_value == value:
|
||||
return
|
||||
|
||||
_import_settings[key] = value
|
||||
|
||||
|
||||
func _on_texture_load_pressed(type: int):
|
||||
_load_texture_type = type
|
||||
_load_texture_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _on_LoadTextureDialog_file_selected(fpath: String):
|
||||
_set_source_image(fpath, _load_texture_type)
|
||||
|
||||
if _load_texture_type == HTerrainTextureSet.SRC_TYPE_ALBEDO:
|
||||
_smart_pick_files(fpath)
|
||||
|
||||
|
||||
# Attempts to load source images of other types by looking at how the albedo file was named
|
||||
func _smart_pick_files(albedo_fpath: String):
|
||||
var albedo_words = _smart_pick_file_keywords[HTerrainTextureSet.SRC_TYPE_ALBEDO]
|
||||
|
||||
var albedo_fname := albedo_fpath.get_file()
|
||||
var albedo_fname_lower = albedo_fname.to_lower()
|
||||
var fname_pattern = ""
|
||||
|
||||
for albedo_word in albedo_words:
|
||||
var i = albedo_fname_lower.find(albedo_word, 0)
|
||||
if i != -1:
|
||||
fname_pattern = \
|
||||
albedo_fname.substr(0, i) + "{0}" + albedo_fname.substr(i + len(albedo_word))
|
||||
break
|
||||
|
||||
if fname_pattern == "":
|
||||
return
|
||||
|
||||
var dirpath := albedo_fpath.get_base_dir()
|
||||
var fnames := _get_files_in_directory(dirpath, _logger)
|
||||
|
||||
var types := [
|
||||
HTerrainTextureSet.SRC_TYPE_BUMP,
|
||||
HTerrainTextureSet.SRC_TYPE_NORMAL,
|
||||
HTerrainTextureSet.SRC_TYPE_ROUGHNESS
|
||||
]
|
||||
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
|
||||
for type in types:
|
||||
var slot = _slots_data[slot_index]
|
||||
if slot.texture_paths[type] != "":
|
||||
# Already set, don't overwrite unwantedly
|
||||
continue
|
||||
|
||||
var keywords = _smart_pick_file_keywords[type]
|
||||
|
||||
for key in keywords:
|
||||
var expected_fname = fname_pattern.format([key])
|
||||
|
||||
var found := false
|
||||
|
||||
for i in len(fnames):
|
||||
var fname : String = fnames[i]
|
||||
|
||||
# TODO We should probably ignore extensions?
|
||||
if fname.to_lower() == expected_fname.to_lower():
|
||||
var fpath = dirpath.path_join(fname)
|
||||
_set_source_image(fpath, type)
|
||||
found = true
|
||||
break
|
||||
|
||||
if found:
|
||||
break
|
||||
|
||||
|
||||
static func _get_files_in_directory(dirpath: String, logger) -> Array:
|
||||
var dir := DirAccess.open(dirpath)
|
||||
var err := DirAccess.get_open_error()
|
||||
if err != OK:
|
||||
logger.error("Could not open directory {0}: {1}" \
|
||||
.format([dirpath, HT_Errors.get_message(err)]))
|
||||
return []
|
||||
|
||||
dir.include_hidden = false
|
||||
dir.include_navigational = false
|
||||
|
||||
err = dir.list_dir_begin()
|
||||
if err != OK:
|
||||
logger.error("Could not probe directory {0}: {1}" \
|
||||
.format([dirpath, HT_Errors.get_message(err)]))
|
||||
return []
|
||||
|
||||
var files := []
|
||||
var fname := dir.get_next()
|
||||
while fname != "":
|
||||
if not dir.current_is_dir():
|
||||
files.append(fname)
|
||||
fname = dir.get_next()
|
||||
|
||||
return files
|
||||
|
||||
|
||||
func _on_texture_clear_pressed(type: int):
|
||||
_set_source_image("", type)
|
||||
|
||||
|
||||
func _on_SlotsList_item_selected(index: int):
|
||||
_select_slot(index)
|
||||
|
||||
|
||||
func _on_ImportModeSelector_item_selected(index: int):
|
||||
var mode : int = _import_mode_selector.get_item_id(index)
|
||||
if mode != _import_mode:
|
||||
#_set_import_property("mode", mode)
|
||||
_import_mode = mode
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _on_CompressionSelector_item_selected(index: int):
|
||||
var compression : int = _compression_selector.get_item_id(index)
|
||||
_set_import_property("compression", compression)
|
||||
|
||||
|
||||
func _on_MipmapsCheckbox_toggled(button_pressed: bool):
|
||||
_set_import_property("mipmaps", button_pressed)
|
||||
|
||||
|
||||
func _on_ResolutionSpinBox_value_changed(value):
|
||||
_set_import_property("resolution", int(value))
|
||||
|
||||
|
||||
func _on_TextureArrayPrefixLineEdit_text_changed(new_text: String):
|
||||
_set_import_property("output_prefix", new_text)
|
||||
|
||||
|
||||
func _on_AddSlotButton_pressed():
|
||||
var i := len(_slots_data)
|
||||
_slots_data.append(HT_TextureSetImportEditorSlot.new())
|
||||
_update_ui_from_data()
|
||||
_select_slot(i)
|
||||
|
||||
|
||||
func _on_RemoveSlotButton_pressed():
|
||||
if _slots_list.get_item_count() == 0:
|
||||
return
|
||||
var selected_item = _slots_list.get_selected_items()[0]
|
||||
_delete_confirmation_popup.title = "Delete slot {0}".format([selected_item])
|
||||
_delete_confirmation_popup.dialog_text = "Delete import slot {0}?".format([selected_item])
|
||||
_delete_confirmation_popup.popup_centered()
|
||||
|
||||
|
||||
func _on_delete_confirmation_popup_confirmed():
|
||||
var selected_item : int = _slots_list.get_selected_items()[0]
|
||||
_slots_data.remove_at(selected_item)
|
||||
_update_ui_from_data()
|
||||
|
||||
|
||||
func _on_CancelButton_pressed():
|
||||
hide()
|
||||
|
||||
|
||||
func _on_BrowseImportDirectory_pressed():
|
||||
_open_dir_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _on_ImportDirectoryLineEdit_text_changed(new_text: String):
|
||||
pass
|
||||
|
||||
|
||||
func _on_OpenDirDialog_dir_selected(dir_path: String):
|
||||
_import_directory_line_edit.text = dir_path
|
||||
|
||||
|
||||
func _show_error(message: String):
|
||||
_error_popup.dialog_text = message
|
||||
_error_popup.popup_centered()
|
||||
|
||||
|
||||
func _on_NormalMapFlipY_toggled(button_pressed: bool):
|
||||
var slot_index : int = _slots_list.get_selected_items()[0]
|
||||
var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
|
||||
slot.flip_normalmap_y = button_pressed
|
||||
_normalmap_material.set_shader_parameter("u_flip_y", slot.flip_normalmap_y)
|
||||
|
||||
|
||||
# class ButtonDisabler:
|
||||
# var _button : Button
|
||||
|
||||
# func _init(b: Button):
|
||||
# _button = b
|
||||
# _button.disabled = true
|
||||
|
||||
# func _notification(what: int):
|
||||
# if what == NOTIFICATION_PREDELETE:
|
||||
# _button.disabled = false
|
||||
|
||||
|
||||
func _get_undo_redo_for_texture_set() -> UndoRedo:
|
||||
return _undo_redo_manager.get_history_undo_redo(
|
||||
_undo_redo_manager.get_object_history_id(_texture_set))
|
||||
|
||||
|
||||
func _on_ImportButton_pressed():
|
||||
if _texture_set == null:
|
||||
_show_error("No HTerrainTextureSet selected.")
|
||||
return
|
||||
|
||||
var import_dir := _import_directory_line_edit.text.strip_edges()
|
||||
|
||||
var prefix := ""
|
||||
if _texture_set.resource_path != "":
|
||||
prefix = _texture_set.resource_path.get_file().get_basename() + "_"
|
||||
|
||||
var files_data_result : HT_Result
|
||||
if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
|
||||
files_data_result = _generate_packed_images(import_dir, prefix)
|
||||
else:
|
||||
files_data_result = _generate_packed_texarray_images(import_dir, prefix)
|
||||
|
||||
if not files_data_result.success:
|
||||
_show_error(files_data_result.get_message())
|
||||
return
|
||||
|
||||
var files_data : Array = files_data_result.value
|
||||
|
||||
if len(files_data) == 0:
|
||||
_show_error("There are no files to save.\nYou must setup at least one slot of textures.")
|
||||
return
|
||||
|
||||
for fd in files_data:
|
||||
var dir_path : String = fd.path.get_base_dir()
|
||||
if not DirAccess.dir_exists_absolute(dir_path):
|
||||
_show_error("The directory {0} could not be found.".format([dir_path]))
|
||||
return
|
||||
|
||||
if _WRITE_IMPORT_FILES:
|
||||
for fd in files_data:
|
||||
var import_fpath = fd.path + ".import"
|
||||
if not HT_Util.write_import_file(fd.import_file_data, import_fpath, _logger):
|
||||
_show_error("Failed to write file {0}: {1}".format([import_fpath]))
|
||||
return
|
||||
|
||||
if _editor_file_system == null:
|
||||
_show_error("EditorFileSystem is not setup, can't trigger import system.")
|
||||
return
|
||||
|
||||
# ______
|
||||
# .-" "-.
|
||||
# / \
|
||||
# _ | | _
|
||||
# ( \ |, .-. .-. ,| / )
|
||||
# > "=._ | )(__/ \__)( | _.=" <
|
||||
# (_/"=._"=._ |/ /\ \| _.="_.="\_)
|
||||
# "=._ (_ ^^ _)"_.="
|
||||
# "=\__|IIIIII|__/="
|
||||
# _.="| \IIIIII/ |"=._
|
||||
# _ _.="_.="\ /"=._"=._ _
|
||||
# ( \_.="_.=" `--------` "=._"=._/ )
|
||||
# > _.=" "=._ <
|
||||
# (_/ \_)
|
||||
#
|
||||
# TODO What I need here is a way to trigger the import of specific files!
|
||||
# It exists, but is not exposed, so I have to rely on a VERY fragile and hacky use of scan()...
|
||||
# I'm not even sure it works tbh. It's terrible.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1615
|
||||
_editor_file_system.scan()
|
||||
while _editor_file_system.is_scanning():
|
||||
_logger.debug("Waiting for scan to complete...")
|
||||
await get_tree().process_frame
|
||||
if not is_inside_tree():
|
||||
# oops?
|
||||
return
|
||||
_logger.debug("Scanning complete")
|
||||
# Looks like import takes place AFTER scanning, so let's yield some more...
|
||||
for fd in len(files_data) * 2:
|
||||
_logger.debug("Yielding some more")
|
||||
await get_tree().process_frame
|
||||
|
||||
var failed_resource_paths := []
|
||||
|
||||
# Using UndoRedo is mandatory for Godot to consider the resource as modified...
|
||||
# ...yet if files get deleted, that won't be undoable anyways, but whatever :shrug:
|
||||
var ur := _get_undo_redo_for_texture_set()
|
||||
|
||||
# Check imported textures
|
||||
if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
|
||||
for fd in files_data:
|
||||
var texture : Texture2D = load(fd.path)
|
||||
if texture == null:
|
||||
failed_resource_paths.append(fd.path)
|
||||
continue
|
||||
fd.texture = texture
|
||||
|
||||
else:
|
||||
for fd in files_data:
|
||||
var texture_array : TextureLayered = load(fd.path)
|
||||
if texture_array == null:
|
||||
failed_resource_paths.append(fd.path)
|
||||
continue
|
||||
fd.texture_array = texture_array
|
||||
|
||||
if len(failed_resource_paths) > 0:
|
||||
var failed_list := "\n".join(PackedStringArray(failed_resource_paths))
|
||||
_show_error("Some resources failed to load:\n" + failed_list)
|
||||
return
|
||||
|
||||
# All is OK, commit action to modify the texture set with imported textures
|
||||
|
||||
if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
|
||||
ur.create_action("HTerrainTextureSet: import textures")
|
||||
|
||||
HT_TextureSetEditor.backup_for_undo(_texture_set, ur)
|
||||
|
||||
ur.add_do_method(_texture_set.clear)
|
||||
ur.add_do_method(_texture_set.set_mode.bind(_import_mode))
|
||||
|
||||
for i in len(_slots_data):
|
||||
ur.add_do_method(_texture_set.insert_slot.bind(-1))
|
||||
for fd in files_data:
|
||||
ur.add_do_method(_texture_set.set_texture.bind(fd.slot_index, fd.type, fd.texture))
|
||||
|
||||
else:
|
||||
ur.create_action("HTerrainTextureSet: import texture arrays")
|
||||
|
||||
HT_TextureSetEditor.backup_for_undo(_texture_set, ur)
|
||||
|
||||
ur.add_do_method(_texture_set.clear)
|
||||
ur.add_do_method(_texture_set.set_mode.bind(_import_mode))
|
||||
|
||||
for fd in files_data:
|
||||
ur.add_do_method(_texture_set.set_texture_array.bind(fd.type, fd.texture_array))
|
||||
|
||||
ur.commit_action()
|
||||
|
||||
_logger.debug("Done importing")
|
||||
|
||||
_info_popup.dialog_text = "Importing complete!"
|
||||
_info_popup.popup_centered()
|
||||
|
||||
import_finished.emit()
|
||||
|
||||
|
||||
class HT_PackedImageInfo:
|
||||
var path := "" # Where the packed image is saved
|
||||
var slot_index : int # Slot in texture set, when using individual textures
|
||||
var type : int # 0:Albedo+Bump, 1:Normal+Roughness
|
||||
var import_file_data := {} # Data to write into the .import file (when enabled...)
|
||||
var image : Image
|
||||
var is_default := false
|
||||
var texture : Texture2D
|
||||
var texture_array : TextureLayered
|
||||
|
||||
|
||||
# Shared code between the two import modes
|
||||
func _generate_packed_images2() -> HT_Result:
|
||||
var resolution : int = _import_settings.resolution
|
||||
var images_infos := []
|
||||
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
var src_types := HTerrainTextureSet.get_src_types_from_type(type)
|
||||
|
||||
for slot_index in len(_slots_data):
|
||||
var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
|
||||
|
||||
# Albedo or Normal
|
||||
var src0 : String = slot.texture_paths[src_types[0]]
|
||||
# Bump or Roughness
|
||||
var src1 : String = slot.texture_paths[src_types[1]]
|
||||
|
||||
if src0 == "":
|
||||
if src_types[0] == HTerrainTextureSet.SRC_TYPE_ALBEDO:
|
||||
return HT_Result.new(false,
|
||||
"Albedo texture is missing in slot {0}".format([slot_index]))
|
||||
|
||||
var is_default := (src0 == "" and src1 == "")
|
||||
|
||||
if src0 == "":
|
||||
src0 = HTerrainTextureSet.get_source_texture_default_color_code(src_types[0])
|
||||
if src1 == "":
|
||||
src1 = HTerrainTextureSet.get_source_texture_default_color_code(src_types[1])
|
||||
|
||||
var pack_sources := {
|
||||
"rgb": src0,
|
||||
"a": src1
|
||||
}
|
||||
|
||||
if HTerrainTextureSet.SRC_TYPE_NORMAL in src_types and slot.flip_normalmap_y:
|
||||
pack_sources["normalmap_flip_y"] = true
|
||||
|
||||
var packed_image_result := HT_PackedTextureUtil.generate_image(
|
||||
pack_sources, resolution, _logger)
|
||||
if not packed_image_result.success:
|
||||
return packed_image_result
|
||||
var packed_image : Image = packed_image_result.value
|
||||
|
||||
var fd := HT_PackedImageInfo.new()
|
||||
fd.slot_index = slot_index
|
||||
fd.type = type
|
||||
fd.image = packed_image
|
||||
fd.is_default = is_default
|
||||
|
||||
images_infos.append(fd)
|
||||
|
||||
return HT_Result.new(true).with_value(images_infos)
|
||||
|
||||
|
||||
func _generate_packed_images(import_dir: String, prefix: String) -> HT_Result:
|
||||
var images_infos_result := _generate_packed_images2()
|
||||
if not images_infos_result.success:
|
||||
return images_infos_result
|
||||
var images_infos : Array = images_infos_result.value
|
||||
|
||||
for info_index in len(images_infos):
|
||||
var info : HT_PackedImageInfo = images_infos[info_index]
|
||||
|
||||
var type_name := HTerrainTextureSet.get_texture_type_name(info.type)
|
||||
var fpath := import_dir.path_join(
|
||||
str(prefix, "slot", info.slot_index, "_", type_name, ".png"))
|
||||
|
||||
var err := info.image.save_png(fpath)
|
||||
if err != OK:
|
||||
return HT_Result.new(false,
|
||||
"Could not save image {0}, {1}".format([fpath, HT_Errors.get_message(err)]))
|
||||
|
||||
info.path = fpath
|
||||
info.import_file_data = {
|
||||
"remap": {
|
||||
"importer": "texture",
|
||||
"type": "CompressedTexture2D"
|
||||
},
|
||||
"deps": {
|
||||
"source_file": fpath
|
||||
},
|
||||
"params": {
|
||||
"compress/mode": ResourceImporterTexture_Unexposed.COMPRESS_VRAM_COMPRESSED,
|
||||
"compress/high_quality": false,
|
||||
"compress/lossy_quality": 0.7,
|
||||
"mipmaps/generate": true,
|
||||
"mipmaps/limit": -1,
|
||||
"roughness/mode": ResourceImporterTexture_Unexposed.ROUGHNESS_DISABLED,
|
||||
"process/fix_alpha_border": false
|
||||
}
|
||||
}
|
||||
|
||||
return HT_Result.new(true).with_value(images_infos)
|
||||
|
||||
|
||||
static func _assemble_texarray_images(images: Array[Image], resolution: Vector2i) -> Image:
|
||||
# Godot expects some kind of grid. Let's be lazy and do a grid with only one row.
|
||||
var atlas := Image.create(resolution.x * len(images), resolution.y, false, Image.FORMAT_RGBA8)
|
||||
for index in len(images):
|
||||
var image : Image = images[index]
|
||||
if image.get_size() != resolution:
|
||||
image.resize(resolution.x, resolution.y, Image.INTERPOLATE_BILINEAR)
|
||||
atlas.blit_rect(image,
|
||||
Rect2i(0, 0, image.get_width(), image.get_height()),
|
||||
Vector2i(index * resolution.x, 0))
|
||||
return atlas
|
||||
|
||||
|
||||
func _generate_packed_texarray_images(import_dir: String, prefix: String) -> HT_Result:
|
||||
var images_infos_result := _generate_packed_images2()
|
||||
if not images_infos_result.success:
|
||||
return images_infos_result
|
||||
var individual_images_infos : Array = images_infos_result.value
|
||||
|
||||
var resolution : int = _import_settings.resolution
|
||||
|
||||
var texarray_images_infos := []
|
||||
var slot_count := len(_slots_data)
|
||||
|
||||
for type in HTerrainTextureSet.TYPE_COUNT:
|
||||
var texarray_images : Array[Image] = []
|
||||
texarray_images.resize(slot_count)
|
||||
|
||||
var fully_defaulted_slots := 0
|
||||
|
||||
for i in slot_count:
|
||||
var info : HT_PackedImageInfo = individual_images_infos[type * slot_count + i]
|
||||
if info.type == type:
|
||||
texarray_images[i] = info.image
|
||||
if info.is_default:
|
||||
fully_defaulted_slots += 1
|
||||
|
||||
if fully_defaulted_slots == len(texarray_images):
|
||||
# No need to generate this file at all
|
||||
continue
|
||||
|
||||
var texarray_image := _assemble_texarray_images(texarray_images,
|
||||
Vector2i(resolution, resolution))
|
||||
|
||||
var type_name := HTerrainTextureSet.get_texture_type_name(type)
|
||||
var fpath := import_dir.path_join(str(prefix, type_name, "_array.png"))
|
||||
|
||||
var err := texarray_image.save_png(fpath)
|
||||
if err != OK:
|
||||
return HT_Result.new(false,
|
||||
"Could not save image {0}, {1}".format([fpath, HT_Errors.get_message(err)]))
|
||||
|
||||
var texarray_image_info := HT_PackedImageInfo.new()
|
||||
texarray_image_info.type = type
|
||||
texarray_image_info.path = fpath
|
||||
texarray_image_info.import_file_data = {
|
||||
"remap": {
|
||||
"importer": "2d_array_texture",
|
||||
"type": "CompressedTexture2DArray"
|
||||
},
|
||||
"deps": {
|
||||
"source_file": fpath
|
||||
},
|
||||
"params": {
|
||||
"compress/mode": ResourceImporterTextureLayered_Unexposed.COMPRESS_VRAM_COMPRESSED,
|
||||
"compress/high_quality": false,
|
||||
"compress/lossy_quality": 0.7,
|
||||
"mipmaps/generate": true,
|
||||
"mipmaps/limit": -1,
|
||||
"process/fix_alpha_border": false,
|
||||
"slices/horizontal": len(texarray_images),
|
||||
"slices/vertical": 1
|
||||
}
|
||||
}
|
||||
|
||||
texarray_images_infos.append(texarray_image_info)
|
||||
|
||||
return HT_Result.new(true).with_value(texarray_images_infos)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://m0rb80ho8ptd
|
||||
@@ -0,0 +1,218 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://3indvrto5vd5"]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://addons/zylann.hterrain/tools/util/dialog_fitter.tscn" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://dqgaomu3tr1ym" path="res://addons/zylann.hterrain/tools/texture_editor/set_editor/source_file_item_editor.tscn" id="3"]
|
||||
[ext_resource type="Script" uid="uid://m0rb80ho8ptd" path="res://addons/zylann.hterrain/tools/texture_editor/set_editor/texture_set_import_editor.gd" id="4"]
|
||||
|
||||
[node name="TextureSetImportEditor" type="AcceptDialog"]
|
||||
title = "Texture Set Import Tool"
|
||||
size = Vector2i(652, 623)
|
||||
min_size = Vector2i(652, 480)
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="Import" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -18.0
|
||||
|
||||
[node name="HS" type="HSplitContainer" parent="Import"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="Import/HS"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Label" type="Label" parent="Import/HS/VB"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Slots"
|
||||
|
||||
[node name="SlotsList" type="ItemList" parent="Import/HS/VB"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
item_count = 7
|
||||
item_0/text = "Item 0"
|
||||
item_1/text = "Item 1"
|
||||
item_2/text = "Item 2"
|
||||
item_3/text = "Item 3"
|
||||
item_4/text = "Item 4"
|
||||
item_5/text = "Item 5"
|
||||
item_6/text = "Item 6"
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="Import/HS/VB"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddSlotButton" type="Button" parent="Import/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "+"
|
||||
|
||||
[node name="Control" type="Control" parent="Import/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RemoveSlotButton" type="Button" parent="Import/HS/VB/HB"]
|
||||
layout_mode = 2
|
||||
text = "-"
|
||||
|
||||
[node name="VB2" type="VBoxContainer" parent="Import/HS"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Import/HS/VB2"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="Import/HS/VB2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Albedo" parent="Import/HS/VB2/HB" instance=ExtResource("3")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Bump" parent="Import/HS/VB2/HB" instance=ExtResource("3")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Normal" parent="Import/HS/VB2/HB" instance=ExtResource("3")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NormalMapFlipY" type="CheckBox" parent="Import/HS/VB2/HB/Normal"]
|
||||
layout_mode = 2
|
||||
text = "Flip Y"
|
||||
|
||||
[node name="Roughness" parent="Import/HS/VB2/HB" instance=ExtResource("3")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Control" type="Control" parent="Import/HS/VB2"]
|
||||
custom_minimum_size = Vector2(0, 4)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Control2" type="Control" parent="Import/HS/VB2"]
|
||||
custom_minimum_size = Vector2(0, 4)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Label3" type="Label" parent="Import/HS/VB2"]
|
||||
modulate = Color(0.564706, 0.564706, 0.564706, 1)
|
||||
layout_mode = 2
|
||||
text = "These images should remain accessible for import to work.
|
||||
Tip: you can place them in a folder with a `.gdignore` file, so they won't take space in your exported game."
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Spacer3" type="Control" parent="Import"]
|
||||
custom_minimum_size = Vector2(0, 8)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="Import"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GC" type="GridContainer" parent="Import"]
|
||||
layout_mode = 2
|
||||
columns = 4
|
||||
|
||||
[node name="Label2" type="Label" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
text = "Import mode: "
|
||||
|
||||
[node name="ImportModeSelector" type="OptionButton" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="MipmapsCheckbox" type="CheckBox" parent="Import/GC"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Mipmaps"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="Import/GC"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Compression:"
|
||||
|
||||
[node name="CompressionSelector" type="OptionButton" parent="Import/GC"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="FilterCheckBox" type="CheckBox" parent="Import/GC"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Filter"
|
||||
|
||||
[node name="Spacer" type="Control" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label3" type="Label" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
text = "Resolution:"
|
||||
|
||||
[node name="ResolutionSpinBox" type="SpinBox" parent="Import/GC"]
|
||||
layout_mode = 2
|
||||
min_value = 1.0
|
||||
max_value = 4096.0
|
||||
value = 1.0
|
||||
|
||||
[node name="HB2" type="HBoxContainer" parent="Import"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label2" type="Label" parent="Import/HB2"]
|
||||
layout_mode = 2
|
||||
text = "Import directory"
|
||||
|
||||
[node name="ImportDirectoryLineEdit" type="LineEdit" parent="Import/HB2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="BrowseImportDirectory" type="Button" parent="Import/HB2"]
|
||||
layout_mode = 2
|
||||
text = "..."
|
||||
|
||||
[node name="Spacer" type="Control" parent="Import"]
|
||||
custom_minimum_size = Vector2(0, 8)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HB" type="HBoxContainer" parent="Import"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ImportButton" type="Button" parent="Import/HB"]
|
||||
layout_mode = 2
|
||||
text = "Import to TextureSet"
|
||||
|
||||
[node name="CancelButton" type="Button" parent="Import/HB"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Import"]
|
||||
custom_minimum_size = Vector2(0, 8)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DialogFitter" parent="." instance=ExtResource("1")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 644.0
|
||||
offset_bottom = 605.0
|
||||
|
||||
[connection signal="item_selected" from="Import/HS/VB/SlotsList" to="." method="_on_SlotsList_item_selected"]
|
||||
[connection signal="pressed" from="Import/HS/VB/HB/AddSlotButton" to="." method="_on_AddSlotButton_pressed"]
|
||||
[connection signal="pressed" from="Import/HS/VB/HB/RemoveSlotButton" to="." method="_on_RemoveSlotButton_pressed"]
|
||||
[connection signal="toggled" from="Import/HS/VB2/HB/Normal/NormalMapFlipY" to="." method="_on_NormalMapFlipY_toggled"]
|
||||
[connection signal="item_selected" from="Import/GC/ImportModeSelector" to="." method="_on_ImportModeSelector_item_selected"]
|
||||
[connection signal="toggled" from="Import/GC/MipmapsCheckbox" to="." method="_on_MipmapsCheckbox_toggled"]
|
||||
[connection signal="item_selected" from="Import/GC/CompressionSelector" to="." method="_on_CompressionSelector_item_selected"]
|
||||
[connection signal="toggled" from="Import/GC/FilterCheckBox" to="." method="_on_FilterCheckBox_toggled"]
|
||||
[connection signal="value_changed" from="Import/GC/ResolutionSpinBox" to="." method="_on_ResolutionSpinBox_value_changed"]
|
||||
[connection signal="text_changed" from="Import/HB2/ImportDirectoryLineEdit" to="." method="_on_ImportDirectoryLineEdit_text_changed"]
|
||||
[connection signal="pressed" from="Import/HB2/BrowseImportDirectory" to="." method="_on_BrowseImportDirectory_pressed"]
|
||||
[connection signal="pressed" from="Import/HB/ImportButton" to="." method="_on_ImportButton_pressed"]
|
||||
[connection signal="pressed" from="Import/HB/CancelButton" to="." method="_on_CancelButton_pressed"]
|
||||
|
||||
[editable path="Import/HS/VB2/HB/Normal"]
|
||||
134
addons/zylann.hterrain/tools/texture_editor/texture_editor.gd
Executable file
134
addons/zylann.hterrain/tools/texture_editor/texture_editor.gd
Executable file
@@ -0,0 +1,134 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const HTerrain = preload("../../hterrain.gd")
|
||||
const HTerrainTextureSet = preload("../../hterrain_texture_set.gd")
|
||||
const HT_TextureList = preload("./texture_list.gd")
|
||||
const HT_Logger = preload("../../util/logger.gd")
|
||||
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
|
||||
const EMPTY_ICON_TEXTURE_PATH = "res://addons/zylann.hterrain/tools/icons/empty.png"
|
||||
|
||||
signal texture_selected(index)
|
||||
signal edit_pressed(index)
|
||||
signal import_pressed
|
||||
|
||||
@onready var _textures_list: HT_TextureList = $TextureList
|
||||
@onready var _buttons_container : HBoxContainer = $HBoxContainer
|
||||
|
||||
var _terrain : HTerrain = null
|
||||
var _texture_set : HTerrainTextureSet = null
|
||||
|
||||
var _texture_list_need_update := false
|
||||
var _empty_icon : Texture2D
|
||||
|
||||
var _logger = HT_Logger.get_for(self)
|
||||
|
||||
|
||||
func _ready():
|
||||
_empty_icon = load(EMPTY_ICON_TEXTURE_PATH)
|
||||
if _empty_icon == null:
|
||||
_logger.error(str("Failed to load empty icon ", EMPTY_ICON_TEXTURE_PATH))
|
||||
|
||||
# Default amount, will be updated when a terrain is assigned
|
||||
_textures_list.clear()
|
||||
for i in range(4):
|
||||
_textures_list.add_item(str(i), _empty_icon)
|
||||
|
||||
|
||||
func set_terrain(terrain: HTerrain):
|
||||
_terrain = terrain
|
||||
|
||||
|
||||
static func _get_slot_count(terrain: HTerrain) -> int:
|
||||
var texture_set = terrain.get_texture_set()
|
||||
if texture_set == null:
|
||||
return 0
|
||||
return texture_set.get_slots_count()
|
||||
|
||||
|
||||
func _process(delta: float):
|
||||
var texture_set = null
|
||||
if _terrain != null:
|
||||
texture_set = _terrain.get_texture_set()
|
||||
|
||||
if _texture_set != texture_set:
|
||||
if _texture_set != null:
|
||||
_texture_set.changed.disconnect(_on_texture_set_changed)
|
||||
|
||||
_texture_set = texture_set
|
||||
|
||||
if _texture_set != null:
|
||||
_texture_set.changed.connect(_on_texture_set_changed)
|
||||
|
||||
_update_texture_list()
|
||||
|
||||
if _texture_list_need_update:
|
||||
_update_texture_list()
|
||||
_texture_list_need_update = false
|
||||
|
||||
|
||||
func _on_texture_set_changed():
|
||||
_texture_list_need_update = true
|
||||
|
||||
|
||||
func _update_texture_list():
|
||||
_textures_list.clear()
|
||||
|
||||
if _terrain == null:
|
||||
_set_buttons_active(false)
|
||||
return
|
||||
var texture_set := _terrain.get_texture_set()
|
||||
if texture_set == null:
|
||||
_set_buttons_active(false)
|
||||
return
|
||||
_set_buttons_active(true)
|
||||
|
||||
var slots_count := texture_set.get_slots_count()
|
||||
|
||||
match texture_set.get_mode():
|
||||
HTerrainTextureSet.MODE_TEXTURES:
|
||||
for slot_index in slots_count:
|
||||
var texture := texture_set.get_texture(
|
||||
slot_index, HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
var hint := _get_slot_hint_name(slot_index, _terrain.get_shader_type())
|
||||
if texture == null:
|
||||
texture = _empty_icon
|
||||
_textures_list.add_item(hint, texture)
|
||||
|
||||
HTerrainTextureSet.MODE_TEXTURE_ARRAYS:
|
||||
var texture_array = texture_set.get_texture_array(HTerrainTextureSet.TYPE_ALBEDO_BUMP)
|
||||
for slot_index in slots_count:
|
||||
var hint := _get_slot_hint_name(slot_index, _terrain.get_shader_type())
|
||||
_textures_list.add_item(hint, texture_array, slot_index)
|
||||
|
||||
|
||||
func _set_buttons_active(active: bool):
|
||||
for i in _buttons_container.get_child_count():
|
||||
var child = _buttons_container.get_child(i)
|
||||
if child is Button:
|
||||
child.disabled = not active
|
||||
|
||||
|
||||
static func _get_slot_hint_name(i: int, stype: String) -> String:
|
||||
if i == 3 and (stype == HTerrain.SHADER_CLASSIC4 or stype == HTerrain.SHADER_CLASSIC4_LITE):
|
||||
return "cliff"
|
||||
return str(i)
|
||||
|
||||
|
||||
func _on_TextureList_item_selected(index: int):
|
||||
texture_selected.emit(index)
|
||||
|
||||
|
||||
func _on_TextureList_item_activated(index: int):
|
||||
edit_pressed.emit(index)
|
||||
|
||||
|
||||
func _on_EditButton_pressed():
|
||||
var selected_slot := _textures_list.get_selected_item()
|
||||
if selected_slot == -1:
|
||||
selected_slot = 0
|
||||
edit_pressed.emit(selected_slot)
|
||||
|
||||
|
||||
func _on_ImportButton_pressed():
|
||||
import_pressed.emit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dssroq58kx6iw
|
||||
@@ -0,0 +1,49 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://duelkxj5cpvgg"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dssroq58kx6iw" path="res://addons/zylann.hterrain/tools/texture_editor/texture_editor.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://addons/zylann.hterrain/tools/texture_editor/texture_list.tscn" id="2"]
|
||||
|
||||
[node name="TextureEditor" type="Control"]
|
||||
offset_right = 352.0
|
||||
offset_bottom = 104.0
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("1")
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TextureList" parent="." instance=ExtResource("2")]
|
||||
offset_bottom = -26.0
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -24.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="EditButton" type="Button" parent="HBoxContainer"]
|
||||
offset_right = 48.0
|
||||
offset_bottom = 24.0
|
||||
text = "Edit..."
|
||||
|
||||
[node name="ImportButton" type="Button" parent="HBoxContainer"]
|
||||
offset_left = 52.0
|
||||
offset_right = 120.0
|
||||
offset_bottom = 24.0
|
||||
text = "Import..."
|
||||
|
||||
[node name="Label" type="Label" parent="HBoxContainer"]
|
||||
offset_left = 124.0
|
||||
offset_top = 5.0
|
||||
offset_right = 179.0
|
||||
offset_bottom = 19.0
|
||||
text = "Textures"
|
||||
|
||||
[connection signal="item_activated" from="TextureList" to="." method="_on_TextureList_item_activated"]
|
||||
[connection signal="item_selected" from="TextureList" to="." method="_on_TextureList_item_selected"]
|
||||
[connection signal="pressed" from="HBoxContainer/EditButton" to="." method="_on_EditButton_pressed"]
|
||||
[connection signal="pressed" from="HBoxContainer/ImportButton" to="." method="_on_ImportButton_pressed"]
|
||||
79
addons/zylann.hterrain/tools/texture_editor/texture_list.gd
Executable file
79
addons/zylann.hterrain/tools/texture_editor/texture_list.gd
Executable file
@@ -0,0 +1,79 @@
|
||||
|
||||
# I needed a custom container for this because textures edited by this plugin are often
|
||||
# unfit to display in a GUI, they need to go through a shader (either discarding alpha,
|
||||
# or picking layers of a TextureArray). Unfortunately, ItemList does not have custom item drawing,
|
||||
# and items cannot have individual shaders.
|
||||
# I could create new textures just for that but it would be expensive.
|
||||
|
||||
@tool
|
||||
extends ScrollContainer
|
||||
|
||||
const HT_TextureListItemScene = preload("./texture_list_item.tscn")
|
||||
const HT_TextureListItem = preload("./texture_list_item.gd")
|
||||
|
||||
signal item_selected(index)
|
||||
signal item_activated(index)
|
||||
|
||||
@onready var _container : Container = $Container
|
||||
|
||||
|
||||
var _selected_item := -1
|
||||
|
||||
|
||||
# TEST
|
||||
#func _ready():
|
||||
# add_item("First", load("res://addons/zylann.hterrain_demo/textures/ground/bricks_albedo_bump.png"), 0)
|
||||
# add_item("Second", load("res://addons/zylann.hterrain_demo/textures/ground/grass_albedo_bump.png"), 0)
|
||||
# add_item("Third", load("res://addons/zylann.hterrain_demo/textures/ground/leaves_albedo_bump.png"), 0)
|
||||
# add_item("Fourth", load("res://addons/zylann.hterrain_demo/textures/ground/sand_albedo_bump.png"), 0)
|
||||
# var texture_array = load("res://tests/texarray/textures/array_albedo_atlas.png")
|
||||
# add_item("Ninth", texture_array, 2)
|
||||
# add_item("Sixth", texture_array, 3)
|
||||
|
||||
|
||||
# Note: the texture can be a TextureArray, which does not inherit Texture
|
||||
func add_item(text: String, texture: Texture, texture_layer: int = 0):
|
||||
var item : HT_TextureListItem = HT_TextureListItemScene.instantiate()
|
||||
_container.add_child(item)
|
||||
item.set_text(text)
|
||||
item.set_texture(texture, texture_layer)
|
||||
|
||||
|
||||
func get_item_count() -> int:
|
||||
return _container.get_child_count()
|
||||
|
||||
|
||||
func set_item_texture(index: int, tex: Texture, layer: int = 0):
|
||||
var child : HT_TextureListItem = _container.get_child(index)
|
||||
child.set_texture(tex, layer)
|
||||
|
||||
|
||||
func get_selected_item() -> int:
|
||||
return _selected_item
|
||||
|
||||
|
||||
func clear():
|
||||
for i in _container.get_child_count():
|
||||
var child = _container.get_child(i)
|
||||
if child is Control:
|
||||
child.queue_free()
|
||||
_selected_item = -1
|
||||
|
||||
|
||||
func _on_item_selected(item: HT_TextureListItem):
|
||||
_selected_item = item.get_index()
|
||||
for i in _container.get_child_count():
|
||||
var child = _container.get_child(i)
|
||||
if child is HT_TextureListItem and child != item:
|
||||
child.set_selected(false, false)
|
||||
item_selected.emit(_selected_item)
|
||||
|
||||
|
||||
func _on_item_activated(item: HT_TextureListItem):
|
||||
item_activated.emit(item.get_index())
|
||||
|
||||
|
||||
func _draw():
|
||||
# TODO Draw same background as Panel
|
||||
# Draw a background
|
||||
draw_rect(get_rect(), Color(0,0,0,0.3))
|
||||
@@ -0,0 +1 @@
|
||||
uid://c2uqsylbxcci3
|
||||
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dv2cgpfghlnwq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2uqsylbxcci3" path="res://addons/zylann.hterrain/tools/texture_editor/texture_list.gd" id="1"]
|
||||
[ext_resource type="Script" uid="uid://d2cf2epylmuo6" path="res://addons/zylann.hterrain/tools/texture_editor/flow_container.gd" id="2"]
|
||||
|
||||
[node name="TextureList" type="ScrollContainer"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
scroll_horizontal_enabled = false
|
||||
script = ExtResource("1")
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Container" type="Container" parent="."]
|
||||
offset_right = 800.0
|
||||
offset_bottom = 82.0
|
||||
custom_minimum_size = Vector2(0, 82)
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("2")
|
||||
72
addons/zylann.hterrain/tools/texture_editor/texture_list_item.gd
Executable file
72
addons/zylann.hterrain/tools/texture_editor/texture_list_item.gd
Executable file
@@ -0,0 +1,72 @@
|
||||
@tool
|
||||
extends PanelContainer
|
||||
# Had to use PanelContainer, because due to variable font sizes in the editor,
|
||||
# the contents of the VBoxContainer can vary in size, and so in height.
|
||||
# Which means the entire item can have variable size, not just because of DPI.
|
||||
# In such cases, the hierarchy must be made of containers that grow based on their children.
|
||||
|
||||
const HT_ColorMaterial = preload("./display_color_material.tres")
|
||||
const HT_ColorSliceShader = preload("./display_color_slice.gdshader")
|
||||
# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
|
||||
#const HT_DummyTexture = preload("../icons/empty.png")
|
||||
const DUMMY_TEXTURE_PATH = "res://addons/zylann.hterrain/tools/icons/empty.png"
|
||||
|
||||
@onready var _texture_rect : TextureRect = $VB/TextureRect
|
||||
@onready var _label : Label = $VB/Label
|
||||
|
||||
|
||||
var _selected := false
|
||||
|
||||
|
||||
func set_text(text: String):
|
||||
_label.text = text
|
||||
|
||||
|
||||
func set_texture(texture: Texture, texture_layer: int):
|
||||
if texture is TextureLayered:
|
||||
var mat = _texture_rect.material
|
||||
if mat == null or not (mat is ShaderMaterial):
|
||||
mat = ShaderMaterial.new()
|
||||
mat.shader = HT_ColorSliceShader
|
||||
_texture_rect.material = mat
|
||||
mat.set_shader_parameter("u_texture_array", texture)
|
||||
mat.set_shader_parameter("u_index", texture_layer)
|
||||
_texture_rect.texture = load(DUMMY_TEXTURE_PATH)
|
||||
else:
|
||||
_texture_rect.texture = texture
|
||||
_texture_rect.material = HT_ColorMaterial
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent):
|
||||
if event is InputEventMouseButton:
|
||||
if event.pressed:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
grab_focus()
|
||||
set_selected(true, true)
|
||||
if event.double_click:
|
||||
# Don't do this at home.
|
||||
# I do it here because this script is very related to its container anyways.
|
||||
get_parent().get_parent()._on_item_activated(self)
|
||||
|
||||
|
||||
func set_selected(selected: bool, notify: bool):
|
||||
if selected == _selected:
|
||||
return
|
||||
_selected = selected
|
||||
queue_redraw()
|
||||
if _selected:
|
||||
_label.modulate = Color(0,0,0)
|
||||
else:
|
||||
_label.modulate = Color(1,1,1)
|
||||
if notify:
|
||||
get_parent().get_parent()._on_item_selected(self)
|
||||
|
||||
|
||||
func _draw():
|
||||
var color : Color
|
||||
if _selected:
|
||||
color = get_theme_color("accent_color", "Editor")
|
||||
else:
|
||||
color = Color(0.0, 0.0, 0.0, 0.5)
|
||||
# Draw background
|
||||
draw_rect(Rect2(Vector2(), size), color)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdqqab3vyiy0k
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://daugk4kdnx6vy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bdqqab3vyiy0k" path="res://addons/zylann.hterrain/tools/texture_editor/texture_list_item.gd" id="2"]
|
||||
|
||||
[node name="TextureListItem" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(64, 80)
|
||||
offset_right = 64.0
|
||||
offset_bottom = 80.0
|
||||
focus_mode = 1
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VB"]
|
||||
custom_minimum_size = Vector2(60, 60)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
mouse_filter = 2
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Label" type="Label" parent="VB"]
|
||||
layout_mode = 2
|
||||
text = "Texture"
|
||||
Reference in New Issue
Block a user