generated from krampus/template-godot4
Upgraded Terrain3D to v0.9.3a-beta
This commit is contained in:
parent
c99c470b00
commit
81e3f6ce93
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
Copyright (c) 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<img src="doc/docs/images/terrain3d.png">
|
||||
![Terrain3D Logo](/doc/docs/images/terrain3d.jpg)
|
||||
|
||||
# Terrain3D
|
||||
A high performance, editable terrain system for Godot 4.
|
||||
|
@ -7,7 +7,7 @@ A high performance, editable terrain system for Godot 4.
|
|||
* Written in C++ as a GDExtension addon, which works with official engine builds
|
||||
* Can be accessed by GDScript, C#, and any language Godot supports
|
||||
* Geometric Clipmap Mesh Terrain, as used in The Witcher 3. See [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html)
|
||||
* Up to 16k x 16k in 1k regions (imagine multiple islands without paying for 16k^2 vram)
|
||||
* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in variable sized regions
|
||||
* Up to 32 textures
|
||||
* Up to 10 levels of detail
|
||||
* Foliage instancing
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -7,29 +7,36 @@ extends EditorPlugin
|
|||
const UI: Script = preload("res://addons/terrain_3d/src/ui.gd")
|
||||
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
|
||||
const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
|
||||
const PS_DOCK_POSITION: String = "terrain3d/config/dock_position"
|
||||
const PS_DOCK_PINNED: String = "terrain3d/config/dock_pinned"
|
||||
|
||||
var modifier_ctrl: bool
|
||||
var modifier_alt: bool
|
||||
var modifier_shift: bool
|
||||
var _last_modifiers: int = 0
|
||||
var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
|
||||
|
||||
var terrain: Terrain3D
|
||||
var _last_terrain: Terrain3D
|
||||
var nav_region: NavigationRegion3D
|
||||
|
||||
var editor: Terrain3DEditor
|
||||
var editor_settings: EditorSettings
|
||||
var ui: Node # Terrain3DUI see Godot #75388
|
||||
var asset_dock: PanelContainer
|
||||
var region_gizmo: RegionGizmo
|
||||
var visible: bool
|
||||
var current_region_position: Vector2
|
||||
var mouse_global_position: Vector3 = Vector3.ZERO
|
||||
|
||||
# Track negative input (CTRL)
|
||||
var _negative_input: bool = false
|
||||
# Track state prior to pressing CTRL: -1 not tracked, 0 false, 1 true
|
||||
var _prev_enable_state: int = -1
|
||||
var godot_editor_window: Window # The Godot Editor window
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
|
||||
godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
|
||||
godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
editor = Terrain3DEditor.new()
|
||||
setup_editor_settings()
|
||||
ui = UI.new()
|
||||
ui.plugin = self
|
||||
add_child(ui)
|
||||
|
@ -41,7 +48,7 @@ func _enter_tree() -> void:
|
|||
asset_dock = load(ASSET_DOCK).instantiate()
|
||||
asset_dock.initialize(self)
|
||||
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
asset_dock.remove_dock(true)
|
||||
asset_dock.queue_free()
|
||||
|
@ -49,25 +56,46 @@ func _exit_tree() -> void:
|
|||
editor.free()
|
||||
|
||||
scene_changed.disconnect(_on_scene_changed)
|
||||
godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _on_godot_focus_entered() -> void:
|
||||
_read_input()
|
||||
ui.update_decal()
|
||||
|
||||
|
||||
## EditorPlugin selection function call chain isn't consistent. Here's the map of calls:
|
||||
## Assume we handle Terrain3D and NavigationRegion3D
|
||||
# Click Terrain3D: _handles(Terrain3D), _make_visible(true), _edit(Terrain3D)
|
||||
# Deselect: _make_visible(false), _edit(null)
|
||||
# Click other node: _handles(OtherNode)
|
||||
# Click NavRegion3D: _handles(NavReg3D), _make_visible(true), _edit(NavReg3D)
|
||||
# Click NavRegion3D, Terrain3D: _handles(Terrain3D), _edit(Terrain3D)
|
||||
# Click Terrain3D, NavRegion3D: _handles(NavReg3D), _edit(NavReg3D)
|
||||
func _handles(p_object: Object) -> bool:
|
||||
if p_object is Terrain3D:
|
||||
return true
|
||||
elif p_object is NavigationRegion3D and is_instance_valid(_last_terrain):
|
||||
return true
|
||||
|
||||
# Terrain3DObjects requires access to EditorUndoRedoManager. The only way to make sure it
|
||||
# always has it, is to pass it in here. _edit is NOT called if the node is cut and pasted.
|
||||
if p_object is Terrain3DObjects:
|
||||
elif p_object is Terrain3DObjects:
|
||||
p_object.editor_setup(self)
|
||||
elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
|
||||
p_object.get_parent().editor_setup(self)
|
||||
|
||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree() and p_object is NavigationRegion3D:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
|
||||
if p_visible and is_selected():
|
||||
ui.set_visible(true)
|
||||
asset_dock.update_dock()
|
||||
else:
|
||||
ui.set_visible(false)
|
||||
|
||||
|
||||
func _edit(p_object: Object) -> void:
|
||||
if !p_object:
|
||||
_clear()
|
||||
|
@ -77,39 +105,41 @@ func _edit(p_object: Object) -> void:
|
|||
return
|
||||
terrain = p_object
|
||||
_last_terrain = terrain
|
||||
terrain.set_plugin(self)
|
||||
terrain.set_editor(editor)
|
||||
editor.set_terrain(terrain)
|
||||
region_gizmo.set_node_3d(terrain)
|
||||
terrain.add_gizmo(region_gizmo)
|
||||
terrain.set_plugin(self)
|
||||
ui.set_visible(true)
|
||||
terrain.set_meta("_edit_lock_", true)
|
||||
|
||||
# Deprecated 0.9.3 - Remove 1.0
|
||||
if terrain.storage:
|
||||
ui.terrain_menu.directory_setup.directory_setup_popup()
|
||||
|
||||
# Connect to new Assets resource
|
||||
# Get alerted when a new asset list is loaded
|
||||
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
|
||||
terrain.assets_changed.connect(asset_dock.update_assets)
|
||||
asset_dock.update_assets()
|
||||
# Connect to new Storage resource
|
||||
if not terrain.storage_changed.is_connected(_load_storage):
|
||||
terrain.storage_changed.connect(_load_storage)
|
||||
_load_storage()
|
||||
# Get alerted when the region map changes
|
||||
if not terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.connect(update_region_grid)
|
||||
update_region_grid()
|
||||
else:
|
||||
_clear()
|
||||
|
||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree():
|
||||
if is_terrain_valid(_last_terrain):
|
||||
if p_object is NavigationRegion3D:
|
||||
ui.set_visible(true, true)
|
||||
nav_region = p_object
|
||||
else:
|
||||
nav_region = null
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
|
||||
visible = p_visible
|
||||
ui.set_visible(visible)
|
||||
update_region_grid()
|
||||
asset_dock.update_dock(visible)
|
||||
|
||||
|
||||
|
||||
func _clear() -> void:
|
||||
if is_terrain_valid():
|
||||
terrain.storage_changed.disconnect(_load_storage)
|
||||
if terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.disconnect(update_region_grid)
|
||||
|
||||
terrain.clear_gizmos()
|
||||
terrain = null
|
||||
|
@ -124,29 +154,12 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
|||
if not is_terrain_valid():
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
## Track negative input (CTRL)
|
||||
if p_event is InputEventKey and not p_event.echo and p_event.keycode == KEY_CTRL:
|
||||
if p_event.is_pressed():
|
||||
_negative_input = true
|
||||
_prev_enable_state = int(ui.toolbar_settings.get_setting("enable"))
|
||||
ui.toolbar_settings.set_setting("enable", false)
|
||||
else:
|
||||
_negative_input = false
|
||||
ui.toolbar_settings.set_setting("enable", bool(_prev_enable_state))
|
||||
_prev_enable_state = -1
|
||||
_read_input(p_event)
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
## Setup active camera & viewport
|
||||
|
||||
if _prev_enable_state >= 0 and not Input.is_key_pressed(KEY_CTRL):
|
||||
_negative_input = false
|
||||
ui.toolbar_settings.set_setting("enable", bool(_prev_enable_state))
|
||||
_prev_enable_state = -1
|
||||
|
||||
## Setup for active camera & viewport
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
|
@ -169,88 +182,131 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
|||
else:
|
||||
# Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.z): # max double or nan
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
## Update decal
|
||||
ui.decal.global_position = mouse_global_position
|
||||
ui.decal.albedo_mix = 1.0
|
||||
if ui.decal_timer.is_stopped():
|
||||
ui.update_decal()
|
||||
else:
|
||||
ui.decal_timer.start()
|
||||
|
||||
## Update region highlight
|
||||
var region_size = terrain.get_storage().get_region_size()
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (region_size * terrain.get_mesh_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and editor.is_operating():
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif p_event is InputEventMouseButton:
|
||||
ui.update_decal()
|
||||
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
if p_event.is_pressed():
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.get_storage().has_region(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
|
||||
if _input_mode != -1: # Not cam rotation
|
||||
## Update region highlight
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if _input_mode > 0 and editor.is_operating():
|
||||
# Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
|
||||
ui.brush_data["mouse_pressure"] = p_event.pressure
|
||||
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif editor.is_operating():
|
||||
# Mouse released, save undo data
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if p_event is InputEventMouseButton and _input_mode > 0:
|
||||
if p_event.is_pressed():
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.data.has_regionp(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# _input_apply released, save undo data
|
||||
elif editor.is_operating():
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
func _read_input(p_event: InputEvent = null) -> void:
|
||||
## Determine if user is moving camera or applying
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or \
|
||||
p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
_input_mode = 1
|
||||
else:
|
||||
_input_mode = 0
|
||||
|
||||
func _load_storage() -> void:
|
||||
if terrain:
|
||||
update_region_grid()
|
||||
match get_setting("editors/3d/navigation/navigation_scheme", 0):
|
||||
2, 1: # Modo, Maya
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
( Input.is_key_pressed(KEY_ALT) and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
( Input.is_key_pressed(KEY_ALT) and p_event.get_button_index() == MOUSE_BUTTON_LEFT )):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
0, _: # Godot
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_MIDDLE ):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
if _input_mode < 0:
|
||||
return
|
||||
|
||||
## Determine modifiers pressed
|
||||
modifier_shift = Input.is_key_pressed(KEY_SHIFT)
|
||||
modifier_ctrl = Input.is_key_pressed(KEY_CTRL)
|
||||
# Keybind enum: Alt,Space,Meta,Capslock
|
||||
var alt_key: int
|
||||
match get_setting("terrain3d/config/alt_key_bind", 0):
|
||||
3: alt_key = KEY_CAPSLOCK
|
||||
2: alt_key = KEY_META
|
||||
1: alt_key = KEY_SPACE
|
||||
0, _: alt_key = KEY_ALT
|
||||
modifier_alt = Input.is_key_pressed(alt_key)
|
||||
|
||||
# Return if modifiers haven't changed AND brush_data has them;
|
||||
# modifiers disappear from brush_data when clicking asset_dock (Why?)
|
||||
var current_mods: int = int(modifier_shift) | int(modifier_ctrl) << 1 | int(modifier_alt) << 2
|
||||
if _last_modifiers == current_mods and ui.brush_data.has("modifier_shift"):
|
||||
return
|
||||
|
||||
_last_modifiers = current_mods
|
||||
ui.brush_data["modifier_shift"] = modifier_shift
|
||||
ui.brush_data["modifier_ctrl"] = modifier_ctrl
|
||||
ui.brush_data["modifier_alt"] = modifier_alt
|
||||
ui.update_modifiers()
|
||||
|
||||
|
||||
func update_region_grid() -> void:
|
||||
if not region_gizmo:
|
||||
return
|
||||
region_gizmo.set_hidden(not ui.visible)
|
||||
|
||||
region_gizmo.set_hidden(not visible)
|
||||
|
||||
if is_terrain_valid():
|
||||
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
||||
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
||||
region_gizmo.region_position = current_region_position
|
||||
region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_storage().get_region_offsets()
|
||||
region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_data().get_region_locations()
|
||||
|
||||
terrain.update_gizmos()
|
||||
return
|
||||
|
@ -278,22 +334,80 @@ func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
|
|||
t = p_terrain
|
||||
else:
|
||||
t = terrain
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.get_storage():
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.data:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func is_selected() -> bool:
|
||||
var selected: Array[Node] = get_editor_interface().get_selection().get_selected_nodes()
|
||||
var selected: Array[Node] = EditorInterface.get_selection().get_selected_nodes()
|
||||
for node in selected:
|
||||
if node.get_instance_id() == _last_terrain.get_instance_id():
|
||||
return true
|
||||
|
||||
if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \
|
||||
node is Terrain3D:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func select_terrain() -> void:
|
||||
if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected():
|
||||
var es: EditorSelection = get_editor_interface().get_selection()
|
||||
var es: EditorSelection = EditorInterface.get_selection()
|
||||
es.clear()
|
||||
es.add_node(_last_terrain)
|
||||
|
||||
|
||||
## Editor Settings
|
||||
|
||||
|
||||
func setup_editor_settings() -> void:
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
if not editor_settings.has_setting("terrain3d/config/alt_key_bind"):
|
||||
editor_settings.set("terrain3d/config/alt_key_bind", 0)
|
||||
var property_info = {
|
||||
"name": "terrain3d/config/alt_key_bind",
|
||||
"type": TYPE_INT,
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": "Alt,Space,Meta,Capslock"
|
||||
}
|
||||
editor_settings.add_property_info(property_info)
|
||||
|
||||
_cleanup_old_settings()
|
||||
|
||||
|
||||
# Remove or rename old settings
|
||||
func _cleanup_old_settings() -> void:
|
||||
# Rename deprecated settings - Remove in 1.0
|
||||
var value: Variant
|
||||
var rename_arr := [ "terrain3d/config/dock_slot", "terrain3d/config/dock_tile_size",
|
||||
"terrain3d/config/dock_floating", "terrain3d/config/dock_always_on_top",
|
||||
"terrain3d/config/dock_window_size", "terrain3d/config/dock_window_position", ]
|
||||
for es: String in rename_arr:
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
editor_settings.erase(es)
|
||||
editor_settings.set_setting(es.replace("/config/dock_", "/dock/"), value)
|
||||
|
||||
# Special handling
|
||||
var es: String = "terrain3d/tool_settings/slope"
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
if typeof(value) == TYPE_FLOAT:
|
||||
editor_settings.erase(es)
|
||||
|
||||
|
||||
func set_setting(p_str: String, p_value: Variant) -> void:
|
||||
editor_settings.set_setting(p_str, p_value)
|
||||
|
||||
|
||||
func get_setting(p_str: String, p_default: Variant) -> Variant:
|
||||
if editor_settings.has_setting(p_str):
|
||||
return editor_settings.get_setting(p_str)
|
||||
else:
|
||||
return p_default
|
||||
|
||||
|
||||
func has_setting(p_str: String) -> bool:
|
||||
return editor_settings.has_setting(p_str)
|
||||
|
||||
|
||||
func erase_setting(p_str: String) -> void:
|
||||
editor_settings.erase(p_str)
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
# 1. Click import. The output window and console will report when finished.
|
||||
# 1. Clear the script from your Terrain3D node, and save your scene.
|
||||
#
|
||||
# The instance transforms are now stored in your Storage resource.
|
||||
# The instance transforms are now stored in your region files.
|
||||
#
|
||||
# Use clear_instances to erase all instances that match the assign_mesh_id.
|
||||
#
|
||||
|
|
|
@ -1,30 +1,41 @@
|
|||
// This shader is the minimum needed to allow the terrain to function, without any texturing.
|
||||
|
||||
shader_type spatial;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
|
||||
|
||||
// Private uniforms
|
||||
uniform float _region_size = 1024.0;
|
||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||
uniform float _mesh_vertex_spacing = 1.0;
|
||||
uniform float _mesh_vertex_density = 1.0; // = 1/_mesh_vertex_spacing
|
||||
uniform int _region_map_size = 16;
|
||||
uniform int _region_map[256];
|
||||
uniform vec2 _region_offsets[256];
|
||||
uniform float _vertex_spacing = 1.0;
|
||||
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
|
||||
uniform int _region_map_size = 32;
|
||||
uniform int _region_map[1024];
|
||||
uniform vec2 _region_locations[1024];
|
||||
uniform sampler2DArray _height_maps : repeat_disable;
|
||||
uniform usampler2DArray _control_maps : repeat_disable;
|
||||
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
||||
uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
|
||||
uniform float _texture_uv_scale_array[32];
|
||||
uniform float _texture_uv_rotation_array[32];
|
||||
uniform float _texture_detile_array[32];
|
||||
uniform vec4 _texture_color_array[32];
|
||||
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
||||
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
||||
|
||||
// Public uniforms
|
||||
uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
|
||||
|
||||
// Varyings & Types
|
||||
varying flat vec3 v_vertex; // World coordinate vertex location
|
||||
varying flat vec3 v_camera_pos;
|
||||
varying float v_vertex_xz_dist;
|
||||
varying flat ivec3 v_region;
|
||||
varying flat vec2 v_uv_offset;
|
||||
varying flat vec2 v_uv2_offset;
|
||||
varying vec3 v_normal;
|
||||
varying float v_region_border_mask;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
|
@ -33,26 +44,22 @@ varying flat vec2 v_uv2_offset;
|
|||
// Takes in UV world space coordinates, returns ivec3 with:
|
||||
// XY: (0 to _region_size) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
ivec3 get_region_uv(vec2 uv) {
|
||||
uv *= _region_texel_size;
|
||||
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x>=0 && pos.x<_region_map_size && pos.y>=0 && pos.y<_region_map_size);
|
||||
ivec3 get_region_uv(const vec2 uv) {
|
||||
ivec2 pos = ivec2(floor(uv * _region_texel_size)) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
return ivec3(ivec2((uv - _region_offsets[layer_index]) * _region_size), layer_index);
|
||||
return ivec3(ivec2(mod(uv,_region_size)), layer_index);
|
||||
}
|
||||
|
||||
// Takes in UV2 region space coordinates, returns vec3 with:
|
||||
// XY: (0 to 1) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
vec3 get_region_uv2(vec2 uv) {
|
||||
// Vertex function added half a texel to UV2, to center the UV's. vertex(), fragment() and get_height()
|
||||
// call this with reclaimed versions of UV2, so to keep the last row/column within the correct
|
||||
// window, take back the half pixel before the floor().
|
||||
ivec2 pos = ivec2(floor(uv - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x>=0 && pos.x<_region_map_size && pos.y>=0 && pos.y<_region_map_size);
|
||||
vec3 get_region_uv2(const vec2 uv2) {
|
||||
// Remove Texel Offset to ensure correct region index.
|
||||
ivec2 pos = ivec2(floor(uv2 - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
// The return value is still texel-centered.
|
||||
return vec3(uv - _region_offsets[layer_index], float(layer_index));
|
||||
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
|
||||
}
|
||||
|
||||
// 1 lookup
|
||||
|
@ -66,63 +73,75 @@ float get_height(vec2 uv) {
|
|||
}
|
||||
|
||||
void vertex() {
|
||||
// Get camera pos in world vertex coords
|
||||
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
|
||||
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
vec3 vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
v_vertex_xz_dist = length(v_vertex.xz - v_camera_pos.xz);
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = round(vertex.xz * _mesh_vertex_density);
|
||||
UV = round(v_vertex.xz * _vertex_density);
|
||||
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
|
||||
|
||||
// Discard vertices for Holes. 1 lookup
|
||||
ivec3 region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, region, 0).r;
|
||||
v_region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, v_region, 0).r;
|
||||
bool hole = bool(control >>2u & 0x1u);
|
||||
|
||||
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||
(hole || (_background_mode == 0u && region.z < 0)) ) {
|
||||
VERTEX.x = 0./0.;
|
||||
} else {
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
UV2 = (UV + vec2(0.5)) * _region_texel_size;
|
||||
|
||||
// Get final vertex location and save it
|
||||
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
|
||||
VERTEX.x = 0. / 0.;
|
||||
} else {
|
||||
// Set final vertex height & calculate vertex normals. 3 lookups.
|
||||
VERTEX.y = get_height(UV2);
|
||||
v_vertex.y = VERTEX.y;
|
||||
v_normal = vec3(
|
||||
v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
|
||||
_vertex_spacing,
|
||||
v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
|
||||
);
|
||||
// Due to a bug caused by the GPUs linear interpolation across edges of region maps,
|
||||
// mask region edges and use vertex normals only across region boundaries.
|
||||
v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
|
||||
}
|
||||
|
||||
|
||||
// Transform UVs to local to avoid poor precision during varying interpolation.
|
||||
v_uv_offset = MODEL_MATRIX[3].xz * _mesh_vertex_density;
|
||||
v_uv_offset = MODEL_MATRIX[3].xz * _vertex_density;
|
||||
UV -= v_uv_offset;
|
||||
v_uv2_offset = v_uv_offset * _region_texel_size;
|
||||
UV2 -= v_uv2_offset;
|
||||
|
||||
// Convert model space to view space w/ skip_vertex_transform render mode
|
||||
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
||||
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
|
||||
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Fragment
|
||||
////////////////////////
|
||||
|
||||
// 3 lookups
|
||||
// 0 - 3 lookups
|
||||
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
|
||||
// Get the height of the current vertex
|
||||
float height = get_height(uv);
|
||||
|
||||
// Get the heights to the right and in front, but because of hardware
|
||||
// interpolation on the edges of the heightmaps, the values are off
|
||||
// causing the normal map to look weird. So, near the edges of the map
|
||||
// get the heights to the left or behind instead. Hacky solution that
|
||||
// reduces the artifact, but doesn't fix it entirely. See #185.
|
||||
float u, v;
|
||||
if(mod(uv.y*_region_size, _region_size) > _region_size-2.) {
|
||||
v = get_height(uv + vec2(0, -_region_texel_size)) - height;
|
||||
} else {
|
||||
v = height - get_height(uv + vec2(0, _region_texel_size));
|
||||
}
|
||||
if(mod(uv.x*_region_size, _region_size) > _region_size-2.) {
|
||||
u = get_height(uv + vec2(-_region_texel_size, 0)) - height;
|
||||
float u, v, height;
|
||||
vec3 normal;
|
||||
// Use vertex normals within radius of vertex_normals_distance, and along region borders.
|
||||
if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
|
||||
normal = normalize(v_normal);
|
||||
} else {
|
||||
height = get_height(uv);
|
||||
u = height - get_height(uv + vec2(_region_texel_size, 0));
|
||||
v = height - get_height(uv + vec2(0, _region_texel_size));
|
||||
normal = normalize(vec3(u, _vertex_spacing, v));
|
||||
}
|
||||
|
||||
vec3 normal = vec3(u, _mesh_vertex_spacing, v);
|
||||
normal = normalize(normal);
|
||||
tangent = cross(normal, vec3(0, 0, 1));
|
||||
binormal = cross(normal, tangent);
|
||||
return normal;
|
||||
|
@ -143,4 +162,3 @@ void fragment() {
|
|||
// Apply PBR
|
||||
ALBEDO=vec3(.2);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,8 +61,8 @@
|
|||
#warning += """No Terrain3D node found"""
|
||||
#return
|
||||
#
|
||||
#if not _terrain.storage:
|
||||
#warning += """Terrain3D storage is not initialized"""
|
||||
#if not _terrain.data:
|
||||
#warning += """Terrain3DData is not initialized"""
|
||||
#return
|
||||
#
|
||||
## Get global transform
|
||||
|
@ -70,8 +70,8 @@
|
|||
#var gt_inverse := gt.affine_inverse()
|
||||
#for i in transforms.list.size():
|
||||
#var location: Vector3 = (gt * transforms.list[i]).origin
|
||||
#var height: float = _terrain.storage.get_height(location)
|
||||
#var normal: Vector3 = _terrain.storage.get_normal(location)
|
||||
#var height: float = _terrain.data.get_height(location)
|
||||
#var normal: Vector3 = _terrain.data.get_normal(location)
|
||||
#
|
||||
#if align_with_collision_normal and not is_nan(normal.x):
|
||||
#transforms.list[i].basis.y = normal
|
||||
|
|
BIN
addons/terrain_3d/icons/picker.svg (Stored with Git LFS)
BIN
addons/terrain_3d/icons/picker.svg (Stored with Git LFS)
Binary file not shown.
|
@ -1,38 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c11ip32w7ln4v"
|
||||
path="res://.godot/imported/picker.svg-0ed48f8d7e66014d2aac4b303bc65df6.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/picker.svg"
|
||||
dest_files=["res://.godot/imported/picker.svg-0ed48f8d7e66014d2aac4b303bc65df6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
|
@ -3,5 +3,5 @@
|
|||
name="Terrain3D"
|
||||
description="A high performance, editable terrain system for Godot 4."
|
||||
author="Cory Petkovsek & Roope Palmroos"
|
||||
version="0.9.2"
|
||||
version="0.9.3a"
|
||||
script="editor.gd"
|
||||
|
|
|
@ -6,12 +6,13 @@ signal confirmation_closed
|
|||
signal confirmation_confirmed
|
||||
signal confirmation_canceled
|
||||
|
||||
const PS_DOCK_SLOT: String = "terrain3d/config/dock_slot"
|
||||
const PS_DOCK_TILE_SIZE: String = "terrain3d/config/dock_tile_size"
|
||||
const PS_DOCK_FLOATING: String = "terrain3d/config/dock_floating"
|
||||
const PS_DOCK_PINNED: String = "terrain3d/config/dock_always_on_top"
|
||||
const PS_DOCK_WINDOW_POSITION: String = "terrain3d/config/dock_window_position"
|
||||
const PS_DOCK_WINDOW_SIZE: String = "terrain3d/config/dock_window_size"
|
||||
const ES_DOCK_SLOT: String = "terrain3d/dock/slot"
|
||||
const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size"
|
||||
const ES_DOCK_FLOATING: String = "terrain3d/dock/floating"
|
||||
const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top"
|
||||
const ES_DOCK_WINDOW_POSITION: String = "terrain3d/dock/window_position"
|
||||
const ES_DOCK_WINDOW_SIZE: String = "terrain3d/dock/window_size"
|
||||
const ES_DOCK_TAB: String = "terrain3d/dock/tab"
|
||||
|
||||
var texture_list: ListContainer
|
||||
var mesh_list: ListContainer
|
||||
|
@ -40,10 +41,6 @@ enum {
|
|||
}
|
||||
var state: int = HIDDEN
|
||||
|
||||
var window: Window
|
||||
var _godot_editor_window: Window # The main Godot Editor window
|
||||
var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN
|
||||
|
||||
enum {
|
||||
POS_LEFT_UL = 0,
|
||||
POS_LEFT_BL = 1,
|
||||
|
@ -59,17 +56,15 @@ enum {
|
|||
var slot: int = POS_RIGHT_BR
|
||||
var _initialized: bool = false
|
||||
var plugin: EditorPlugin
|
||||
var editor_settings: EditorSettings
|
||||
var window: Window
|
||||
var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN
|
||||
|
||||
|
||||
func initialize(p_plugin: EditorPlugin) -> void:
|
||||
if p_plugin:
|
||||
plugin = p_plugin
|
||||
|
||||
# Get editor window. Structure is root:Window/EditorNode/Base Control
|
||||
_godot_editor_window = plugin.get_editor_interface().get_base_control().get_parent().get_parent()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
placement_opt = $Box/Buttons/PlacementOpt
|
||||
pinned_btn = $Box/Buttons/Pinned
|
||||
floating_btn = $Box/Buttons/Floating
|
||||
|
@ -93,7 +88,6 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
asset_container.add_child(mesh_list)
|
||||
_current_list = texture_list
|
||||
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
load_editor_settings()
|
||||
|
||||
# Connect signals
|
||||
|
@ -103,7 +97,7 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
placement_opt.item_selected.connect(set_slot)
|
||||
floating_btn.pressed.connect(make_dock_float)
|
||||
pinned_btn.toggled.connect(_on_pin_changed)
|
||||
pinned_btn.visible = false
|
||||
pinned_btn.visible = ( window != null )
|
||||
size_slider.value_changed.connect(_on_slider_changed)
|
||||
plugin.ui.toolbar.tool_changed.connect(_on_tool_changed)
|
||||
|
||||
|
@ -111,7 +105,7 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
|
||||
|
||||
_initialized = true
|
||||
update_dock(plugin.visible)
|
||||
update_dock()
|
||||
update_layout()
|
||||
|
||||
|
||||
|
@ -122,7 +116,7 @@ func _ready() -> void:
|
|||
# Setup styles
|
||||
set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
|
||||
# Avoid saving icon resources in tscn when editing w/ a tool script
|
||||
if plugin.get_editor_interface().get_edited_scene_root() != self:
|
||||
if EditorInterface.get_edited_scene_root() != self:
|
||||
pinned_btn.icon = get_theme_icon("Pin", "EditorIcons")
|
||||
pinned_btn.text = ""
|
||||
floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
|
||||
|
@ -154,7 +148,7 @@ func set_slot(p_slot: int) -> void:
|
|||
placement_opt.selected = slot
|
||||
save_editor_settings()
|
||||
plugin.select_terrain()
|
||||
update_dock(plugin.visible)
|
||||
update_dock()
|
||||
|
||||
|
||||
func remove_dock(p_force: bool = false) -> void:
|
||||
|
@ -167,50 +161,42 @@ func remove_dock(p_force: bool = false) -> void:
|
|||
state = HIDDEN
|
||||
|
||||
# If windowed and destination is not window or final exit, otherwise leave
|
||||
elif state == WINDOWED and p_force:
|
||||
if not window:
|
||||
return
|
||||
elif state == WINDOWED and p_force and window:
|
||||
var parent: Node = get_parent()
|
||||
if parent:
|
||||
parent.remove_child(self)
|
||||
_godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
||||
_godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
_godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
||||
window.hide()
|
||||
window.queue_free()
|
||||
window = null
|
||||
plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
||||
plugin.godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
plugin.godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
||||
window.hide()
|
||||
window.queue_free()
|
||||
window = null
|
||||
floating_btn.button_pressed = false
|
||||
floating_btn.visible = true
|
||||
pinned_btn.visible = false
|
||||
placement_opt.visible = true
|
||||
state = HIDDEN
|
||||
update_dock(plugin.visible) # return window to side/bottom
|
||||
update_dock() # return window to side/bottom
|
||||
|
||||
|
||||
func update_dock(p_visible: bool) -> void:
|
||||
func update_dock() -> void:
|
||||
if not _initialized or window:
|
||||
return
|
||||
|
||||
update_assets()
|
||||
if not _initialized:
|
||||
return
|
||||
|
||||
if window:
|
||||
return
|
||||
elif floating_btn.button_pressed:
|
||||
# No window, but floating button pressed, occurs when from editor settings
|
||||
make_dock_float()
|
||||
return
|
||||
|
||||
# Move dock to new destination
|
||||
remove_dock()
|
||||
# Add dock to new destination
|
||||
# Sidebar
|
||||
if slot < POS_BOTTOM:
|
||||
state = SIDEBAR
|
||||
plugin.add_control_to_dock(slot, self)
|
||||
# Bottom
|
||||
elif slot == POS_BOTTOM:
|
||||
state = BOTTOM
|
||||
plugin.add_control_to_bottom_panel(self, "Terrain3D")
|
||||
if p_visible:
|
||||
plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
|
||||
func update_layout() -> void:
|
||||
if not _initialized:
|
||||
|
@ -257,6 +243,8 @@ func update_thumbnails() -> void:
|
|||
_last_thumb_update_time = Time.get_ticks_msec()
|
||||
for mesh_asset in mesh_list.entries:
|
||||
mesh_asset.queue_redraw()
|
||||
|
||||
|
||||
## Dock Button handlers
|
||||
|
||||
|
||||
|
@ -282,7 +270,9 @@ func _on_textures_pressed() -> void:
|
|||
textures_btn.button_pressed = true
|
||||
meshes_btn.button_pressed = false
|
||||
texture_list.set_selected_id(texture_list.selected_id)
|
||||
plugin.get_editor_interface().edit_node(plugin.terrain)
|
||||
if plugin.is_terrain_valid():
|
||||
EditorInterface.edit_node(plugin.terrain)
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_meshes_pressed() -> void:
|
||||
|
@ -293,14 +283,16 @@ func _on_meshes_pressed() -> void:
|
|||
meshes_btn.button_pressed = true
|
||||
textures_btn.button_pressed = false
|
||||
mesh_list.set_selected_id(mesh_list.selected_id)
|
||||
plugin.get_editor_interface().edit_node(plugin.terrain)
|
||||
if plugin.is_terrain_valid():
|
||||
EditorInterface.edit_node(plugin.terrain)
|
||||
update_thumbnails()
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||
if p_tool == Terrain3DEditor.INSTANCER:
|
||||
_on_meshes_pressed()
|
||||
elif p_tool == Terrain3DEditor.TEXTURE:
|
||||
elif p_tool in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
|
||||
_on_textures_pressed()
|
||||
|
||||
|
||||
|
@ -320,24 +312,34 @@ func update_assets() -> void:
|
|||
|
||||
_current_list.update_asset_list()
|
||||
|
||||
|
||||
## Window Management
|
||||
|
||||
|
||||
func make_dock_float() -> void:
|
||||
# If already created (eg from editor Make Floating)
|
||||
# If not already created (eg from editor panel 'Make Floating' button)
|
||||
if not window:
|
||||
remove_dock()
|
||||
create_window()
|
||||
|
||||
state = WINDOWED
|
||||
visible = true # Asset dock contents are hidden when popping out of the bottom!
|
||||
pinned_btn.visible = true
|
||||
floating_btn.visible = false
|
||||
placement_opt.visible = false
|
||||
window.title = "Terrain3D Asset Dock"
|
||||
window.always_on_top = pinned_btn.button_pressed
|
||||
window.close_requested.connect(remove_dock.bind(true))
|
||||
visible = true # Is hidden when pops off of bottom. ??
|
||||
_godot_editor_window.grab_focus()
|
||||
window.window_input.connect(_on_window_input)
|
||||
window.focus_exited.connect(save_editor_settings)
|
||||
window.mouse_exited.connect(save_editor_settings)
|
||||
window.size_changed.connect(save_editor_settings)
|
||||
plugin.godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
|
||||
plugin.godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
plugin.godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
update_assets()
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func create_window() -> void:
|
||||
|
@ -348,79 +350,80 @@ func create_window() -> void:
|
|||
mc.add_child(self)
|
||||
window.add_child(mc)
|
||||
window.set_transient(false)
|
||||
window.set_size(get_setting(PS_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
||||
window.set_position(get_setting(PS_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
||||
window.set_size(plugin.get_setting(ES_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
||||
window.set_position(plugin.get_setting(ES_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
||||
plugin.add_child(window)
|
||||
window.show()
|
||||
window.window_input.connect(_on_window_input)
|
||||
window.focus_exited.connect(_on_window_focus_exited)
|
||||
_godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
|
||||
_godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
_godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
|
||||
|
||||
|
||||
func clamp_window_position() -> void:
|
||||
if window and window.visible:
|
||||
var bounds: Vector2i
|
||||
if EditorInterface.get_editor_settings().get_setting("interface/editor/single_window_mode"):
|
||||
bounds = EditorInterface.get_base_control().size
|
||||
else:
|
||||
bounds = DisplayServer.screen_get_position(window.current_screen)
|
||||
bounds += DisplayServer.screen_get_size(window.current_screen)
|
||||
var margin: int = 40
|
||||
window.position.x = clamp(window.position.x, -window.size.x + 2*margin, bounds.x - margin)
|
||||
window.position.y = clamp(window.position.y, 25, bounds.y - margin)
|
||||
|
||||
|
||||
func _on_window_input(event: InputEvent) -> void:
|
||||
# Capture CTRL+S when doc focused to save scene)
|
||||
# Capture CTRL+S when doc focused to save scene
|
||||
if event is InputEventKey and event.keycode == KEY_S and event.pressed and event.is_command_or_control_pressed():
|
||||
save_editor_settings()
|
||||
plugin.get_editor_interface().save_scene()
|
||||
|
||||
|
||||
func _on_window_focus_exited() -> void:
|
||||
# Capture window position w/o other changes
|
||||
save_editor_settings()
|
||||
EditorInterface.save_scene()
|
||||
|
||||
|
||||
func _on_godot_window_entered() -> void:
|
||||
if is_instance_valid(window) and window.has_focus():
|
||||
_godot_editor_window.grab_focus()
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
|
||||
|
||||
func _on_godot_focus_entered() -> void:
|
||||
# If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window
|
||||
if is_instance_valid(window):
|
||||
if _godot_last_state == Window.MODE_MINIMIZED and _godot_editor_window.mode != Window.MODE_MINIMIZED:
|
||||
if _godot_last_state == Window.MODE_MINIMIZED and plugin.godot_editor_window.mode != Window.MODE_MINIMIZED:
|
||||
window.show()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
_godot_editor_window.grab_focus()
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
|
||||
|
||||
func _on_godot_focus_exited() -> void:
|
||||
if is_instance_valid(window) and _godot_editor_window.mode == Window.MODE_MINIMIZED:
|
||||
if is_instance_valid(window) and plugin.godot_editor_window.mode == Window.MODE_MINIMIZED:
|
||||
window.hide()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
|
||||
|
||||
## Manage Editor Settings
|
||||
|
||||
|
||||
func get_setting(p_str: String, p_default: Variant) -> Variant:
|
||||
if editor_settings.has_setting(p_str):
|
||||
return editor_settings.get_setting(p_str)
|
||||
else:
|
||||
return p_default
|
||||
|
||||
|
||||
func load_editor_settings() -> void:
|
||||
floating_btn.button_pressed = get_setting(PS_DOCK_FLOATING, false)
|
||||
pinned_btn.button_pressed = get_setting(PS_DOCK_PINNED, true)
|
||||
size_slider.value = get_setting(PS_DOCK_TILE_SIZE, 83)
|
||||
set_slot(get_setting(PS_DOCK_SLOT, POS_BOTTOM))
|
||||
floating_btn.button_pressed = plugin.get_setting(ES_DOCK_FLOATING, false)
|
||||
pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true)
|
||||
size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 83)
|
||||
_on_slider_changed(size_slider.value)
|
||||
# Window pos/size set on window creation in update_dock
|
||||
update_dock(plugin.visible)
|
||||
|
||||
|
||||
set_slot(plugin.get_setting(ES_DOCK_SLOT, POS_BOTTOM))
|
||||
if floating_btn.button_pressed:
|
||||
make_dock_float()
|
||||
# TODO Don't save tab until thumbnail generation more reliable
|
||||
#if plugin.get_setting(ES_DOCK_TAB, 0) == 1:
|
||||
# _on_meshes_pressed()
|
||||
|
||||
|
||||
func save_editor_settings() -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
editor_settings.set_setting(PS_DOCK_SLOT, slot)
|
||||
editor_settings.set_setting(PS_DOCK_TILE_SIZE, size_slider.value)
|
||||
editor_settings.set_setting(PS_DOCK_FLOATING, floating_btn.button_pressed)
|
||||
editor_settings.set_setting(PS_DOCK_PINNED, pinned_btn.button_pressed)
|
||||
clamp_window_position()
|
||||
plugin.set_setting(ES_DOCK_SLOT, slot)
|
||||
plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value)
|
||||
plugin.set_setting(ES_DOCK_FLOATING, floating_btn.button_pressed)
|
||||
plugin.set_setting(ES_DOCK_PINNED, pinned_btn.button_pressed)
|
||||
# TODO Don't save tab until thumbnail generation more reliable
|
||||
# plugin.set_setting(ES_DOCK_TAB, 0 if _current_list == texture_list else 1)
|
||||
if window:
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_SIZE, window.size)
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_POSITION, window.position)
|
||||
plugin.set_setting(ES_DOCK_WINDOW_SIZE, window.size)
|
||||
plugin.set_setting(ES_DOCK_WINDOW_POSITION, window.position)
|
||||
|
||||
|
||||
##############################################################
|
||||
|
@ -527,7 +530,8 @@ class ListContainer extends Container:
|
|||
plugin.select_terrain()
|
||||
|
||||
# Select Paint tool if clicking a texture
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE and plugin.editor.get_tool() != Terrain3DEditor.TEXTURE:
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE and \
|
||||
not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
|
||||
var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintBaseTexture")
|
||||
if paint_btn:
|
||||
paint_btn.set_pressed(true)
|
||||
|
@ -545,7 +549,7 @@ class ListContainer extends Container:
|
|||
|
||||
func _on_resource_inspected(p_resource: Resource) -> void:
|
||||
await get_tree().create_timer(.01).timeout
|
||||
plugin.get_editor_interface().edit_resource(p_resource)
|
||||
EditorInterface.edit_resource(p_resource)
|
||||
|
||||
|
||||
func _on_resource_changed(p_resource: Resource, p_id: int) -> void:
|
||||
|
@ -575,7 +579,7 @@ class ListContainer extends Container:
|
|||
|
||||
# If removing an entry, clear inspector
|
||||
if not p_resource:
|
||||
plugin.get_editor_interface().inspect_object(null)
|
||||
EditorInterface.inspect_object(null)
|
||||
|
||||
# If null resource, remove last
|
||||
if not p_resource:
|
||||
|
@ -694,7 +698,7 @@ class ListEntry extends VBoxContainer:
|
|||
else:
|
||||
name_label.text = "Add Mesh"
|
||||
|
||||
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
match p_what:
|
||||
NOTIFICATION_DRAW:
|
||||
|
|
|
@ -5,37 +5,39 @@
|
|||
[node name="bake_lod_dialog" type="ConfirmationDialog"]
|
||||
title = "Bake Terrain3D Mesh"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(400, 115)
|
||||
size = Vector2i(400, 155)
|
||||
visible = true
|
||||
script = ExtResource("1_sf76d")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
offset_right = 392.0
|
||||
offset_bottom = 106.0
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "LOD:"
|
||||
|
||||
[node name="LodBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
||||
[node name="LodBox" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 8.0
|
||||
value = 4.0
|
||||
|
||||
[node name="DescriptionLabel" type="Label" parent="VBoxContainer"]
|
||||
[node name="DescriptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 2
|
||||
|
|
|
@ -44,11 +44,14 @@ func bake_mesh_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _bake_mesh
|
||||
bake_lod_dialog.description = BAKE_MESH_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(bake_lod_dialog)
|
||||
EditorInterface.popup_dialog_centered(bake_lod_dialog)
|
||||
|
||||
|
||||
func _bake_mesh() -> void:
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DStorage.HEIGHT_FILTER_NEAREST)
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_NEAREST)
|
||||
if !mesh:
|
||||
push_error("Failed to bake mesh from Terrain3D")
|
||||
return
|
||||
|
@ -65,7 +68,7 @@ func _bake_mesh() -> void:
|
|||
|
||||
undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true)
|
||||
undo.add_undo_method(plugin.terrain, &"remove_child", mesh_instance)
|
||||
undo.add_do_property(mesh_instance, &"owner", plugin.terrain.owner)
|
||||
undo.add_do_property(mesh_instance, &"owner", EditorInterface.get_edited_scene_root())
|
||||
undo.add_do_reference(mesh_instance)
|
||||
|
||||
else:
|
||||
|
@ -86,11 +89,14 @@ func bake_occluder_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _bake_occluder
|
||||
bake_lod_dialog.description = BAKE_OCCLUDER_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(bake_lod_dialog)
|
||||
EditorInterface.popup_dialog_centered(bake_lod_dialog)
|
||||
|
||||
|
||||
func _bake_occluder() -> void:
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DStorage.HEIGHT_FILTER_MINIMUM)
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_MINIMUM)
|
||||
if !mesh:
|
||||
push_error("Failed to bake mesh from Terrain3D")
|
||||
return
|
||||
|
@ -113,7 +119,7 @@ func _bake_occluder() -> void:
|
|||
|
||||
undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true)
|
||||
undo.add_undo_method(plugin.terrain, &"remove_child", occluder_instance)
|
||||
undo.add_do_property(occluder_instance, &"owner", plugin.terrain.owner)
|
||||
undo.add_do_property(occluder_instance, &"owner", EditorInterface.get_edited_scene_root())
|
||||
undo.add_do_reference(occluder_instance)
|
||||
|
||||
else:
|
||||
|
@ -153,7 +159,7 @@ func find_nav_region_terrains(p_nav_region: NavigationRegion3D) -> Array[Terrain
|
|||
|
||||
func find_terrain_nav_regions(p_terrain: Terrain3D) -> Array[NavigationRegion3D]:
|
||||
var result: Array[NavigationRegion3D] = []
|
||||
var root: Node = plugin.get_editor_interface().get_edited_scene_root()
|
||||
var root: Node = EditorInterface.get_edited_scene_root()
|
||||
if not root:
|
||||
return result
|
||||
for nav_region in root.find_children("", "NavigationRegion3D", true, true):
|
||||
|
@ -169,6 +175,9 @@ func bake_nav_mesh() -> void:
|
|||
print("Terrain3DNavigation: Finished baking 1 NavigationMesh.")
|
||||
|
||||
elif plugin.terrain:
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
# A Terrain3D is selected. There are potentially multiple navmeshes to bake and we need to
|
||||
# find them all. (The multiple navmesh use-case is likely on very large scenes with lots of
|
||||
# geometry. Each navmesh in this case would define its own, non-overlapping, baking AABB, to
|
||||
|
@ -329,11 +338,17 @@ func set_up_navigation_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _set_up_navigation
|
||||
confirm_dialog.dialog_text = SET_UP_NAVIGATION_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(confirm_dialog)
|
||||
EditorInterface.popup_dialog_centered(confirm_dialog)
|
||||
|
||||
|
||||
func _set_up_navigation() -> void:
|
||||
assert(plugin.terrain)
|
||||
if plugin.terrain == EditorInterface.get_edited_scene_root():
|
||||
push_error("Terrain3D Navigation setup not possible if Terrain3D node is scene root")
|
||||
return
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions")
|
||||
return
|
||||
var terrain: Terrain3D = plugin.terrain
|
||||
|
||||
var nav_region := NavigationRegion3D.new()
|
||||
|
@ -348,7 +363,7 @@ func _set_up_navigation() -> void:
|
|||
undo_redo.add_do_reference(nav_region)
|
||||
undo_redo.commit_action()
|
||||
|
||||
plugin.get_editor_interface().inspect_object(nav_region)
|
||||
EditorInterface.inspect_object(nav_region)
|
||||
assert(plugin.nav_region == nav_region)
|
||||
|
||||
bake_nav_mesh()
|
||||
|
|
|
@ -1,21 +1,38 @@
|
|||
extends Object
|
||||
extends RefCounted
|
||||
|
||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/src/channel_packer.tscn"
|
||||
const TEMPLATE_PATH: String = "res://addons/terrain_3d/src/channel_packer_import_template.txt"
|
||||
|
||||
const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/src/channel_packer_dragdrop.gd"
|
||||
enum {
|
||||
IMAGE_ALBEDO,
|
||||
IMAGE_HEIGHT,
|
||||
IMAGE_NORMAL,
|
||||
IMAGE_ROUGHNESS,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
}
|
||||
|
||||
enum {
|
||||
IMAGE_ALBEDO,
|
||||
IMAGE_HEIGHT,
|
||||
IMAGE_NORMAL,
|
||||
IMAGE_ROUGHNESS
|
||||
}
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var editor_interface: EditorInterface
|
||||
var dialog: AcceptDialog
|
||||
var save_file_dialog: FileDialog
|
||||
var open_file_dialog: FileDialog
|
||||
var window: Window
|
||||
var save_file_dialog: EditorFileDialog
|
||||
var open_file_dialog: EditorFileDialog
|
||||
var invert_green_checkbox: CheckBox
|
||||
var invert_smooth_checkbox: CheckBox
|
||||
var invert_height_checkbox: CheckBox
|
||||
var lumin_height_button: Button
|
||||
var generate_mipmaps_checkbox: CheckBox
|
||||
var high_quality_checkbox: CheckBox
|
||||
var align_normals_checkbox: CheckBox
|
||||
var resize_toggle_checkbox: CheckBox
|
||||
var resize_option_box: SpinBox
|
||||
var height_channel: Array[Button]
|
||||
var height_channel_selected: int = 0
|
||||
var roughness_channel: Array[Button]
|
||||
var roughness_channel_selected: int = 0
|
||||
var last_opened_directory: String
|
||||
var last_saved_directory: String
|
||||
var packing_albedo: bool = false
|
||||
|
@ -24,133 +41,283 @@ var images: Array[Image] = [null, null, null, null]
|
|||
var status_label: Label
|
||||
var no_op: Callable = func(): pass
|
||||
var last_file_selected_fn: Callable = no_op
|
||||
var normal_vector: Vector3
|
||||
|
||||
|
||||
func pack_textures_popup() -> void:
|
||||
if dialog != null:
|
||||
print("Terrain3DChannelPacker: Cannot open pack tool, dialog already open.")
|
||||
if window != null:
|
||||
window.show()
|
||||
window.move_to_foreground()
|
||||
window.move_to_center()
|
||||
return
|
||||
|
||||
dialog = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
dialog.confirmed.connect(_on_close_requested)
|
||||
dialog.canceled.connect(_on_close_requested)
|
||||
status_label = dialog.find_child("StatusLabel")
|
||||
invert_green_checkbox = dialog.find_child("InvertGreenChannelCheckBox")
|
||||
|
||||
editor_interface = plugin.get_editor_interface()
|
||||
_init_file_dialogs()
|
||||
editor_interface.popup_dialog_centered(dialog)
|
||||
|
||||
_init_texture_picker(dialog.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
||||
_init_texture_picker(dialog.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(dialog.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(dialog.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
window = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
window.close_requested.connect(_on_close_requested)
|
||||
window.window_input.connect(func(event:InputEvent):
|
||||
if event is InputEventKey:
|
||||
if event.pressed and event.keycode == KEY_ESCAPE:
|
||||
_on_close_requested()
|
||||
)
|
||||
window.find_child("CloseButton").pressed.connect(_on_close_requested)
|
||||
|
||||
status_label = window.find_child("StatusLabel") as Label
|
||||
invert_green_checkbox = window.find_child("InvertGreenChannelCheckBox") as CheckBox
|
||||
invert_smooth_checkbox = window.find_child("InvertSmoothCheckBox") as CheckBox
|
||||
invert_height_checkbox = window.find_child("ConvertDepthToHeight") as CheckBox
|
||||
lumin_height_button = window.find_child("LuminanceAsHeightButton") as Button
|
||||
generate_mipmaps_checkbox = window.find_child("GenerateMipmapsCheckBox") as CheckBox
|
||||
high_quality_checkbox = window.find_child("HighQualityCheckBox") as CheckBox
|
||||
align_normals_checkbox = window.find_child("AlignNormalsCheckBox") as CheckBox
|
||||
resize_toggle_checkbox = window.find_child("ResizeToggle") as CheckBox
|
||||
resize_option_box = window.find_child("ResizeOptionButton") as SpinBox
|
||||
height_channel = [
|
||||
window.find_child("HeightChannelR") as Button,
|
||||
window.find_child("HeightChannelG") as Button,
|
||||
window.find_child("HeightChannelB") as Button,
|
||||
window.find_child("HeightChannelA") as Button
|
||||
]
|
||||
roughness_channel = [
|
||||
window.find_child("RoughnessChannelR") as Button,
|
||||
window.find_child("RoughnessChannelG") as Button,
|
||||
window.find_child("RoughnessChannelB") as Button,
|
||||
window.find_child("RoughnessChannelA") as Button
|
||||
]
|
||||
|
||||
height_channel[0].pressed.connect(func() -> void: height_channel_selected = 0)
|
||||
height_channel[1].pressed.connect(func() -> void: height_channel_selected = 1)
|
||||
height_channel[2].pressed.connect(func() -> void: height_channel_selected = 2)
|
||||
height_channel[3].pressed.connect(func() -> void: height_channel_selected = 3)
|
||||
|
||||
roughness_channel[0].pressed.connect(func() -> void: roughness_channel_selected = 0)
|
||||
roughness_channel[1].pressed.connect(func() -> void: roughness_channel_selected = 1)
|
||||
roughness_channel[2].pressed.connect(func() -> void: roughness_channel_selected = 2)
|
||||
roughness_channel[3].pressed.connect(func() -> void: roughness_channel_selected = 3)
|
||||
|
||||
plugin.add_child(window)
|
||||
_init_file_dialogs()
|
||||
|
||||
# the dialog disables the parent window "on top" so, restore it after 1 frame to alow the dialog to clear.
|
||||
var set_on_top_fn: Callable = func(_file: String = "") -> void:
|
||||
await RenderingServer.frame_post_draw
|
||||
window.always_on_top = true
|
||||
save_file_dialog.file_selected.connect(set_on_top_fn)
|
||||
save_file_dialog.canceled.connect(set_on_top_fn)
|
||||
open_file_dialog.file_selected.connect(set_on_top_fn)
|
||||
open_file_dialog.canceled.connect(set_on_top_fn)
|
||||
|
||||
_init_texture_picker(window.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
||||
_init_texture_picker(window.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
|
||||
var pack_button_path: String = "Panel/MarginContainer/VBoxContainer/PackButton"
|
||||
(dialog.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
(window.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
last_file_selected_fn = no_op
|
||||
images = [null, null, null, null]
|
||||
dialog.queue_free()
|
||||
dialog = null
|
||||
window.queue_free()
|
||||
window = null
|
||||
|
||||
|
||||
func _init_file_dialogs() -> void:
|
||||
save_file_dialog = FileDialog.new()
|
||||
save_file_dialog = EditorFileDialog.new()
|
||||
save_file_dialog.set_filters(PackedStringArray(["*.png"]))
|
||||
save_file_dialog.set_file_mode(FileDialog.FILE_MODE_SAVE_FILE)
|
||||
save_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
save_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
|
||||
save_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
save_file_dialog.file_selected.connect(_on_save_file_selected)
|
||||
|
||||
open_file_dialog = FileDialog.new()
|
||||
open_file_dialog.set_filters(PackedStringArray(["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", ".ktx"]))
|
||||
open_file_dialog.set_file_mode(FileDialog.FILE_MODE_OPEN_FILE)
|
||||
open_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
|
||||
dialog.add_child(save_file_dialog)
|
||||
dialog.add_child(open_file_dialog)
|
||||
save_file_dialog.ok_button_text = "Save"
|
||||
save_file_dialog.size = Vector2i(550, 550)
|
||||
#save_file_dialog.transient = false
|
||||
#save_file_dialog.exclusive = false
|
||||
#save_file_dialog.popup_window = true
|
||||
|
||||
open_file_dialog = EditorFileDialog.new()
|
||||
open_file_dialog.set_filters(PackedStringArray(
|
||||
["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", "*.ktx", "*.dds"]))
|
||||
open_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_OPEN_FILE)
|
||||
open_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
open_file_dialog.ok_button_text = "Open"
|
||||
open_file_dialog.size = Vector2i(550, 550)
|
||||
#open_file_dialog.transient = false
|
||||
#open_file_dialog.exclusive = false
|
||||
#open_file_dialog.popup_window = true
|
||||
|
||||
window.add_child(save_file_dialog)
|
||||
window.add_child(open_file_dialog)
|
||||
|
||||
|
||||
func _init_texture_picker(p_parent: Node, p_image_index: int) -> void:
|
||||
var line_edit: LineEdit = p_parent.find_child("LineEdit")
|
||||
var file_pick_button: Button = p_parent.find_child("PickButton")
|
||||
var clear_button: Button = p_parent.find_child("ClearButton")
|
||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect")
|
||||
var texture_button: Button = p_parent.find_child("TextureButton")
|
||||
|
||||
var line_edit: LineEdit = p_parent.find_child("LineEdit") as LineEdit
|
||||
var file_pick_button: Button = p_parent.find_child("PickButton") as Button
|
||||
var clear_button: Button = p_parent.find_child("ClearButton") as Button
|
||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect") as TextureRect
|
||||
var texture_button: Button = p_parent.find_child("TextureButton") as Button
|
||||
texture_button.set_script(load(DRAG_DROP_SCRIPT) as GDScript)
|
||||
|
||||
var set_channel_fn: Callable = func(used_channels: int) -> void:
|
||||
var channel_count: int = 4
|
||||
# enum Image.UsedChannels
|
||||
match used_channels:
|
||||
Image.USED_CHANNELS_L, Image.USED_CHANNELS_R: channel_count = 1
|
||||
Image.USED_CHANNELS_LA, Image.USED_CHANNELS_RG: channel_count = 2
|
||||
Image.USED_CHANNELS_RGB: channel_count = 3
|
||||
Image.USED_CHANNELS_RGBA: channel_count = 4
|
||||
if p_image_index == IMAGE_HEIGHT:
|
||||
for i in 4:
|
||||
height_channel[i].visible = i < channel_count
|
||||
height_channel[0].button_pressed = true
|
||||
height_channel[0].pressed.emit()
|
||||
elif p_image_index == IMAGE_ROUGHNESS:
|
||||
for i in 4:
|
||||
roughness_channel[i].visible = i < channel_count
|
||||
roughness_channel[0].button_pressed = true
|
||||
roughness_channel[0].pressed.emit()
|
||||
|
||||
var load_image_fn: Callable = func(path: String):
|
||||
var image: Image = Image.new()
|
||||
var error: int = OK
|
||||
# Special case for dds files
|
||||
if path.get_extension() == "dds":
|
||||
image = ResourceLoader.load(path).get_image()
|
||||
if not image.is_empty():
|
||||
# if the dds file is loaded, we must clear any mipmaps and
|
||||
# decompress if needed in order to do per pixel operations.
|
||||
image.clear_mipmaps()
|
||||
image.decompress()
|
||||
else:
|
||||
error = FAILED
|
||||
else:
|
||||
error = image.load(path)
|
||||
if error != OK:
|
||||
_show_message(ERROR, "Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
_show_message(INFO, "Loaded texture '" + path + "'")
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
_set_wh_labels(p_image_index, image.get_width(), image.get_height())
|
||||
if p_image_index == IMAGE_NORMAL:
|
||||
_set_normal_vector(image)
|
||||
if p_image_index == IMAGE_HEIGHT or p_image_index == IMAGE_ROUGHNESS:
|
||||
set_channel_fn.call(image.detect_used_channels())
|
||||
|
||||
var os_drop_fn: Callable = func(files: PackedStringArray) -> void:
|
||||
# OS drag drop holds mouse focus until released,
|
||||
# Get mouse pos and check directly if inside texture_rect
|
||||
var rect = texture_button.get_global_rect()
|
||||
var mouse_position = texture_button.get_global_mouse_position()
|
||||
if rect.has_point(mouse_position):
|
||||
if files.size() != 1:
|
||||
_show_message(ERROR, "Cannot load multiple files")
|
||||
else:
|
||||
line_edit.text = files[0]
|
||||
load_image_fn.call(files[0])
|
||||
|
||||
var godot_drop_fn: Callable = func(path: String) -> void:
|
||||
path = ProjectSettings.globalize_path(path)
|
||||
line_edit.text = path
|
||||
load_image_fn.call(path)
|
||||
|
||||
var open_fn: Callable = func() -> void:
|
||||
open_file_dialog.current_path = last_opened_directory
|
||||
if last_file_selected_fn != no_op:
|
||||
open_file_dialog.file_selected.disconnect(last_file_selected_fn)
|
||||
last_file_selected_fn = func(path: String) -> void:
|
||||
line_edit.text = path
|
||||
line_edit.caret_column = path.length()
|
||||
last_opened_directory = path.get_base_dir() + "/"
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
_show_success("Loaded texture '" + path + "'")
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
load_image_fn.call(path)
|
||||
open_file_dialog.file_selected.connect(last_file_selected_fn)
|
||||
open_file_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
var line_edit_submit_fn: Callable = func(path: String) -> void:
|
||||
line_edit.text = path
|
||||
load_image_fn.call(path)
|
||||
|
||||
var clear_fn: Callable = func() -> void:
|
||||
line_edit.text = ""
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
|
||||
# allow user to edit textbox and press enter because Godot's file picker doesn't work 100% of the time
|
||||
var line_edit_submit_fn: Callable = func(path: String) -> void:
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
|
||||
_set_wh_labels(p_image_index, -1, -1)
|
||||
|
||||
line_edit.text_submitted.connect(line_edit_submit_fn)
|
||||
file_pick_button.pressed.connect(open_fn)
|
||||
texture_button.pressed.connect(open_fn)
|
||||
clear_button.pressed.connect(clear_fn)
|
||||
_set_button_icon(file_pick_button, "Folder")
|
||||
_set_button_icon(clear_button, "Remove")
|
||||
texture_button.dropped.connect(godot_drop_fn)
|
||||
window.files_dropped.connect(os_drop_fn)
|
||||
|
||||
if p_image_index == IMAGE_HEIGHT:
|
||||
var lumin_fn: Callable = func() -> void:
|
||||
if !images[IMAGE_ALBEDO]:
|
||||
_show_message(ERROR, "Albedo Image Required for Operation")
|
||||
else:
|
||||
line_edit.text = "Generated Height"
|
||||
var height_texture: Image = Terrain3DUtil.luminance_to_height(images[IMAGE_ALBEDO])
|
||||
if height_texture.is_empty():
|
||||
_show_message(ERROR, "Height Texture Generation error")
|
||||
# blur the image by resizing down and back..
|
||||
var w: int = height_texture.get_width()
|
||||
var h: int = height_texture.get_height()
|
||||
height_texture.resize(w / 4, h / 4)
|
||||
height_texture.resize(w, h, Image.INTERPOLATE_CUBIC)
|
||||
# "Load" the height texture
|
||||
images[IMAGE_HEIGHT] = height_texture
|
||||
texture_rect.texture = ImageTexture.create_from_image(images[IMAGE_HEIGHT])
|
||||
_set_wh_labels(IMAGE_HEIGHT, height_texture.get_width(), height_texture.get_height())
|
||||
set_channel_fn.call(Image.USED_CHANNELS_R)
|
||||
_show_message(INFO, "Height Texture generated sucsessfully")
|
||||
lumin_height_button.pressed.connect(lumin_fn)
|
||||
plugin.ui.set_button_editor_icon(file_pick_button, "Folder")
|
||||
plugin.ui.set_button_editor_icon(clear_button, "Remove")
|
||||
|
||||
|
||||
func _set_button_icon(p_button: Button, p_icon_name: String) -> void:
|
||||
var editor_base: Control = editor_interface.get_base_control()
|
||||
var icon: Texture2D = editor_base.get_theme_icon(p_icon_name, "EditorIcons")
|
||||
p_button.icon = icon
|
||||
func _set_wh_labels(p_image_index: int, width: int, height: int) -> void:
|
||||
var w: String = ""
|
||||
var h: String = ""
|
||||
if width > 0 and height > 0:
|
||||
w = "w: " + str(width)
|
||||
h = "h: " + str(height)
|
||||
match p_image_index:
|
||||
0:
|
||||
window.find_child("AlbedoW").text = w
|
||||
window.find_child("AlbedoH").text = h
|
||||
1:
|
||||
window.find_child("HeightW").text = w
|
||||
window.find_child("HeightH").text = h
|
||||
2:
|
||||
window.find_child("NormalW").text = w
|
||||
window.find_child("NormalH").text = h
|
||||
3:
|
||||
window.find_child("RoughnessW").text = w
|
||||
window.find_child("RoughnessH").text = h
|
||||
|
||||
|
||||
func _show_error(p_text: String) -> void:
|
||||
push_error("Terrain3DChannelPacker: " + p_text)
|
||||
func _show_message(p_level: int, p_text: String) -> void:
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
||||
|
||||
|
||||
func _show_success(p_text: String) -> void:
|
||||
print("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||
match p_level:
|
||||
INFO:
|
||||
print("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||
WARN:
|
||||
push_warning("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0.9, 0))
|
||||
ERROR,_:
|
||||
push_error("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
||||
|
||||
|
||||
func _create_import_file(png_path: String) -> void:
|
||||
var dst_import_path: String = png_path + ".import"
|
||||
|
||||
var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
|
||||
var template_content: String = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var import_content: String = template_content.replace("$SOURCE_FILE", png_path)
|
||||
template_content = template_content.replace(
|
||||
"$SOURCE_FILE", png_path).replace(
|
||||
"$HIGH_QUALITY", str(high_quality_checkbox.button_pressed)).replace(
|
||||
"$GENERATE_MIPMAPS", str(generate_mipmaps_checkbox.button_pressed)
|
||||
)
|
||||
var import_content: String = template_content
|
||||
file = FileAccess.open(dst_import_path, FileAccess.WRITE)
|
||||
file.store_string(import_content)
|
||||
file.close()
|
||||
|
@ -159,11 +326,10 @@ func _create_import_file(png_path: String) -> void:
|
|||
func _on_pack_button_pressed() -> void:
|
||||
packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null
|
||||
var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null
|
||||
|
||||
|
||||
if not packing_albedo and not packing_normal_roughness:
|
||||
_show_error("Please select an albedo and height texture or a normal and roughness texture.")
|
||||
_show_message(WARN, "Please select an albedo and height texture or a normal and roughness texture")
|
||||
return
|
||||
|
||||
if packing_albedo:
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
|
||||
save_file_dialog.title = "Save Packed Albedo/Height Texture"
|
||||
|
@ -179,34 +345,119 @@ func _on_pack_button_pressed() -> void:
|
|||
|
||||
func _on_save_file_selected(p_dst_path) -> void:
|
||||
last_saved_directory = p_dst_path.get_base_dir() + "/"
|
||||
var error: int
|
||||
if packing_albedo:
|
||||
_pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false)
|
||||
error = _pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false,
|
||||
invert_height_checkbox.button_pressed, false, height_channel_selected)
|
||||
else:
|
||||
_pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path, invert_green_checkbox.button_pressed)
|
||||
error = _pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path,
|
||||
invert_green_checkbox.button_pressed, invert_smooth_checkbox.button_pressed,
|
||||
align_normals_checkbox.button_pressed, roughness_channel_selected)
|
||||
|
||||
if error == OK:
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
if window.visible:
|
||||
window.hide()
|
||||
await EditorInterface.get_resource_filesystem().resources_reimported
|
||||
# wait 1 extra frame, to ensure the UI is responsive.
|
||||
await RenderingServer.frame_post_draw
|
||||
window.show()
|
||||
|
||||
if queue_pack_normal_roughness:
|
||||
queue_pack_normal_roughness = false
|
||||
packing_albedo = false
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
||||
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
||||
|
||||
save_file_dialog.call_deferred("popup_centered_ratio")
|
||||
save_file_dialog.call_deferred("move_to_foreground")
|
||||
|
||||
|
||||
func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool) -> void:
|
||||
func _alignment_basis(normal: Vector3) -> Basis:
|
||||
var up: Vector3 = Vector3(0, 0, 1)
|
||||
var v: Vector3 = normal.cross(up)
|
||||
var c: float = normal.dot(up)
|
||||
var k: float = 1.0 / (1.0 + c)
|
||||
|
||||
var vxy: float = v.x * v.y * k
|
||||
var vxz: float = v.x * v.z * k
|
||||
var vyz: float = v.y * v.z * k
|
||||
|
||||
return Basis(Vector3(v.x * v.x * k + c, vxy - v.z, vxz + v.y),
|
||||
Vector3(vxy + v.z, v.y * v.y * k + c, vyz - v.x),
|
||||
Vector3(vxz - v.y, vyz + v.x, v.z * v.z * k + c)
|
||||
)
|
||||
|
||||
|
||||
func _set_normal_vector(source: Image, quiet: bool = false) -> void:
|
||||
# Calculate texture normal sum direction
|
||||
var normal: Image = source
|
||||
var sum: Color = Color(0.0, 0.0, 0.0, 0.0)
|
||||
for x in normal.get_height():
|
||||
for y in normal.get_width():
|
||||
sum += normal.get_pixel(x, y)
|
||||
var div: float = normal.get_height() * normal.get_width()
|
||||
sum /= Color(div, div, div)
|
||||
sum *= 2.0
|
||||
sum -= Color(1.0, 1.0, 1.0)
|
||||
normal_vector = Vector3(sum.r, sum.g, sum.b).normalized()
|
||||
if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && !quiet:
|
||||
_show_message(WARN, "Normal Texture Not Orthoganol to UV plane.\nFor Compatability with Detiling and Rotation, Select Orthoganolize Normals")
|
||||
|
||||
|
||||
func _align_normals(source: Image, iteration: int = 0) -> void:
|
||||
# generate matrix to re-align the normalmap
|
||||
var mat3: Basis = _alignment_basis(normal_vector)
|
||||
# re-align the normal map pixels
|
||||
for x in source.get_height():
|
||||
for y in source.get_width():
|
||||
var old_pixel: Color = source.get_pixel(x, y)
|
||||
var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b)
|
||||
vector_pixel *= 2.0
|
||||
vector_pixel -= Vector3.ONE
|
||||
vector_pixel = vector_pixel.normalized()
|
||||
vector_pixel = vector_pixel * mat3
|
||||
vector_pixel += Vector3.ONE
|
||||
vector_pixel *= 0.5
|
||||
var new_pixel: Color = Color(vector_pixel.x, vector_pixel.y, vector_pixel.z, old_pixel.a)
|
||||
source.set_pixel(x, y, new_pixel)
|
||||
_set_normal_vector(source, true)
|
||||
if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && iteration < 3:
|
||||
++iteration
|
||||
_align_normals(source, iteration)
|
||||
|
||||
|
||||
func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool,
|
||||
p_invert_smooth: bool, p_align_normals : bool, p_alpha_channel: int) -> Error:
|
||||
if p_rgb_image and p_a_image:
|
||||
if p_rgb_image.get_size() != p_a_image.get_size():
|
||||
_show_error("Textures must be the same size.")
|
||||
return
|
||||
|
||||
var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image, p_invert_green)
|
||||
|
||||
if p_rgb_image.get_size() != p_a_image.get_size() and !resize_toggle_checkbox.button_pressed:
|
||||
_show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions")
|
||||
return FAILED
|
||||
|
||||
if resize_toggle_checkbox.button_pressed:
|
||||
var size: int = max(128, resize_option_box.value)
|
||||
p_rgb_image.resize(size, size, Image.INTERPOLATE_CUBIC)
|
||||
p_a_image.resize(size, size, Image.INTERPOLATE_CUBIC)
|
||||
|
||||
if p_align_normals and normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999:
|
||||
_align_normals(p_rgb_image)
|
||||
elif p_align_normals:
|
||||
_show_message(INFO, "Alignment OK, skipping Normal Orthogonalization")
|
||||
|
||||
var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image,
|
||||
p_invert_green, p_invert_smooth, p_alpha_channel)
|
||||
|
||||
if not output_image:
|
||||
_show_error("Failed to pack textures.")
|
||||
return
|
||||
|
||||
_show_message(ERROR, "Failed to pack textures")
|
||||
return FAILED
|
||||
if output_image.detect_used_channels() != 5:
|
||||
_show_message(ERROR, "Packing Error, Alpha Channel empty")
|
||||
return FAILED
|
||||
|
||||
output_image.save_png(p_dst_path)
|
||||
editor_interface.get_resource_filesystem().scan_sources()
|
||||
_create_import_file(p_dst_path)
|
||||
_show_success("Packed to " + p_dst_path + ".")
|
||||
_show_message(INFO, "Packed to " + p_dst_path + ".")
|
||||
return OK
|
||||
else:
|
||||
_show_error("Failed to load one or more textures.")
|
||||
_show_message(ERROR, "Failed to load one or more textures")
|
||||
return FAILED
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://nud6dwjcnj5v"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://nud6dwjcnj5v"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
|
||||
bg_color = Color(0.211765, 0.239216, 0.290196, 1)
|
||||
|
@ -30,21 +30,21 @@ corner_radius_bottom_left = 5
|
|||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"]
|
||||
|
||||
[node name="AcceptDialog" type="AcceptDialog"]
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"]
|
||||
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_bs6ki"]
|
||||
|
||||
[node name="Window" type="Window"]
|
||||
title = "Terrain3D Channel Packer"
|
||||
initial_position = 1
|
||||
size = Vector2i(660, 900)
|
||||
visible = true
|
||||
ok_button_text = "Close"
|
||||
size = Vector2i(680, 835)
|
||||
unresizable = true
|
||||
always_on_top = true
|
||||
|
||||
[node name="Panel" type="Panel" 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 = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
|
||||
|
@ -54,10 +54,10 @@ layout_mode = 1
|
|||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = -1.0
|
||||
offset_bottom = -53.0
|
||||
offset_left = 5.0
|
||||
offset_top = 5.0
|
||||
offset_right = -5.0
|
||||
offset_bottom = 5.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
|
@ -67,12 +67,12 @@ theme_override_constants/margin_bottom = 5
|
|||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="AlbedoHeightPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 250)
|
||||
custom_minimum_size = Vector2(0, 290)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
|
||||
|
@ -147,6 +147,27 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="AlbedoWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AlbedoW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="AlbedoH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="LuminanceAsHeightButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Generate Height from Luminance"
|
||||
icon_alignment = 2
|
||||
|
||||
[node name="HeightVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -204,8 +225,63 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="HeightWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="HeightW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HeightH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ConvertDepthToHeight" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Convert Depth to Height"
|
||||
icon_alignment = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="HeightChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Source Channel: "
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="HeightChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "R"
|
||||
|
||||
[node name="HeightChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "G"
|
||||
|
||||
[node name="HeightChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "B"
|
||||
|
||||
[node name="HeightChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "A"
|
||||
|
||||
[node name="NormalRoughnessPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 280)
|
||||
custom_minimum_size = Vector2(0, 290)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||
|
||||
|
@ -281,9 +357,33 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
[node name="NormalWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
text = "Convert DirectX to OpenGL"
|
||||
alignment = 1
|
||||
|
||||
[node name="NormalW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="NormalH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Convert DirectX to OpenGL"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AlignNormalsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Orthoganolise Normals"
|
||||
|
||||
[node name="RoughnessVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
@ -342,18 +442,112 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="NormalRoughnessHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
[node name="RoughnessWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="RoughnessW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="RoughnessH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="InvertSmoothCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Convert Smoothness to Roughness"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="RoughnessChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Source Channel: "
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="RoughnessChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "R"
|
||||
|
||||
[node name="RoughnessChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "G"
|
||||
|
||||
[node name="RoughnessChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "B"
|
||||
|
||||
[node name="RoughnessChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "A"
|
||||
|
||||
[node name="GeneralOptionsLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "General Options"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="GeneralOptionsHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 35)
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ResizeToggle" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
text = " Resize Packed Image"
|
||||
|
||||
[node name="ResizeOptionButton" type="SpinBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
tooltip_text = "A value of 0 disables resizing."
|
||||
min_value = 128.0
|
||||
max_value = 4096.0
|
||||
step = 128.0
|
||||
value = 1024.0
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GenerateMipmapsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
button_pressed = true
|
||||
text = "Generate Mipmaps"
|
||||
|
||||
[node name="HighQualityCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
text = "Import High Quality"
|
||||
|
||||
[node name="PackButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Pack textures as..."
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
custom_minimum_size = Vector2(0, 60)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
text = "Use this to create a packed Albedo + Height texture and/or a packed Normal + Roughness texture.
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
You can then use these textures with Terrain3D."
|
||||
autowrap_mode = 2
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="CloseButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[connection signal="toggled" from="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeToggle" to="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeOptionButton" method="set_visible"]
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
@tool
|
||||
extends Button
|
||||
|
||||
signal dropped
|
||||
|
||||
func _can_drop_data(p_position, p_data) -> bool:
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
if p_data.files.size() == 1:
|
||||
match p_data.files[0].get_extension():
|
||||
"png", "bmp", "exr", "hdr", "jpg", "jpeg", "tga", "svg", "webp", "ktx", "dds":
|
||||
return true
|
||||
return false
|
||||
|
||||
func _drop_data(p_position, p_data) -> void:
|
||||
dropped.emit(p_data.files[0])
|
|
@ -14,12 +14,12 @@ source_file="$SOURCE_FILE"
|
|||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/high_quality=$HIGH_QUALITY
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=2
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=$GENERATE_MIPMAPS
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
@ -29,4 +29,4 @@ process/normal_map_invert_y=false
|
|||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
extends Node
|
||||
|
||||
const DIRECTORY_SETUP: String = "res://addons/terrain_3d/src/directory_setup.tscn"
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var dialog: ConfirmationDialog
|
||||
var select_dir_btn: Button
|
||||
var selected_dir_le: LineEdit
|
||||
var select_upg_btn: Button
|
||||
var upgrade_file_le: LineEdit
|
||||
var editor_file_dialog: EditorFileDialog
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
editor_file_dialog = EditorFileDialog.new()
|
||||
editor_file_dialog.set_filters(PackedStringArray(["*.res"]))
|
||||
editor_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
|
||||
editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
|
||||
editor_file_dialog.ok_button_text = "Open"
|
||||
editor_file_dialog.title = "Open a folder or file"
|
||||
editor_file_dialog.file_selected.connect(_on_file_selected)
|
||||
editor_file_dialog.dir_selected.connect(_on_dir_selected)
|
||||
editor_file_dialog.size = Vector2i(850, 550)
|
||||
editor_file_dialog.transient = false
|
||||
editor_file_dialog.exclusive = false
|
||||
editor_file_dialog.popup_window = true
|
||||
add_child(editor_file_dialog)
|
||||
|
||||
|
||||
func directory_setup_popup() -> void:
|
||||
dialog = load(DIRECTORY_SETUP).instantiate()
|
||||
dialog.hide()
|
||||
|
||||
# Nodes
|
||||
select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir")
|
||||
selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit")
|
||||
select_upg_btn = dialog.get_node("Margin/VBox/UpgradeHBox/SelectResFile")
|
||||
upgrade_file_le = dialog.get_node("Margin/VBox/UpgradeHBox/LineEdit")
|
||||
upgrade_file_le.text = ""
|
||||
|
||||
if plugin.terrain.data_directory:
|
||||
selected_dir_le.text = plugin.terrain.data_directory
|
||||
|
||||
if plugin.terrain.storage:
|
||||
upgrade_file_le.text = plugin.terrain.storage.get_path()
|
||||
|
||||
# Icons
|
||||
plugin.ui.set_button_editor_icon(select_upg_btn, "Folder")
|
||||
plugin.ui.set_button_editor_icon(select_dir_btn, "Folder")
|
||||
|
||||
#Signals
|
||||
select_upg_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_FILE))
|
||||
select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR))
|
||||
dialog.confirmed.connect(_on_close_requested)
|
||||
dialog.canceled.connect(_on_close_requested)
|
||||
dialog.get_ok_button().pressed.connect(_on_ok_pressed)
|
||||
|
||||
# Popup
|
||||
EditorInterface.popup_dialog_centered(dialog)
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
dialog.queue_free()
|
||||
dialog = null
|
||||
|
||||
|
||||
func _on_select_file_pressed(file_mode: EditorFileDialog.FileMode) -> void:
|
||||
editor_file_dialog.file_mode = file_mode
|
||||
editor_file_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_dir_selected(path: String) -> void:
|
||||
selected_dir_le.text = path
|
||||
|
||||
|
||||
func _on_file_selected(path: String) -> void:
|
||||
upgrade_file_le.text = path
|
||||
|
||||
|
||||
func _on_ok_pressed() -> void:
|
||||
if not plugin.terrain:
|
||||
push_error("Not connected terrain. Click the Terrain3D node first")
|
||||
return
|
||||
if selected_dir_le.text.is_empty():
|
||||
push_error("No data directory specified")
|
||||
return
|
||||
if not DirAccess.dir_exists_absolute(selected_dir_le.text):
|
||||
push_error("Directory doesn't exist: ", selected_dir_le.text)
|
||||
return
|
||||
# Check if directory empty of terrain files
|
||||
var data_found: bool = false
|
||||
var files: Array = DirAccess.get_files_at(selected_dir_le.text)
|
||||
for file in files:
|
||||
if file.begins_with("terrain3d") || file.ends_with(".res"):
|
||||
data_found = true
|
||||
break
|
||||
|
||||
print("Setting terrain directory: ", selected_dir_le.text)
|
||||
plugin.terrain.data_directory = selected_dir_le.text
|
||||
|
||||
if not upgrade_file_le.text.is_empty():
|
||||
if data_found:
|
||||
push_warning("Target directory already has terrain data. Specify an empty directory to upgrade")
|
||||
return
|
||||
if not FileAccess.file_exists(upgrade_file_le.text):
|
||||
push_error("File doesn't exist: ", upgrade_file_le.text)
|
||||
return
|
||||
|
||||
if not plugin.terrain.storage or \
|
||||
( plugin.terrain.storage and plugin.terrain.storage.get_path() != upgrade_file_le.text):
|
||||
print("Loading storage file: ", upgrade_file_le.text)
|
||||
plugin.terrain.set_storage(load(upgrade_file_le.text))
|
||||
|
||||
if plugin.terrain.storage:
|
||||
print("Begining upgrade of: ", upgrade_file_le.text)
|
||||
plugin.terrain.split_storage()
|
|
@ -0,0 +1,66 @@
|
|||
[gd_scene format=3 uid="uid://by3kr2nqbqr67"]
|
||||
|
||||
[node name="DirectorySetup" type="ConfirmationDialog"]
|
||||
title = "Terrain3D Data Directory Setup"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(750, 574)
|
||||
visible = true
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 742.0
|
||||
offset_bottom = 525.0
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 20
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Instructions" type="Label" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
text = "Terrain3D now stores data in a directory instead of a single file. Each region is stored in a separate file named `terrain3d[-_]##[-_]##.res`. For instance, the region at location (-1, 1) would be named `terrain3d-01_01.res`. Enable Terrain3D / Debug / Show Region Labels for a visual display."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="DirectoryLabel" type="Label" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
text = "
|
||||
Specify the directory to store your data. Any existing region files will be loaded."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="DirHBox" type="HBoxContainer" parent="Margin/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Margin/VBox/DirHBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Data directory"
|
||||
|
||||
[node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="UpgradeLabel" type="Label" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
text = "
|
||||
If you wish to upgrade a storage file from v0.8.4 - v0.9.2, specify it below. Data will be stored in the directory above upon save. The original file will not be touched."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="UpgradeHBox" type="HBoxContainer" parent="Margin/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Margin/VBox/UpgradeHBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Storage .res to upgrade"
|
||||
|
||||
[node name="SelectResFile" type="Button" parent="Margin/VBox/UpgradeHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Spacer" type="Control" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
|
@ -0,0 +1,124 @@
|
|||
@tool
|
||||
class_name DoubleSlider
|
||||
extends Control
|
||||
|
||||
signal value_changed(Vector2)
|
||||
var label: Label
|
||||
var suffix: String
|
||||
var grabbed_handle: int = 0 # -1 left, 0 none, 1 right
|
||||
var min_value: float = 0.0
|
||||
var max_value: float = 100.0
|
||||
var step: float = 1.0
|
||||
var range := Vector2(0, 100)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
update_label()
|
||||
|
||||
|
||||
func set_min(p_value: float) -> void:
|
||||
min_value = p_value
|
||||
if range.x <= min_value:
|
||||
range.x = min_value
|
||||
set_value(range)
|
||||
update_label()
|
||||
|
||||
|
||||
func get_min() -> float:
|
||||
return min_value
|
||||
|
||||
|
||||
func set_max(p_value: float) -> void:
|
||||
max_value = p_value
|
||||
if range.y == 0 or range.y >= max_value:
|
||||
range.y = max_value
|
||||
set_value(range)
|
||||
update_label()
|
||||
|
||||
|
||||
func get_max() -> float:
|
||||
return max_value
|
||||
|
||||
|
||||
func set_step(p_step: float) -> void:
|
||||
step = p_step
|
||||
|
||||
|
||||
func get_step() -> float:
|
||||
return step
|
||||
|
||||
|
||||
func set_value(p_range: Vector2) -> void:
|
||||
range.x = clamp(p_range.x, min_value, max_value)
|
||||
range.y = clamp(p_range.y, min_value, max_value)
|
||||
if range.y < range.x:
|
||||
var tmp: float = range.x
|
||||
range.x = range.y
|
||||
range.y = tmp
|
||||
|
||||
update_label()
|
||||
emit_signal("value_changed", Vector2(range.x, range.y))
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func get_value() -> Vector2:
|
||||
return range
|
||||
|
||||
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix)
|
||||
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
if p_event.is_pressed():
|
||||
var mid_point = (range.x + range.y) / 2.0
|
||||
var xpos: float = p_event.get_position().x * 2.0
|
||||
if xpos >= mid_point:
|
||||
grabbed_handle = 1
|
||||
else:
|
||||
grabbed_handle = -1
|
||||
set_slider(p_event.get_position().x)
|
||||
else:
|
||||
grabbed_handle = 0
|
||||
|
||||
if p_event is InputEventMouseMotion:
|
||||
if grabbed_handle != 0:
|
||||
set_slider(p_event.get_position().x)
|
||||
|
||||
|
||||
func set_slider(p_xpos: float) -> void:
|
||||
if grabbed_handle == 0:
|
||||
return
|
||||
var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value)
|
||||
if(grabbed_handle < 0):
|
||||
range.x = xpos_step
|
||||
else:
|
||||
range.y = xpos_step
|
||||
set_value(range)
|
||||
|
||||
|
||||
func _notification(p_what: int) -> void:
|
||||
if p_what == NOTIFICATION_DRAW:
|
||||
# Draw background bar
|
||||
var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
|
||||
var bg_height: float = bg.get_minimum_size().y
|
||||
var mid_y: float = (size.y - bg_height) / 2.0
|
||||
draw_style_box(bg, Rect2(Vector2(0, mid_y), Vector2(size.x, bg_height)))
|
||||
|
||||
# Draw foreground bar
|
||||
var handle: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - handle.get_size().y / 2
|
||||
var startx: float = (range.x / max_value) * size.x
|
||||
var endx: float = (range.y / max_value) * size.x #- startx
|
||||
draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height)))
|
||||
|
||||
# Draw handles, slightly in so they don't get on the outside edges
|
||||
var handle_pos: Vector2
|
||||
handle_pos.x = clamp(startx - handle.get_size().x/2, -5, size.x)
|
||||
handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10)
|
||||
draw_texture(handle, Vector2(handle_pos.x, -mid_y))
|
||||
draw_texture(handle, Vector2(handle_pos.y, -mid_y))
|
|
@ -5,7 +5,6 @@ signal pressed
|
|||
signal value_changed
|
||||
|
||||
|
||||
const ICON_PICKER: String = "res://addons/terrain_3d/icons/picker.svg"
|
||||
const ICON_PICKER_CHECKED: String = "res://addons/terrain_3d/icons/picker_checked.svg"
|
||||
const MAX_POINTS: int = 2
|
||||
|
||||
|
@ -16,8 +15,8 @@ var points: PackedVector3Array
|
|||
var picking_index: int = -1
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
icon_picker = load(ICON_PICKER)
|
||||
func _enter_tree() -> void:
|
||||
icon_picker = get_theme_icon("ColorPick", "EditorIcons")
|
||||
icon_picker_checked = load(ICON_PICKER_CHECKED)
|
||||
|
||||
points.resize(MAX_POINTS)
|
||||
|
|
|
@ -32,7 +32,7 @@ func _redraw() -> void:
|
|||
|
||||
if show_rect:
|
||||
var modulate: Color = main_color if !use_secondary_color else secondary_color
|
||||
if abs(region_position.x) > 8 or abs(region_position.y) > 8:
|
||||
if abs(region_position.x) > Terrain3DData.REGION_MAP_SIZE*.5 or abs(region_position.y) > Terrain3DData.REGION_MAP_SIZE*.5:
|
||||
modulate = Color.GRAY
|
||||
draw_rect(Vector2(region_size,region_size)*.5 + rect_position, region_size, selection_material, modulate)
|
||||
|
||||
|
@ -44,7 +44,7 @@ func _redraw() -> void:
|
|||
|
||||
draw_rect(Vector2(region_size,region_size)*.5 + grid_tile_position, region_size, material, grid_color)
|
||||
|
||||
draw_rect(Vector2.ZERO, region_size * 16.0, material, border_color)
|
||||
draw_rect(Vector2.ZERO, region_size * Terrain3DData.REGION_MAP_SIZE, material, border_color)
|
||||
|
||||
|
||||
func draw_rect(p_pos: Vector2, p_size: float, p_material: StandardMaterial3D, p_modulate: Color) -> void:
|
||||
|
|
|
@ -1,68 +1,72 @@
|
|||
extends HBoxContainer
|
||||
|
||||
|
||||
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd")
|
||||
const DirectoryWizard: Script = preload("res://addons/terrain_3d/src/directory_setup.gd")
|
||||
const Packer: Script = preload("res://addons/terrain_3d/src/channel_packer.gd")
|
||||
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd")
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var menu_button: MenuButton = MenuButton.new()
|
||||
var baker: Baker = Baker.new()
|
||||
var directory_setup: DirectoryWizard = DirectoryWizard.new()
|
||||
var packer: Packer = Packer.new()
|
||||
var baker: Baker = Baker.new()
|
||||
|
||||
# These are IDs and order must be consistent with add_item and set_disabled IDs
|
||||
enum {
|
||||
MENU_DIRECTORY_SETUP,
|
||||
MENU_PACK_TEXTURES,
|
||||
MENU_SEPARATOR,
|
||||
MENU_BAKE_ARRAY_MESH,
|
||||
MENU_BAKE_OCCLUDER,
|
||||
MENU_BAKE_NAV_MESH,
|
||||
MENU_SEPARATOR,
|
||||
MENU_SEPARATOR2,
|
||||
MENU_SET_UP_NAVIGATION,
|
||||
MENU_PACK_TEXTURES,
|
||||
MENU_BAKE_NAV_MESH,
|
||||
}
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
baker.plugin = plugin
|
||||
directory_setup.plugin = plugin
|
||||
packer.plugin = plugin
|
||||
|
||||
baker.plugin = plugin
|
||||
add_child(directory_setup)
|
||||
add_child(baker)
|
||||
|
||||
menu_button.text = "Terrain3D Tools"
|
||||
menu_button.get_popup().add_item("Bake ArrayMesh", MENU_BAKE_ARRAY_MESH)
|
||||
menu_button.get_popup().add_item("Bake Occluder3D", MENU_BAKE_OCCLUDER)
|
||||
menu_button.get_popup().add_item("Bake NavMesh", MENU_BAKE_NAV_MESH)
|
||||
menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP)
|
||||
menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
||||
menu_button.get_popup().add_item("Set up Navigation", MENU_SET_UP_NAVIGATION)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
||||
menu_button.get_popup().add_item("Pack Textures", MENU_PACK_TEXTURES)
|
||||
menu_button.get_popup().add_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH)
|
||||
menu_button.get_popup().add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR2)
|
||||
menu_button.get_popup().add_item("Set up Navigation...", MENU_SET_UP_NAVIGATION)
|
||||
menu_button.get_popup().add_item("Bake NavMesh...", MENU_BAKE_NAV_MESH)
|
||||
|
||||
menu_button.get_popup().id_pressed.connect(_on_menu_pressed)
|
||||
menu_button.about_to_popup.connect(_on_menu_about_to_popup)
|
||||
add_child(menu_button)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# TODO: If packer isn't freed, Godot complains about ObjectDB instances leaked and
|
||||
# resources still in use at exit. Figure out why.
|
||||
packer.free()
|
||||
|
||||
|
||||
func _on_menu_pressed(p_id: int) -> void:
|
||||
match p_id:
|
||||
MENU_DIRECTORY_SETUP:
|
||||
directory_setup.directory_setup_popup()
|
||||
MENU_PACK_TEXTURES:
|
||||
packer.pack_textures_popup()
|
||||
MENU_BAKE_ARRAY_MESH:
|
||||
baker.bake_mesh_popup()
|
||||
MENU_BAKE_OCCLUDER:
|
||||
baker.bake_occluder_popup()
|
||||
MENU_BAKE_NAV_MESH:
|
||||
baker.bake_nav_mesh()
|
||||
MENU_SET_UP_NAVIGATION:
|
||||
baker.set_up_navigation_popup()
|
||||
MENU_PACK_TEXTURES:
|
||||
packer.pack_textures_popup()
|
||||
MENU_BAKE_NAV_MESH:
|
||||
baker.bake_nav_mesh()
|
||||
|
||||
|
||||
func _on_menu_about_to_popup() -> void:
|
||||
menu_button.get_popup().set_item_disabled(MENU_DIRECTORY_SETUP, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_BAKE_ARRAY_MESH, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_BAKE_OCCLUDER, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
|
||||
|
||||
if plugin.terrain:
|
||||
var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain)
|
|
@ -17,13 +17,14 @@ enum SettingType {
|
|||
PICKER,
|
||||
MULTI_PICKER,
|
||||
SLIDER,
|
||||
LABEL,
|
||||
TYPE_MAX,
|
||||
}
|
||||
|
||||
const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
|
||||
const DEFAULT_BRUSH: String = "circle0.exr"
|
||||
const BRUSH_PATH: String = "res://addons/terrain_3d/brushes"
|
||||
const PICKER_ICON: String = "res://addons/terrain_3d/icons/picker.svg"
|
||||
const ES_TOOL_SETTINGS: String = "terrain3d/tool_settings/"
|
||||
|
||||
# Add settings flags
|
||||
const NONE: int = 0x0
|
||||
|
@ -31,13 +32,14 @@ const ALLOW_LARGER: int = 0x1
|
|||
const ALLOW_SMALLER: int = 0x2
|
||||
const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER
|
||||
const NO_LABEL: int = 0x4
|
||||
const ADD_SEPARATOR: int = 0x8
|
||||
const ADD_SPACER: int = 0x10
|
||||
const ADD_SEPARATOR: int = 0x8 # Add a vertical line before this entry
|
||||
const ADD_SPACER: int = 0x10 # Add a space before this entry
|
||||
const NO_SAVE: int = 0x20 # Don't save this in EditorSettings
|
||||
|
||||
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||
var brush_preview_material: ShaderMaterial
|
||||
var select_brush_button: Button
|
||||
|
||||
var main_list: HBoxContainer
|
||||
var main_list: HFlowContainer
|
||||
var advanced_list: VBoxContainer
|
||||
var height_list: VBoxContainer
|
||||
var scale_list: VBoxContainer
|
||||
|
@ -47,21 +49,26 @@ var settings: Dictionary = {}
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
main_list = HBoxContainer.new()
|
||||
# Remove old editor settings
|
||||
for setting in ["lift_floor", "flatten_peaks", "lift_flatten", "automatic_regions"]:
|
||||
plugin.erase_setting(ES_TOOL_SETTINGS + setting)
|
||||
|
||||
# Setup buttons
|
||||
main_list = HFlowContainer.new()
|
||||
add_child(main_list, true)
|
||||
|
||||
## Common Settings
|
||||
add_brushes(main_list)
|
||||
|
||||
add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":50, "unit":"m",
|
||||
"range":Vector3(2, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
|
||||
add_setting({ "name":"instructions", "label":"Click the terrain to add a region. CTRL+Click to remove. Or select another tool on the left.",
|
||||
"type":SettingType.LABEL, "list":main_list, "flags":NO_LABEL|NO_SAVE })
|
||||
|
||||
add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":20, "unit":"m",
|
||||
"range":Vector3(0.1, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
|
||||
|
||||
add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":10,
|
||||
add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":33,
|
||||
"unit":"%", "range":Vector3(1, 100, 1), "flags":ALLOW_LARGER })
|
||||
|
||||
add_setting({ "name":"enable", "type":SettingType.CHECKBOX, "list":main_list, "default":true })
|
||||
|
||||
add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":50,
|
||||
add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":20,
|
||||
"unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL })
|
||||
|
@ -71,14 +78,21 @@ func _ready() -> void:
|
|||
add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.COLOR, "flags":NO_LABEL })
|
||||
|
||||
add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":-65,
|
||||
"unit":"%", "range":Vector3(-100, 100, 1), "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL })
|
||||
|
||||
add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true })
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
|
||||
add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
"unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
|
||||
# Slope painting filter
|
||||
add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list, "default":Vector2(0, 90),
|
||||
"unit":"°", "range":Vector3(0, 90, 1), "flags":ADD_SEPARATOR })
|
||||
|
||||
add_setting({ "name":"enable_angle", "label":"Angle", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"angle", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
|
@ -88,18 +102,16 @@ func _ready() -> void:
|
|||
add_setting({ "name":"dynamic_angle", "label":"Dynamic", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":false, "flags":ADD_SPACER })
|
||||
|
||||
add_setting({ "name":"enable_scale", "label":"Scale ±", "type":SettingType.CHECKBOX,
|
||||
add_setting({ "name":"enable_scale", "label":"Scale", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
"unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL })
|
||||
add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.SCALE, "flags":NO_LABEL })
|
||||
|
||||
## Slope
|
||||
add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list,
|
||||
"default":0, "unit":"°", "range":Vector3(0, 180, 1) })
|
||||
## Slope sculpting brush
|
||||
add_setting({ "name":"gradient_points", "type":SettingType.MULTI_PICKER, "label":"Points",
|
||||
"list":main_list, "default":Terrain3DEditor.HEIGHT, "flags":ADD_SEPARATOR })
|
||||
"list":main_list, "default":Terrain3DEditor.SCULPT, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"drawable", "type":SettingType.CHECKBOX, "list":main_list, "default":false,
|
||||
"flags":ADD_SEPARATOR })
|
||||
settings["drawable"].toggled.connect(_on_drawable_toggled)
|
||||
|
@ -123,9 +135,9 @@ func _ready() -> void:
|
|||
"default":0, "unit":"°", "range":Vector3(0, 360, 1) })
|
||||
add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360,
|
||||
"unit":"°", "range":Vector3(0, 360, 1) })
|
||||
add_setting({ "name":"fixed_angle", "label":"Fixed Angle (From Y)", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
add_setting({ "name":"fixed_tilt", "label":"Fixed Tilt", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
"default":0, "unit":"°", "range":Vector3(-85, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"random_angle", "label":"Random Angle ±", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
add_setting({ "name":"random_tilt", "label":"Random Tilt ±", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
"default":10, "unit":"°", "range":Vector3(0, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"align_to_normal", "type":SettingType.CHECKBOX, "list":rotation_list, "default":false })
|
||||
|
||||
|
@ -139,14 +151,17 @@ func _ready() -> void:
|
|||
#add_setting({ "name":"blend_mode", "type":SettingType.OPTION, "list":color_list, "default":0,
|
||||
#"range":Vector3(0, 3, 1) })
|
||||
|
||||
if DisplayServer.is_touchscreen_available():
|
||||
add_setting({ "name":"remove", "label":"Invert", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR })
|
||||
|
||||
var spacer: Control = Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_list.add_child(spacer, true)
|
||||
|
||||
## Advanced Settings Menu
|
||||
advanced_list = create_submenu(main_list, "Advanced", Layout.VERTICAL)
|
||||
add_setting({ "name":"automatic_regions", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
"default":true })
|
||||
advanced_list = create_submenu(main_list, "", Layout.VERTICAL, false)
|
||||
add_setting({ "name":"auto_regions", "label":"Add regions while sculpting", "type":SettingType.CHECKBOX,
|
||||
"list":advanced_list, "default":true })
|
||||
add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
"default":true })
|
||||
add_setting({ "name":"show_cursor_while_painting", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
|
@ -158,23 +173,44 @@ func _ready() -> void:
|
|||
"unit":"%", "range":Vector3(0, 100, 1) })
|
||||
|
||||
|
||||
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout) -> Container:
|
||||
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container:
|
||||
var menu_button: Button = Button.new()
|
||||
menu_button.set_text(p_button_name)
|
||||
if p_button_name.is_empty():
|
||||
menu_button.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
|
||||
else:
|
||||
menu_button.set_text(p_button_name)
|
||||
menu_button.set_toggle_mode(true)
|
||||
menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
menu_button.toggled.connect(_on_show_submenu.bind(menu_button))
|
||||
|
||||
var submenu: PopupPanel = PopupPanel.new()
|
||||
submenu.popup_hide.connect(menu_button.set_pressed_no_signal.bind(false))
|
||||
submenu.popup_hide.connect(menu_button.set_pressed.bind(false))
|
||||
var panel_style: StyleBox = get_theme_stylebox("panel", "PopupMenu").duplicate()
|
||||
panel_style.set_content_margin_all(10)
|
||||
submenu.set("theme_override_styles/panel", panel_style)
|
||||
submenu.add_to_group("terrain3d_submenus")
|
||||
|
||||
# Pop up menu on hover, hide on exit
|
||||
menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
|
||||
submenu.mouse_exited.connect(_on_show_submenu.bind(false, menu_button))
|
||||
if p_hover_pop:
|
||||
menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
|
||||
|
||||
submenu.mouse_entered.connect(func(): submenu.set_meta("mouse_entered", true))
|
||||
|
||||
submenu.mouse_exited.connect(func():
|
||||
# On mouse_exit, hide popup unless LineEdit focused
|
||||
var focused_element: Control = submenu.gui_get_focus_owner()
|
||||
if not focused_element is LineEdit:
|
||||
_on_show_submenu(false, menu_button)
|
||||
submenu.set_meta("mouse_entered", false)
|
||||
return
|
||||
|
||||
focused_element.focus_exited.connect(func():
|
||||
# Close submenu once lineedit loses focus
|
||||
if not submenu.get_meta("mouse_entered"):
|
||||
_on_show_submenu(false, menu_button)
|
||||
submenu.set_meta("mouse_entered", false)
|
||||
)
|
||||
)
|
||||
|
||||
var sublist: Container
|
||||
match(p_layout):
|
||||
|
@ -200,20 +236,21 @@ func _on_show_submenu(p_toggled: bool, p_button: Button) -> void:
|
|||
# Hide menu if mouse is not in button or panel
|
||||
var button_rect: Rect2 = Rect2(p_button.get_screen_transform().origin, p_button.get_global_rect().size)
|
||||
var in_button: bool = button_rect.has_point(DisplayServer.mouse_get_position())
|
||||
var panel: PopupPanel = p_button.get_child(0)
|
||||
var panel_rect: Rect2 = Rect2(panel.position, panel.size)
|
||||
var in_panel: bool = panel_rect.has_point(DisplayServer.mouse_get_position())
|
||||
if not p_toggled and ( in_button or in_panel ):
|
||||
var popup: PopupPanel = p_button.get_child(0)
|
||||
var popup_rect: Rect2 = Rect2(popup.position, popup.size)
|
||||
var in_popup: bool = popup_rect.has_point(DisplayServer.mouse_get_position())
|
||||
if not p_toggled and ( in_button or in_popup ):
|
||||
return
|
||||
|
||||
# Hide all submenus before possibly enabling the current one
|
||||
get_tree().call_group("terrain3d_submenus", "set_visible", false)
|
||||
var popup: PopupPanel = p_button.get_child(0)
|
||||
var popup_pos: Vector2 = p_button.get_screen_transform().origin
|
||||
popup.set_visible(p_toggled)
|
||||
popup_pos.y -= popup.get_size().y
|
||||
var popup_pos: Vector2 = p_button.get_screen_transform().origin
|
||||
popup_pos.y -= popup.size.y
|
||||
if popup.get_child_count()>0 and popup.get_child(0) == advanced_list:
|
||||
popup_pos.x -= popup.size.x - p_button.size.x
|
||||
popup.set_position(popup_pos)
|
||||
|
||||
|
||||
|
||||
func add_brushes(p_parent: Control) -> void:
|
||||
var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID)
|
||||
|
@ -231,6 +268,8 @@ func add_brushes(p_parent: Control) -> void:
|
|||
if !dir.current_is_dir() and file_name.ends_with(".exr"):
|
||||
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
||||
img = Terrain3DUtil.black_to_alpha(img)
|
||||
if img.get_width() < 1024 and img.get_height() < 1024:
|
||||
img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
|
||||
var tex: ImageTexture = ImageTexture.create_from_image(img)
|
||||
|
||||
var btn: Button = Button.new()
|
||||
|
@ -270,10 +309,11 @@ func add_brushes(p_parent: Control) -> void:
|
|||
|
||||
select_brush_button = brush_list.get_parent().get_parent()
|
||||
# Optionally erase the main brush button text and replace it with the texture
|
||||
# select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
|
||||
# select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
||||
# select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
# select_brush_button.set_expand_icon(true)
|
||||
select_brush_button.set_text("")
|
||||
select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
|
||||
select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
||||
select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
select_brush_button.set_expand_icon(true)
|
||||
|
||||
|
||||
func _on_brush_hover(p_hovering: bool, p_button: Button) -> void:
|
||||
|
@ -307,13 +347,12 @@ func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position:
|
|||
|
||||
|
||||
func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
assert(p_type == Terrain3DEditor.SCULPT)
|
||||
emit_signal("picking", p_type, _on_point_picked.bind(p_name))
|
||||
|
||||
|
||||
func _on_point_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
|
||||
assert(p_type == Terrain3DEditor.SCULPT)
|
||||
var point: Vector3 = p_global_position
|
||||
point.y = p_color.r
|
||||
settings[p_name].add_point(point)
|
||||
|
@ -342,9 +381,22 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
var pending_children: Array[Control]
|
||||
|
||||
match p_type:
|
||||
SettingType.LABEL:
|
||||
var label := Label.new()
|
||||
label.set_text(p_label)
|
||||
pending_children.push_back(label)
|
||||
control = label
|
||||
|
||||
SettingType.CHECKBOX:
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.set_pressed_no_signal(p_default)
|
||||
if !(p_flags & NO_SAVE):
|
||||
checkbox.set_pressed_no_signal(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
checkbox.toggled.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
checkbox.set_pressed_no_signal(p_default)
|
||||
checkbox.pressed.connect(_on_setting_changed)
|
||||
pending_children.push_back(checkbox)
|
||||
control = checkbox
|
||||
|
@ -352,19 +404,24 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
SettingType.COLOR_SELECT:
|
||||
var picker := ColorPickerButton.new()
|
||||
picker.set_custom_minimum_size(Vector2(100, 25))
|
||||
picker.color = Color.WHITE
|
||||
picker.edit_alpha = false
|
||||
picker.get_picker().set_color_mode(ColorPicker.MODE_HSV)
|
||||
if !(p_flags & NO_SAVE):
|
||||
picker.set_pick_color(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
picker.color_changed.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
picker.set_pick_color(p_default)
|
||||
picker.color_changed.connect(_on_setting_changed)
|
||||
var popup: PopupPanel = picker.get_popup()
|
||||
popup.mouse_exited.connect(Callable(func(p): p.hide()).bind(popup))
|
||||
pending_children.push_back(picker)
|
||||
control = picker
|
||||
|
||||
SettingType.PICKER:
|
||||
var button := Button.new()
|
||||
button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
button.icon = load(PICKER_ICON)
|
||||
button.icon = get_theme_icon("ColorPick", "EditorIcons")
|
||||
button.tooltip_text = "Pick value from the Terrain"
|
||||
button.pressed.connect(_on_pick.bind(p_default))
|
||||
pending_children.push_back(button)
|
||||
|
@ -397,10 +454,9 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
spin_slider.set_max(p_maximum)
|
||||
spin_slider.set_min(p_minimum)
|
||||
spin_slider.set_step(p_step)
|
||||
spin_slider.set_value(p_default)
|
||||
spin_slider.set_suffix(p_suffix)
|
||||
spin_slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
spin_slider.set_custom_minimum_size(Vector2(75, 0))
|
||||
spin_slider.set_custom_minimum_size(Vector2(65, 0))
|
||||
|
||||
# Create horizontal slider linked to the above box
|
||||
slider = HSlider.new()
|
||||
|
@ -409,27 +465,38 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
slider.set_allow_greater(true)
|
||||
if p_flags & ALLOW_SMALLER:
|
||||
slider.set_allow_lesser(true)
|
||||
|
||||
pending_children.push_back(slider)
|
||||
pending_children.push_back(spin_slider)
|
||||
control = spin_slider
|
||||
|
||||
|
||||
else: # DOUBLE_SLIDER
|
||||
var label := Label.new()
|
||||
label.set_custom_minimum_size(Vector2(75, 0))
|
||||
label.set_custom_minimum_size(Vector2(60, 0))
|
||||
label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT)
|
||||
slider = DoubleSlider.new()
|
||||
slider.label = label
|
||||
slider.suffix = p_suffix
|
||||
slider.setting_changed.connect(_on_setting_changed)
|
||||
slider.value_changed.connect(_on_setting_changed)
|
||||
pending_children.push_back(slider)
|
||||
pending_children.push_back(label)
|
||||
control = slider
|
||||
|
||||
slider.set_max(p_maximum)
|
||||
slider.set_min(p_minimum)
|
||||
slider.set_max(p_maximum)
|
||||
slider.set_step(p_step)
|
||||
slider.set_value(p_default)
|
||||
slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
slider.set_custom_minimum_size(Vector2(60, 10))
|
||||
slider.set_custom_minimum_size(Vector2(50, 10))
|
||||
|
||||
if !(p_flags & NO_SAVE):
|
||||
slider.set_value(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
slider.value_changed.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
slider.set_value(p_default)
|
||||
|
||||
control.name = p_name.to_pascal_case()
|
||||
settings[p_name] = control
|
||||
|
@ -491,8 +558,8 @@ func get_setting(p_setting: String) -> Variant:
|
|||
var width: float = clamp( (1 + count_digits(value)) * 19., 50, 80) * clamp(EditorInterface.get_editor_scale(), .9, 2)
|
||||
object.set_custom_minimum_size(Vector2(width, 0))
|
||||
elif object is DoubleSlider:
|
||||
value = Vector2(object.get_min_value(), object.get_max_value())
|
||||
elif object is ButtonGroup:
|
||||
value = object.get_value()
|
||||
elif object is ButtonGroup: # "brush"
|
||||
var img: Image = object.get_pressed_button().get_meta("image")
|
||||
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
||||
value = [ img, tex ]
|
||||
|
@ -509,11 +576,10 @@ func get_setting(p_setting: String) -> Variant:
|
|||
|
||||
func set_setting(p_setting: String, p_value: Variant) -> void:
|
||||
var object: Object = settings.get(p_setting)
|
||||
if object is Range:
|
||||
if object is DoubleSlider: # Expects p_value is Vector2
|
||||
object.set_value(p_value)
|
||||
elif object is Range:
|
||||
object.set_value(p_value)
|
||||
elif object is DoubleSlider: # Expects p_value is Vector2
|
||||
object.set_min_value(p_value.x)
|
||||
object.set_max_value(p_value.y)
|
||||
elif object is ButtonGroup: # Expects p_value is Array [ "button name", boolean ]
|
||||
if p_value is Array and p_value.size() == 2:
|
||||
for button in object.get_buttons():
|
||||
|
@ -523,6 +589,7 @@ func set_setting(p_setting: String, p_value: Variant) -> void:
|
|||
object.button_pressed = p_value
|
||||
elif object is ColorPickerButton:
|
||||
object.color = p_value
|
||||
plugin.set_setting(ES_TOOL_SETTINGS + p_setting, p_value) # Signal doesn't fire on CPB
|
||||
elif object is MultiPicker: # Expects p_value is PackedVector3Array
|
||||
object.points = p_value
|
||||
_on_setting_changed(object)
|
||||
|
@ -548,13 +615,12 @@ func _on_setting_changed(p_data: Variant = null) -> void:
|
|||
if p_data is Button and p_data.get_parent().get_parent() is PopupPanel:
|
||||
if p_data.get_parent().name == "BrushList":
|
||||
# Optionally Set selected brush texture in main brush button
|
||||
# p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
# Hide popup
|
||||
p_data.get_parent().get_parent().set_visible(false)
|
||||
# Hide label
|
||||
if p_data.get_child_count() > 0:
|
||||
p_data.get_child(0).visible = false
|
||||
|
||||
emit_signal("setting_changed")
|
||||
|
||||
|
||||
|
@ -567,7 +633,6 @@ func _get_brush_preview_material() -> ShaderMaterial:
|
|||
if !brush_preview_material:
|
||||
brush_preview_material = ShaderMaterial.new()
|
||||
var shader: Shader = Shader.new()
|
||||
|
||||
var code: String = "shader_type canvas_item;\n"
|
||||
code += "varying vec4 v_vertex_color;\n"
|
||||
code += "void vertex() {\n"
|
||||
|
@ -578,13 +643,11 @@ func _get_brush_preview_material() -> ShaderMaterial:
|
|||
code += " COLOR.a *= pow(tex.r, 0.666);\n"
|
||||
code += " COLOR.rgb = v_vertex_color.rgb;\n"
|
||||
code += "}\n"
|
||||
|
||||
shader.set_code(code)
|
||||
brush_preview_material.set_shader(shader)
|
||||
return brush_preview_material
|
||||
|
||||
|
||||
|
||||
# Counts digits of a number including negative sign, decimal points, and up to 3 decimals
|
||||
func count_digits(p_value: float) -> int:
|
||||
var count: int = 1
|
||||
|
@ -605,75 +668,3 @@ func count_digits(p_value: float) -> int:
|
|||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
#### Sub Class DoubleSlider
|
||||
|
||||
class DoubleSlider extends Range:
|
||||
signal setting_changed(Vector2)
|
||||
var label: Label
|
||||
var suffix: String
|
||||
var grabbed: bool = false
|
||||
var _max_value: float
|
||||
# TODO Needs to clamp min and max values. Currently allows max slider to go negative.
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
grabbed = p_event.is_pressed()
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
if p_event is InputEventMouseMotion:
|
||||
if grabbed:
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
|
||||
func _notification(p_what: int) -> void:
|
||||
if p_what == NOTIFICATION_RESIZED:
|
||||
pass
|
||||
if p_what == NOTIFICATION_DRAW:
|
||||
var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
|
||||
var bg_height: float = bg.get_minimum_size().y
|
||||
draw_style_box(bg, Rect2(Vector2(0, (size.y - bg_height) / 2), Vector2(size.x, bg_height)))
|
||||
|
||||
var grabber: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - grabber.get_size().y / 2
|
||||
|
||||
var minpos: Vector2 = Vector2((min_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
var maxpos: Vector2 = Vector2((max_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
|
||||
draw_style_box(area, Rect2(Vector2(minpos.x + grabber.get_size().x / 2, (size.y - bg_height) / 2), Vector2(maxpos.x - minpos.x, bg_height)))
|
||||
|
||||
draw_texture(grabber, minpos)
|
||||
draw_texture(grabber, maxpos)
|
||||
|
||||
|
||||
func set_max(p_value: float) -> void:
|
||||
max_value = p_value
|
||||
if _max_value == 0:
|
||||
_max_value = max_value
|
||||
update_label()
|
||||
|
||||
|
||||
func set_min_max(p_xpos: float) -> void:
|
||||
var mid_value_normalized: float = ((max_value + min_value) / 2.0) / _max_value
|
||||
var mid_value: float = size.x * mid_value_normalized
|
||||
var min_active: bool = p_xpos < mid_value
|
||||
var xpos_ranged: float = snappedf((p_xpos / size.x) * _max_value, step)
|
||||
|
||||
if min_active:
|
||||
min_value = xpos_ranged
|
||||
else:
|
||||
max_value = xpos_ranged
|
||||
|
||||
min_value = clamp(min_value, 0, max_value - 10)
|
||||
max_value = clamp(max_value, min_value + 10, _max_value)
|
||||
|
||||
update_label()
|
||||
emit_signal("setting_changed", Vector2(min_value, max_value))
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
label.set_text(str(min_value) + suffix + "/" + str(max_value) + suffix)
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
extends VBoxContainer
|
||||
|
||||
|
||||
extends VFlowContainer
|
||||
|
||||
signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation)
|
||||
|
||||
const ICON_REGION_ADD: String = "res://addons/terrain_3d/icons/region_add.svg"
|
||||
const ICON_REGION_REMOVE: String = "res://addons/terrain_3d/icons/region_remove.svg"
|
||||
const ICON_HEIGHT_ADD: String = "res://addons/terrain_3d/icons/height_add.svg"
|
||||
const ICON_HEIGHT_SUB: String = "res://addons/terrain_3d/icons/height_sub.svg"
|
||||
const ICON_HEIGHT_MUL: String = "res://addons/terrain_3d/icons/height_mul.svg"
|
||||
const ICON_HEIGHT_DIV: String = "res://addons/terrain_3d/icons/height_div.svg"
|
||||
const ICON_HEIGHT_FLAT: String = "res://addons/terrain_3d/icons/height_flat.svg"
|
||||
const ICON_HEIGHT_SLOPE: String = "res://addons/terrain_3d/icons/height_slope.svg"
|
||||
const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/height_smooth.svg"
|
||||
|
@ -21,57 +18,126 @@ const ICON_HOLES: String = "res://addons/terrain_3d/icons/holes.svg"
|
|||
const ICON_NAVIGATION: String = "res://addons/terrain_3d/icons/navigation.svg"
|
||||
const ICON_INSTANCER: String = "res://addons/terrain_3d/icons/multimesh.svg"
|
||||
|
||||
var tool_group: ButtonGroup = ButtonGroup.new()
|
||||
var add_tool_group: ButtonGroup = ButtonGroup.new()
|
||||
var sub_tool_group: ButtonGroup = ButtonGroup.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
set_custom_minimum_size(Vector2(20, 0))
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
tool_group.connect("pressed", _on_tool_selected)
|
||||
add_tool_group.connect("pressed", _on_tool_selected)
|
||||
sub_tool_group.connect("pressed", _on_tool_selected)
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.REGION,
|
||||
"add_text":"Add Region", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD,
|
||||
"sub_text":"Remove Region", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_REGION_REMOVE })
|
||||
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.ADD, "Add Region", load(ICON_REGION_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.SUBTRACT, "Remove Region", load(ICON_REGION_REMOVE), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.ADD, "Raise", load(ICON_HEIGHT_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.SUBTRACT, "Lower", load(ICON_HEIGHT_SUB), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.MULTIPLY, "Expand (Away from 0)", load(ICON_HEIGHT_MUL), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.DIVIDE, "Reduce (Towards 0)", load(ICON_HEIGHT_DIV), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.REPLACE, "Flatten", load(ICON_HEIGHT_FLAT), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.GRADIENT, "Slope", load(ICON_HEIGHT_SLOPE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.AVERAGE, "Smooth", load(ICON_HEIGHT_SMOOTH), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE, "Paint Base Texture", load(ICON_PAINT_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.ADD, "Spray Overlay Texture", load(ICON_SPRAY_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.AUTOSHADER, Terrain3DEditor.REPLACE, "Autoshader", load(ICON_AUTOSHADER), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.COLOR, Terrain3DEditor.REPLACE, "Paint Color", load(ICON_COLOR), tool_group)
|
||||
add_tool_button(Terrain3DEditor.ROUGHNESS, Terrain3DEditor.REPLACE, "Paint Wetness", load(ICON_WETNESS), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HOLES, Terrain3DEditor.REPLACE, "Create Holes", load(ICON_HOLES), tool_group)
|
||||
add_tool_button(Terrain3DEditor.NAVIGATION, Terrain3DEditor.REPLACE, "Paint Navigable Area", load(ICON_NAVIGATION), tool_group)
|
||||
add_tool_button(Terrain3DEditor.INSTANCER, Terrain3DEditor.ADD, "Instance Meshes", load(ICON_INSTANCER), tool_group)
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Raise", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_ADD,
|
||||
"sub_text":"Lower", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_SUB })
|
||||
|
||||
var buttons: Array[BaseButton] = tool_group.get_buttons()
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Smooth", "add_op":Terrain3DEditor.AVERAGE, "add_icon":ICON_HEIGHT_SMOOTH })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.HEIGHT,
|
||||
"add_text":"Height", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_FLAT,
|
||||
"sub_text":"Zero", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_FLAT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Slope", "add_op":Terrain3DEditor.GRADIENT, "add_icon":ICON_HEIGHT_SLOPE })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
|
||||
"add_text":"Paint Base Texture", "add_op":Terrain3DEditor.REPLACE, "add_icon":ICON_PAINT_TEXTURE })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
|
||||
"add_text":"Spray Overlay Texture", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_SPRAY_TEXTURE })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.AUTOSHADER,
|
||||
"add_text":"Enable Autoshader", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_AUTOSHADER,
|
||||
"sub_text":"Disable Autoshader", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.COLOR,
|
||||
"add_text":"Paint Color", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_COLOR,
|
||||
"sub_text":"Remove Color", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.ROUGHNESS,
|
||||
"add_text":"Paint Wetness", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_WETNESS,
|
||||
"sub_text":"Remove Wetness", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.HOLES,
|
||||
"add_text":"Add Holes", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HOLES,
|
||||
"sub_text":"Remove Holes", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.NAVIGATION,
|
||||
"add_text":"Paint Navigable Area", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_NAVIGATION,
|
||||
"sub_text":"Remove Navigable Area", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.INSTANCER,
|
||||
"add_text":"Instance Meshes", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_INSTANCER,
|
||||
"sub_text":"Remove Meshes", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
# Select first button
|
||||
var buttons: Array[BaseButton] = add_tool_group.get_buttons()
|
||||
buttons[0].set_pressed(true)
|
||||
show_add_buttons(true)
|
||||
|
||||
|
||||
func add_tool_button(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation,
|
||||
p_tip: String, p_icon: Texture2D, p_group: ButtonGroup) -> void:
|
||||
|
||||
var button: Button = Button.new()
|
||||
button.set_name(p_tip.to_pascal_case())
|
||||
button.set_meta("Tool", p_tool)
|
||||
button.set_meta("Operation", p_operation)
|
||||
button.set_tooltip_text(p_tip)
|
||||
button.set_button_icon(p_icon)
|
||||
button.set_button_group(p_group)
|
||||
func add_tool_button(p_params: Dictionary) -> void:
|
||||
# Additive button
|
||||
var button := Button.new()
|
||||
button.set_name(p_params.get("add_text", "blank").to_pascal_case())
|
||||
button.set_meta("Tool", p_params.get("tool", 0))
|
||||
button.set_meta("Operation", p_params.get("add_op", 0))
|
||||
button.set_meta("ID", add_tool_group.get_buttons().size() + 1)
|
||||
button.set_tooltip_text(p_params.get("add_text", "blank"))
|
||||
button.set_button_icon(load(p_params.get("add_icon")))
|
||||
button.set_flat(true)
|
||||
button.set_toggle_mode(true)
|
||||
button.set_h_size_flags(SIZE_SHRINK_END)
|
||||
add_child(button)
|
||||
button.set_button_group(p_params.get("group", add_tool_group))
|
||||
add_child(button, true)
|
||||
|
||||
# Subtractive button
|
||||
var button2: Button
|
||||
if p_params.has("sub_text"):
|
||||
button2 = Button.new()
|
||||
button2.set_name(p_params.get("sub_text", "blank").to_pascal_case())
|
||||
button2.set_meta("Tool", p_params.get("tool", 0))
|
||||
button2.set_meta("Operation", p_params.get("sub_op", 0))
|
||||
button2.set_meta("ID", button.get_meta("ID"))
|
||||
button2.set_tooltip_text(p_params.get("sub_text", "blank"))
|
||||
button2.set_button_icon(load(p_params.get("sub_icon", p_params.get("add_icon"))))
|
||||
button2.set_flat(true)
|
||||
button2.set_toggle_mode(true)
|
||||
button2.set_h_size_flags(SIZE_SHRINK_END)
|
||||
else:
|
||||
button2 = button.duplicate()
|
||||
button2.set_button_group(p_params.get("group", sub_tool_group))
|
||||
add_child(button2, true)
|
||||
|
||||
|
||||
func show_add_buttons(p_enable: bool) -> void:
|
||||
for button in add_tool_group.get_buttons():
|
||||
button.visible = p_enable
|
||||
for button in sub_tool_group.get_buttons():
|
||||
button.visible = !p_enable
|
||||
|
||||
|
||||
func _on_tool_selected(p_button: BaseButton) -> void:
|
||||
emit_signal("tool_changed", p_button.get_meta("Tool", -1), p_button.get_meta("Operation", -1))
|
||||
# Select same tool on negative bar
|
||||
var group: ButtonGroup = p_button.get_button_group()
|
||||
var change_group: ButtonGroup = add_tool_group if group == sub_tool_group else sub_tool_group
|
||||
var id: int = p_button.get_meta("ID", -2)
|
||||
for button in change_group.get_buttons():
|
||||
button.set_pressed_no_signal(button.get_meta("ID", -1) == id)
|
||||
|
||||
emit_signal("tool_changed", p_button.get_meta("Tool", Terrain3DEditor.TOOL_MAX), p_button.get_meta("Operation", Terrain3DEditor.OP_MAX))
|
||||
|
|
|
@ -5,34 +5,38 @@ extends Node
|
|||
# Includes
|
||||
const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
|
||||
const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd")
|
||||
const TerrainTools: Script = preload("res://addons/terrain_3d/src/terrain_tools.gd")
|
||||
const TerrainMenu: Script = preload("res://addons/terrain_3d/src/terrain_menu.gd")
|
||||
const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd")
|
||||
const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
|
||||
const COLOR_RAISE := Color.WHITE
|
||||
const COLOR_LOWER := Color.BLACK
|
||||
const COLOR_LOWER := Color(.02, .02, .02)
|
||||
const COLOR_SMOOTH := Color(0.5, 0, .1)
|
||||
const COLOR_EXPAND := Color.ORANGE
|
||||
const COLOR_REDUCE := Color.BLUE_VIOLET
|
||||
const COLOR_FLATTEN := Color(0., 0.32, .4)
|
||||
const COLOR_LIFT := Color.ORANGE
|
||||
const COLOR_FLATTEN := Color.BLUE_VIOLET
|
||||
const COLOR_HEIGHT := Color(0., 0.32, .4)
|
||||
const COLOR_SLOPE := Color.YELLOW
|
||||
const COLOR_PAINT := Color.FOREST_GREEN
|
||||
const COLOR_SPRAY := Color.SEA_GREEN
|
||||
const COLOR_PAINT := Color.DARK_GREEN
|
||||
const COLOR_SPRAY := Color.PALE_GREEN
|
||||
const COLOR_ROUGHNESS := Color.ROYAL_BLUE
|
||||
const COLOR_AUTOSHADER := Color.DODGER_BLUE
|
||||
const COLOR_HOLES := Color.BLACK
|
||||
const COLOR_NAVIGATION := Color.REBECCA_PURPLE
|
||||
const COLOR_NAVIGATION := Color(.15, .0, .255)
|
||||
const COLOR_INSTANCER := Color.CRIMSON
|
||||
const COLOR_PICK_COLOR := Color.WHITE
|
||||
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
||||
const COLOR_PICK_ROUGH := Color.ROYAL_BLUE
|
||||
|
||||
const OP_NONE: int = 0x0
|
||||
const OP_POSITIVE_ONLY: int = 0x01
|
||||
const OP_NEGATIVE_ONLY: int = 0x02
|
||||
|
||||
const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr"
|
||||
@onready var ring_texture := ImageTexture.create_from_image(Terrain3DUtil.black_to_alpha(Image.load_from_file(RING1)))
|
||||
|
||||
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||
var toolbar: Toolbar
|
||||
var toolbar_settings: ToolSettings
|
||||
var terrain_tools: TerrainTools
|
||||
var tool_settings: ToolSettings
|
||||
var terrain_menu: TerrainMenu
|
||||
var setting_has_changed: bool = false
|
||||
var visible: bool = false
|
||||
var picking: int = Terrain3DEditor.TOOL_MAX
|
||||
|
@ -42,6 +46,16 @@ var decal_timer: Timer
|
|||
var gradient_decals: Array[Decal]
|
||||
var brush_data: Dictionary
|
||||
var operation_builder: OperationBuilder
|
||||
var last_tool: Terrain3DEditor.Tool
|
||||
var last_operation: Terrain3DEditor.Operation
|
||||
var last_rmb_time: int = 0 # Set in editor.gd
|
||||
|
||||
# Compatibility decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
|
||||
var editor_decal_position: Array[Vector2]
|
||||
var editor_decal_rotation: Array[float]
|
||||
var editor_decal_size: Array[float]
|
||||
var editor_decal_color: Array[Color]
|
||||
var editor_decal_visible: Array[bool]
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
|
@ -49,18 +63,19 @@ func _enter_tree() -> void:
|
|||
toolbar.hide()
|
||||
toolbar.connect("tool_changed", _on_tool_changed)
|
||||
|
||||
toolbar_settings = ToolSettings.new()
|
||||
toolbar_settings.connect("setting_changed", _on_setting_changed)
|
||||
toolbar_settings.connect("picking", _on_picking)
|
||||
toolbar_settings.hide()
|
||||
tool_settings = ToolSettings.new()
|
||||
tool_settings.connect("setting_changed", _on_setting_changed)
|
||||
tool_settings.connect("picking", _on_picking)
|
||||
tool_settings.plugin = plugin
|
||||
tool_settings.hide()
|
||||
|
||||
terrain_tools = TerrainTools.new()
|
||||
terrain_tools.plugin = plugin
|
||||
terrain_tools.hide()
|
||||
terrain_menu = TerrainMenu.new()
|
||||
terrain_menu.plugin = plugin
|
||||
terrain_menu.hide()
|
||||
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_tools)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_menu)
|
||||
|
||||
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
|
||||
|
||||
|
@ -77,10 +92,10 @@ func _enter_tree() -> void:
|
|||
|
||||
func _exit_tree() -> void:
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
|
||||
toolbar.queue_free()
|
||||
toolbar_settings.queue_free()
|
||||
terrain_tools.queue_free()
|
||||
tool_settings.queue_free()
|
||||
terrain_menu.queue_free()
|
||||
decal.queue_free()
|
||||
decal_timer.queue_free()
|
||||
for gradient_decal in gradient_decals:
|
||||
|
@ -88,14 +103,27 @@ func _exit_tree() -> void:
|
|||
gradient_decals.clear()
|
||||
|
||||
|
||||
func set_visible(p_visible: bool) -> void:
|
||||
visible = p_visible
|
||||
terrain_tools.set_visible(p_visible)
|
||||
toolbar.set_visible(p_visible)
|
||||
toolbar_settings.set_visible(p_visible)
|
||||
update_decal()
|
||||
func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
|
||||
terrain_menu.set_visible(p_visible)
|
||||
|
||||
if p_menu_only:
|
||||
toolbar.set_visible(false)
|
||||
tool_settings.set_visible(false)
|
||||
else:
|
||||
visible = p_visible
|
||||
toolbar.set_visible(p_visible)
|
||||
tool_settings.set_visible(p_visible)
|
||||
update_decal()
|
||||
|
||||
if(plugin.editor):
|
||||
if(p_visible):
|
||||
await get_tree().create_timer(.01).timeout # Won't work, otherwise.
|
||||
_on_tool_changed(last_tool, last_operation)
|
||||
else:
|
||||
plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX)
|
||||
plugin.editor.set_operation(Terrain3DEditor.OP_MAX)
|
||||
|
||||
|
||||
func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
|
||||
if p_list:
|
||||
p_list.get_parent().get_parent().visible = p_visible
|
||||
|
@ -103,33 +131,45 @@ func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
|
|||
|
||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||
clear_picking()
|
||||
set_menu_visibility(toolbar_settings.advanced_list, true)
|
||||
set_menu_visibility(toolbar_settings.scale_list, false)
|
||||
set_menu_visibility(toolbar_settings.rotation_list, false)
|
||||
set_menu_visibility(toolbar_settings.height_list, false)
|
||||
set_menu_visibility(toolbar_settings.color_list, false)
|
||||
set_menu_visibility(tool_settings.advanced_list, true)
|
||||
set_menu_visibility(tool_settings.scale_list, false)
|
||||
set_menu_visibility(tool_settings.rotation_list, false)
|
||||
set_menu_visibility(tool_settings.height_list, false)
|
||||
set_menu_visibility(tool_settings.color_list, false)
|
||||
|
||||
# Select which settings to show. Options in tool_settings.gd:_ready
|
||||
var to_show: PackedStringArray = []
|
||||
|
||||
match p_tool:
|
||||
Terrain3DEditor.REGION:
|
||||
to_show.push_back("instructions")
|
||||
to_show.push_back("remove")
|
||||
set_menu_visibility(tool_settings.advanced_list, false)
|
||||
|
||||
Terrain3DEditor.SCULPT:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
if p_operation in [Terrain3DEditor.ADD, Terrain3DEditor.SUBTRACT]:
|
||||
to_show.push_back("remove")
|
||||
elif p_operation == Terrain3DEditor.GRADIENT:
|
||||
to_show.push_back("gradient_points")
|
||||
to_show.push_back("drawable")
|
||||
|
||||
Terrain3DEditor.HEIGHT:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
if p_operation == Terrain3DEditor.REPLACE:
|
||||
to_show.push_back("height")
|
||||
to_show.push_back("height_picker")
|
||||
if p_operation == Terrain3DEditor.GRADIENT:
|
||||
to_show.push_back("gradient_points")
|
||||
to_show.push_back("drawable")
|
||||
|
||||
Terrain3DEditor.TEXTURE:
|
||||
to_show.push_back("height")
|
||||
to_show.push_back("height_picker")
|
||||
|
||||
Terrain3DEditor.TEXTURE:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("enable_texture")
|
||||
if p_operation == Terrain3DEditor.ADD:
|
||||
to_show.push_back("strength")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_angle")
|
||||
to_show.push_back("angle")
|
||||
to_show.push_back("angle_picker")
|
||||
|
@ -144,6 +184,10 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
to_show.push_back("strength")
|
||||
to_show.push_back("color")
|
||||
to_show.push_back("color_picker")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_texture")
|
||||
to_show.push_back("margin")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
to_show.push_back("brush")
|
||||
|
@ -151,52 +195,59 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
to_show.push_back("strength")
|
||||
to_show.push_back("roughness")
|
||||
to_show.push_back("roughness_picker")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_texture")
|
||||
to_show.push_back("margin")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("enable")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.INSTANCER:
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
to_show.push_back("enable")
|
||||
set_menu_visibility(toolbar_settings.height_list, true)
|
||||
to_show.push_back("slope")
|
||||
set_menu_visibility(tool_settings.height_list, true)
|
||||
to_show.push_back("height_offset")
|
||||
to_show.push_back("random_height")
|
||||
set_menu_visibility(toolbar_settings.scale_list, true)
|
||||
set_menu_visibility(tool_settings.scale_list, true)
|
||||
to_show.push_back("fixed_scale")
|
||||
to_show.push_back("random_scale")
|
||||
set_menu_visibility(toolbar_settings.rotation_list, true)
|
||||
set_menu_visibility(tool_settings.rotation_list, true)
|
||||
to_show.push_back("fixed_spin")
|
||||
to_show.push_back("random_spin")
|
||||
to_show.push_back("fixed_angle")
|
||||
to_show.push_back("random_angle")
|
||||
to_show.push_back("fixed_tilt")
|
||||
to_show.push_back("random_tilt")
|
||||
to_show.push_back("align_to_normal")
|
||||
set_menu_visibility(toolbar_settings.color_list, true)
|
||||
set_menu_visibility(tool_settings.color_list, true)
|
||||
to_show.push_back("vertex_color")
|
||||
to_show.push_back("random_darken")
|
||||
to_show.push_back("random_hue")
|
||||
to_show.push_back("remove")
|
||||
|
||||
_:
|
||||
pass
|
||||
|
||||
# Advanced menu settings
|
||||
to_show.push_back("automatic_regions")
|
||||
to_show.push_back("auto_regions")
|
||||
to_show.push_back("align_to_view")
|
||||
to_show.push_back("show_cursor_while_painting")
|
||||
to_show.push_back("gamma")
|
||||
to_show.push_back("jitter")
|
||||
toolbar_settings.show_settings(to_show)
|
||||
tool_settings.show_settings(to_show)
|
||||
|
||||
operation_builder = null
|
||||
if p_operation == Terrain3DEditor.GRADIENT:
|
||||
operation_builder = GradientOperationBuilder.new()
|
||||
operation_builder.tool_settings = toolbar_settings
|
||||
operation_builder.tool_settings = tool_settings
|
||||
|
||||
if plugin.editor:
|
||||
plugin.editor.set_tool(p_tool)
|
||||
plugin.editor.set_operation(p_operation)
|
||||
plugin.editor.set_operation(_modify_operation(p_operation))
|
||||
last_tool = p_tool
|
||||
last_operation = p_operation
|
||||
|
||||
_on_setting_changed()
|
||||
plugin.update_region_grid()
|
||||
|
@ -205,31 +256,67 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
func _on_setting_changed() -> void:
|
||||
if not plugin.asset_dock:
|
||||
return
|
||||
brush_data = toolbar_settings.get_settings()
|
||||
brush_data = tool_settings.get_settings()
|
||||
brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
|
||||
update_decal()
|
||||
plugin.editor.set_brush_data(brush_data)
|
||||
plugin.editor.set_operation(_modify_operation(plugin.editor.get_operation()))
|
||||
|
||||
|
||||
func update_modifiers() -> void:
|
||||
toolbar.show_add_buttons(not plugin.modifier_ctrl)
|
||||
|
||||
if plugin.modifier_shift and not plugin.modifier_ctrl:
|
||||
plugin.editor.set_tool(Terrain3DEditor.SCULPT)
|
||||
plugin.editor.set_operation(Terrain3DEditor.AVERAGE)
|
||||
else:
|
||||
plugin.editor.set_tool(last_tool)
|
||||
if plugin.modifier_ctrl:
|
||||
plugin.editor.set_operation(_modify_operation(last_operation))
|
||||
else:
|
||||
plugin.editor.set_operation(last_operation)
|
||||
|
||||
|
||||
func _modify_operation(p_operation: Terrain3DEditor.Operation) -> Terrain3DEditor.Operation:
|
||||
var remove_checked: bool = false
|
||||
if DisplayServer.is_touchscreen_available():
|
||||
var removable_tools := [Terrain3DEditor.REGION, Terrain3DEditor.SCULPT, Terrain3DEditor.HEIGHT, Terrain3DEditor.AUTOSHADER,
|
||||
Terrain3DEditor.HOLES, Terrain3DEditor.INSTANCER, Terrain3DEditor.NAVIGATION,
|
||||
Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS]
|
||||
remove_checked = brush_data.get("remove", false) && plugin.editor.get_tool() in removable_tools
|
||||
|
||||
if plugin.modifier_ctrl or remove_checked:
|
||||
return _invert_operation(p_operation, OP_NEGATIVE_ONLY)
|
||||
return _invert_operation(p_operation, OP_POSITIVE_ONLY)
|
||||
|
||||
|
||||
func _invert_operation(p_operation: Terrain3DEditor.Operation, flags: int = OP_NONE) -> Terrain3DEditor.Operation:
|
||||
if p_operation == Terrain3DEditor.ADD and ! (flags & OP_POSITIVE_ONLY):
|
||||
return Terrain3DEditor.SUBTRACT
|
||||
elif p_operation == Terrain3DEditor.SUBTRACT and ! (flags & OP_NEGATIVE_ONLY):
|
||||
return Terrain3DEditor.ADD
|
||||
return p_operation
|
||||
|
||||
|
||||
func update_decal() -> void:
|
||||
var mouse_buttons: int = Input.get_mouse_button_mask()
|
||||
# If not a state that should show the decal, hide everything and return
|
||||
if not visible or \
|
||||
not plugin.terrain or \
|
||||
brush_data.is_empty() or \
|
||||
mouse_buttons & MOUSE_BUTTON_RIGHT or \
|
||||
(mouse_buttons & MOUSE_BUTTON_LEFT and not brush_data["show_cursor_while_painting"]) or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION:
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
return
|
||||
else:
|
||||
# Wait for cursor to recenter after right-click before revealing
|
||||
not plugin.terrain or \
|
||||
plugin._input_mode < 0 or \
|
||||
# Wait for cursor to recenter after moving camera before revealing
|
||||
# See https://github.com/godotengine/godot/issues/70098
|
||||
await get_tree().create_timer(.05).timeout
|
||||
decal.visible = true
|
||||
Time.get_ticks_msec() - last_rmb_time <= 30 or \
|
||||
brush_data.is_empty() or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION or \
|
||||
(plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]):
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
return
|
||||
|
||||
decal.size = Vector3.ONE * brush_data["size"]
|
||||
decal.position = plugin.mouse_global_position
|
||||
decal.visible = true
|
||||
decal.size = Vector3.ONE * maxf(brush_data["size"], .5)
|
||||
if brush_data["align_to_view"]:
|
||||
var cam: Camera3D = plugin.terrain.get_camera();
|
||||
if (cam):
|
||||
|
@ -240,7 +327,7 @@ func update_decal() -> void:
|
|||
# Set texture and color
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_PICK_HEIGHT
|
||||
|
@ -252,62 +339,66 @@ func update_decal() -> void:
|
|||
else:
|
||||
decal.texture_albedo = brush_data["brush"][1]
|
||||
match plugin.editor.get_tool():
|
||||
Terrain3DEditor.HEIGHT:
|
||||
Terrain3DEditor.SCULPT:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_RAISE
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_LIFT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
else:
|
||||
decal.modulate = COLOR_RAISE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_LOWER
|
||||
Terrain3DEditor.MULTIPLY:
|
||||
decal.modulate = COLOR_EXPAND
|
||||
Terrain3DEditor.DIVIDE:
|
||||
decal.modulate = COLOR_REDUCE
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
else:
|
||||
decal.modulate = COLOR_LOWER
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .5
|
||||
Terrain3DEditor.AVERAGE:
|
||||
decal.modulate = COLOR_SMOOTH
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .2
|
||||
Terrain3DEditor.GRADIENT:
|
||||
decal.modulate = COLOR_SLOPE
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_HEIGHT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.TEXTURE:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .7
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_SPRAY
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()*.5
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()
|
||||
decal.modulate.a *= clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_ROUGHNESS
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.AUTOSHADER:
|
||||
decal.modulate = COLOR_AUTOSHADER
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .7
|
||||
Terrain3DEditor.HOLES:
|
||||
decal.modulate = COLOR_HOLES
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .85
|
||||
Terrain3DEditor.NAVIGATION:
|
||||
decal.modulate = COLOR_NAVIGATION
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .85
|
||||
Terrain3DEditor.INSTANCER:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.modulate = COLOR_INSTANCER
|
||||
decal.modulate.a = 1.0
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.size.y = max(1000, decal.size.y)
|
||||
decal.albedo_mix = 1.0
|
||||
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
||||
decal_timer.start()
|
||||
|
||||
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
|
||||
|
@ -320,6 +411,8 @@ func update_decal() -> void:
|
|||
point_decal.position = point
|
||||
index += 1
|
||||
|
||||
update_compatibility_decal()
|
||||
|
||||
|
||||
func _get_gradient_decal(index: int) -> Decal:
|
||||
if gradient_decals.size() > index:
|
||||
|
@ -329,7 +422,7 @@ func _get_gradient_decal(index: int) -> Decal:
|
|||
gradient_decal = Decal.new()
|
||||
gradient_decal.texture_albedo = ring_texture
|
||||
gradient_decal.modulate = COLOR_SLOPE
|
||||
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
|
||||
gradient_decal.size.y = 1000.
|
||||
gradient_decal.cull_mask = decal.cull_mask
|
||||
add_child(gradient_decal)
|
||||
|
@ -338,6 +431,61 @@ func _get_gradient_decal(index: int) -> Decal:
|
|||
return gradient_decal
|
||||
|
||||
|
||||
func update_compatibility_decal() -> void:
|
||||
if not plugin.terrain.is_compatibility_mode():
|
||||
return
|
||||
|
||||
# Verify setup
|
||||
if editor_decal_position.size() != 3:
|
||||
editor_decal_position.resize(3)
|
||||
editor_decal_rotation.resize(3)
|
||||
editor_decal_size.resize(3)
|
||||
editor_decal_color.resize(3)
|
||||
editor_decal_visible.resize(3)
|
||||
decal_timer.timeout.connect(func():
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
editor_decal_visible[0] = false
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
)
|
||||
|
||||
# Update compatibility decal
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
if decal.visible:
|
||||
editor_decal_position[0] = Vector2(decal.global_position.x, decal.global_position.z)
|
||||
editor_decal_rotation[0] = decal.rotation.y
|
||||
editor_decal_size[0] = brush_data.get("size")
|
||||
editor_decal_color[0] = decal.modulate
|
||||
editor_decal_visible[0] = decal.visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_0", decal.texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 1:
|
||||
editor_decal_position[1] = Vector2(gradient_decals[0].global_position.x,
|
||||
gradient_decals[0].global_position.z)
|
||||
editor_decal_rotation[1] = gradient_decals[0].rotation.y
|
||||
editor_decal_size[1] = 10.0
|
||||
editor_decal_color[1] = gradient_decals[0].modulate
|
||||
editor_decal_visible[1] = gradient_decals[0].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_1", gradient_decals[0].texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 2:
|
||||
editor_decal_position[2] = Vector2(gradient_decals[1].global_position.x,
|
||||
gradient_decals[1].global_position.z)
|
||||
editor_decal_rotation[2] = gradient_decals[1].rotation.y
|
||||
editor_decal_size[2] = 10.0
|
||||
editor_decal_color[2] = gradient_decals[1].modulate
|
||||
editor_decal_visible[2] = gradient_decals[1].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_2", gradient_decals[1].texture_albedo.get_rid()
|
||||
)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
|
||||
|
||||
func set_decal_rotation(p_rot: float) -> void:
|
||||
decal.rotation.y = p_rot
|
||||
|
||||
|
@ -366,16 +514,16 @@ func pick(p_global_position: Vector3) -> void:
|
|||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
var color: Color
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_HEIGHT, p_global_position)
|
||||
Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT:
|
||||
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_HEIGHT, p_global_position)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_COLOR, p_global_position)
|
||||
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position)
|
||||
Terrain3DEditor.COLOR:
|
||||
color = plugin.terrain.get_storage().get_color(p_global_position)
|
||||
color = plugin.terrain.data.get_color(p_global_position)
|
||||
Terrain3DEditor.ANGLE:
|
||||
color = Color(plugin.terrain.get_storage().get_angle(p_global_position), 0., 0., 1.)
|
||||
color = Color(plugin.terrain.data.get_control_angle(p_global_position), 0., 0., 1.)
|
||||
Terrain3DEditor.SCALE:
|
||||
color = Color(plugin.terrain.get_storage().get_scale(p_global_position), 0., 0., 1.)
|
||||
color = Color(plugin.terrain.data.get_control_scale(p_global_position), 0., 0., 1.)
|
||||
_:
|
||||
push_error("Unsupported picking type: ", picking)
|
||||
return
|
||||
|
@ -385,3 +533,6 @@ func pick(p_global_position: Vector3) -> void:
|
|||
elif operation_builder and operation_builder.is_picking():
|
||||
operation_builder.pick(p_global_position, plugin.terrain)
|
||||
|
||||
|
||||
func set_button_editor_icon(p_button: Button, p_icon_name: String) -> void:
|
||||
p_button.icon = EditorInterface.get_base_control().get_theme_icon(p_icon_name, "EditorIcons")
|
||||
|
|
|
@ -12,64 +12,83 @@ func reset_settings(p_value) -> void:
|
|||
height_file_name = ""
|
||||
control_file_name = ""
|
||||
color_file_name = ""
|
||||
import_position = Vector3.ZERO
|
||||
import_offset = 0.0
|
||||
destination_directory = ""
|
||||
import_position = Vector2i.ZERO
|
||||
height_offset = 0.0
|
||||
import_scale = 1.0
|
||||
r16_range = Vector2(0, 1)
|
||||
r16_size = Vector2i(1024, 1024)
|
||||
storage = null
|
||||
material = null
|
||||
assets = null
|
||||
|
||||
|
||||
func reset_terrain(p_value) -> void:
|
||||
if p_value:
|
||||
storage = null
|
||||
data_directory = ""
|
||||
|
||||
|
||||
func update_heights(p_value) -> void:
|
||||
if p_value and storage:
|
||||
storage.update_height_range()
|
||||
if p_value and data:
|
||||
data.update_height_range()
|
||||
|
||||
|
||||
@export_group("Import File")
|
||||
@export_global_file var height_file_name: String = ""
|
||||
@export_global_file var control_file_name: String = ""
|
||||
@export_global_file var color_file_name: String = ""
|
||||
@export var import_position: Vector3 = Vector3.ZERO
|
||||
@export var import_position: Vector2i = Vector2i(0, 0) : set = set_import_position
|
||||
@export var import_scale: float = 1.0
|
||||
@export var import_offset: float = 0.0
|
||||
@export var height_offset: float = 0.0
|
||||
@export var r16_range: Vector2 = Vector2(0, 1)
|
||||
@export var r16_size: Vector2i = Vector2i(1024, 1024)
|
||||
@export var r16_size: Vector2i = Vector2i(1024, 1024) : set = set_r16_size
|
||||
@export var run_import: bool = false : set = start_import
|
||||
|
||||
@export_dir var destination_directory: String = ""
|
||||
@export var save_to_disk: bool = false : set = save_data
|
||||
|
||||
|
||||
func set_import_position(p_value: Vector2i) -> void:
|
||||
import_position.x = clamp(p_value.x, -8192, 8192)
|
||||
import_position.y = clamp(p_value.y, -8192, 8192)
|
||||
|
||||
|
||||
func set_r16_size(p_value: Vector2i) -> void:
|
||||
r16_size.x = clamp(p_value.x, 0, 16384)
|
||||
r16_size.y = clamp(p_value.y, 0, 16384)
|
||||
|
||||
|
||||
func start_import(p_value: bool) -> void:
|
||||
if p_value:
|
||||
print("Terrain3DImporter: Importing files:\n\t%s\n\t%s\n\t%s" % [ height_file_name, control_file_name, color_file_name])
|
||||
if not storage:
|
||||
storage = Terrain3DStorage.new()
|
||||
|
||||
var imported_images: Array[Image]
|
||||
imported_images.resize(Terrain3DStorage.TYPE_MAX)
|
||||
imported_images.resize(Terrain3DRegion.TYPE_MAX)
|
||||
var min_max := Vector2(0, 1)
|
||||
var img: Image
|
||||
if height_file_name:
|
||||
img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size)
|
||||
min_max = Terrain3DUtil.get_min_max(img)
|
||||
imported_images[Terrain3DStorage.TYPE_HEIGHT] = img
|
||||
imported_images[Terrain3DRegion.TYPE_HEIGHT] = img
|
||||
if control_file_name:
|
||||
img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
||||
imported_images[Terrain3DStorage.TYPE_CONTROL] = img
|
||||
imported_images[Terrain3DRegion.TYPE_CONTROL] = img
|
||||
if color_file_name:
|
||||
img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
||||
imported_images[Terrain3DStorage.TYPE_COLOR] = img
|
||||
imported_images[Terrain3DRegion.TYPE_COLOR] = img
|
||||
if assets.get_texture_count() == 0:
|
||||
material.show_checkered = false
|
||||
material.show_colormap = true
|
||||
storage.import_images(imported_images, import_position, import_offset, import_scale)
|
||||
var pos := Vector3(import_position.x, 0, import_position.y)
|
||||
data.import_images(imported_images, pos, height_offset, import_scale)
|
||||
print("Terrain3DImporter: Import finished")
|
||||
|
||||
|
||||
func save_data(p_value: bool) -> void:
|
||||
if destination_directory.is_empty():
|
||||
push_error("Set destination directory first")
|
||||
return
|
||||
data.save_directory(destination_directory)
|
||||
|
||||
|
||||
@export_group("Export File")
|
||||
enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
||||
@export_enum("Height:0", "Control:1", "Color:2") var map_type: int = TYPE_HEIGHT
|
||||
|
@ -77,6 +96,6 @@ enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
|||
@export var run_export: bool = false : set = start_export
|
||||
|
||||
func start_export(p_value: bool) -> void:
|
||||
var err: int = storage.export_image(file_name_out, map_type)
|
||||
var err: int = data.export_image(file_name_out, map_type)
|
||||
print("Terrain3DImporter: Export error status: ", err, " ", error_string(err))
|
||||
|
||||
|
|
|
@ -1,16 +1,63 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://blaieaqp413k7"]
|
||||
[gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"]
|
||||
|
||||
[sub_resource type="Terrain3DStorage" id="Terrain3DStorage_rmuvl"]
|
||||
[sub_resource type="Gradient" id="Gradient_88f3t"]
|
||||
offsets = PackedFloat32Array(0.2, 1)
|
||||
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_cjpaa"]
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_muvel"]
|
||||
noise_type = 2
|
||||
frequency = 0.03
|
||||
cellular_jitter = 3.0
|
||||
cellular_return_type = 0
|
||||
domain_warp_enabled = true
|
||||
domain_warp_type = 1
|
||||
domain_warp_amplitude = 50.0
|
||||
domain_warp_fractal_type = 2
|
||||
domain_warp_fractal_lacunarity = 1.5
|
||||
domain_warp_fractal_gain = 1.0
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ve0yk"]
|
||||
seamless = true
|
||||
color_ramp = SubResource("Gradient_88f3t")
|
||||
noise = SubResource("FastNoiseLite_muvel")
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_p55u0"]
|
||||
_shader_parameters = {
|
||||
"blend_sharpness": 0.87,
|
||||
"height_blending": true,
|
||||
"macro_variation1": Color(1, 1, 1, 1),
|
||||
"macro_variation2": Color(1, 1, 1, 1),
|
||||
"noise1_angle": 0.0,
|
||||
"noise1_offset": Vector2(0.5, 0.5),
|
||||
"noise1_scale": 0.04,
|
||||
"noise2_scale": 0.076,
|
||||
"noise3_scale": 0.225,
|
||||
"noise_texture": SubResource("NoiseTexture2D_ve0yk"),
|
||||
"vertex_normals_distance": 128.0
|
||||
}
|
||||
show_checkered = true
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_gbxcd"]
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8rvqy"]
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
backlight_enabled = true
|
||||
backlight = Color(0.5, 0.5, 0.5, 1)
|
||||
|
||||
[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_7je72"]
|
||||
height_offset = 0.5
|
||||
density = 10.0
|
||||
material_override = SubResource("StandardMaterial3D_8rvqy")
|
||||
generated_type = 1
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_op32e"]
|
||||
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_7je72")])
|
||||
|
||||
[node name="Importer" type="Terrain3D"]
|
||||
storage = SubResource("Terrain3DStorage_rmuvl")
|
||||
material = SubResource("Terrain3DMaterial_cjpaa")
|
||||
assets = SubResource("Terrain3DAssets_gbxcd")
|
||||
material = SubResource("Terrain3DMaterial_p55u0")
|
||||
assets = SubResource("Terrain3DAssets_op32e")
|
||||
mesh_lods = 8
|
||||
top_level = true
|
||||
script = ExtResource("1_60b8f")
|
||||
metadata/_edit_lock_ = true
|
||||
|
|
|
@ -42,22 +42,22 @@ func editor_setup(p_plugin) -> void:
|
|||
func get_terrain() -> Terrain3D:
|
||||
var terrain := instance_from_id(_terrain_id) as Terrain3D
|
||||
if not terrain or terrain.is_queued_for_deletion() or not terrain.is_inside_tree():
|
||||
var terrains: Array[Node] = EditorInterface.get_edited_scene_root().find_children("", "Terrain3D")
|
||||
var terrains: Array[Node] = Engine.get_singleton(&"EditorInterface").get_edited_scene_root().find_children("", "Terrain3D")
|
||||
if terrains.size() > 0:
|
||||
terrain = terrains[0]
|
||||
_terrain_id = terrain.get_instance_id() if terrain else 0
|
||||
|
||||
if terrain and terrain.storage and not terrain.storage.maps_edited.is_connected(_on_maps_edited):
|
||||
terrain.storage.maps_edited.connect(_on_maps_edited)
|
||||
if terrain and terrain.data and not terrain.data.maps_edited.is_connected(_on_maps_edited):
|
||||
terrain.data.maps_edited.connect(_on_maps_edited)
|
||||
|
||||
return terrain
|
||||
|
||||
|
||||
func _get_terrain_height(p_global_position: Vector3) -> float:
|
||||
var terrain: Terrain3D = get_terrain()
|
||||
if not terrain or not terrain.storage:
|
||||
if not terrain or not terrain.data:
|
||||
return 0.0
|
||||
var height: float = terrain.storage.get_height(p_global_position)
|
||||
var height: float = terrain.data.get_height(p_global_position)
|
||||
if is_nan(height):
|
||||
return 0.0
|
||||
return height
|
||||
|
@ -105,7 +105,7 @@ func _on_child_exiting_tree(p_node: Node) -> void:
|
|||
|
||||
|
||||
func _is_node_selected(p_node: Node) -> bool:
|
||||
var editor_sel = EditorInterface.get_selection()
|
||||
var editor_sel = Engine.get_singleton(&"EditorInterface").get_selection()
|
||||
return editor_sel.get_transformable_selected_nodes().has(p_node)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
[gd_scene load_steps=56 format=3 uid="uid://bm2o3mex10v11"]
|
||||
[gd_scene load_steps=55 format=3 uid="uid://bm2o3mex10v11"]
|
||||
|
||||
[ext_resource type="Terrain3DStorage" uid="uid://bu1gewgsgc5hm" path="res://levels/debug_level/terrain_3d_storage.res" id="1_nlsu2"]
|
||||
[ext_resource type="Texture2D" path="res://assets/textures/grass_fairway/grass_fairway_albedo.dds" id="2_e4m27"]
|
||||
[ext_resource type="Texture2D" path="res://assets/textures/grass_rough/grass_rough_albedo.dds" id="4_p0awb"]
|
||||
[ext_resource type="Texture2D" path="res://assets/textures/grass_rough/grass_rough_normal.dds" id="4_yhjhv"]
|
||||
|
@ -51,30 +50,33 @@ _shader_parameters = {
|
|||
"noise3_scale": 0.021,
|
||||
"noise_texture": SubResource("NoiseTexture2D_sb318"),
|
||||
"vertex_normals_distance": 128.0,
|
||||
"world_noise_blend_far": 1.0,
|
||||
"world_noise_blend_near": 0.48,
|
||||
"world_noise_height": 74.0,
|
||||
"world_noise_lod_distance": 16384.0,
|
||||
"world_noise_max_octaves": 8,
|
||||
"world_noise_min_octaves": 2,
|
||||
"world_noise_offset": Vector3(0, -8, 0),
|
||||
"world_noise_region_blend": 0.33,
|
||||
"world_noise_scale": 5.0
|
||||
}
|
||||
world_background = 2
|
||||
texture_filtering = 1
|
||||
auto_shader = true
|
||||
show_region_grid = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2u1w6"]
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_l1sla"]
|
||||
transparency = 4
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
backlight_enabled = true
|
||||
backlight = Color(0.5, 0.5, 0.5, 1)
|
||||
distance_fade_mode = 1
|
||||
distance_fade_min_distance = 960.0
|
||||
distance_fade_max_distance = 480.0
|
||||
|
||||
[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_8g62o"]
|
||||
name = "palm_tree"
|
||||
[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_iaq2t"]
|
||||
height_offset = 0.5
|
||||
density = 10.0
|
||||
material_override = SubResource("StandardMaterial3D_2u1w6")
|
||||
material_override = SubResource("StandardMaterial3D_l1sla")
|
||||
generated_type = 1
|
||||
|
||||
[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_kocfk"]
|
||||
|
@ -105,7 +107,7 @@ normal_texture = ExtResource("9_rrguh")
|
|||
uv_scale = 0.02
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_eq5uw"]
|
||||
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_8g62o")])
|
||||
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_iaq2t")])
|
||||
texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_kocfk"), SubResource("Terrain3DTextureAsset_vmhlw"), SubResource("Terrain3DTextureAsset_k6h8c"), SubResource("Terrain3DTextureAsset_13d2a")])
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_h8tes"]
|
||||
|
@ -252,9 +254,11 @@ point_count = 5
|
|||
[node name="TestLevel" type="Node3D"]
|
||||
|
||||
[node name="Terrain3D" type="Terrain3D" parent="."]
|
||||
storage = ExtResource("1_nlsu2")
|
||||
data_directory = "res://levels/debug_level/terrain_3d_data"
|
||||
material = SubResource("Terrain3DMaterial_woy2k")
|
||||
assets = SubResource("Terrain3DAssets_eq5uw")
|
||||
show_grid = true
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_k6wwx")
|
||||
|
|
Binary file not shown.
Binary file not shown.
BIN
levels/debug_level/terrain_3d_storage.res (Stored with Git LFS)
BIN
levels/debug_level/terrain_3d_storage.res (Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue