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
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
# Terrain3D
|
||||||
A high performance, editable terrain system for Godot 4.
|
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
|
* Written in C++ as a GDExtension addon, which works with official engine builds
|
||||||
* Can be accessed by GDScript, C#, and any language Godot supports
|
* 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)
|
* 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 32 textures
|
||||||
* Up to 10 levels of detail
|
* Up to 10 levels of detail
|
||||||
* Foliage instancing
|
* 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 UI: Script = preload("res://addons/terrain_3d/src/ui.gd")
|
||||||
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.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 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 terrain: Terrain3D
|
||||||
var _last_terrain: Terrain3D
|
var _last_terrain: Terrain3D
|
||||||
var nav_region: NavigationRegion3D
|
var nav_region: NavigationRegion3D
|
||||||
|
|
||||||
var editor: Terrain3DEditor
|
var editor: Terrain3DEditor
|
||||||
|
var editor_settings: EditorSettings
|
||||||
var ui: Node # Terrain3DUI see Godot #75388
|
var ui: Node # Terrain3DUI see Godot #75388
|
||||||
var asset_dock: PanelContainer
|
var asset_dock: PanelContainer
|
||||||
var region_gizmo: RegionGizmo
|
var region_gizmo: RegionGizmo
|
||||||
var visible: bool
|
|
||||||
var current_region_position: Vector2
|
var current_region_position: Vector2
|
||||||
var mouse_global_position: Vector3 = Vector3.ZERO
|
var mouse_global_position: Vector3 = Vector3.ZERO
|
||||||
|
var godot_editor_window: Window # The Godot Editor window
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
func _enter_tree() -> void:
|
||||||
editor = Terrain3DEditor.new()
|
editor = Terrain3DEditor.new()
|
||||||
|
setup_editor_settings()
|
||||||
ui = UI.new()
|
ui = UI.new()
|
||||||
ui.plugin = self
|
ui.plugin = self
|
||||||
add_child(ui)
|
add_child(ui)
|
||||||
|
@ -41,7 +48,7 @@ func _enter_tree() -> void:
|
||||||
asset_dock = load(ASSET_DOCK).instantiate()
|
asset_dock = load(ASSET_DOCK).instantiate()
|
||||||
asset_dock.initialize(self)
|
asset_dock.initialize(self)
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
asset_dock.remove_dock(true)
|
asset_dock.remove_dock(true)
|
||||||
asset_dock.queue_free()
|
asset_dock.queue_free()
|
||||||
|
@ -49,25 +56,46 @@ func _exit_tree() -> void:
|
||||||
editor.free()
|
editor.free()
|
||||||
|
|
||||||
scene_changed.disconnect(_on_scene_changed)
|
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:
|
func _handles(p_object: Object) -> bool:
|
||||||
if p_object is Terrain3D:
|
if p_object is Terrain3D:
|
||||||
return true
|
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
|
# 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.
|
# 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)
|
p_object.editor_setup(self)
|
||||||
elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
|
elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
|
||||||
p_object.get_parent().editor_setup(self)
|
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
|
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:
|
func _edit(p_object: Object) -> void:
|
||||||
if !p_object:
|
if !p_object:
|
||||||
_clear()
|
_clear()
|
||||||
|
@ -77,39 +105,41 @@ func _edit(p_object: Object) -> void:
|
||||||
return
|
return
|
||||||
terrain = p_object
|
terrain = p_object
|
||||||
_last_terrain = terrain
|
_last_terrain = terrain
|
||||||
|
terrain.set_plugin(self)
|
||||||
|
terrain.set_editor(editor)
|
||||||
editor.set_terrain(terrain)
|
editor.set_terrain(terrain)
|
||||||
region_gizmo.set_node_3d(terrain)
|
region_gizmo.set_node_3d(terrain)
|
||||||
terrain.add_gizmo(region_gizmo)
|
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):
|
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
|
||||||
terrain.assets_changed.connect(asset_dock.update_assets)
|
terrain.assets_changed.connect(asset_dock.update_assets)
|
||||||
asset_dock.update_assets()
|
asset_dock.update_assets()
|
||||||
# Connect to new Storage resource
|
# Get alerted when the region map changes
|
||||||
if not terrain.storage_changed.is_connected(_load_storage):
|
if not terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||||
terrain.storage_changed.connect(_load_storage)
|
terrain.data.region_map_changed.connect(update_region_grid)
|
||||||
_load_storage()
|
update_region_grid()
|
||||||
else:
|
else:
|
||||||
_clear()
|
_clear()
|
||||||
|
|
||||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree():
|
if is_terrain_valid(_last_terrain):
|
||||||
if p_object is NavigationRegion3D:
|
if p_object is NavigationRegion3D:
|
||||||
|
ui.set_visible(true, true)
|
||||||
nav_region = p_object
|
nav_region = p_object
|
||||||
else:
|
else:
|
||||||
nav_region = null
|
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:
|
func _clear() -> void:
|
||||||
if is_terrain_valid():
|
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.clear_gizmos()
|
||||||
terrain = null
|
terrain = null
|
||||||
|
@ -124,29 +154,12 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
||||||
if not is_terrain_valid():
|
if not is_terrain_valid():
|
||||||
return AFTER_GUI_INPUT_PASS
|
return AFTER_GUI_INPUT_PASS
|
||||||
|
|
||||||
## Track negative input (CTRL)
|
_read_input(p_event)
|
||||||
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
|
|
||||||
|
|
||||||
## Handle mouse movement
|
## Handle mouse movement
|
||||||
if p_event is InputEventMouseMotion:
|
if p_event is InputEventMouseMotion:
|
||||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
## Setup active camera & viewport
|
||||||
return AFTER_GUI_INPUT_PASS
|
|
||||||
|
|
||||||
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
|
# Snap terrain to current camera
|
||||||
terrain.set_camera(p_viewport_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:
|
||||||
# Else look for intersection with terrain
|
# Else look for intersection with terrain
|
||||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
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
|
return AFTER_GUI_INPUT_STOP
|
||||||
mouse_global_position = intersection_point
|
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()
|
ui.update_decal()
|
||||||
|
|
||||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
if _input_mode != -1: # Not cam rotation
|
||||||
if p_event.is_pressed():
|
## Update region highlight
|
||||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||||
return AFTER_GUI_INPUT_STOP
|
/ (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
|
||||||
|
if current_region_position != region_position:
|
||||||
# If picking
|
current_region_position = region_position
|
||||||
if ui.is_picking():
|
update_region_grid()
|
||||||
ui.pick(mouse_global_position)
|
|
||||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
if _input_mode > 0 and editor.is_operating():
|
||||||
return AFTER_GUI_INPUT_STOP
|
# Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
|
||||||
|
ui.brush_data["mouse_pressure"] = p_event.pressure
|
||||||
# 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)
|
|
||||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||||
return AFTER_GUI_INPUT_STOP
|
return AFTER_GUI_INPUT_STOP
|
||||||
|
|
||||||
elif editor.is_operating():
|
return AFTER_GUI_INPUT_PASS
|
||||||
# Mouse released, save undo data
|
|
||||||
editor.stop_operation()
|
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
|
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
|
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:
|
match get_setting("editors/3d/navigation/navigation_scheme", 0):
|
||||||
if terrain:
|
2, 1: # Modo, Maya
|
||||||
update_region_grid()
|
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:
|
func update_region_grid() -> void:
|
||||||
if not region_gizmo:
|
if not region_gizmo:
|
||||||
return
|
return
|
||||||
|
region_gizmo.set_hidden(not ui.visible)
|
||||||
|
|
||||||
region_gizmo.set_hidden(not visible)
|
|
||||||
|
|
||||||
if is_terrain_valid():
|
if is_terrain_valid():
|
||||||
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
||||||
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
||||||
region_gizmo.region_position = current_region_position
|
region_gizmo.region_position = current_region_position
|
||||||
region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing()
|
region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
|
||||||
region_gizmo.grid = terrain.get_storage().get_region_offsets()
|
region_gizmo.grid = terrain.get_data().get_region_locations()
|
||||||
|
|
||||||
terrain.update_gizmos()
|
terrain.update_gizmos()
|
||||||
return
|
return
|
||||||
|
@ -278,22 +334,80 @@ func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
|
||||||
t = p_terrain
|
t = p_terrain
|
||||||
else:
|
else:
|
||||||
t = terrain
|
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 true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
func is_selected() -> bool:
|
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:
|
for node in selected:
|
||||||
if node.get_instance_id() == _last_terrain.get_instance_id():
|
if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \
|
||||||
return true
|
node is Terrain3D:
|
||||||
|
return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
func select_terrain() -> void:
|
func select_terrain() -> void:
|
||||||
if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected():
|
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.clear()
|
||||||
es.add_node(_last_terrain)
|
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. Click import. The output window and console will report when finished.
|
||||||
# 1. Clear the script from your Terrain3D node, and save your scene.
|
# 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.
|
# 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.
|
// This shader is the minimum needed to allow the terrain to function, without any texturing.
|
||||||
|
|
||||||
shader_type spatial;
|
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
|
// Private uniforms
|
||||||
uniform float _region_size = 1024.0;
|
uniform float _region_size = 1024.0;
|
||||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||||
uniform float _mesh_vertex_spacing = 1.0;
|
uniform float _vertex_spacing = 1.0;
|
||||||
uniform float _mesh_vertex_density = 1.0; // = 1/_mesh_vertex_spacing
|
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
|
||||||
uniform int _region_map_size = 16;
|
uniform int _region_map_size = 32;
|
||||||
uniform int _region_map[256];
|
uniform int _region_map[1024];
|
||||||
uniform vec2 _region_offsets[256];
|
uniform vec2 _region_locations[1024];
|
||||||
uniform sampler2DArray _height_maps : repeat_disable;
|
uniform sampler2DArray _height_maps : repeat_disable;
|
||||||
uniform usampler2DArray _control_maps : repeat_disable;
|
uniform usampler2DArray _control_maps : repeat_disable;
|
||||||
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, 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_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||||
uniform sampler2DArray _texture_array_normal : hint_normal, 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_scale_array[32];
|
||||||
uniform float _texture_uv_rotation_array[32];
|
uniform float _texture_detile_array[32];
|
||||||
uniform vec4 _texture_color_array[32];
|
uniform vec4 _texture_color_array[32];
|
||||||
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
||||||
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
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_uv_offset;
|
||||||
varying flat vec2 v_uv2_offset;
|
varying flat vec2 v_uv2_offset;
|
||||||
|
varying vec3 v_normal;
|
||||||
|
varying float v_region_border_mask;
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
// Vertex
|
// Vertex
|
||||||
|
@ -33,26 +44,22 @@ varying flat vec2 v_uv2_offset;
|
||||||
// Takes in UV world space coordinates, returns ivec3 with:
|
// Takes in UV world space coordinates, returns ivec3 with:
|
||||||
// XY: (0 to _region_size) coordinates within a region
|
// XY: (0 to _region_size) coordinates within a region
|
||||||
// Z: layer index used for texturearrays, -1 if not in a region
|
// Z: layer index used for texturearrays, -1 if not in a region
|
||||||
ivec3 get_region_uv(vec2 uv) {
|
ivec3 get_region_uv(const vec2 uv) {
|
||||||
uv *= _region_texel_size;
|
ivec2 pos = ivec2(floor(uv * _region_texel_size)) + (_region_map_size / 2);
|
||||||
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
|
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||||
int bounds = int(pos.x>=0 && pos.x<_region_map_size && pos.y>=0 && pos.y<_region_map_size);
|
|
||||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
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:
|
// Takes in UV2 region space coordinates, returns vec3 with:
|
||||||
// XY: (0 to 1) coordinates within a region
|
// XY: (0 to 1) coordinates within a region
|
||||||
// Z: layer index used for texturearrays, -1 if not in a region
|
// Z: layer index used for texturearrays, -1 if not in a region
|
||||||
vec3 get_region_uv2(vec2 uv) {
|
vec3 get_region_uv2(const vec2 uv2) {
|
||||||
// Vertex function added half a texel to UV2, to center the UV's. vertex(), fragment() and get_height()
|
// Remove Texel Offset to ensure correct region index.
|
||||||
// call this with reclaimed versions of UV2, so to keep the last row/column within the correct
|
ivec2 pos = ivec2(floor(uv2 - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||||
// window, take back the half pixel before the floor().
|
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||||
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);
|
|
||||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||||
// The return value is still texel-centered.
|
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
|
||||||
return vec3(uv - _region_offsets[layer_index], float(layer_index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1 lookup
|
// 1 lookup
|
||||||
|
@ -66,63 +73,75 @@ float get_height(vec2 uv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void vertex() {
|
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
|
// 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 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
|
// Discard vertices for Holes. 1 lookup
|
||||||
ivec3 region = get_region_uv(UV);
|
v_region = get_region_uv(UV);
|
||||||
uint control = texelFetch(_control_maps, region, 0).r;
|
uint control = texelFetch(_control_maps, v_region, 0).r;
|
||||||
bool hole = bool(control >>2u & 0x1u);
|
bool hole = bool(control >>2u & 0x1u);
|
||||||
|
|
||||||
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
||||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||||
(hole || (_background_mode == 0u && region.z < 0)) ) {
|
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
|
||||||
VERTEX.x = 0./0.;
|
VERTEX.x = 0. / 0.;
|
||||||
} else {
|
} else {
|
||||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
// Set final vertex height & calculate vertex normals. 3 lookups.
|
||||||
UV2 = (UV + vec2(0.5)) * _region_texel_size;
|
|
||||||
|
|
||||||
// Get final vertex location and save it
|
|
||||||
VERTEX.y = get_height(UV2);
|
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.
|
// 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;
|
UV -= v_uv_offset;
|
||||||
v_uv2_offset = v_uv_offset * _region_texel_size;
|
v_uv2_offset = v_uv_offset * _region_texel_size;
|
||||||
UV2 -= v_uv2_offset;
|
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
|
// Fragment
|
||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
// 3 lookups
|
// 0 - 3 lookups
|
||||||
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
|
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
|
||||||
// Get the height of the current vertex
|
float u, v, height;
|
||||||
float height = get_height(uv);
|
vec3 normal;
|
||||||
|
// Use vertex normals within radius of vertex_normals_distance, and along region borders.
|
||||||
// Get the heights to the right and in front, but because of hardware
|
if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
|
||||||
// interpolation on the edges of the heightmaps, the values are off
|
normal = normalize(v_normal);
|
||||||
// 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;
|
|
||||||
} else {
|
} else {
|
||||||
|
height = get_height(uv);
|
||||||
u = height - get_height(uv + vec2(_region_texel_size, 0));
|
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));
|
tangent = cross(normal, vec3(0, 0, 1));
|
||||||
binormal = cross(normal, tangent);
|
binormal = cross(normal, tangent);
|
||||||
return normal;
|
return normal;
|
||||||
|
@ -143,4 +162,3 @@ void fragment() {
|
||||||
// Apply PBR
|
// Apply PBR
|
||||||
ALBEDO=vec3(.2);
|
ALBEDO=vec3(.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,8 @@
|
||||||
#warning += """No Terrain3D node found"""
|
#warning += """No Terrain3D node found"""
|
||||||
#return
|
#return
|
||||||
#
|
#
|
||||||
#if not _terrain.storage:
|
#if not _terrain.data:
|
||||||
#warning += """Terrain3D storage is not initialized"""
|
#warning += """Terrain3DData is not initialized"""
|
||||||
#return
|
#return
|
||||||
#
|
#
|
||||||
## Get global transform
|
## Get global transform
|
||||||
|
@ -70,8 +70,8 @@
|
||||||
#var gt_inverse := gt.affine_inverse()
|
#var gt_inverse := gt.affine_inverse()
|
||||||
#for i in transforms.list.size():
|
#for i in transforms.list.size():
|
||||||
#var location: Vector3 = (gt * transforms.list[i]).origin
|
#var location: Vector3 = (gt * transforms.list[i]).origin
|
||||||
#var height: float = _terrain.storage.get_height(location)
|
#var height: float = _terrain.data.get_height(location)
|
||||||
#var normal: Vector3 = _terrain.storage.get_normal(location)
|
#var normal: Vector3 = _terrain.data.get_normal(location)
|
||||||
#
|
#
|
||||||
#if align_with_collision_normal and not is_nan(normal.x):
|
#if align_with_collision_normal and not is_nan(normal.x):
|
||||||
#transforms.list[i].basis.y = normal
|
#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"
|
name="Terrain3D"
|
||||||
description="A high performance, editable terrain system for Godot 4."
|
description="A high performance, editable terrain system for Godot 4."
|
||||||
author="Cory Petkovsek & Roope Palmroos"
|
author="Cory Petkovsek & Roope Palmroos"
|
||||||
version="0.9.2"
|
version="0.9.3a"
|
||||||
script="editor.gd"
|
script="editor.gd"
|
||||||
|
|
|
@ -6,12 +6,13 @@ signal confirmation_closed
|
||||||
signal confirmation_confirmed
|
signal confirmation_confirmed
|
||||||
signal confirmation_canceled
|
signal confirmation_canceled
|
||||||
|
|
||||||
const PS_DOCK_SLOT: String = "terrain3d/config/dock_slot"
|
const ES_DOCK_SLOT: String = "terrain3d/dock/slot"
|
||||||
const PS_DOCK_TILE_SIZE: String = "terrain3d/config/dock_tile_size"
|
const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size"
|
||||||
const PS_DOCK_FLOATING: String = "terrain3d/config/dock_floating"
|
const ES_DOCK_FLOATING: String = "terrain3d/dock/floating"
|
||||||
const PS_DOCK_PINNED: String = "terrain3d/config/dock_always_on_top"
|
const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top"
|
||||||
const PS_DOCK_WINDOW_POSITION: String = "terrain3d/config/dock_window_position"
|
const ES_DOCK_WINDOW_POSITION: String = "terrain3d/dock/window_position"
|
||||||
const PS_DOCK_WINDOW_SIZE: String = "terrain3d/config/dock_window_size"
|
const ES_DOCK_WINDOW_SIZE: String = "terrain3d/dock/window_size"
|
||||||
|
const ES_DOCK_TAB: String = "terrain3d/dock/tab"
|
||||||
|
|
||||||
var texture_list: ListContainer
|
var texture_list: ListContainer
|
||||||
var mesh_list: ListContainer
|
var mesh_list: ListContainer
|
||||||
|
@ -40,10 +41,6 @@ enum {
|
||||||
}
|
}
|
||||||
var state: int = HIDDEN
|
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 {
|
enum {
|
||||||
POS_LEFT_UL = 0,
|
POS_LEFT_UL = 0,
|
||||||
POS_LEFT_BL = 1,
|
POS_LEFT_BL = 1,
|
||||||
|
@ -59,17 +56,15 @@ enum {
|
||||||
var slot: int = POS_RIGHT_BR
|
var slot: int = POS_RIGHT_BR
|
||||||
var _initialized: bool = false
|
var _initialized: bool = false
|
||||||
var plugin: EditorPlugin
|
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:
|
func initialize(p_plugin: EditorPlugin) -> void:
|
||||||
if p_plugin:
|
if p_plugin:
|
||||||
plugin = 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
|
placement_opt = $Box/Buttons/PlacementOpt
|
||||||
pinned_btn = $Box/Buttons/Pinned
|
pinned_btn = $Box/Buttons/Pinned
|
||||||
floating_btn = $Box/Buttons/Floating
|
floating_btn = $Box/Buttons/Floating
|
||||||
|
@ -93,7 +88,6 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
||||||
asset_container.add_child(mesh_list)
|
asset_container.add_child(mesh_list)
|
||||||
_current_list = texture_list
|
_current_list = texture_list
|
||||||
|
|
||||||
editor_settings = EditorInterface.get_editor_settings()
|
|
||||||
load_editor_settings()
|
load_editor_settings()
|
||||||
|
|
||||||
# Connect signals
|
# Connect signals
|
||||||
|
@ -103,7 +97,7 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
||||||
placement_opt.item_selected.connect(set_slot)
|
placement_opt.item_selected.connect(set_slot)
|
||||||
floating_btn.pressed.connect(make_dock_float)
|
floating_btn.pressed.connect(make_dock_float)
|
||||||
pinned_btn.toggled.connect(_on_pin_changed)
|
pinned_btn.toggled.connect(_on_pin_changed)
|
||||||
pinned_btn.visible = false
|
pinned_btn.visible = ( window != null )
|
||||||
size_slider.value_changed.connect(_on_slider_changed)
|
size_slider.value_changed.connect(_on_slider_changed)
|
||||||
plugin.ui.toolbar.tool_changed.connect(_on_tool_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())
|
textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
|
||||||
|
|
||||||
_initialized = true
|
_initialized = true
|
||||||
update_dock(plugin.visible)
|
update_dock()
|
||||||
update_layout()
|
update_layout()
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,7 +116,7 @@ func _ready() -> void:
|
||||||
# Setup styles
|
# Setup styles
|
||||||
set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
|
set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
|
||||||
# Avoid saving icon resources in tscn when editing w/ a tool script
|
# 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.icon = get_theme_icon("Pin", "EditorIcons")
|
||||||
pinned_btn.text = ""
|
pinned_btn.text = ""
|
||||||
floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
|
floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
|
||||||
|
@ -154,7 +148,7 @@ func set_slot(p_slot: int) -> void:
|
||||||
placement_opt.selected = slot
|
placement_opt.selected = slot
|
||||||
save_editor_settings()
|
save_editor_settings()
|
||||||
plugin.select_terrain()
|
plugin.select_terrain()
|
||||||
update_dock(plugin.visible)
|
update_dock()
|
||||||
|
|
||||||
|
|
||||||
func remove_dock(p_force: bool = false) -> void:
|
func remove_dock(p_force: bool = false) -> void:
|
||||||
|
@ -167,50 +161,42 @@ func remove_dock(p_force: bool = false) -> void:
|
||||||
state = HIDDEN
|
state = HIDDEN
|
||||||
|
|
||||||
# If windowed and destination is not window or final exit, otherwise leave
|
# If windowed and destination is not window or final exit, otherwise leave
|
||||||
elif state == WINDOWED and p_force:
|
elif state == WINDOWED and p_force and window:
|
||||||
if not window:
|
|
||||||
return
|
|
||||||
var parent: Node = get_parent()
|
var parent: Node = get_parent()
|
||||||
if parent:
|
if parent:
|
||||||
parent.remove_child(self)
|
parent.remove_child(self)
|
||||||
_godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
||||||
_godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
plugin.godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||||
_godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
plugin.godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
||||||
window.hide()
|
window.hide()
|
||||||
window.queue_free()
|
window.queue_free()
|
||||||
window = null
|
window = null
|
||||||
floating_btn.button_pressed = false
|
floating_btn.button_pressed = false
|
||||||
floating_btn.visible = true
|
floating_btn.visible = true
|
||||||
pinned_btn.visible = false
|
pinned_btn.visible = false
|
||||||
placement_opt.visible = true
|
placement_opt.visible = true
|
||||||
state = HIDDEN
|
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()
|
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()
|
remove_dock()
|
||||||
# Add dock to new destination
|
|
||||||
# Sidebar
|
# Sidebar
|
||||||
if slot < POS_BOTTOM:
|
if slot < POS_BOTTOM:
|
||||||
state = SIDEBAR
|
state = SIDEBAR
|
||||||
plugin.add_control_to_dock(slot, self)
|
plugin.add_control_to_dock(slot, self)
|
||||||
|
# Bottom
|
||||||
elif slot == POS_BOTTOM:
|
elif slot == POS_BOTTOM:
|
||||||
state = BOTTOM
|
state = BOTTOM
|
||||||
plugin.add_control_to_bottom_panel(self, "Terrain3D")
|
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:
|
func update_layout() -> void:
|
||||||
if not _initialized:
|
if not _initialized:
|
||||||
|
@ -257,6 +243,8 @@ func update_thumbnails() -> void:
|
||||||
_last_thumb_update_time = Time.get_ticks_msec()
|
_last_thumb_update_time = Time.get_ticks_msec()
|
||||||
for mesh_asset in mesh_list.entries:
|
for mesh_asset in mesh_list.entries:
|
||||||
mesh_asset.queue_redraw()
|
mesh_asset.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
## Dock Button handlers
|
## Dock Button handlers
|
||||||
|
|
||||||
|
|
||||||
|
@ -282,7 +270,9 @@ func _on_textures_pressed() -> void:
|
||||||
textures_btn.button_pressed = true
|
textures_btn.button_pressed = true
|
||||||
meshes_btn.button_pressed = false
|
meshes_btn.button_pressed = false
|
||||||
texture_list.set_selected_id(texture_list.selected_id)
|
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:
|
func _on_meshes_pressed() -> void:
|
||||||
|
@ -293,14 +283,16 @@ func _on_meshes_pressed() -> void:
|
||||||
meshes_btn.button_pressed = true
|
meshes_btn.button_pressed = true
|
||||||
textures_btn.button_pressed = false
|
textures_btn.button_pressed = false
|
||||||
mesh_list.set_selected_id(mesh_list.selected_id)
|
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()
|
update_thumbnails()
|
||||||
|
save_editor_settings()
|
||||||
|
|
||||||
|
|
||||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||||
if p_tool == Terrain3DEditor.INSTANCER:
|
if p_tool == Terrain3DEditor.INSTANCER:
|
||||||
_on_meshes_pressed()
|
_on_meshes_pressed()
|
||||||
elif p_tool == Terrain3DEditor.TEXTURE:
|
elif p_tool in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
|
||||||
_on_textures_pressed()
|
_on_textures_pressed()
|
||||||
|
|
||||||
|
|
||||||
|
@ -320,24 +312,34 @@ func update_assets() -> void:
|
||||||
|
|
||||||
_current_list.update_asset_list()
|
_current_list.update_asset_list()
|
||||||
|
|
||||||
|
|
||||||
## Window Management
|
## Window Management
|
||||||
|
|
||||||
|
|
||||||
func make_dock_float() -> void:
|
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:
|
if not window:
|
||||||
remove_dock()
|
remove_dock()
|
||||||
create_window()
|
create_window()
|
||||||
|
|
||||||
state = WINDOWED
|
state = WINDOWED
|
||||||
|
visible = true # Asset dock contents are hidden when popping out of the bottom!
|
||||||
pinned_btn.visible = true
|
pinned_btn.visible = true
|
||||||
floating_btn.visible = false
|
floating_btn.visible = false
|
||||||
placement_opt.visible = false
|
placement_opt.visible = false
|
||||||
window.title = "Terrain3D Asset Dock"
|
window.title = "Terrain3D Asset Dock"
|
||||||
window.always_on_top = pinned_btn.button_pressed
|
window.always_on_top = pinned_btn.button_pressed
|
||||||
window.close_requested.connect(remove_dock.bind(true))
|
window.close_requested.connect(remove_dock.bind(true))
|
||||||
visible = true # Is hidden when pops off of bottom. ??
|
window.window_input.connect(_on_window_input)
|
||||||
_godot_editor_window.grab_focus()
|
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:
|
func create_window() -> void:
|
||||||
|
@ -348,79 +350,80 @@ func create_window() -> void:
|
||||||
mc.add_child(self)
|
mc.add_child(self)
|
||||||
window.add_child(mc)
|
window.add_child(mc)
|
||||||
window.set_transient(false)
|
window.set_transient(false)
|
||||||
window.set_size(get_setting(PS_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
window.set_size(plugin.get_setting(ES_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
||||||
window.set_position(get_setting(PS_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
window.set_position(plugin.get_setting(ES_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
||||||
plugin.add_child(window)
|
plugin.add_child(window)
|
||||||
window.show()
|
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)
|
func clamp_window_position() -> void:
|
||||||
_godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
if window and window.visible:
|
||||||
_godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
|
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:
|
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():
|
if event is InputEventKey and event.keycode == KEY_S and event.pressed and event.is_command_or_control_pressed():
|
||||||
save_editor_settings()
|
save_editor_settings()
|
||||||
plugin.get_editor_interface().save_scene()
|
EditorInterface.save_scene()
|
||||||
|
|
||||||
|
|
||||||
func _on_window_focus_exited() -> void:
|
|
||||||
# Capture window position w/o other changes
|
|
||||||
save_editor_settings()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_godot_window_entered() -> void:
|
func _on_godot_window_entered() -> void:
|
||||||
if is_instance_valid(window) and window.has_focus():
|
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:
|
func _on_godot_focus_entered() -> void:
|
||||||
# If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window
|
# If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window
|
||||||
if is_instance_valid(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()
|
window.show()
|
||||||
_godot_last_state = _godot_editor_window.mode
|
_godot_last_state = plugin.godot_editor_window.mode
|
||||||
_godot_editor_window.grab_focus()
|
plugin.godot_editor_window.grab_focus()
|
||||||
|
|
||||||
|
|
||||||
func _on_godot_focus_exited() -> void:
|
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()
|
window.hide()
|
||||||
_godot_last_state = _godot_editor_window.mode
|
_godot_last_state = plugin.godot_editor_window.mode
|
||||||
|
|
||||||
|
|
||||||
## Manage Editor Settings
|
## 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:
|
func load_editor_settings() -> void:
|
||||||
floating_btn.button_pressed = get_setting(PS_DOCK_FLOATING, false)
|
floating_btn.button_pressed = plugin.get_setting(ES_DOCK_FLOATING, false)
|
||||||
pinned_btn.button_pressed = get_setting(PS_DOCK_PINNED, true)
|
pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true)
|
||||||
size_slider.value = get_setting(PS_DOCK_TILE_SIZE, 83)
|
size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 83)
|
||||||
set_slot(get_setting(PS_DOCK_SLOT, POS_BOTTOM))
|
|
||||||
_on_slider_changed(size_slider.value)
|
_on_slider_changed(size_slider.value)
|
||||||
# Window pos/size set on window creation in update_dock
|
set_slot(plugin.get_setting(ES_DOCK_SLOT, POS_BOTTOM))
|
||||||
update_dock(plugin.visible)
|
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:
|
func save_editor_settings() -> void:
|
||||||
if not _initialized:
|
if not _initialized:
|
||||||
return
|
return
|
||||||
editor_settings.set_setting(PS_DOCK_SLOT, slot)
|
clamp_window_position()
|
||||||
editor_settings.set_setting(PS_DOCK_TILE_SIZE, size_slider.value)
|
plugin.set_setting(ES_DOCK_SLOT, slot)
|
||||||
editor_settings.set_setting(PS_DOCK_FLOATING, floating_btn.button_pressed)
|
plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value)
|
||||||
editor_settings.set_setting(PS_DOCK_PINNED, pinned_btn.button_pressed)
|
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:
|
if window:
|
||||||
editor_settings.set_setting(PS_DOCK_WINDOW_SIZE, window.size)
|
plugin.set_setting(ES_DOCK_WINDOW_SIZE, window.size)
|
||||||
editor_settings.set_setting(PS_DOCK_WINDOW_POSITION, window.position)
|
plugin.set_setting(ES_DOCK_WINDOW_POSITION, window.position)
|
||||||
|
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
|
@ -527,7 +530,8 @@ class ListContainer extends Container:
|
||||||
plugin.select_terrain()
|
plugin.select_terrain()
|
||||||
|
|
||||||
# Select Paint tool if clicking a texture
|
# 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")
|
var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintBaseTexture")
|
||||||
if paint_btn:
|
if paint_btn:
|
||||||
paint_btn.set_pressed(true)
|
paint_btn.set_pressed(true)
|
||||||
|
@ -545,7 +549,7 @@ class ListContainer extends Container:
|
||||||
|
|
||||||
func _on_resource_inspected(p_resource: Resource) -> void:
|
func _on_resource_inspected(p_resource: Resource) -> void:
|
||||||
await get_tree().create_timer(.01).timeout
|
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:
|
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 removing an entry, clear inspector
|
||||||
if not p_resource:
|
if not p_resource:
|
||||||
plugin.get_editor_interface().inspect_object(null)
|
EditorInterface.inspect_object(null)
|
||||||
|
|
||||||
# If null resource, remove last
|
# If null resource, remove last
|
||||||
if not p_resource:
|
if not p_resource:
|
||||||
|
@ -694,7 +698,7 @@ class ListEntry extends VBoxContainer:
|
||||||
else:
|
else:
|
||||||
name_label.text = "Add Mesh"
|
name_label.text = "Add Mesh"
|
||||||
|
|
||||||
|
|
||||||
func _notification(p_what) -> void:
|
func _notification(p_what) -> void:
|
||||||
match p_what:
|
match p_what:
|
||||||
NOTIFICATION_DRAW:
|
NOTIFICATION_DRAW:
|
||||||
|
|
|
@ -5,37 +5,39 @@
|
||||||
[node name="bake_lod_dialog" type="ConfirmationDialog"]
|
[node name="bake_lod_dialog" type="ConfirmationDialog"]
|
||||||
title = "Bake Terrain3D Mesh"
|
title = "Bake Terrain3D Mesh"
|
||||||
position = Vector2i(0, 36)
|
position = Vector2i(0, 36)
|
||||||
size = Vector2i(400, 115)
|
size = Vector2i(400, 155)
|
||||||
visible = true
|
visible = true
|
||||||
script = ExtResource("1_sf76d")
|
script = ExtResource("1_sf76d")
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = 8.0
|
offset_left = 8.0
|
||||||
offset_top = 8.0
|
offset_top = 8.0
|
||||||
offset_right = -8.0
|
offset_right = 392.0
|
||||||
offset_bottom = -49.0
|
offset_bottom = 106.0
|
||||||
grow_horizontal = 2
|
theme_override_constants/margin_left = 10
|
||||||
grow_vertical = 2
|
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
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 20
|
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
|
layout_mode = 2
|
||||||
text = "LOD:"
|
text = "LOD:"
|
||||||
|
|
||||||
[node name="LodBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
[node name="LodBox" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
max_value = 8.0
|
max_value = 8.0
|
||||||
value = 4.0
|
value = 4.0
|
||||||
|
|
||||||
[node name="DescriptionLabel" type="Label" parent="VBoxContainer"]
|
[node name="DescriptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
|
|
@ -44,11 +44,14 @@ func bake_mesh_popup() -> void:
|
||||||
if plugin.terrain:
|
if plugin.terrain:
|
||||||
bake_method = _bake_mesh
|
bake_method = _bake_mesh
|
||||||
bake_lod_dialog.description = BAKE_MESH_DESCRIPTION
|
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:
|
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:
|
if !mesh:
|
||||||
push_error("Failed to bake mesh from Terrain3D")
|
push_error("Failed to bake mesh from Terrain3D")
|
||||||
return
|
return
|
||||||
|
@ -65,7 +68,7 @@ func _bake_mesh() -> void:
|
||||||
|
|
||||||
undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true)
|
undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true)
|
||||||
undo.add_undo_method(plugin.terrain, &"remove_child", mesh_instance)
|
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)
|
undo.add_do_reference(mesh_instance)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -86,11 +89,14 @@ func bake_occluder_popup() -> void:
|
||||||
if plugin.terrain:
|
if plugin.terrain:
|
||||||
bake_method = _bake_occluder
|
bake_method = _bake_occluder
|
||||||
bake_lod_dialog.description = BAKE_OCCLUDER_DESCRIPTION
|
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:
|
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:
|
if !mesh:
|
||||||
push_error("Failed to bake mesh from Terrain3D")
|
push_error("Failed to bake mesh from Terrain3D")
|
||||||
return
|
return
|
||||||
|
@ -113,7 +119,7 @@ func _bake_occluder() -> void:
|
||||||
|
|
||||||
undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true)
|
undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true)
|
||||||
undo.add_undo_method(plugin.terrain, &"remove_child", occluder_instance)
|
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)
|
undo.add_do_reference(occluder_instance)
|
||||||
|
|
||||||
else:
|
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]:
|
func find_terrain_nav_regions(p_terrain: Terrain3D) -> Array[NavigationRegion3D]:
|
||||||
var result: 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:
|
if not root:
|
||||||
return result
|
return result
|
||||||
for nav_region in root.find_children("", "NavigationRegion3D", true, true):
|
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.")
|
print("Terrain3DNavigation: Finished baking 1 NavigationMesh.")
|
||||||
|
|
||||||
elif plugin.terrain:
|
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
|
# 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
|
# 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
|
# 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:
|
if plugin.terrain:
|
||||||
bake_method = _set_up_navigation
|
bake_method = _set_up_navigation
|
||||||
confirm_dialog.dialog_text = SET_UP_NAVIGATION_DESCRIPTION
|
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:
|
func _set_up_navigation() -> void:
|
||||||
assert(plugin.terrain)
|
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 terrain: Terrain3D = plugin.terrain
|
||||||
|
|
||||||
var nav_region := NavigationRegion3D.new()
|
var nav_region := NavigationRegion3D.new()
|
||||||
|
@ -348,7 +363,7 @@ func _set_up_navigation() -> void:
|
||||||
undo_redo.add_do_reference(nav_region)
|
undo_redo.add_do_reference(nav_region)
|
||||||
undo_redo.commit_action()
|
undo_redo.commit_action()
|
||||||
|
|
||||||
plugin.get_editor_interface().inspect_object(nav_region)
|
EditorInterface.inspect_object(nav_region)
|
||||||
assert(plugin.nav_region == nav_region)
|
assert(plugin.nav_region == nav_region)
|
||||||
|
|
||||||
bake_nav_mesh()
|
bake_nav_mesh()
|
||||||
|
|
|
@ -1,21 +1,38 @@
|
||||||
extends Object
|
extends RefCounted
|
||||||
|
|
||||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/src/channel_packer.tscn"
|
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 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 {
|
enum {
|
||||||
IMAGE_ALBEDO,
|
INFO,
|
||||||
IMAGE_HEIGHT,
|
WARN,
|
||||||
IMAGE_NORMAL,
|
ERROR,
|
||||||
IMAGE_ROUGHNESS,
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
IMAGE_ALBEDO,
|
||||||
|
IMAGE_HEIGHT,
|
||||||
|
IMAGE_NORMAL,
|
||||||
|
IMAGE_ROUGHNESS
|
||||||
}
|
}
|
||||||
|
|
||||||
var plugin: EditorPlugin
|
var plugin: EditorPlugin
|
||||||
var editor_interface: EditorInterface
|
var window: Window
|
||||||
var dialog: AcceptDialog
|
var save_file_dialog: EditorFileDialog
|
||||||
var save_file_dialog: FileDialog
|
var open_file_dialog: EditorFileDialog
|
||||||
var open_file_dialog: FileDialog
|
|
||||||
var invert_green_checkbox: CheckBox
|
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_opened_directory: String
|
||||||
var last_saved_directory: String
|
var last_saved_directory: String
|
||||||
var packing_albedo: bool = false
|
var packing_albedo: bool = false
|
||||||
|
@ -24,133 +41,283 @@ var images: Array[Image] = [null, null, null, null]
|
||||||
var status_label: Label
|
var status_label: Label
|
||||||
var no_op: Callable = func(): pass
|
var no_op: Callable = func(): pass
|
||||||
var last_file_selected_fn: Callable = no_op
|
var last_file_selected_fn: Callable = no_op
|
||||||
|
var normal_vector: Vector3
|
||||||
|
|
||||||
|
|
||||||
func pack_textures_popup() -> void:
|
func pack_textures_popup() -> void:
|
||||||
if dialog != null:
|
if window != null:
|
||||||
print("Terrain3DChannelPacker: Cannot open pack tool, dialog already open.")
|
window.show()
|
||||||
|
window.move_to_foreground()
|
||||||
|
window.move_to_center()
|
||||||
return
|
return
|
||||||
|
window = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||||
dialog = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
window.close_requested.connect(_on_close_requested)
|
||||||
dialog.confirmed.connect(_on_close_requested)
|
window.window_input.connect(func(event:InputEvent):
|
||||||
dialog.canceled.connect(_on_close_requested)
|
if event is InputEventKey:
|
||||||
status_label = dialog.find_child("StatusLabel")
|
if event.pressed and event.keycode == KEY_ESCAPE:
|
||||||
invert_green_checkbox = dialog.find_child("InvertGreenChannelCheckBox")
|
_on_close_requested()
|
||||||
|
)
|
||||||
editor_interface = plugin.get_editor_interface()
|
window.find_child("CloseButton").pressed.connect(_on_close_requested)
|
||||||
_init_file_dialogs()
|
|
||||||
editor_interface.popup_dialog_centered(dialog)
|
status_label = window.find_child("StatusLabel") as Label
|
||||||
|
invert_green_checkbox = window.find_child("InvertGreenChannelCheckBox") as CheckBox
|
||||||
_init_texture_picker(dialog.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
invert_smooth_checkbox = window.find_child("InvertSmoothCheckBox") as CheckBox
|
||||||
_init_texture_picker(dialog.find_child("HeightVBox"), IMAGE_HEIGHT)
|
invert_height_checkbox = window.find_child("ConvertDepthToHeight") as CheckBox
|
||||||
_init_texture_picker(dialog.find_child("NormalVBox"), IMAGE_NORMAL)
|
lumin_height_button = window.find_child("LuminanceAsHeightButton") as Button
|
||||||
_init_texture_picker(dialog.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
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"
|
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:
|
func _on_close_requested() -> void:
|
||||||
last_file_selected_fn = no_op
|
last_file_selected_fn = no_op
|
||||||
images = [null, null, null, null]
|
images = [null, null, null, null]
|
||||||
dialog.queue_free()
|
window.queue_free()
|
||||||
dialog = null
|
window = null
|
||||||
|
|
||||||
|
|
||||||
func _init_file_dialogs() -> void:
|
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_filters(PackedStringArray(["*.png"]))
|
||||||
save_file_dialog.set_file_mode(FileDialog.FILE_MODE_SAVE_FILE)
|
save_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
|
||||||
save_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
save_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||||
save_file_dialog.file_selected.connect(_on_save_file_selected)
|
save_file_dialog.file_selected.connect(_on_save_file_selected)
|
||||||
|
save_file_dialog.ok_button_text = "Save"
|
||||||
open_file_dialog = FileDialog.new()
|
save_file_dialog.size = Vector2i(550, 550)
|
||||||
open_file_dialog.set_filters(PackedStringArray(["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", ".ktx"]))
|
#save_file_dialog.transient = false
|
||||||
open_file_dialog.set_file_mode(FileDialog.FILE_MODE_OPEN_FILE)
|
#save_file_dialog.exclusive = false
|
||||||
open_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
#save_file_dialog.popup_window = true
|
||||||
|
|
||||||
dialog.add_child(save_file_dialog)
|
open_file_dialog = EditorFileDialog.new()
|
||||||
dialog.add_child(open_file_dialog)
|
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:
|
func _init_texture_picker(p_parent: Node, p_image_index: int) -> void:
|
||||||
var line_edit: LineEdit = p_parent.find_child("LineEdit")
|
var line_edit: LineEdit = p_parent.find_child("LineEdit") as LineEdit
|
||||||
var file_pick_button: Button = p_parent.find_child("PickButton")
|
var file_pick_button: Button = p_parent.find_child("PickButton") as Button
|
||||||
var clear_button: Button = p_parent.find_child("ClearButton")
|
var clear_button: Button = p_parent.find_child("ClearButton") as Button
|
||||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect")
|
var texture_rect: TextureRect = p_parent.find_child("TextureRect") as TextureRect
|
||||||
var texture_button: Button = p_parent.find_child("TextureButton")
|
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:
|
var open_fn: Callable = func() -> void:
|
||||||
open_file_dialog.current_path = last_opened_directory
|
open_file_dialog.current_path = last_opened_directory
|
||||||
if last_file_selected_fn != no_op:
|
if last_file_selected_fn != no_op:
|
||||||
open_file_dialog.file_selected.disconnect(last_file_selected_fn)
|
open_file_dialog.file_selected.disconnect(last_file_selected_fn)
|
||||||
last_file_selected_fn = func(path: String) -> void:
|
last_file_selected_fn = func(path: String) -> void:
|
||||||
line_edit.text = path
|
line_edit.text = path
|
||||||
line_edit.caret_column = path.length()
|
load_image_fn.call(path)
|
||||||
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
|
|
||||||
open_file_dialog.file_selected.connect(last_file_selected_fn)
|
open_file_dialog.file_selected.connect(last_file_selected_fn)
|
||||||
open_file_dialog.popup_centered_ratio()
|
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:
|
var clear_fn: Callable = func() -> void:
|
||||||
line_edit.text = ""
|
line_edit.text = ""
|
||||||
texture_rect.texture = null
|
texture_rect.texture = null
|
||||||
images[p_image_index] = null
|
images[p_image_index] = null
|
||||||
|
_set_wh_labels(p_image_index, -1, -1)
|
||||||
# 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
|
|
||||||
|
|
||||||
line_edit.text_submitted.connect(line_edit_submit_fn)
|
line_edit.text_submitted.connect(line_edit_submit_fn)
|
||||||
file_pick_button.pressed.connect(open_fn)
|
file_pick_button.pressed.connect(open_fn)
|
||||||
texture_button.pressed.connect(open_fn)
|
texture_button.pressed.connect(open_fn)
|
||||||
clear_button.pressed.connect(clear_fn)
|
clear_button.pressed.connect(clear_fn)
|
||||||
_set_button_icon(file_pick_button, "Folder")
|
texture_button.dropped.connect(godot_drop_fn)
|
||||||
_set_button_icon(clear_button, "Remove")
|
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:
|
func _set_wh_labels(p_image_index: int, width: int, height: int) -> void:
|
||||||
var editor_base: Control = editor_interface.get_base_control()
|
var w: String = ""
|
||||||
var icon: Texture2D = editor_base.get_theme_icon(p_icon_name, "EditorIcons")
|
var h: String = ""
|
||||||
p_button.icon = icon
|
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:
|
func _show_message(p_level: int, p_text: String) -> void:
|
||||||
push_error("Terrain3DChannelPacker: " + p_text)
|
|
||||||
status_label.text = p_text
|
status_label.text = p_text
|
||||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
match p_level:
|
||||||
|
INFO:
|
||||||
|
print("Terrain3DChannelPacker: " + p_text)
|
||||||
func _show_success(p_text: String) -> void:
|
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||||
print("Terrain3DChannelPacker: " + p_text)
|
WARN:
|
||||||
status_label.text = p_text
|
push_warning("Terrain3DChannelPacker: " + p_text)
|
||||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
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:
|
func _create_import_file(png_path: String) -> void:
|
||||||
var dst_import_path: String = png_path + ".import"
|
var dst_import_path: String = png_path + ".import"
|
||||||
|
|
||||||
var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
|
var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
|
||||||
var template_content: String = file.get_as_text()
|
var template_content: String = file.get_as_text()
|
||||||
file.close()
|
file.close()
|
||||||
|
template_content = template_content.replace(
|
||||||
var import_content: String = template_content.replace("$SOURCE_FILE", png_path)
|
"$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 = FileAccess.open(dst_import_path, FileAccess.WRITE)
|
||||||
file.store_string(import_content)
|
file.store_string(import_content)
|
||||||
file.close()
|
file.close()
|
||||||
|
@ -159,11 +326,10 @@ func _create_import_file(png_path: String) -> void:
|
||||||
func _on_pack_button_pressed() -> void:
|
func _on_pack_button_pressed() -> void:
|
||||||
packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null
|
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
|
var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null
|
||||||
|
|
||||||
if not packing_albedo and not packing_normal_roughness:
|
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
|
return
|
||||||
|
|
||||||
if packing_albedo:
|
if packing_albedo:
|
||||||
save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
|
save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
|
||||||
save_file_dialog.title = "Save Packed Albedo/Height Texture"
|
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:
|
func _on_save_file_selected(p_dst_path) -> void:
|
||||||
last_saved_directory = p_dst_path.get_base_dir() + "/"
|
last_saved_directory = p_dst_path.get_base_dir() + "/"
|
||||||
|
var error: int
|
||||||
if packing_albedo:
|
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:
|
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:
|
if queue_pack_normal_roughness:
|
||||||
queue_pack_normal_roughness = false
|
queue_pack_normal_roughness = false
|
||||||
packing_albedo = false
|
packing_albedo = false
|
||||||
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
||||||
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
||||||
|
|
||||||
save_file_dialog.call_deferred("popup_centered_ratio")
|
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 and p_a_image:
|
||||||
if p_rgb_image.get_size() != p_a_image.get_size():
|
if p_rgb_image.get_size() != p_a_image.get_size() and !resize_toggle_checkbox.button_pressed:
|
||||||
_show_error("Textures must be the same size.")
|
_show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions")
|
||||||
return
|
return FAILED
|
||||||
|
|
||||||
var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image, p_invert_green)
|
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:
|
if not output_image:
|
||||||
_show_error("Failed to pack textures.")
|
_show_message(ERROR, "Failed to pack textures")
|
||||||
return
|
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)
|
output_image.save_png(p_dst_path)
|
||||||
editor_interface.get_resource_filesystem().scan_sources()
|
|
||||||
_create_import_file(p_dst_path)
|
_create_import_file(p_dst_path)
|
||||||
_show_success("Packed to " + p_dst_path + ".")
|
_show_message(INFO, "Packed to " + p_dst_path + ".")
|
||||||
|
return OK
|
||||||
else:
|
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"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
|
||||||
bg_color = Color(0.211765, 0.239216, 0.290196, 1)
|
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"]
|
[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"
|
title = "Terrain3D Channel Packer"
|
||||||
initial_position = 1
|
initial_position = 1
|
||||||
size = Vector2i(660, 900)
|
size = Vector2i(680, 835)
|
||||||
visible = true
|
unresizable = true
|
||||||
ok_button_text = "Close"
|
always_on_top = true
|
||||||
|
|
||||||
[node name="Panel" type="Panel" parent="."]
|
[node name="Panel" type="Panel" parent="."]
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 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_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
|
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
|
||||||
|
@ -54,10 +54,10 @@ layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = 4.0
|
offset_left = 5.0
|
||||||
offset_top = 4.0
|
offset_top = 5.0
|
||||||
offset_right = -1.0
|
offset_right = -5.0
|
||||||
offset_bottom = -53.0
|
offset_bottom = 5.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_constants/margin_left = 5
|
theme_override_constants/margin_left = 5
|
||||||
|
@ -67,12 +67,12 @@ theme_override_constants/margin_bottom = 5
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 0
|
|
||||||
theme_override_constants/separation = 10
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
[node name="AlbedoHeightPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
[node name="AlbedoHeightPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||||
custom_minimum_size = Vector2(0, 250)
|
custom_minimum_size = Vector2(0, 290)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
mouse_filter = 1
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
|
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
|
||||||
|
@ -147,6 +147,27 @@ grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
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"]
|
[node name="HeightVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
@ -204,8 +225,63 @@ grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
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"]
|
[node name="NormalRoughnessPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||||
custom_minimum_size = Vector2(0, 280)
|
custom_minimum_size = Vector2(0, 290)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||||
|
|
||||||
|
@ -281,9 +357,33 @@ grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
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
|
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"]
|
[node name="RoughnessVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
@ -342,18 +442,112 @@ grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
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
|
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"]
|
[node name="PackButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Pack textures as..."
|
text = "Pack textures as..."
|
||||||
|
|
||||||
[node name="StatusLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
[node name="StatusLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
||||||
custom_minimum_size = Vector2(0, 10)
|
custom_minimum_size = Vector2(0, 60)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
horizontal_alignment = 1
|
||||||
text = "Use this to create a packed Albedo + Height texture and/or a packed Normal + Roughness texture.
|
autowrap_mode = 3
|
||||||
|
|
||||||
You can then use these textures with Terrain3D."
|
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||||
autowrap_mode = 2
|
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]
|
[params]
|
||||||
|
|
||||||
compress/mode=2
|
compress/mode=2
|
||||||
compress/high_quality=false
|
compress/high_quality=$HIGH_QUALITY
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=2
|
compress/normal_map=2
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=$GENERATE_MIPMAPS
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
@ -29,4 +29,4 @@ process/normal_map_invert_y=false
|
||||||
process/hdr_as_srgb=false
|
process/hdr_as_srgb=false
|
||||||
process/hdr_clamp_exposure=false
|
process/hdr_clamp_exposure=false
|
||||||
process/size_limit=0
|
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
|
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 ICON_PICKER_CHECKED: String = "res://addons/terrain_3d/icons/picker_checked.svg"
|
||||||
const MAX_POINTS: int = 2
|
const MAX_POINTS: int = 2
|
||||||
|
|
||||||
|
@ -16,8 +15,8 @@ var points: PackedVector3Array
|
||||||
var picking_index: int = -1
|
var picking_index: int = -1
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _enter_tree() -> void:
|
||||||
icon_picker = load(ICON_PICKER)
|
icon_picker = get_theme_icon("ColorPick", "EditorIcons")
|
||||||
icon_picker_checked = load(ICON_PICKER_CHECKED)
|
icon_picker_checked = load(ICON_PICKER_CHECKED)
|
||||||
|
|
||||||
points.resize(MAX_POINTS)
|
points.resize(MAX_POINTS)
|
||||||
|
|
|
@ -32,7 +32,7 @@ func _redraw() -> void:
|
||||||
|
|
||||||
if show_rect:
|
if show_rect:
|
||||||
var modulate: Color = main_color if !use_secondary_color else secondary_color
|
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
|
modulate = Color.GRAY
|
||||||
draw_rect(Vector2(region_size,region_size)*.5 + rect_position, region_size, selection_material, modulate)
|
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(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:
|
func draw_rect(p_pos: Vector2, p_size: float, p_material: StandardMaterial3D, p_modulate: Color) -> void:
|
||||||
|
|
|
@ -1,68 +1,72 @@
|
||||||
extends HBoxContainer
|
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 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 plugin: EditorPlugin
|
||||||
var menu_button: MenuButton = MenuButton.new()
|
var menu_button: MenuButton = MenuButton.new()
|
||||||
var baker: Baker = Baker.new()
|
var directory_setup: DirectoryWizard = DirectoryWizard.new()
|
||||||
var packer: Packer = Packer.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 {
|
enum {
|
||||||
|
MENU_DIRECTORY_SETUP,
|
||||||
|
MENU_PACK_TEXTURES,
|
||||||
|
MENU_SEPARATOR,
|
||||||
MENU_BAKE_ARRAY_MESH,
|
MENU_BAKE_ARRAY_MESH,
|
||||||
MENU_BAKE_OCCLUDER,
|
MENU_BAKE_OCCLUDER,
|
||||||
MENU_BAKE_NAV_MESH,
|
MENU_SEPARATOR2,
|
||||||
MENU_SEPARATOR,
|
|
||||||
MENU_SET_UP_NAVIGATION,
|
MENU_SET_UP_NAVIGATION,
|
||||||
MENU_PACK_TEXTURES,
|
MENU_BAKE_NAV_MESH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
baker.plugin = plugin
|
directory_setup.plugin = plugin
|
||||||
packer.plugin = plugin
|
packer.plugin = plugin
|
||||||
|
baker.plugin = plugin
|
||||||
|
add_child(directory_setup)
|
||||||
add_child(baker)
|
add_child(baker)
|
||||||
|
|
||||||
menu_button.text = "Terrain3D Tools"
|
menu_button.text = "Terrain3D Tools"
|
||||||
menu_button.get_popup().add_item("Bake ArrayMesh", MENU_BAKE_ARRAY_MESH)
|
menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP)
|
||||||
menu_button.get_popup().add_item("Bake Occluder3D", MENU_BAKE_OCCLUDER)
|
menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES)
|
||||||
menu_button.get_popup().add_item("Bake NavMesh", MENU_BAKE_NAV_MESH)
|
|
||||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
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_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH)
|
||||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
menu_button.get_popup().add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER)
|
||||||
menu_button.get_popup().add_item("Pack Textures", MENU_PACK_TEXTURES)
|
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.get_popup().id_pressed.connect(_on_menu_pressed)
|
||||||
menu_button.about_to_popup.connect(_on_menu_about_to_popup)
|
menu_button.about_to_popup.connect(_on_menu_about_to_popup)
|
||||||
add_child(menu_button)
|
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:
|
func _on_menu_pressed(p_id: int) -> void:
|
||||||
match p_id:
|
match p_id:
|
||||||
|
MENU_DIRECTORY_SETUP:
|
||||||
|
directory_setup.directory_setup_popup()
|
||||||
|
MENU_PACK_TEXTURES:
|
||||||
|
packer.pack_textures_popup()
|
||||||
MENU_BAKE_ARRAY_MESH:
|
MENU_BAKE_ARRAY_MESH:
|
||||||
baker.bake_mesh_popup()
|
baker.bake_mesh_popup()
|
||||||
MENU_BAKE_OCCLUDER:
|
MENU_BAKE_OCCLUDER:
|
||||||
baker.bake_occluder_popup()
|
baker.bake_occluder_popup()
|
||||||
MENU_BAKE_NAV_MESH:
|
|
||||||
baker.bake_nav_mesh()
|
|
||||||
MENU_SET_UP_NAVIGATION:
|
MENU_SET_UP_NAVIGATION:
|
||||||
baker.set_up_navigation_popup()
|
baker.set_up_navigation_popup()
|
||||||
MENU_PACK_TEXTURES:
|
MENU_BAKE_NAV_MESH:
|
||||||
packer.pack_textures_popup()
|
baker.bake_nav_mesh()
|
||||||
|
|
||||||
|
|
||||||
func _on_menu_about_to_popup() -> void:
|
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_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_BAKE_OCCLUDER, not plugin.terrain)
|
||||||
menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
|
|
||||||
|
|
||||||
if plugin.terrain:
|
if plugin.terrain:
|
||||||
var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain)
|
var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain)
|
|
@ -17,13 +17,14 @@ enum SettingType {
|
||||||
PICKER,
|
PICKER,
|
||||||
MULTI_PICKER,
|
MULTI_PICKER,
|
||||||
SLIDER,
|
SLIDER,
|
||||||
|
LABEL,
|
||||||
TYPE_MAX,
|
TYPE_MAX,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
|
const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
|
||||||
const DEFAULT_BRUSH: String = "circle0.exr"
|
const DEFAULT_BRUSH: String = "circle0.exr"
|
||||||
const BRUSH_PATH: String = "res://addons/terrain_3d/brushes"
|
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
|
# Add settings flags
|
||||||
const NONE: int = 0x0
|
const NONE: int = 0x0
|
||||||
|
@ -31,13 +32,14 @@ const ALLOW_LARGER: int = 0x1
|
||||||
const ALLOW_SMALLER: int = 0x2
|
const ALLOW_SMALLER: int = 0x2
|
||||||
const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER
|
const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER
|
||||||
const NO_LABEL: int = 0x4
|
const NO_LABEL: int = 0x4
|
||||||
const ADD_SEPARATOR: int = 0x8
|
const ADD_SEPARATOR: int = 0x8 # Add a vertical line before this entry
|
||||||
const ADD_SPACER: int = 0x10
|
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 brush_preview_material: ShaderMaterial
|
||||||
var select_brush_button: Button
|
var select_brush_button: Button
|
||||||
|
var main_list: HFlowContainer
|
||||||
var main_list: HBoxContainer
|
|
||||||
var advanced_list: VBoxContainer
|
var advanced_list: VBoxContainer
|
||||||
var height_list: VBoxContainer
|
var height_list: VBoxContainer
|
||||||
var scale_list: VBoxContainer
|
var scale_list: VBoxContainer
|
||||||
|
@ -47,21 +49,26 @@ var settings: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
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)
|
add_child(main_list, true)
|
||||||
|
|
||||||
## Common Settings
|
|
||||||
add_brushes(main_list)
|
add_brushes(main_list)
|
||||||
|
|
||||||
add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":50, "unit":"m",
|
add_setting({ "name":"instructions", "label":"Click the terrain to add a region. CTRL+Click to remove. Or select another tool on the left.",
|
||||||
"range":Vector3(2, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
|
"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 })
|
"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":20,
|
||||||
|
|
||||||
add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":50,
|
|
||||||
"unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS })
|
"unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||||
add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list,
|
add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list,
|
||||||
"default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL })
|
"default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL })
|
||||||
|
@ -71,14 +78,21 @@ func _ready() -> void:
|
||||||
add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list,
|
add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list,
|
||||||
"default":Terrain3DEditor.COLOR, "flags":NO_LABEL })
|
"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 })
|
"unit":"%", "range":Vector3(-100, 100, 1), "flags":ADD_SEPARATOR })
|
||||||
add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list,
|
add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list,
|
||||||
"default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL })
|
"default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL })
|
||||||
|
|
||||||
add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
|
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,
|
add_setting({ "name":"enable_angle", "label":"Angle", "type":SettingType.CHECKBOX,
|
||||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||||
add_setting({ "name":"angle", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
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,
|
add_setting({ "name":"dynamic_angle", "label":"Dynamic", "type":SettingType.CHECKBOX,
|
||||||
"list":main_list, "default":false, "flags":ADD_SPACER })
|
"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 })
|
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||||
add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||||
"unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL })
|
"unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL })
|
||||||
add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list,
|
add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list,
|
||||||
"default":Terrain3DEditor.SCALE, "flags":NO_LABEL })
|
"default":Terrain3DEditor.SCALE, "flags":NO_LABEL })
|
||||||
|
|
||||||
## Slope
|
## Slope sculpting brush
|
||||||
add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list,
|
|
||||||
"default":0, "unit":"°", "range":Vector3(0, 180, 1) })
|
|
||||||
add_setting({ "name":"gradient_points", "type":SettingType.MULTI_PICKER, "label":"Points",
|
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,
|
add_setting({ "name":"drawable", "type":SettingType.CHECKBOX, "list":main_list, "default":false,
|
||||||
"flags":ADD_SEPARATOR })
|
"flags":ADD_SEPARATOR })
|
||||||
settings["drawable"].toggled.connect(_on_drawable_toggled)
|
settings["drawable"].toggled.connect(_on_drawable_toggled)
|
||||||
|
@ -123,9 +135,9 @@ func _ready() -> void:
|
||||||
"default":0, "unit":"°", "range":Vector3(0, 360, 1) })
|
"default":0, "unit":"°", "range":Vector3(0, 360, 1) })
|
||||||
add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360,
|
add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360,
|
||||||
"unit":"°", "range":Vector3(0, 360, 1) })
|
"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 })
|
"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 })
|
"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 })
|
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,
|
#add_setting({ "name":"blend_mode", "type":SettingType.OPTION, "list":color_list, "default":0,
|
||||||
#"range":Vector3(0, 3, 1) })
|
#"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()
|
var spacer: Control = Control.new()
|
||||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
main_list.add_child(spacer, true)
|
main_list.add_child(spacer, true)
|
||||||
|
|
||||||
## Advanced Settings Menu
|
## Advanced Settings Menu
|
||||||
advanced_list = create_submenu(main_list, "Advanced", Layout.VERTICAL)
|
advanced_list = create_submenu(main_list, "", Layout.VERTICAL, false)
|
||||||
add_setting({ "name":"automatic_regions", "type":SettingType.CHECKBOX, "list":advanced_list,
|
add_setting({ "name":"auto_regions", "label":"Add regions while sculpting", "type":SettingType.CHECKBOX,
|
||||||
"default":true })
|
"list":advanced_list, "default":true })
|
||||||
add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list,
|
add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||||
"default":true })
|
"default":true })
|
||||||
add_setting({ "name":"show_cursor_while_painting", "type":SettingType.CHECKBOX, "list":advanced_list,
|
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) })
|
"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()
|
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_toggle_mode(true)
|
||||||
menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||||
menu_button.toggled.connect(_on_show_submenu.bind(menu_button))
|
menu_button.toggled.connect(_on_show_submenu.bind(menu_button))
|
||||||
|
|
||||||
var submenu: PopupPanel = PopupPanel.new()
|
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()
|
var panel_style: StyleBox = get_theme_stylebox("panel", "PopupMenu").duplicate()
|
||||||
panel_style.set_content_margin_all(10)
|
panel_style.set_content_margin_all(10)
|
||||||
submenu.set("theme_override_styles/panel", panel_style)
|
submenu.set("theme_override_styles/panel", panel_style)
|
||||||
submenu.add_to_group("terrain3d_submenus")
|
submenu.add_to_group("terrain3d_submenus")
|
||||||
|
|
||||||
# Pop up menu on hover, hide on exit
|
# Pop up menu on hover, hide on exit
|
||||||
menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
|
if p_hover_pop:
|
||||||
submenu.mouse_exited.connect(_on_show_submenu.bind(false, menu_button))
|
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
|
var sublist: Container
|
||||||
match(p_layout):
|
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
|
# 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 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 in_button: bool = button_rect.has_point(DisplayServer.mouse_get_position())
|
||||||
var panel: PopupPanel = p_button.get_child(0)
|
var popup: PopupPanel = p_button.get_child(0)
|
||||||
var panel_rect: Rect2 = Rect2(panel.position, panel.size)
|
var popup_rect: Rect2 = Rect2(popup.position, popup.size)
|
||||||
var in_panel: bool = panel_rect.has_point(DisplayServer.mouse_get_position())
|
var in_popup: bool = popup_rect.has_point(DisplayServer.mouse_get_position())
|
||||||
if not p_toggled and ( in_button or in_panel ):
|
if not p_toggled and ( in_button or in_popup ):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Hide all submenus before possibly enabling the current one
|
# Hide all submenus before possibly enabling the current one
|
||||||
get_tree().call_group("terrain3d_submenus", "set_visible", false)
|
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.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)
|
popup.set_position(popup_pos)
|
||||||
|
|
||||||
|
|
||||||
func add_brushes(p_parent: Control) -> void:
|
func add_brushes(p_parent: Control) -> void:
|
||||||
var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID)
|
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"):
|
if !dir.current_is_dir() and file_name.ends_with(".exr"):
|
||||||
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
||||||
img = Terrain3DUtil.black_to_alpha(img)
|
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 tex: ImageTexture = ImageTexture.create_from_image(img)
|
||||||
|
|
||||||
var btn: Button = Button.new()
|
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()
|
select_brush_button = brush_list.get_parent().get_parent()
|
||||||
# Optionally erase the main brush button text and replace it with the texture
|
# 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_text("")
|
||||||
# select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
|
||||||
# select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
||||||
# select_brush_button.set_expand_icon(true)
|
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:
|
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:
|
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))
|
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:
|
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
|
var point: Vector3 = p_global_position
|
||||||
point.y = p_color.r
|
point.y = p_color.r
|
||||||
settings[p_name].add_point(point)
|
settings[p_name].add_point(point)
|
||||||
|
@ -342,9 +381,22 @@ func add_setting(p_args: Dictionary) -> void:
|
||||||
var pending_children: Array[Control]
|
var pending_children: Array[Control]
|
||||||
|
|
||||||
match p_type:
|
match p_type:
|
||||||
|
SettingType.LABEL:
|
||||||
|
var label := Label.new()
|
||||||
|
label.set_text(p_label)
|
||||||
|
pending_children.push_back(label)
|
||||||
|
control = label
|
||||||
|
|
||||||
SettingType.CHECKBOX:
|
SettingType.CHECKBOX:
|
||||||
var checkbox := CheckBox.new()
|
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)
|
checkbox.pressed.connect(_on_setting_changed)
|
||||||
pending_children.push_back(checkbox)
|
pending_children.push_back(checkbox)
|
||||||
control = checkbox
|
control = checkbox
|
||||||
|
@ -352,19 +404,24 @@ func add_setting(p_args: Dictionary) -> void:
|
||||||
SettingType.COLOR_SELECT:
|
SettingType.COLOR_SELECT:
|
||||||
var picker := ColorPickerButton.new()
|
var picker := ColorPickerButton.new()
|
||||||
picker.set_custom_minimum_size(Vector2(100, 25))
|
picker.set_custom_minimum_size(Vector2(100, 25))
|
||||||
picker.color = Color.WHITE
|
|
||||||
picker.edit_alpha = false
|
picker.edit_alpha = false
|
||||||
picker.get_picker().set_color_mode(ColorPicker.MODE_HSV)
|
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)
|
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)
|
pending_children.push_back(picker)
|
||||||
control = picker
|
control = picker
|
||||||
|
|
||||||
SettingType.PICKER:
|
SettingType.PICKER:
|
||||||
var button := Button.new()
|
var button := Button.new()
|
||||||
button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
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.tooltip_text = "Pick value from the Terrain"
|
||||||
button.pressed.connect(_on_pick.bind(p_default))
|
button.pressed.connect(_on_pick.bind(p_default))
|
||||||
pending_children.push_back(button)
|
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_max(p_maximum)
|
||||||
spin_slider.set_min(p_minimum)
|
spin_slider.set_min(p_minimum)
|
||||||
spin_slider.set_step(p_step)
|
spin_slider.set_step(p_step)
|
||||||
spin_slider.set_value(p_default)
|
|
||||||
spin_slider.set_suffix(p_suffix)
|
spin_slider.set_suffix(p_suffix)
|
||||||
spin_slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
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
|
# Create horizontal slider linked to the above box
|
||||||
slider = HSlider.new()
|
slider = HSlider.new()
|
||||||
|
@ -409,27 +465,38 @@ func add_setting(p_args: Dictionary) -> void:
|
||||||
slider.set_allow_greater(true)
|
slider.set_allow_greater(true)
|
||||||
if p_flags & ALLOW_SMALLER:
|
if p_flags & ALLOW_SMALLER:
|
||||||
slider.set_allow_lesser(true)
|
slider.set_allow_lesser(true)
|
||||||
|
|
||||||
pending_children.push_back(slider)
|
pending_children.push_back(slider)
|
||||||
pending_children.push_back(spin_slider)
|
pending_children.push_back(spin_slider)
|
||||||
control = spin_slider
|
control = spin_slider
|
||||||
|
|
||||||
else: # DOUBLE_SLIDER
|
else: # DOUBLE_SLIDER
|
||||||
var label := Label.new()
|
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 = DoubleSlider.new()
|
||||||
slider.label = label
|
slider.label = label
|
||||||
slider.suffix = p_suffix
|
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(slider)
|
||||||
pending_children.push_back(label)
|
pending_children.push_back(label)
|
||||||
control = slider
|
control = slider
|
||||||
|
|
||||||
slider.set_max(p_maximum)
|
|
||||||
slider.set_min(p_minimum)
|
slider.set_min(p_minimum)
|
||||||
|
slider.set_max(p_maximum)
|
||||||
slider.set_step(p_step)
|
slider.set_step(p_step)
|
||||||
slider.set_value(p_default)
|
slider.set_value(p_default)
|
||||||
slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
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()
|
control.name = p_name.to_pascal_case()
|
||||||
settings[p_name] = control
|
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)
|
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))
|
object.set_custom_minimum_size(Vector2(width, 0))
|
||||||
elif object is DoubleSlider:
|
elif object is DoubleSlider:
|
||||||
value = Vector2(object.get_min_value(), object.get_max_value())
|
value = object.get_value()
|
||||||
elif object is ButtonGroup:
|
elif object is ButtonGroup: # "brush"
|
||||||
var img: Image = object.get_pressed_button().get_meta("image")
|
var img: Image = object.get_pressed_button().get_meta("image")
|
||||||
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
||||||
value = [ img, tex ]
|
value = [ img, tex ]
|
||||||
|
@ -509,11 +576,10 @@ func get_setting(p_setting: String) -> Variant:
|
||||||
|
|
||||||
func set_setting(p_setting: String, p_value: Variant) -> void:
|
func set_setting(p_setting: String, p_value: Variant) -> void:
|
||||||
var object: Object = settings.get(p_setting)
|
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)
|
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 ]
|
elif object is ButtonGroup: # Expects p_value is Array [ "button name", boolean ]
|
||||||
if p_value is Array and p_value.size() == 2:
|
if p_value is Array and p_value.size() == 2:
|
||||||
for button in object.get_buttons():
|
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
|
object.button_pressed = p_value
|
||||||
elif object is ColorPickerButton:
|
elif object is ColorPickerButton:
|
||||||
object.color = p_value
|
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
|
elif object is MultiPicker: # Expects p_value is PackedVector3Array
|
||||||
object.points = p_value
|
object.points = p_value
|
||||||
_on_setting_changed(object)
|
_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 is Button and p_data.get_parent().get_parent() is PopupPanel:
|
||||||
if p_data.get_parent().name == "BrushList":
|
if p_data.get_parent().name == "BrushList":
|
||||||
# Optionally Set selected brush texture in main brush button
|
# 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
|
# Hide popup
|
||||||
p_data.get_parent().get_parent().set_visible(false)
|
p_data.get_parent().get_parent().set_visible(false)
|
||||||
# Hide label
|
# Hide label
|
||||||
if p_data.get_child_count() > 0:
|
if p_data.get_child_count() > 0:
|
||||||
p_data.get_child(0).visible = false
|
p_data.get_child(0).visible = false
|
||||||
|
|
||||||
emit_signal("setting_changed")
|
emit_signal("setting_changed")
|
||||||
|
|
||||||
|
|
||||||
|
@ -567,7 +633,6 @@ func _get_brush_preview_material() -> ShaderMaterial:
|
||||||
if !brush_preview_material:
|
if !brush_preview_material:
|
||||||
brush_preview_material = ShaderMaterial.new()
|
brush_preview_material = ShaderMaterial.new()
|
||||||
var shader: Shader = Shader.new()
|
var shader: Shader = Shader.new()
|
||||||
|
|
||||||
var code: String = "shader_type canvas_item;\n"
|
var code: String = "shader_type canvas_item;\n"
|
||||||
code += "varying vec4 v_vertex_color;\n"
|
code += "varying vec4 v_vertex_color;\n"
|
||||||
code += "void vertex() {\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.a *= pow(tex.r, 0.666);\n"
|
||||||
code += " COLOR.rgb = v_vertex_color.rgb;\n"
|
code += " COLOR.rgb = v_vertex_color.rgb;\n"
|
||||||
code += "}\n"
|
code += "}\n"
|
||||||
|
|
||||||
shader.set_code(code)
|
shader.set_code(code)
|
||||||
brush_preview_material.set_shader(shader)
|
brush_preview_material.set_shader(shader)
|
||||||
return brush_preview_material
|
return brush_preview_material
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Counts digits of a number including negative sign, decimal points, and up to 3 decimals
|
# Counts digits of a number including negative sign, decimal points, and up to 3 decimals
|
||||||
func count_digits(p_value: float) -> int:
|
func count_digits(p_value: float) -> int:
|
||||||
var count: int = 1
|
var count: int = 1
|
||||||
|
@ -605,75 +668,3 @@ func count_digits(p_value: float) -> int:
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
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)
|
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_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_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_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_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_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_SLOPE: String = "res://addons/terrain_3d/icons/height_slope.svg"
|
||||||
const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/height_smooth.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_NAVIGATION: String = "res://addons/terrain_3d/icons/navigation.svg"
|
||||||
const ICON_INSTANCER: String = "res://addons/terrain_3d/icons/multimesh.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:
|
func _init() -> void:
|
||||||
set_custom_minimum_size(Vector2(20, 0))
|
set_custom_minimum_size(Vector2(20, 0))
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
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_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({ "tool":Terrain3DEditor.SCULPT,
|
||||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.MULTIPLY, "Expand (Away from 0)", load(ICON_HEIGHT_MUL), tool_group)
|
"add_text":"Raise", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_ADD,
|
||||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.DIVIDE, "Reduce (Towards 0)", load(ICON_HEIGHT_DIV), tool_group)
|
"sub_text":"Lower", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_SUB })
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
buttons[0].set_pressed(true)
|
||||||
|
show_add_buttons(true)
|
||||||
|
|
||||||
|
|
||||||
func add_tool_button(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation,
|
func add_tool_button(p_params: Dictionary) -> void:
|
||||||
p_tip: String, p_icon: Texture2D, p_group: ButtonGroup) -> void:
|
# Additive button
|
||||||
|
var button := Button.new()
|
||||||
var button: Button = Button.new()
|
button.set_name(p_params.get("add_text", "blank").to_pascal_case())
|
||||||
button.set_name(p_tip.to_pascal_case())
|
button.set_meta("Tool", p_params.get("tool", 0))
|
||||||
button.set_meta("Tool", p_tool)
|
button.set_meta("Operation", p_params.get("add_op", 0))
|
||||||
button.set_meta("Operation", p_operation)
|
button.set_meta("ID", add_tool_group.get_buttons().size() + 1)
|
||||||
button.set_tooltip_text(p_tip)
|
button.set_tooltip_text(p_params.get("add_text", "blank"))
|
||||||
button.set_button_icon(p_icon)
|
button.set_button_icon(load(p_params.get("add_icon")))
|
||||||
button.set_button_group(p_group)
|
|
||||||
button.set_flat(true)
|
button.set_flat(true)
|
||||||
button.set_toggle_mode(true)
|
button.set_toggle_mode(true)
|
||||||
button.set_h_size_flags(SIZE_SHRINK_END)
|
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:
|
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
|
# Includes
|
||||||
const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
|
const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
|
||||||
const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.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 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 GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
|
||||||
const COLOR_RAISE := Color.WHITE
|
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_SMOOTH := Color(0.5, 0, .1)
|
||||||
const COLOR_EXPAND := Color.ORANGE
|
const COLOR_LIFT := Color.ORANGE
|
||||||
const COLOR_REDUCE := Color.BLUE_VIOLET
|
const COLOR_FLATTEN := Color.BLUE_VIOLET
|
||||||
const COLOR_FLATTEN := Color(0., 0.32, .4)
|
const COLOR_HEIGHT := Color(0., 0.32, .4)
|
||||||
const COLOR_SLOPE := Color.YELLOW
|
const COLOR_SLOPE := Color.YELLOW
|
||||||
const COLOR_PAINT := Color.FOREST_GREEN
|
const COLOR_PAINT := Color.DARK_GREEN
|
||||||
const COLOR_SPRAY := Color.SEA_GREEN
|
const COLOR_SPRAY := Color.PALE_GREEN
|
||||||
const COLOR_ROUGHNESS := Color.ROYAL_BLUE
|
const COLOR_ROUGHNESS := Color.ROYAL_BLUE
|
||||||
const COLOR_AUTOSHADER := Color.DODGER_BLUE
|
const COLOR_AUTOSHADER := Color.DODGER_BLUE
|
||||||
const COLOR_HOLES := Color.BLACK
|
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_INSTANCER := Color.CRIMSON
|
||||||
const COLOR_PICK_COLOR := Color.WHITE
|
const COLOR_PICK_COLOR := Color.WHITE
|
||||||
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
||||||
const COLOR_PICK_ROUGH := Color.ROYAL_BLUE
|
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"
|
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)))
|
@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 plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||||
var toolbar: Toolbar
|
var toolbar: Toolbar
|
||||||
var toolbar_settings: ToolSettings
|
var tool_settings: ToolSettings
|
||||||
var terrain_tools: TerrainTools
|
var terrain_menu: TerrainMenu
|
||||||
var setting_has_changed: bool = false
|
var setting_has_changed: bool = false
|
||||||
var visible: bool = false
|
var visible: bool = false
|
||||||
var picking: int = Terrain3DEditor.TOOL_MAX
|
var picking: int = Terrain3DEditor.TOOL_MAX
|
||||||
|
@ -42,6 +46,16 @@ var decal_timer: Timer
|
||||||
var gradient_decals: Array[Decal]
|
var gradient_decals: Array[Decal]
|
||||||
var brush_data: Dictionary
|
var brush_data: Dictionary
|
||||||
var operation_builder: OperationBuilder
|
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:
|
func _enter_tree() -> void:
|
||||||
|
@ -49,18 +63,19 @@ func _enter_tree() -> void:
|
||||||
toolbar.hide()
|
toolbar.hide()
|
||||||
toolbar.connect("tool_changed", _on_tool_changed)
|
toolbar.connect("tool_changed", _on_tool_changed)
|
||||||
|
|
||||||
toolbar_settings = ToolSettings.new()
|
tool_settings = ToolSettings.new()
|
||||||
toolbar_settings.connect("setting_changed", _on_setting_changed)
|
tool_settings.connect("setting_changed", _on_setting_changed)
|
||||||
toolbar_settings.connect("picking", _on_picking)
|
tool_settings.connect("picking", _on_picking)
|
||||||
toolbar_settings.hide()
|
tool_settings.plugin = plugin
|
||||||
|
tool_settings.hide()
|
||||||
|
|
||||||
terrain_tools = TerrainTools.new()
|
terrain_menu = TerrainMenu.new()
|
||||||
terrain_tools.plugin = plugin
|
terrain_menu.plugin = plugin
|
||||||
terrain_tools.hide()
|
terrain_menu.hide()
|
||||||
|
|
||||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
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_BOTTOM, tool_settings)
|
||||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_tools)
|
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_menu)
|
||||||
|
|
||||||
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
|
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
|
||||||
|
|
||||||
|
@ -77,10 +92,10 @@ func _enter_tree() -> void:
|
||||||
|
|
||||||
func _exit_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_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.queue_free()
|
||||||
toolbar_settings.queue_free()
|
tool_settings.queue_free()
|
||||||
terrain_tools.queue_free()
|
terrain_menu.queue_free()
|
||||||
decal.queue_free()
|
decal.queue_free()
|
||||||
decal_timer.queue_free()
|
decal_timer.queue_free()
|
||||||
for gradient_decal in gradient_decals:
|
for gradient_decal in gradient_decals:
|
||||||
|
@ -88,14 +103,27 @@ func _exit_tree() -> void:
|
||||||
gradient_decals.clear()
|
gradient_decals.clear()
|
||||||
|
|
||||||
|
|
||||||
func set_visible(p_visible: bool) -> void:
|
func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
|
||||||
visible = p_visible
|
terrain_menu.set_visible(p_visible)
|
||||||
terrain_tools.set_visible(p_visible)
|
|
||||||
toolbar.set_visible(p_visible)
|
|
||||||
toolbar_settings.set_visible(p_visible)
|
|
||||||
update_decal()
|
|
||||||
|
|
||||||
|
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:
|
func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
|
||||||
if p_list:
|
if p_list:
|
||||||
p_list.get_parent().get_parent().visible = p_visible
|
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:
|
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||||
clear_picking()
|
clear_picking()
|
||||||
set_menu_visibility(toolbar_settings.advanced_list, true)
|
set_menu_visibility(tool_settings.advanced_list, true)
|
||||||
set_menu_visibility(toolbar_settings.scale_list, false)
|
set_menu_visibility(tool_settings.scale_list, false)
|
||||||
set_menu_visibility(toolbar_settings.rotation_list, false)
|
set_menu_visibility(tool_settings.rotation_list, false)
|
||||||
set_menu_visibility(toolbar_settings.height_list, false)
|
set_menu_visibility(tool_settings.height_list, false)
|
||||||
set_menu_visibility(toolbar_settings.color_list, false)
|
set_menu_visibility(tool_settings.color_list, false)
|
||||||
|
|
||||||
# Select which settings to show. Options in tool_settings.gd:_ready
|
# Select which settings to show. Options in tool_settings.gd:_ready
|
||||||
var to_show: PackedStringArray = []
|
var to_show: PackedStringArray = []
|
||||||
|
|
||||||
match p_tool:
|
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:
|
Terrain3DEditor.HEIGHT:
|
||||||
to_show.push_back("brush")
|
to_show.push_back("brush")
|
||||||
to_show.push_back("size")
|
to_show.push_back("size")
|
||||||
to_show.push_back("strength")
|
to_show.push_back("strength")
|
||||||
if p_operation == Terrain3DEditor.REPLACE:
|
to_show.push_back("height")
|
||||||
to_show.push_back("height")
|
to_show.push_back("height_picker")
|
||||||
to_show.push_back("height_picker")
|
|
||||||
if p_operation == Terrain3DEditor.GRADIENT:
|
Terrain3DEditor.TEXTURE:
|
||||||
to_show.push_back("gradient_points")
|
|
||||||
to_show.push_back("drawable")
|
|
||||||
|
|
||||||
Terrain3DEditor.TEXTURE:
|
|
||||||
to_show.push_back("brush")
|
to_show.push_back("brush")
|
||||||
to_show.push_back("size")
|
to_show.push_back("size")
|
||||||
to_show.push_back("enable_texture")
|
to_show.push_back("enable_texture")
|
||||||
if p_operation == Terrain3DEditor.ADD:
|
if p_operation == Terrain3DEditor.ADD:
|
||||||
to_show.push_back("strength")
|
to_show.push_back("strength")
|
||||||
|
to_show.push_back("slope")
|
||||||
to_show.push_back("enable_angle")
|
to_show.push_back("enable_angle")
|
||||||
to_show.push_back("angle")
|
to_show.push_back("angle")
|
||||||
to_show.push_back("angle_picker")
|
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("strength")
|
||||||
to_show.push_back("color")
|
to_show.push_back("color")
|
||||||
to_show.push_back("color_picker")
|
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:
|
Terrain3DEditor.ROUGHNESS:
|
||||||
to_show.push_back("brush")
|
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("strength")
|
||||||
to_show.push_back("roughness")
|
to_show.push_back("roughness")
|
||||||
to_show.push_back("roughness_picker")
|
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:
|
Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION:
|
||||||
to_show.push_back("brush")
|
to_show.push_back("brush")
|
||||||
to_show.push_back("size")
|
to_show.push_back("size")
|
||||||
to_show.push_back("enable")
|
to_show.push_back("remove")
|
||||||
|
|
||||||
Terrain3DEditor.INSTANCER:
|
Terrain3DEditor.INSTANCER:
|
||||||
to_show.push_back("size")
|
to_show.push_back("size")
|
||||||
to_show.push_back("strength")
|
to_show.push_back("strength")
|
||||||
to_show.push_back("enable")
|
to_show.push_back("slope")
|
||||||
set_menu_visibility(toolbar_settings.height_list, true)
|
set_menu_visibility(tool_settings.height_list, true)
|
||||||
to_show.push_back("height_offset")
|
to_show.push_back("height_offset")
|
||||||
to_show.push_back("random_height")
|
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("fixed_scale")
|
||||||
to_show.push_back("random_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("fixed_spin")
|
||||||
to_show.push_back("random_spin")
|
to_show.push_back("random_spin")
|
||||||
to_show.push_back("fixed_angle")
|
to_show.push_back("fixed_tilt")
|
||||||
to_show.push_back("random_angle")
|
to_show.push_back("random_tilt")
|
||||||
to_show.push_back("align_to_normal")
|
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("vertex_color")
|
||||||
to_show.push_back("random_darken")
|
to_show.push_back("random_darken")
|
||||||
to_show.push_back("random_hue")
|
to_show.push_back("random_hue")
|
||||||
|
to_show.push_back("remove")
|
||||||
|
|
||||||
_:
|
_:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Advanced menu settings
|
# 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("align_to_view")
|
||||||
to_show.push_back("show_cursor_while_painting")
|
to_show.push_back("show_cursor_while_painting")
|
||||||
to_show.push_back("gamma")
|
to_show.push_back("gamma")
|
||||||
to_show.push_back("jitter")
|
to_show.push_back("jitter")
|
||||||
toolbar_settings.show_settings(to_show)
|
tool_settings.show_settings(to_show)
|
||||||
|
|
||||||
operation_builder = null
|
operation_builder = null
|
||||||
if p_operation == Terrain3DEditor.GRADIENT:
|
if p_operation == Terrain3DEditor.GRADIENT:
|
||||||
operation_builder = GradientOperationBuilder.new()
|
operation_builder = GradientOperationBuilder.new()
|
||||||
operation_builder.tool_settings = toolbar_settings
|
operation_builder.tool_settings = tool_settings
|
||||||
|
|
||||||
if plugin.editor:
|
if plugin.editor:
|
||||||
plugin.editor.set_tool(p_tool)
|
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()
|
_on_setting_changed()
|
||||||
plugin.update_region_grid()
|
plugin.update_region_grid()
|
||||||
|
@ -205,31 +256,67 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
||||||
func _on_setting_changed() -> void:
|
func _on_setting_changed() -> void:
|
||||||
if not plugin.asset_dock:
|
if not plugin.asset_dock:
|
||||||
return
|
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()
|
brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
|
||||||
update_decal()
|
update_decal()
|
||||||
plugin.editor.set_brush_data(brush_data)
|
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:
|
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 \
|
if not visible or \
|
||||||
not plugin.terrain or \
|
not plugin.terrain or \
|
||||||
brush_data.is_empty() or \
|
plugin._input_mode < 0 or \
|
||||||
mouse_buttons & MOUSE_BUTTON_RIGHT or \
|
# Wait for cursor to recenter after moving camera before revealing
|
||||||
(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
|
|
||||||
# See https://github.com/godotengine/godot/issues/70098
|
# See https://github.com/godotengine/godot/issues/70098
|
||||||
await get_tree().create_timer(.05).timeout
|
Time.get_ticks_msec() - last_rmb_time <= 30 or \
|
||||||
decal.visible = true
|
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"]:
|
if brush_data["align_to_view"]:
|
||||||
var cam: Camera3D = plugin.terrain.get_camera();
|
var cam: Camera3D = plugin.terrain.get_camera();
|
||||||
if (cam):
|
if (cam):
|
||||||
|
@ -240,7 +327,7 @@ func update_decal() -> void:
|
||||||
# Set texture and color
|
# Set texture and color
|
||||||
if picking != Terrain3DEditor.TOOL_MAX:
|
if picking != Terrain3DEditor.TOOL_MAX:
|
||||||
decal.texture_albedo = ring_texture
|
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:
|
match picking:
|
||||||
Terrain3DEditor.HEIGHT:
|
Terrain3DEditor.HEIGHT:
|
||||||
decal.modulate = COLOR_PICK_HEIGHT
|
decal.modulate = COLOR_PICK_HEIGHT
|
||||||
|
@ -252,62 +339,66 @@ func update_decal() -> void:
|
||||||
else:
|
else:
|
||||||
decal.texture_albedo = brush_data["brush"][1]
|
decal.texture_albedo = brush_data["brush"][1]
|
||||||
match plugin.editor.get_tool():
|
match plugin.editor.get_tool():
|
||||||
Terrain3DEditor.HEIGHT:
|
Terrain3DEditor.SCULPT:
|
||||||
match plugin.editor.get_operation():
|
match plugin.editor.get_operation():
|
||||||
Terrain3DEditor.ADD:
|
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:
|
Terrain3DEditor.SUBTRACT:
|
||||||
decal.modulate = COLOR_LOWER
|
if plugin.modifier_alt:
|
||||||
Terrain3DEditor.MULTIPLY:
|
decal.modulate = COLOR_FLATTEN
|
||||||
decal.modulate = COLOR_EXPAND
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||||
Terrain3DEditor.DIVIDE:
|
else:
|
||||||
decal.modulate = COLOR_REDUCE
|
decal.modulate = COLOR_LOWER
|
||||||
Terrain3DEditor.REPLACE:
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .5
|
||||||
decal.modulate = COLOR_FLATTEN
|
|
||||||
Terrain3DEditor.AVERAGE:
|
Terrain3DEditor.AVERAGE:
|
||||||
decal.modulate = COLOR_SMOOTH
|
decal.modulate = COLOR_SMOOTH
|
||||||
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .2
|
||||||
Terrain3DEditor.GRADIENT:
|
Terrain3DEditor.GRADIENT:
|
||||||
decal.modulate = COLOR_SLOPE
|
decal.modulate = COLOR_SLOPE
|
||||||
_:
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||||
decal.modulate = Color.WHITE
|
Terrain3DEditor.HEIGHT:
|
||||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
decal.modulate = COLOR_HEIGHT
|
||||||
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||||
Terrain3DEditor.TEXTURE:
|
Terrain3DEditor.TEXTURE:
|
||||||
match plugin.editor.get_operation():
|
match plugin.editor.get_operation():
|
||||||
Terrain3DEditor.REPLACE:
|
Terrain3DEditor.REPLACE:
|
||||||
decal.modulate = COLOR_PAINT
|
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:
|
Terrain3DEditor.ADD:
|
||||||
decal.modulate = COLOR_SPRAY
|
decal.modulate = COLOR_SPRAY
|
||||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||||
_:
|
|
||||||
decal.modulate = Color.WHITE
|
|
||||||
Terrain3DEditor.COLOR:
|
Terrain3DEditor.COLOR:
|
||||||
decal.modulate = brush_data["color"].srgb_to_linear()*.5
|
decal.modulate = brush_data["color"].srgb_to_linear()
|
||||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
decal.modulate.a *= clamp(brush_data["strength"], .2, .5)
|
||||||
Terrain3DEditor.ROUGHNESS:
|
Terrain3DEditor.ROUGHNESS:
|
||||||
decal.modulate = COLOR_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:
|
Terrain3DEditor.AUTOSHADER:
|
||||||
decal.modulate = COLOR_AUTOSHADER
|
decal.modulate = COLOR_AUTOSHADER
|
||||||
decal.modulate.a = 1.0
|
decal.modulate.a = .7
|
||||||
Terrain3DEditor.HOLES:
|
Terrain3DEditor.HOLES:
|
||||||
decal.modulate = COLOR_HOLES
|
decal.modulate = COLOR_HOLES
|
||||||
decal.modulate.a = 1.0
|
decal.modulate.a = .85
|
||||||
Terrain3DEditor.NAVIGATION:
|
Terrain3DEditor.NAVIGATION:
|
||||||
decal.modulate = COLOR_NAVIGATION
|
decal.modulate = COLOR_NAVIGATION
|
||||||
decal.modulate.a = 1.0
|
decal.modulate.a = .85
|
||||||
Terrain3DEditor.INSTANCER:
|
Terrain3DEditor.INSTANCER:
|
||||||
decal.texture_albedo = ring_texture
|
decal.texture_albedo = ring_texture
|
||||||
decal.modulate = COLOR_INSTANCER
|
decal.modulate = COLOR_INSTANCER
|
||||||
decal.modulate.a = 1.0
|
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.size.y = max(1000, decal.size.y)
|
||||||
decal.albedo_mix = 1.0
|
decal.albedo_mix = 1.0
|
||||||
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
||||||
decal_timer.start()
|
decal_timer.start()
|
||||||
|
|
||||||
for gradient_decal in gradient_decals:
|
for gradient_decal in gradient_decals:
|
||||||
gradient_decal.visible = false
|
gradient_decal.visible = false
|
||||||
|
|
||||||
|
@ -320,6 +411,8 @@ func update_decal() -> void:
|
||||||
point_decal.position = point
|
point_decal.position = point
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
|
update_compatibility_decal()
|
||||||
|
|
||||||
|
|
||||||
func _get_gradient_decal(index: int) -> Decal:
|
func _get_gradient_decal(index: int) -> Decal:
|
||||||
if gradient_decals.size() > index:
|
if gradient_decals.size() > index:
|
||||||
|
@ -329,7 +422,7 @@ func _get_gradient_decal(index: int) -> Decal:
|
||||||
gradient_decal = Decal.new()
|
gradient_decal = Decal.new()
|
||||||
gradient_decal.texture_albedo = ring_texture
|
gradient_decal.texture_albedo = ring_texture
|
||||||
gradient_decal.modulate = COLOR_SLOPE
|
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.size.y = 1000.
|
||||||
gradient_decal.cull_mask = decal.cull_mask
|
gradient_decal.cull_mask = decal.cull_mask
|
||||||
add_child(gradient_decal)
|
add_child(gradient_decal)
|
||||||
|
@ -338,6 +431,61 @@ func _get_gradient_decal(index: int) -> Decal:
|
||||||
return gradient_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:
|
func set_decal_rotation(p_rot: float) -> void:
|
||||||
decal.rotation.y = p_rot
|
decal.rotation.y = p_rot
|
||||||
|
|
||||||
|
@ -366,16 +514,16 @@ func pick(p_global_position: Vector3) -> void:
|
||||||
if picking != Terrain3DEditor.TOOL_MAX:
|
if picking != Terrain3DEditor.TOOL_MAX:
|
||||||
var color: Color
|
var color: Color
|
||||||
match picking:
|
match picking:
|
||||||
Terrain3DEditor.HEIGHT:
|
Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT:
|
||||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_HEIGHT, p_global_position)
|
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_HEIGHT, p_global_position)
|
||||||
Terrain3DEditor.ROUGHNESS:
|
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:
|
Terrain3DEditor.COLOR:
|
||||||
color = plugin.terrain.get_storage().get_color(p_global_position)
|
color = plugin.terrain.data.get_color(p_global_position)
|
||||||
Terrain3DEditor.ANGLE:
|
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:
|
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)
|
push_error("Unsupported picking type: ", picking)
|
||||||
return
|
return
|
||||||
|
@ -385,3 +533,6 @@ func pick(p_global_position: Vector3) -> void:
|
||||||
elif operation_builder and operation_builder.is_picking():
|
elif operation_builder and operation_builder.is_picking():
|
||||||
operation_builder.pick(p_global_position, plugin.terrain)
|
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 = ""
|
height_file_name = ""
|
||||||
control_file_name = ""
|
control_file_name = ""
|
||||||
color_file_name = ""
|
color_file_name = ""
|
||||||
import_position = Vector3.ZERO
|
destination_directory = ""
|
||||||
import_offset = 0.0
|
import_position = Vector2i.ZERO
|
||||||
|
height_offset = 0.0
|
||||||
import_scale = 1.0
|
import_scale = 1.0
|
||||||
r16_range = Vector2(0, 1)
|
r16_range = Vector2(0, 1)
|
||||||
r16_size = Vector2i(1024, 1024)
|
r16_size = Vector2i(1024, 1024)
|
||||||
storage = null
|
|
||||||
material = null
|
material = null
|
||||||
assets = null
|
assets = null
|
||||||
|
|
||||||
|
|
||||||
func reset_terrain(p_value) -> void:
|
func reset_terrain(p_value) -> void:
|
||||||
if p_value:
|
data_directory = ""
|
||||||
storage = null
|
|
||||||
|
|
||||||
|
|
||||||
func update_heights(p_value) -> void:
|
func update_heights(p_value) -> void:
|
||||||
if p_value and storage:
|
if p_value and data:
|
||||||
storage.update_height_range()
|
data.update_height_range()
|
||||||
|
|
||||||
|
|
||||||
@export_group("Import File")
|
@export_group("Import File")
|
||||||
@export_global_file var height_file_name: String = ""
|
@export_global_file var height_file_name: String = ""
|
||||||
@export_global_file var control_file_name: String = ""
|
@export_global_file var control_file_name: String = ""
|
||||||
@export_global_file var color_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_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_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 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:
|
func start_import(p_value: bool) -> void:
|
||||||
if p_value:
|
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])
|
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]
|
var imported_images: Array[Image]
|
||||||
imported_images.resize(Terrain3DStorage.TYPE_MAX)
|
imported_images.resize(Terrain3DRegion.TYPE_MAX)
|
||||||
var min_max := Vector2(0, 1)
|
var min_max := Vector2(0, 1)
|
||||||
var img: Image
|
var img: Image
|
||||||
if height_file_name:
|
if height_file_name:
|
||||||
img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size)
|
img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size)
|
||||||
min_max = Terrain3DUtil.get_min_max(img)
|
min_max = Terrain3DUtil.get_min_max(img)
|
||||||
imported_images[Terrain3DStorage.TYPE_HEIGHT] = img
|
imported_images[Terrain3DRegion.TYPE_HEIGHT] = img
|
||||||
if control_file_name:
|
if control_file_name:
|
||||||
img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
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:
|
if color_file_name:
|
||||||
img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
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:
|
if assets.get_texture_count() == 0:
|
||||||
material.show_checkered = false
|
material.show_checkered = false
|
||||||
material.show_colormap = true
|
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")
|
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")
|
@export_group("Export File")
|
||||||
enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
||||||
@export_enum("Height:0", "Control:1", "Color:2") var map_type: int = TYPE_HEIGHT
|
@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
|
@export var run_export: bool = false : set = start_export
|
||||||
|
|
||||||
func start_export(p_value: bool) -> void:
|
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))
|
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"]
|
[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
|
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"]
|
[node name="Importer" type="Terrain3D"]
|
||||||
storage = SubResource("Terrain3DStorage_rmuvl")
|
material = SubResource("Terrain3DMaterial_p55u0")
|
||||||
material = SubResource("Terrain3DMaterial_cjpaa")
|
assets = SubResource("Terrain3DAssets_op32e")
|
||||||
assets = SubResource("Terrain3DAssets_gbxcd")
|
mesh_lods = 8
|
||||||
|
top_level = true
|
||||||
script = ExtResource("1_60b8f")
|
script = ExtResource("1_60b8f")
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
|
@ -42,22 +42,22 @@ func editor_setup(p_plugin) -> void:
|
||||||
func get_terrain() -> Terrain3D:
|
func get_terrain() -> Terrain3D:
|
||||||
var terrain := instance_from_id(_terrain_id) as 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():
|
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:
|
if terrains.size() > 0:
|
||||||
terrain = terrains[0]
|
terrain = terrains[0]
|
||||||
_terrain_id = terrain.get_instance_id() if terrain else 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):
|
if terrain and terrain.data and not terrain.data.maps_edited.is_connected(_on_maps_edited):
|
||||||
terrain.storage.maps_edited.connect(_on_maps_edited)
|
terrain.data.maps_edited.connect(_on_maps_edited)
|
||||||
|
|
||||||
return terrain
|
return terrain
|
||||||
|
|
||||||
|
|
||||||
func _get_terrain_height(p_global_position: Vector3) -> float:
|
func _get_terrain_height(p_global_position: Vector3) -> float:
|
||||||
var terrain: Terrain3D = get_terrain()
|
var terrain: Terrain3D = get_terrain()
|
||||||
if not terrain or not terrain.storage:
|
if not terrain or not terrain.data:
|
||||||
return 0.0
|
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):
|
if is_nan(height):
|
||||||
return 0.0
|
return 0.0
|
||||||
return height
|
return height
|
||||||
|
@ -105,7 +105,7 @@ func _on_child_exiting_tree(p_node: Node) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _is_node_selected(p_node: Node) -> bool:
|
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)
|
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_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_albedo.dds" id="4_p0awb"]
|
||||||
[ext_resource type="Texture2D" path="res://assets/textures/grass_rough/grass_rough_normal.dds" id="4_yhjhv"]
|
[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,
|
"noise3_scale": 0.021,
|
||||||
"noise_texture": SubResource("NoiseTexture2D_sb318"),
|
"noise_texture": SubResource("NoiseTexture2D_sb318"),
|
||||||
"vertex_normals_distance": 128.0,
|
"vertex_normals_distance": 128.0,
|
||||||
"world_noise_blend_far": 1.0,
|
|
||||||
"world_noise_blend_near": 0.48,
|
|
||||||
"world_noise_height": 74.0,
|
"world_noise_height": 74.0,
|
||||||
"world_noise_lod_distance": 16384.0,
|
"world_noise_lod_distance": 16384.0,
|
||||||
"world_noise_max_octaves": 8,
|
"world_noise_max_octaves": 8,
|
||||||
"world_noise_min_octaves": 2,
|
"world_noise_min_octaves": 2,
|
||||||
"world_noise_offset": Vector3(0, -8, 0),
|
"world_noise_offset": Vector3(0, -8, 0),
|
||||||
|
"world_noise_region_blend": 0.33,
|
||||||
"world_noise_scale": 5.0
|
"world_noise_scale": 5.0
|
||||||
}
|
}
|
||||||
world_background = 2
|
world_background = 2
|
||||||
texture_filtering = 1
|
texture_filtering = 1
|
||||||
auto_shader = true
|
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
|
cull_mode = 2
|
||||||
vertex_color_use_as_albedo = true
|
vertex_color_use_as_albedo = true
|
||||||
backlight_enabled = true
|
backlight_enabled = true
|
||||||
backlight = Color(0.5, 0.5, 0.5, 1)
|
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"]
|
[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_iaq2t"]
|
||||||
name = "palm_tree"
|
|
||||||
height_offset = 0.5
|
height_offset = 0.5
|
||||||
density = 10.0
|
density = 10.0
|
||||||
material_override = SubResource("StandardMaterial3D_2u1w6")
|
material_override = SubResource("StandardMaterial3D_l1sla")
|
||||||
generated_type = 1
|
generated_type = 1
|
||||||
|
|
||||||
[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_kocfk"]
|
[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_kocfk"]
|
||||||
|
@ -105,7 +107,7 @@ normal_texture = ExtResource("9_rrguh")
|
||||||
uv_scale = 0.02
|
uv_scale = 0.02
|
||||||
|
|
||||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_eq5uw"]
|
[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")])
|
texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_kocfk"), SubResource("Terrain3DTextureAsset_vmhlw"), SubResource("Terrain3DTextureAsset_k6h8c"), SubResource("Terrain3DTextureAsset_13d2a")])
|
||||||
|
|
||||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_h8tes"]
|
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_h8tes"]
|
||||||
|
@ -252,9 +254,11 @@ point_count = 5
|
||||||
[node name="TestLevel" type="Node3D"]
|
[node name="TestLevel" type="Node3D"]
|
||||||
|
|
||||||
[node name="Terrain3D" type="Terrain3D" parent="."]
|
[node name="Terrain3D" type="Terrain3D" parent="."]
|
||||||
storage = ExtResource("1_nlsu2")
|
data_directory = "res://levels/debug_level/terrain_3d_data"
|
||||||
material = SubResource("Terrain3DMaterial_woy2k")
|
material = SubResource("Terrain3DMaterial_woy2k")
|
||||||
assets = SubResource("Terrain3DAssets_eq5uw")
|
assets = SubResource("Terrain3DAssets_eq5uw")
|
||||||
|
show_grid = true
|
||||||
|
metadata/_edit_lock_ = true
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||||
environment = SubResource("Environment_k6wwx")
|
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