mirror of
https://gitlab.com/open-fpsz/open-fpsz.git
synced 2026-01-19 11:34:45 +00:00
✨ jetpack stuttering, physics updates, deps upgrades
This commit is contained in:
parent
8be7043b83
commit
c961300822
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.
|
|
@ -17,9 +17,9 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -17,9 +17,9 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -17,9 +17,9 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
+hmuH+erxzRxY/FPATmWbEaqOys=
|
||||
et0C7sxAlu4eIDcq2ihFQ2BhDSk=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
WsqyDktXR1oDgMLbvIUu+PMJsJAnbBKIUYnKgafFEGc=
|
||||
ZnG0hD4DciikOVWrf1Ai1Qedz9hESuIFvUujZAebHRY=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -17,9 +17,9 @@
|
|||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright (c) Mikael Hermansson and Godot Jolt contributors.</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.13.0</string>
|
||||
<string>0.14.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
GsvlA3T0mwbtJS37DcxlHyr4Vro=
|
||||
oIAzxlQz4Hun6JnLVOu9jafYxGE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<dict>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
4Rs/lwMrLlMFf5H+0QtaW/gUNP59U2nHlU4LBmarG1Q=
|
||||
FA6I/u5+Ww0DzXAvawYXs792eum+8Bim8uHBbg98jqY=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
Copyright (c) 2024 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<img src="doc/docs/images/terrain3d.png">
|
||||

|
||||
|
||||
# Terrain3D
|
||||
A high performance, editable terrain system for Godot 4.
|
||||
|
|
@ -7,7 +7,7 @@ A high performance, editable terrain system for Godot 4.
|
|||
* Written in C++ as a GDExtension addon, which works with official engine builds
|
||||
* Can be accessed by GDScript, C#, and any language Godot supports
|
||||
* Geometric Clipmap Mesh Terrain, as used in The Witcher 3. See [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html)
|
||||
* Up to 16k x 16k in 1k regions (imagine multiple islands without paying for 16k^2 vram)
|
||||
* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in variable sized regions
|
||||
* Up to 32 textures
|
||||
* Up to 10 levels of detail
|
||||
* Foliage instancing
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -7,29 +7,36 @@ extends EditorPlugin
|
|||
const UI: Script = preload("res://addons/terrain_3d/src/ui.gd")
|
||||
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
|
||||
const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
|
||||
const PS_DOCK_POSITION: String = "terrain3d/config/dock_position"
|
||||
const PS_DOCK_PINNED: String = "terrain3d/config/dock_pinned"
|
||||
|
||||
var modifier_ctrl: bool
|
||||
var modifier_alt: bool
|
||||
var modifier_shift: bool
|
||||
var _last_modifiers: int = 0
|
||||
var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
|
||||
|
||||
var terrain: Terrain3D
|
||||
var _last_terrain: Terrain3D
|
||||
var nav_region: NavigationRegion3D
|
||||
|
||||
var editor: Terrain3DEditor
|
||||
var editor_settings: EditorSettings
|
||||
var ui: Node # Terrain3DUI see Godot #75388
|
||||
var asset_dock: PanelContainer
|
||||
var region_gizmo: RegionGizmo
|
||||
var visible: bool
|
||||
var current_region_position: Vector2
|
||||
var mouse_global_position: Vector3 = Vector3.ZERO
|
||||
|
||||
# Track negative input (CTRL)
|
||||
var _negative_input: bool = false
|
||||
# Track state prior to pressing CTRL: -1 not tracked, 0 false, 1 true
|
||||
var _prev_enable_state: int = -1
|
||||
var godot_editor_window: Window # The Godot Editor window
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
|
||||
godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
|
||||
godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
editor = Terrain3DEditor.new()
|
||||
setup_editor_settings()
|
||||
ui = UI.new()
|
||||
ui.plugin = self
|
||||
add_child(ui)
|
||||
|
|
@ -41,7 +48,7 @@ func _enter_tree() -> void:
|
|||
asset_dock = load(ASSET_DOCK).instantiate()
|
||||
asset_dock.initialize(self)
|
||||
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
asset_dock.remove_dock(true)
|
||||
asset_dock.queue_free()
|
||||
|
|
@ -49,25 +56,46 @@ func _exit_tree() -> void:
|
|||
editor.free()
|
||||
|
||||
scene_changed.disconnect(_on_scene_changed)
|
||||
godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
|
||||
|
||||
func _on_godot_focus_entered() -> void:
|
||||
_read_input()
|
||||
ui.update_decal()
|
||||
|
||||
|
||||
## EditorPlugin selection function call chain isn't consistent. Here's the map of calls:
|
||||
## Assume we handle Terrain3D and NavigationRegion3D
|
||||
# Click Terrain3D: _handles(Terrain3D), _make_visible(true), _edit(Terrain3D)
|
||||
# Deselect: _make_visible(false), _edit(null)
|
||||
# Click other node: _handles(OtherNode)
|
||||
# Click NavRegion3D: _handles(NavReg3D), _make_visible(true), _edit(NavReg3D)
|
||||
# Click NavRegion3D, Terrain3D: _handles(Terrain3D), _edit(Terrain3D)
|
||||
# Click Terrain3D, NavRegion3D: _handles(NavReg3D), _edit(NavReg3D)
|
||||
func _handles(p_object: Object) -> bool:
|
||||
if p_object is Terrain3D:
|
||||
return true
|
||||
elif p_object is NavigationRegion3D and is_instance_valid(_last_terrain):
|
||||
return true
|
||||
|
||||
# Terrain3DObjects requires access to EditorUndoRedoManager. The only way to make sure it
|
||||
# always has it, is to pass it in here. _edit is NOT called if the node is cut and pasted.
|
||||
if p_object is Terrain3DObjects:
|
||||
elif p_object is Terrain3DObjects:
|
||||
p_object.editor_setup(self)
|
||||
elif p_object is Node3D and p_object.get_parent() is Terrain3DObjects:
|
||||
p_object.get_parent().editor_setup(self)
|
||||
|
||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree() and p_object is NavigationRegion3D:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
|
||||
if p_visible and is_selected():
|
||||
ui.set_visible(true)
|
||||
asset_dock.update_dock()
|
||||
else:
|
||||
ui.set_visible(false)
|
||||
|
||||
|
||||
func _edit(p_object: Object) -> void:
|
||||
if !p_object:
|
||||
_clear()
|
||||
|
|
@ -77,39 +105,41 @@ func _edit(p_object: Object) -> void:
|
|||
return
|
||||
terrain = p_object
|
||||
_last_terrain = terrain
|
||||
terrain.set_plugin(self)
|
||||
terrain.set_editor(editor)
|
||||
editor.set_terrain(terrain)
|
||||
region_gizmo.set_node_3d(terrain)
|
||||
terrain.add_gizmo(region_gizmo)
|
||||
terrain.set_plugin(self)
|
||||
ui.set_visible(true)
|
||||
terrain.set_meta("_edit_lock_", true)
|
||||
|
||||
# Deprecated 0.9.3 - Remove 1.0
|
||||
if terrain.storage:
|
||||
ui.terrain_menu.directory_setup.directory_setup_popup()
|
||||
|
||||
# Connect to new Assets resource
|
||||
# Get alerted when a new asset list is loaded
|
||||
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
|
||||
terrain.assets_changed.connect(asset_dock.update_assets)
|
||||
asset_dock.update_assets()
|
||||
# Connect to new Storage resource
|
||||
if not terrain.storage_changed.is_connected(_load_storage):
|
||||
terrain.storage_changed.connect(_load_storage)
|
||||
_load_storage()
|
||||
# Get alerted when the region map changes
|
||||
if not terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.connect(update_region_grid)
|
||||
update_region_grid()
|
||||
else:
|
||||
_clear()
|
||||
|
||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree():
|
||||
if is_terrain_valid(_last_terrain):
|
||||
if p_object is NavigationRegion3D:
|
||||
ui.set_visible(true, true)
|
||||
nav_region = p_object
|
||||
else:
|
||||
nav_region = null
|
||||
|
||||
|
||||
func _make_visible(p_visible: bool, p_redraw: bool = false) -> void:
|
||||
visible = p_visible
|
||||
ui.set_visible(visible)
|
||||
update_region_grid()
|
||||
asset_dock.update_dock(visible)
|
||||
|
||||
|
||||
|
||||
func _clear() -> void:
|
||||
if is_terrain_valid():
|
||||
terrain.storage_changed.disconnect(_load_storage)
|
||||
if terrain.data.region_map_changed.is_connected(update_region_grid):
|
||||
terrain.data.region_map_changed.disconnect(update_region_grid)
|
||||
|
||||
terrain.clear_gizmos()
|
||||
terrain = null
|
||||
|
|
@ -124,29 +154,12 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
|||
if not is_terrain_valid():
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
## Track negative input (CTRL)
|
||||
if p_event is InputEventKey and not p_event.echo and p_event.keycode == KEY_CTRL:
|
||||
if p_event.is_pressed():
|
||||
_negative_input = true
|
||||
_prev_enable_state = int(ui.toolbar_settings.get_setting("enable"))
|
||||
ui.toolbar_settings.set_setting("enable", false)
|
||||
else:
|
||||
_negative_input = false
|
||||
ui.toolbar_settings.set_setting("enable", bool(_prev_enable_state))
|
||||
_prev_enable_state = -1
|
||||
_read_input(p_event)
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
## Setup active camera & viewport
|
||||
|
||||
if _prev_enable_state >= 0 and not Input.is_key_pressed(KEY_CTRL):
|
||||
_negative_input = false
|
||||
ui.toolbar_settings.set_setting("enable", bool(_prev_enable_state))
|
||||
_prev_enable_state = -1
|
||||
|
||||
## Setup for active camera & viewport
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
|
|
@ -169,88 +182,131 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
|||
else:
|
||||
# Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.z): # max double or nan
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
## Update decal
|
||||
ui.decal.global_position = mouse_global_position
|
||||
ui.decal.albedo_mix = 1.0
|
||||
if ui.decal_timer.is_stopped():
|
||||
ui.update_decal()
|
||||
else:
|
||||
ui.decal_timer.start()
|
||||
|
||||
## Update region highlight
|
||||
var region_size = terrain.get_storage().get_region_size()
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (region_size * terrain.get_mesh_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and editor.is_operating():
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif p_event is InputEventMouseButton:
|
||||
ui.update_decal()
|
||||
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
if p_event.is_pressed():
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.get_storage().has_region(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
|
||||
if _input_mode != -1: # Not cam rotation
|
||||
## Update region highlight
|
||||
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
|
||||
/ (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
|
||||
if current_region_position != region_position:
|
||||
current_region_position = region_position
|
||||
update_region_grid()
|
||||
|
||||
if _input_mode > 0 and editor.is_operating():
|
||||
# Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
|
||||
ui.brush_data["mouse_pressure"] = p_event.pressure
|
||||
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
elif editor.is_operating():
|
||||
# Mouse released, save undo data
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if p_event is InputEventMouseButton and _input_mode > 0:
|
||||
if p_event.is_pressed():
|
||||
# If picking
|
||||
if ui.is_picking():
|
||||
ui.pick(mouse_global_position)
|
||||
if not ui.operation_builder or not ui.operation_builder.is_ready():
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If adjusting regions
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
var has_region: bool = terrain.data.has_regionp(mouse_global_position)
|
||||
var op: int = editor.get_operation()
|
||||
if ( has_region and op == Terrain3DEditor.ADD) or \
|
||||
( not has_region and op == Terrain3DEditor.SUBTRACT ):
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# If an automatic operation is ready to go (e.g. gradient)
|
||||
if ui.operation_builder and ui.operation_builder.is_ready():
|
||||
ui.operation_builder.apply_operation(editor, mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
|
||||
# Mouse clicked, start editing
|
||||
editor.start_operation(mouse_global_position)
|
||||
editor.operate(mouse_global_position, p_viewport_camera.rotation.y)
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
# _input_apply released, save undo data
|
||||
elif editor.is_operating():
|
||||
editor.stop_operation()
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
func _read_input(p_event: InputEvent = null) -> void:
|
||||
## Determine if user is moving camera or applying
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or \
|
||||
p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
_input_mode = 1
|
||||
else:
|
||||
_input_mode = 0
|
||||
|
||||
func _load_storage() -> void:
|
||||
if terrain:
|
||||
update_region_grid()
|
||||
match get_setting("editors/3d/navigation/navigation_scheme", 0):
|
||||
2, 1: # Modo, Maya
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
( Input.is_key_pressed(KEY_ALT) and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
( Input.is_key_pressed(KEY_ALT) and p_event.get_button_index() == MOUSE_BUTTON_LEFT )):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
0, _: # Godot
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) or \
|
||||
Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE):
|
||||
_input_mode = -1
|
||||
if p_event is InputEventMouseButton and p_event.is_released() and \
|
||||
( p_event.get_button_index() == MOUSE_BUTTON_RIGHT or \
|
||||
p_event.get_button_index() == MOUSE_BUTTON_MIDDLE ):
|
||||
ui.last_rmb_time = Time.get_ticks_msec()
|
||||
if _input_mode < 0:
|
||||
return
|
||||
|
||||
## Determine modifiers pressed
|
||||
modifier_shift = Input.is_key_pressed(KEY_SHIFT)
|
||||
modifier_ctrl = Input.is_key_pressed(KEY_CTRL)
|
||||
# Keybind enum: Alt,Space,Meta,Capslock
|
||||
var alt_key: int
|
||||
match get_setting("terrain3d/config/alt_key_bind", 0):
|
||||
3: alt_key = KEY_CAPSLOCK
|
||||
2: alt_key = KEY_META
|
||||
1: alt_key = KEY_SPACE
|
||||
0, _: alt_key = KEY_ALT
|
||||
modifier_alt = Input.is_key_pressed(alt_key)
|
||||
|
||||
# Return if modifiers haven't changed AND brush_data has them;
|
||||
# modifiers disappear from brush_data when clicking asset_dock (Why?)
|
||||
var current_mods: int = int(modifier_shift) | int(modifier_ctrl) << 1 | int(modifier_alt) << 2
|
||||
if _last_modifiers == current_mods and ui.brush_data.has("modifier_shift"):
|
||||
return
|
||||
|
||||
_last_modifiers = current_mods
|
||||
ui.brush_data["modifier_shift"] = modifier_shift
|
||||
ui.brush_data["modifier_ctrl"] = modifier_ctrl
|
||||
ui.brush_data["modifier_alt"] = modifier_alt
|
||||
ui.update_modifiers()
|
||||
|
||||
|
||||
func update_region_grid() -> void:
|
||||
if not region_gizmo:
|
||||
return
|
||||
region_gizmo.set_hidden(not ui.visible)
|
||||
|
||||
region_gizmo.set_hidden(not visible)
|
||||
|
||||
if is_terrain_valid():
|
||||
region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
|
||||
region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
|
||||
region_gizmo.region_position = current_region_position
|
||||
region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_storage().get_region_offsets()
|
||||
region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
|
||||
region_gizmo.grid = terrain.get_data().get_region_locations()
|
||||
|
||||
terrain.update_gizmos()
|
||||
return
|
||||
|
|
@ -278,22 +334,80 @@ func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
|
|||
t = p_terrain
|
||||
else:
|
||||
t = terrain
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.get_storage():
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.data:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func is_selected() -> bool:
|
||||
var selected: Array[Node] = get_editor_interface().get_selection().get_selected_nodes()
|
||||
var selected: Array[Node] = EditorInterface.get_selection().get_selected_nodes()
|
||||
for node in selected:
|
||||
if node.get_instance_id() == _last_terrain.get_instance_id():
|
||||
return true
|
||||
|
||||
if ( is_instance_valid(_last_terrain) and node.get_instance_id() == _last_terrain.get_instance_id() ) or \
|
||||
node is Terrain3D:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func select_terrain() -> void:
|
||||
if is_instance_valid(_last_terrain) and is_terrain_valid(_last_terrain) and not is_selected():
|
||||
var es: EditorSelection = get_editor_interface().get_selection()
|
||||
var es: EditorSelection = EditorInterface.get_selection()
|
||||
es.clear()
|
||||
es.add_node(_last_terrain)
|
||||
|
||||
|
||||
## Editor Settings
|
||||
|
||||
|
||||
func setup_editor_settings() -> void:
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
if not editor_settings.has_setting("terrain3d/config/alt_key_bind"):
|
||||
editor_settings.set("terrain3d/config/alt_key_bind", 0)
|
||||
var property_info = {
|
||||
"name": "terrain3d/config/alt_key_bind",
|
||||
"type": TYPE_INT,
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": "Alt,Space,Meta,Capslock"
|
||||
}
|
||||
editor_settings.add_property_info(property_info)
|
||||
|
||||
_cleanup_old_settings()
|
||||
|
||||
|
||||
# Remove or rename old settings
|
||||
func _cleanup_old_settings() -> void:
|
||||
# Rename deprecated settings - Remove in 1.0
|
||||
var value: Variant
|
||||
var rename_arr := [ "terrain3d/config/dock_slot", "terrain3d/config/dock_tile_size",
|
||||
"terrain3d/config/dock_floating", "terrain3d/config/dock_always_on_top",
|
||||
"terrain3d/config/dock_window_size", "terrain3d/config/dock_window_position", ]
|
||||
for es: String in rename_arr:
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
editor_settings.erase(es)
|
||||
editor_settings.set_setting(es.replace("/config/dock_", "/dock/"), value)
|
||||
|
||||
# Special handling
|
||||
var es: String = "terrain3d/tool_settings/slope"
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
if typeof(value) == TYPE_FLOAT:
|
||||
editor_settings.erase(es)
|
||||
|
||||
|
||||
func set_setting(p_str: String, p_value: Variant) -> void:
|
||||
editor_settings.set_setting(p_str, p_value)
|
||||
|
||||
|
||||
func get_setting(p_str: String, p_default: Variant) -> Variant:
|
||||
if editor_settings.has_setting(p_str):
|
||||
return editor_settings.get_setting(p_str)
|
||||
else:
|
||||
return p_default
|
||||
|
||||
|
||||
func has_setting(p_str: String) -> bool:
|
||||
return editor_settings.has_setting(p_str)
|
||||
|
||||
|
||||
func erase_setting(p_str: String) -> void:
|
||||
editor_settings.erase(p_str)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# 1. Click import. The output window and console will report when finished.
|
||||
# 1. Clear the script from your Terrain3D node, and save your scene.
|
||||
#
|
||||
# The instance transforms are now stored in your Storage resource.
|
||||
# The instance transforms are now stored in your region files.
|
||||
#
|
||||
# Use clear_instances to erase all instances that match the assign_mesh_id.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -1,30 +1,41 @@
|
|||
// This shader is the minimum needed to allow the terrain to function, without any texturing.
|
||||
|
||||
shader_type spatial;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
|
||||
|
||||
// Private uniforms
|
||||
uniform float _region_size = 1024.0;
|
||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||
uniform float _mesh_vertex_spacing = 1.0;
|
||||
uniform float _mesh_vertex_density = 1.0; // = 1/_mesh_vertex_spacing
|
||||
uniform int _region_map_size = 16;
|
||||
uniform int _region_map[256];
|
||||
uniform vec2 _region_offsets[256];
|
||||
uniform float _vertex_spacing = 1.0;
|
||||
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
|
||||
uniform int _region_map_size = 32;
|
||||
uniform int _region_map[1024];
|
||||
uniform vec2 _region_locations[1024];
|
||||
uniform sampler2DArray _height_maps : repeat_disable;
|
||||
uniform usampler2DArray _control_maps : repeat_disable;
|
||||
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
||||
uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
|
||||
uniform float _texture_uv_scale_array[32];
|
||||
uniform float _texture_uv_rotation_array[32];
|
||||
uniform float _texture_detile_array[32];
|
||||
uniform vec4 _texture_color_array[32];
|
||||
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
||||
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
||||
|
||||
// Public uniforms
|
||||
uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
|
||||
|
||||
// Varyings & Types
|
||||
varying flat vec3 v_vertex; // World coordinate vertex location
|
||||
varying flat vec3 v_camera_pos;
|
||||
varying float v_vertex_xz_dist;
|
||||
varying flat ivec3 v_region;
|
||||
varying flat vec2 v_uv_offset;
|
||||
varying flat vec2 v_uv2_offset;
|
||||
varying vec3 v_normal;
|
||||
varying float v_region_border_mask;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
|
|
@ -33,26 +44,22 @@ varying flat vec2 v_uv2_offset;
|
|||
// Takes in UV world space coordinates, returns ivec3 with:
|
||||
// XY: (0 to _region_size) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
ivec3 get_region_uv(vec2 uv) {
|
||||
uv *= _region_texel_size;
|
||||
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x>=0 && pos.x<_region_map_size && pos.y>=0 && pos.y<_region_map_size);
|
||||
ivec3 get_region_uv(const vec2 uv) {
|
||||
ivec2 pos = ivec2(floor(uv * _region_texel_size)) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
return ivec3(ivec2((uv - _region_offsets[layer_index]) * _region_size), layer_index);
|
||||
return ivec3(ivec2(mod(uv,_region_size)), layer_index);
|
||||
}
|
||||
|
||||
// Takes in UV2 region space coordinates, returns vec3 with:
|
||||
// XY: (0 to 1) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
vec3 get_region_uv2(vec2 uv) {
|
||||
// Vertex function added half a texel to UV2, to center the UV's. vertex(), fragment() and get_height()
|
||||
// call this with reclaimed versions of UV2, so to keep the last row/column within the correct
|
||||
// window, take back the half pixel before the floor().
|
||||
ivec2 pos = ivec2(floor(uv - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x>=0 && pos.x<_region_map_size && pos.y>=0 && pos.y<_region_map_size);
|
||||
vec3 get_region_uv2(const vec2 uv2) {
|
||||
// Remove Texel Offset to ensure correct region index.
|
||||
ivec2 pos = ivec2(floor(uv2 - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
// The return value is still texel-centered.
|
||||
return vec3(uv - _region_offsets[layer_index], float(layer_index));
|
||||
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
|
||||
}
|
||||
|
||||
// 1 lookup
|
||||
|
|
@ -66,63 +73,75 @@ float get_height(vec2 uv) {
|
|||
}
|
||||
|
||||
void vertex() {
|
||||
// Get camera pos in world vertex coords
|
||||
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
|
||||
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
vec3 vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
v_vertex_xz_dist = length(v_vertex.xz - v_camera_pos.xz);
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = round(vertex.xz * _mesh_vertex_density);
|
||||
UV = round(v_vertex.xz * _vertex_density);
|
||||
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
|
||||
|
||||
// Discard vertices for Holes. 1 lookup
|
||||
ivec3 region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, region, 0).r;
|
||||
v_region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, v_region, 0).r;
|
||||
bool hole = bool(control >>2u & 0x1u);
|
||||
|
||||
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||
(hole || (_background_mode == 0u && region.z < 0)) ) {
|
||||
VERTEX.x = 0./0.;
|
||||
} else {
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
UV2 = (UV + vec2(0.5)) * _region_texel_size;
|
||||
|
||||
// Get final vertex location and save it
|
||||
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
|
||||
VERTEX.x = 0. / 0.;
|
||||
} else {
|
||||
// Set final vertex height & calculate vertex normals. 3 lookups.
|
||||
VERTEX.y = get_height(UV2);
|
||||
v_vertex.y = VERTEX.y;
|
||||
v_normal = vec3(
|
||||
v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
|
||||
_vertex_spacing,
|
||||
v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
|
||||
);
|
||||
// Due to a bug caused by the GPUs linear interpolation across edges of region maps,
|
||||
// mask region edges and use vertex normals only across region boundaries.
|
||||
v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
|
||||
}
|
||||
|
||||
|
||||
// Transform UVs to local to avoid poor precision during varying interpolation.
|
||||
v_uv_offset = MODEL_MATRIX[3].xz * _mesh_vertex_density;
|
||||
v_uv_offset = MODEL_MATRIX[3].xz * _vertex_density;
|
||||
UV -= v_uv_offset;
|
||||
v_uv2_offset = v_uv_offset * _region_texel_size;
|
||||
UV2 -= v_uv2_offset;
|
||||
|
||||
// Convert model space to view space w/ skip_vertex_transform render mode
|
||||
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
||||
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
|
||||
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Fragment
|
||||
////////////////////////
|
||||
|
||||
// 3 lookups
|
||||
// 0 - 3 lookups
|
||||
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
|
||||
// Get the height of the current vertex
|
||||
float height = get_height(uv);
|
||||
|
||||
// Get the heights to the right and in front, but because of hardware
|
||||
// interpolation on the edges of the heightmaps, the values are off
|
||||
// causing the normal map to look weird. So, near the edges of the map
|
||||
// get the heights to the left or behind instead. Hacky solution that
|
||||
// reduces the artifact, but doesn't fix it entirely. See #185.
|
||||
float u, v;
|
||||
if(mod(uv.y*_region_size, _region_size) > _region_size-2.) {
|
||||
v = get_height(uv + vec2(0, -_region_texel_size)) - height;
|
||||
} else {
|
||||
v = height - get_height(uv + vec2(0, _region_texel_size));
|
||||
}
|
||||
if(mod(uv.x*_region_size, _region_size) > _region_size-2.) {
|
||||
u = get_height(uv + vec2(-_region_texel_size, 0)) - height;
|
||||
float u, v, height;
|
||||
vec3 normal;
|
||||
// Use vertex normals within radius of vertex_normals_distance, and along region borders.
|
||||
if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
|
||||
normal = normalize(v_normal);
|
||||
} else {
|
||||
height = get_height(uv);
|
||||
u = height - get_height(uv + vec2(_region_texel_size, 0));
|
||||
v = height - get_height(uv + vec2(0, _region_texel_size));
|
||||
normal = normalize(vec3(u, _vertex_spacing, v));
|
||||
}
|
||||
|
||||
vec3 normal = vec3(u, _mesh_vertex_spacing, v);
|
||||
normal = normalize(normal);
|
||||
tangent = cross(normal, vec3(0, 0, 1));
|
||||
binormal = cross(normal, tangent);
|
||||
return normal;
|
||||
|
|
@ -143,4 +162,3 @@ void fragment() {
|
|||
// Apply PBR
|
||||
ALBEDO=vec3(.2);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@
|
|||
#warning += """No Terrain3D node found"""
|
||||
#return
|
||||
#
|
||||
#if not _terrain.storage:
|
||||
#warning += """Terrain3D storage is not initialized"""
|
||||
#if not _terrain.data:
|
||||
#warning += """Terrain3DData is not initialized"""
|
||||
#return
|
||||
#
|
||||
## Get global transform
|
||||
|
|
@ -70,8 +70,8 @@
|
|||
#var gt_inverse := gt.affine_inverse()
|
||||
#for i in transforms.list.size():
|
||||
#var location: Vector3 = (gt * transforms.list[i]).origin
|
||||
#var height: float = _terrain.storage.get_height(location)
|
||||
#var normal: Vector3 = _terrain.storage.get_normal(location)
|
||||
#var height: float = _terrain.data.get_height(location)
|
||||
#var normal: Vector3 = _terrain.data.get_normal(location)
|
||||
#
|
||||
#if align_with_collision_normal and not is_nan(normal.x):
|
||||
#transforms.list[i].basis.y = normal
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg1109"
|
||||
sodipodi:docname="icon_picker.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1113" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1111"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="50.375"
|
||||
inkscape:cx="7.9900744"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1109" />
|
||||
<path
|
||||
d="M6 5H5v2h1v5a2 2 0 0 0 1 1.728V15h2v-1.27A2 2 0 0 0 10 12V7h1V5h-1V3a1 1 0 0 0-4 0zm1 2h2v5a1 1 0 0 1-2 0z"
|
||||
fill="#8eef97"
|
||||
id="path1107"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,38 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c11ip32w7ln4v"
|
||||
path="res://.godot/imported/picker.svg-0ed48f8d7e66014d2aac4b303bc65df6.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/picker.svg"
|
||||
dest_files=["res://.godot/imported/picker.svg-0ed48f8d7e66014d2aac4b303bc65df6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
@ -3,5 +3,5 @@
|
|||
name="Terrain3D"
|
||||
description="A high performance, editable terrain system for Godot 4."
|
||||
author="Cory Petkovsek & Roope Palmroos"
|
||||
version="0.9.2"
|
||||
version="0.9.3a"
|
||||
script="editor.gd"
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ signal confirmation_closed
|
|||
signal confirmation_confirmed
|
||||
signal confirmation_canceled
|
||||
|
||||
const PS_DOCK_SLOT: String = "terrain3d/config/dock_slot"
|
||||
const PS_DOCK_TILE_SIZE: String = "terrain3d/config/dock_tile_size"
|
||||
const PS_DOCK_FLOATING: String = "terrain3d/config/dock_floating"
|
||||
const PS_DOCK_PINNED: String = "terrain3d/config/dock_always_on_top"
|
||||
const PS_DOCK_WINDOW_POSITION: String = "terrain3d/config/dock_window_position"
|
||||
const PS_DOCK_WINDOW_SIZE: String = "terrain3d/config/dock_window_size"
|
||||
const ES_DOCK_SLOT: String = "terrain3d/dock/slot"
|
||||
const ES_DOCK_TILE_SIZE: String = "terrain3d/dock/tile_size"
|
||||
const ES_DOCK_FLOATING: String = "terrain3d/dock/floating"
|
||||
const ES_DOCK_PINNED: String = "terrain3d/dock/always_on_top"
|
||||
const ES_DOCK_WINDOW_POSITION: String = "terrain3d/dock/window_position"
|
||||
const ES_DOCK_WINDOW_SIZE: String = "terrain3d/dock/window_size"
|
||||
const ES_DOCK_TAB: String = "terrain3d/dock/tab"
|
||||
|
||||
var texture_list: ListContainer
|
||||
var mesh_list: ListContainer
|
||||
|
|
@ -40,10 +41,6 @@ enum {
|
|||
}
|
||||
var state: int = HIDDEN
|
||||
|
||||
var window: Window
|
||||
var _godot_editor_window: Window # The main Godot Editor window
|
||||
var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN
|
||||
|
||||
enum {
|
||||
POS_LEFT_UL = 0,
|
||||
POS_LEFT_BL = 1,
|
||||
|
|
@ -59,17 +56,15 @@ enum {
|
|||
var slot: int = POS_RIGHT_BR
|
||||
var _initialized: bool = false
|
||||
var plugin: EditorPlugin
|
||||
var editor_settings: EditorSettings
|
||||
var window: Window
|
||||
var _godot_last_state: Window.Mode = Window.MODE_FULLSCREEN
|
||||
|
||||
|
||||
func initialize(p_plugin: EditorPlugin) -> void:
|
||||
if p_plugin:
|
||||
plugin = p_plugin
|
||||
|
||||
# Get editor window. Structure is root:Window/EditorNode/Base Control
|
||||
_godot_editor_window = plugin.get_editor_interface().get_base_control().get_parent().get_parent()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
placement_opt = $Box/Buttons/PlacementOpt
|
||||
pinned_btn = $Box/Buttons/Pinned
|
||||
floating_btn = $Box/Buttons/Floating
|
||||
|
|
@ -93,7 +88,6 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
asset_container.add_child(mesh_list)
|
||||
_current_list = texture_list
|
||||
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
load_editor_settings()
|
||||
|
||||
# Connect signals
|
||||
|
|
@ -103,7 +97,7 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
placement_opt.item_selected.connect(set_slot)
|
||||
floating_btn.pressed.connect(make_dock_float)
|
||||
pinned_btn.toggled.connect(_on_pin_changed)
|
||||
pinned_btn.visible = false
|
||||
pinned_btn.visible = ( window != null )
|
||||
size_slider.value_changed.connect(_on_slider_changed)
|
||||
plugin.ui.toolbar.tool_changed.connect(_on_tool_changed)
|
||||
|
||||
|
|
@ -111,7 +105,7 @@ func initialize(p_plugin: EditorPlugin) -> void:
|
|||
textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
|
||||
|
||||
_initialized = true
|
||||
update_dock(plugin.visible)
|
||||
update_dock()
|
||||
update_layout()
|
||||
|
||||
|
||||
|
|
@ -122,7 +116,7 @@ func _ready() -> void:
|
|||
# Setup styles
|
||||
set("theme_override_styles/panel", get_theme_stylebox("panel", "Panel"))
|
||||
# Avoid saving icon resources in tscn when editing w/ a tool script
|
||||
if plugin.get_editor_interface().get_edited_scene_root() != self:
|
||||
if EditorInterface.get_edited_scene_root() != self:
|
||||
pinned_btn.icon = get_theme_icon("Pin", "EditorIcons")
|
||||
pinned_btn.text = ""
|
||||
floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
|
||||
|
|
@ -154,7 +148,7 @@ func set_slot(p_slot: int) -> void:
|
|||
placement_opt.selected = slot
|
||||
save_editor_settings()
|
||||
plugin.select_terrain()
|
||||
update_dock(plugin.visible)
|
||||
update_dock()
|
||||
|
||||
|
||||
func remove_dock(p_force: bool = false) -> void:
|
||||
|
|
@ -167,50 +161,42 @@ func remove_dock(p_force: bool = false) -> void:
|
|||
state = HIDDEN
|
||||
|
||||
# If windowed and destination is not window or final exit, otherwise leave
|
||||
elif state == WINDOWED and p_force:
|
||||
if not window:
|
||||
return
|
||||
elif state == WINDOWED and p_force and window:
|
||||
var parent: Node = get_parent()
|
||||
if parent:
|
||||
parent.remove_child(self)
|
||||
_godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
||||
_godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
_godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
||||
window.hide()
|
||||
window.queue_free()
|
||||
window = null
|
||||
plugin.godot_editor_window.mouse_entered.disconnect(_on_godot_window_entered)
|
||||
plugin.godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
|
||||
plugin.godot_editor_window.focus_exited.disconnect(_on_godot_focus_exited)
|
||||
window.hide()
|
||||
window.queue_free()
|
||||
window = null
|
||||
floating_btn.button_pressed = false
|
||||
floating_btn.visible = true
|
||||
pinned_btn.visible = false
|
||||
placement_opt.visible = true
|
||||
state = HIDDEN
|
||||
update_dock(plugin.visible) # return window to side/bottom
|
||||
update_dock() # return window to side/bottom
|
||||
|
||||
|
||||
func update_dock(p_visible: bool) -> void:
|
||||
func update_dock() -> void:
|
||||
if not _initialized or window:
|
||||
return
|
||||
|
||||
update_assets()
|
||||
if not _initialized:
|
||||
return
|
||||
|
||||
if window:
|
||||
return
|
||||
elif floating_btn.button_pressed:
|
||||
# No window, but floating button pressed, occurs when from editor settings
|
||||
make_dock_float()
|
||||
return
|
||||
|
||||
# Move dock to new destination
|
||||
remove_dock()
|
||||
# Add dock to new destination
|
||||
# Sidebar
|
||||
if slot < POS_BOTTOM:
|
||||
state = SIDEBAR
|
||||
plugin.add_control_to_dock(slot, self)
|
||||
# Bottom
|
||||
elif slot == POS_BOTTOM:
|
||||
state = BOTTOM
|
||||
plugin.add_control_to_bottom_panel(self, "Terrain3D")
|
||||
if p_visible:
|
||||
plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
|
||||
func update_layout() -> void:
|
||||
if not _initialized:
|
||||
|
|
@ -257,6 +243,8 @@ func update_thumbnails() -> void:
|
|||
_last_thumb_update_time = Time.get_ticks_msec()
|
||||
for mesh_asset in mesh_list.entries:
|
||||
mesh_asset.queue_redraw()
|
||||
|
||||
|
||||
## Dock Button handlers
|
||||
|
||||
|
||||
|
|
@ -282,7 +270,9 @@ func _on_textures_pressed() -> void:
|
|||
textures_btn.button_pressed = true
|
||||
meshes_btn.button_pressed = false
|
||||
texture_list.set_selected_id(texture_list.selected_id)
|
||||
plugin.get_editor_interface().edit_node(plugin.terrain)
|
||||
if plugin.is_terrain_valid():
|
||||
EditorInterface.edit_node(plugin.terrain)
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_meshes_pressed() -> void:
|
||||
|
|
@ -293,14 +283,16 @@ func _on_meshes_pressed() -> void:
|
|||
meshes_btn.button_pressed = true
|
||||
textures_btn.button_pressed = false
|
||||
mesh_list.set_selected_id(mesh_list.selected_id)
|
||||
plugin.get_editor_interface().edit_node(plugin.terrain)
|
||||
if plugin.is_terrain_valid():
|
||||
EditorInterface.edit_node(plugin.terrain)
|
||||
update_thumbnails()
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||
if p_tool == Terrain3DEditor.INSTANCER:
|
||||
_on_meshes_pressed()
|
||||
elif p_tool == Terrain3DEditor.TEXTURE:
|
||||
elif p_tool in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
|
||||
_on_textures_pressed()
|
||||
|
||||
|
||||
|
|
@ -320,24 +312,34 @@ func update_assets() -> void:
|
|||
|
||||
_current_list.update_asset_list()
|
||||
|
||||
|
||||
## Window Management
|
||||
|
||||
|
||||
func make_dock_float() -> void:
|
||||
# If already created (eg from editor Make Floating)
|
||||
# If not already created (eg from editor panel 'Make Floating' button)
|
||||
if not window:
|
||||
remove_dock()
|
||||
create_window()
|
||||
|
||||
state = WINDOWED
|
||||
visible = true # Asset dock contents are hidden when popping out of the bottom!
|
||||
pinned_btn.visible = true
|
||||
floating_btn.visible = false
|
||||
placement_opt.visible = false
|
||||
window.title = "Terrain3D Asset Dock"
|
||||
window.always_on_top = pinned_btn.button_pressed
|
||||
window.close_requested.connect(remove_dock.bind(true))
|
||||
visible = true # Is hidden when pops off of bottom. ??
|
||||
_godot_editor_window.grab_focus()
|
||||
window.window_input.connect(_on_window_input)
|
||||
window.focus_exited.connect(save_editor_settings)
|
||||
window.mouse_exited.connect(save_editor_settings)
|
||||
window.size_changed.connect(save_editor_settings)
|
||||
plugin.godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
|
||||
plugin.godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
plugin.godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
update_assets()
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func create_window() -> void:
|
||||
|
|
@ -348,79 +350,80 @@ func create_window() -> void:
|
|||
mc.add_child(self)
|
||||
window.add_child(mc)
|
||||
window.set_transient(false)
|
||||
window.set_size(get_setting(PS_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
||||
window.set_position(get_setting(PS_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
||||
window.set_size(plugin.get_setting(ES_DOCK_WINDOW_SIZE, Vector2i(512, 512)))
|
||||
window.set_position(plugin.get_setting(ES_DOCK_WINDOW_POSITION, Vector2i(704, 284)))
|
||||
plugin.add_child(window)
|
||||
window.show()
|
||||
window.window_input.connect(_on_window_input)
|
||||
window.focus_exited.connect(_on_window_focus_exited)
|
||||
_godot_editor_window.mouse_entered.connect(_on_godot_window_entered)
|
||||
_godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
_godot_editor_window.focus_exited.connect(_on_godot_focus_exited)
|
||||
|
||||
|
||||
func clamp_window_position() -> void:
|
||||
if window and window.visible:
|
||||
var bounds: Vector2i
|
||||
if EditorInterface.get_editor_settings().get_setting("interface/editor/single_window_mode"):
|
||||
bounds = EditorInterface.get_base_control().size
|
||||
else:
|
||||
bounds = DisplayServer.screen_get_position(window.current_screen)
|
||||
bounds += DisplayServer.screen_get_size(window.current_screen)
|
||||
var margin: int = 40
|
||||
window.position.x = clamp(window.position.x, -window.size.x + 2*margin, bounds.x - margin)
|
||||
window.position.y = clamp(window.position.y, 25, bounds.y - margin)
|
||||
|
||||
|
||||
func _on_window_input(event: InputEvent) -> void:
|
||||
# Capture CTRL+S when doc focused to save scene)
|
||||
# Capture CTRL+S when doc focused to save scene
|
||||
if event is InputEventKey and event.keycode == KEY_S and event.pressed and event.is_command_or_control_pressed():
|
||||
save_editor_settings()
|
||||
plugin.get_editor_interface().save_scene()
|
||||
|
||||
|
||||
func _on_window_focus_exited() -> void:
|
||||
# Capture window position w/o other changes
|
||||
save_editor_settings()
|
||||
EditorInterface.save_scene()
|
||||
|
||||
|
||||
func _on_godot_window_entered() -> void:
|
||||
if is_instance_valid(window) and window.has_focus():
|
||||
_godot_editor_window.grab_focus()
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
|
||||
|
||||
func _on_godot_focus_entered() -> void:
|
||||
# If asset dock is windowed, and Godot was minimized, and now is not, restore asset dock window
|
||||
if is_instance_valid(window):
|
||||
if _godot_last_state == Window.MODE_MINIMIZED and _godot_editor_window.mode != Window.MODE_MINIMIZED:
|
||||
if _godot_last_state == Window.MODE_MINIMIZED and plugin.godot_editor_window.mode != Window.MODE_MINIMIZED:
|
||||
window.show()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
_godot_editor_window.grab_focus()
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
plugin.godot_editor_window.grab_focus()
|
||||
|
||||
|
||||
func _on_godot_focus_exited() -> void:
|
||||
if is_instance_valid(window) and _godot_editor_window.mode == Window.MODE_MINIMIZED:
|
||||
if is_instance_valid(window) and plugin.godot_editor_window.mode == Window.MODE_MINIMIZED:
|
||||
window.hide()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
_godot_last_state = plugin.godot_editor_window.mode
|
||||
|
||||
|
||||
## Manage Editor Settings
|
||||
|
||||
|
||||
func get_setting(p_str: String, p_default: Variant) -> Variant:
|
||||
if editor_settings.has_setting(p_str):
|
||||
return editor_settings.get_setting(p_str)
|
||||
else:
|
||||
return p_default
|
||||
|
||||
|
||||
func load_editor_settings() -> void:
|
||||
floating_btn.button_pressed = get_setting(PS_DOCK_FLOATING, false)
|
||||
pinned_btn.button_pressed = get_setting(PS_DOCK_PINNED, true)
|
||||
size_slider.value = get_setting(PS_DOCK_TILE_SIZE, 83)
|
||||
set_slot(get_setting(PS_DOCK_SLOT, POS_BOTTOM))
|
||||
floating_btn.button_pressed = plugin.get_setting(ES_DOCK_FLOATING, false)
|
||||
pinned_btn.button_pressed = plugin.get_setting(ES_DOCK_PINNED, true)
|
||||
size_slider.value = plugin.get_setting(ES_DOCK_TILE_SIZE, 83)
|
||||
_on_slider_changed(size_slider.value)
|
||||
# Window pos/size set on window creation in update_dock
|
||||
update_dock(plugin.visible)
|
||||
|
||||
|
||||
set_slot(plugin.get_setting(ES_DOCK_SLOT, POS_BOTTOM))
|
||||
if floating_btn.button_pressed:
|
||||
make_dock_float()
|
||||
# TODO Don't save tab until thumbnail generation more reliable
|
||||
#if plugin.get_setting(ES_DOCK_TAB, 0) == 1:
|
||||
# _on_meshes_pressed()
|
||||
|
||||
|
||||
func save_editor_settings() -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
editor_settings.set_setting(PS_DOCK_SLOT, slot)
|
||||
editor_settings.set_setting(PS_DOCK_TILE_SIZE, size_slider.value)
|
||||
editor_settings.set_setting(PS_DOCK_FLOATING, floating_btn.button_pressed)
|
||||
editor_settings.set_setting(PS_DOCK_PINNED, pinned_btn.button_pressed)
|
||||
clamp_window_position()
|
||||
plugin.set_setting(ES_DOCK_SLOT, slot)
|
||||
plugin.set_setting(ES_DOCK_TILE_SIZE, size_slider.value)
|
||||
plugin.set_setting(ES_DOCK_FLOATING, floating_btn.button_pressed)
|
||||
plugin.set_setting(ES_DOCK_PINNED, pinned_btn.button_pressed)
|
||||
# TODO Don't save tab until thumbnail generation more reliable
|
||||
# plugin.set_setting(ES_DOCK_TAB, 0 if _current_list == texture_list else 1)
|
||||
if window:
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_SIZE, window.size)
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_POSITION, window.position)
|
||||
plugin.set_setting(ES_DOCK_WINDOW_SIZE, window.size)
|
||||
plugin.set_setting(ES_DOCK_WINDOW_POSITION, window.position)
|
||||
|
||||
|
||||
##############################################################
|
||||
|
|
@ -527,7 +530,8 @@ class ListContainer extends Container:
|
|||
plugin.select_terrain()
|
||||
|
||||
# Select Paint tool if clicking a texture
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE and plugin.editor.get_tool() != Terrain3DEditor.TEXTURE:
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE and \
|
||||
not plugin.editor.get_tool() in [ Terrain3DEditor.TEXTURE, Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS ]:
|
||||
var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintBaseTexture")
|
||||
if paint_btn:
|
||||
paint_btn.set_pressed(true)
|
||||
|
|
@ -545,7 +549,7 @@ class ListContainer extends Container:
|
|||
|
||||
func _on_resource_inspected(p_resource: Resource) -> void:
|
||||
await get_tree().create_timer(.01).timeout
|
||||
plugin.get_editor_interface().edit_resource(p_resource)
|
||||
EditorInterface.edit_resource(p_resource)
|
||||
|
||||
|
||||
func _on_resource_changed(p_resource: Resource, p_id: int) -> void:
|
||||
|
|
@ -575,7 +579,7 @@ class ListContainer extends Container:
|
|||
|
||||
# If removing an entry, clear inspector
|
||||
if not p_resource:
|
||||
plugin.get_editor_interface().inspect_object(null)
|
||||
EditorInterface.inspect_object(null)
|
||||
|
||||
# If null resource, remove last
|
||||
if not p_resource:
|
||||
|
|
@ -694,7 +698,7 @@ class ListEntry extends VBoxContainer:
|
|||
else:
|
||||
name_label.text = "Add Mesh"
|
||||
|
||||
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
match p_what:
|
||||
NOTIFICATION_DRAW:
|
||||
|
|
|
|||
|
|
@ -5,37 +5,39 @@
|
|||
[node name="bake_lod_dialog" type="ConfirmationDialog"]
|
||||
title = "Bake Terrain3D Mesh"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(400, 115)
|
||||
size = Vector2i(400, 155)
|
||||
visible = true
|
||||
script = ExtResource("1_sf76d")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
offset_right = 392.0
|
||||
offset_bottom = 106.0
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "LOD:"
|
||||
|
||||
[node name="LodBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
||||
[node name="LodBox" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 8.0
|
||||
value = 4.0
|
||||
|
||||
[node name="DescriptionLabel" type="Label" parent="VBoxContainer"]
|
||||
[node name="DescriptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 2
|
||||
|
|
|
|||
|
|
@ -44,11 +44,14 @@ func bake_mesh_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _bake_mesh
|
||||
bake_lod_dialog.description = BAKE_MESH_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(bake_lod_dialog)
|
||||
EditorInterface.popup_dialog_centered(bake_lod_dialog)
|
||||
|
||||
|
||||
func _bake_mesh() -> void:
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DStorage.HEIGHT_FILTER_NEAREST)
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_NEAREST)
|
||||
if !mesh:
|
||||
push_error("Failed to bake mesh from Terrain3D")
|
||||
return
|
||||
|
|
@ -65,7 +68,7 @@ func _bake_mesh() -> void:
|
|||
|
||||
undo.add_do_method(plugin.terrain, &"add_child", mesh_instance, true)
|
||||
undo.add_undo_method(plugin.terrain, &"remove_child", mesh_instance)
|
||||
undo.add_do_property(mesh_instance, &"owner", plugin.terrain.owner)
|
||||
undo.add_do_property(mesh_instance, &"owner", EditorInterface.get_edited_scene_root())
|
||||
undo.add_do_reference(mesh_instance)
|
||||
|
||||
else:
|
||||
|
|
@ -86,11 +89,14 @@ func bake_occluder_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _bake_occluder
|
||||
bake_lod_dialog.description = BAKE_OCCLUDER_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(bake_lod_dialog)
|
||||
EditorInterface.popup_dialog_centered(bake_lod_dialog)
|
||||
|
||||
|
||||
func _bake_occluder() -> void:
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DStorage.HEIGHT_FILTER_MINIMUM)
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
var mesh: Mesh = plugin.terrain.bake_mesh(bake_lod_dialog.lod, Terrain3DData.HEIGHT_FILTER_MINIMUM)
|
||||
if !mesh:
|
||||
push_error("Failed to bake mesh from Terrain3D")
|
||||
return
|
||||
|
|
@ -113,7 +119,7 @@ func _bake_occluder() -> void:
|
|||
|
||||
undo.add_do_method(plugin.terrain, &"add_child", occluder_instance, true)
|
||||
undo.add_undo_method(plugin.terrain, &"remove_child", occluder_instance)
|
||||
undo.add_do_property(occluder_instance, &"owner", plugin.terrain.owner)
|
||||
undo.add_do_property(occluder_instance, &"owner", EditorInterface.get_edited_scene_root())
|
||||
undo.add_do_reference(occluder_instance)
|
||||
|
||||
else:
|
||||
|
|
@ -153,7 +159,7 @@ func find_nav_region_terrains(p_nav_region: NavigationRegion3D) -> Array[Terrain
|
|||
|
||||
func find_terrain_nav_regions(p_terrain: Terrain3D) -> Array[NavigationRegion3D]:
|
||||
var result: Array[NavigationRegion3D] = []
|
||||
var root: Node = plugin.get_editor_interface().get_edited_scene_root()
|
||||
var root: Node = EditorInterface.get_edited_scene_root()
|
||||
if not root:
|
||||
return result
|
||||
for nav_region in root.find_children("", "NavigationRegion3D", true, true):
|
||||
|
|
@ -169,6 +175,9 @@ func bake_nav_mesh() -> void:
|
|||
print("Terrain3DNavigation: Finished baking 1 NavigationMesh.")
|
||||
|
||||
elif plugin.terrain:
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions to bake")
|
||||
return
|
||||
# A Terrain3D is selected. There are potentially multiple navmeshes to bake and we need to
|
||||
# find them all. (The multiple navmesh use-case is likely on very large scenes with lots of
|
||||
# geometry. Each navmesh in this case would define its own, non-overlapping, baking AABB, to
|
||||
|
|
@ -329,11 +338,17 @@ func set_up_navigation_popup() -> void:
|
|||
if plugin.terrain:
|
||||
bake_method = _set_up_navigation
|
||||
confirm_dialog.dialog_text = SET_UP_NAVIGATION_DESCRIPTION
|
||||
plugin.get_editor_interface().popup_dialog_centered(confirm_dialog)
|
||||
EditorInterface.popup_dialog_centered(confirm_dialog)
|
||||
|
||||
|
||||
func _set_up_navigation() -> void:
|
||||
assert(plugin.terrain)
|
||||
if plugin.terrain == EditorInterface.get_edited_scene_root():
|
||||
push_error("Terrain3D Navigation setup not possible if Terrain3D node is scene root")
|
||||
return
|
||||
if plugin.terrain.data.get_region_count() == 0:
|
||||
push_error("Terrain3D has no active regions")
|
||||
return
|
||||
var terrain: Terrain3D = plugin.terrain
|
||||
|
||||
var nav_region := NavigationRegion3D.new()
|
||||
|
|
@ -348,7 +363,7 @@ func _set_up_navigation() -> void:
|
|||
undo_redo.add_do_reference(nav_region)
|
||||
undo_redo.commit_action()
|
||||
|
||||
plugin.get_editor_interface().inspect_object(nav_region)
|
||||
EditorInterface.inspect_object(nav_region)
|
||||
assert(plugin.nav_region == nav_region)
|
||||
|
||||
bake_nav_mesh()
|
||||
|
|
|
|||
|
|
@ -1,21 +1,38 @@
|
|||
extends Object
|
||||
extends RefCounted
|
||||
|
||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/src/channel_packer.tscn"
|
||||
const TEMPLATE_PATH: String = "res://addons/terrain_3d/src/channel_packer_import_template.txt"
|
||||
|
||||
const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/src/channel_packer_dragdrop.gd"
|
||||
enum {
|
||||
IMAGE_ALBEDO,
|
||||
IMAGE_HEIGHT,
|
||||
IMAGE_NORMAL,
|
||||
IMAGE_ROUGHNESS,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
}
|
||||
|
||||
enum {
|
||||
IMAGE_ALBEDO,
|
||||
IMAGE_HEIGHT,
|
||||
IMAGE_NORMAL,
|
||||
IMAGE_ROUGHNESS
|
||||
}
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var editor_interface: EditorInterface
|
||||
var dialog: AcceptDialog
|
||||
var save_file_dialog: FileDialog
|
||||
var open_file_dialog: FileDialog
|
||||
var window: Window
|
||||
var save_file_dialog: EditorFileDialog
|
||||
var open_file_dialog: EditorFileDialog
|
||||
var invert_green_checkbox: CheckBox
|
||||
var invert_smooth_checkbox: CheckBox
|
||||
var invert_height_checkbox: CheckBox
|
||||
var lumin_height_button: Button
|
||||
var generate_mipmaps_checkbox: CheckBox
|
||||
var high_quality_checkbox: CheckBox
|
||||
var align_normals_checkbox: CheckBox
|
||||
var resize_toggle_checkbox: CheckBox
|
||||
var resize_option_box: SpinBox
|
||||
var height_channel: Array[Button]
|
||||
var height_channel_selected: int = 0
|
||||
var roughness_channel: Array[Button]
|
||||
var roughness_channel_selected: int = 0
|
||||
var last_opened_directory: String
|
||||
var last_saved_directory: String
|
||||
var packing_albedo: bool = false
|
||||
|
|
@ -24,133 +41,283 @@ var images: Array[Image] = [null, null, null, null]
|
|||
var status_label: Label
|
||||
var no_op: Callable = func(): pass
|
||||
var last_file_selected_fn: Callable = no_op
|
||||
var normal_vector: Vector3
|
||||
|
||||
|
||||
func pack_textures_popup() -> void:
|
||||
if dialog != null:
|
||||
print("Terrain3DChannelPacker: Cannot open pack tool, dialog already open.")
|
||||
if window != null:
|
||||
window.show()
|
||||
window.move_to_foreground()
|
||||
window.move_to_center()
|
||||
return
|
||||
|
||||
dialog = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
dialog.confirmed.connect(_on_close_requested)
|
||||
dialog.canceled.connect(_on_close_requested)
|
||||
status_label = dialog.find_child("StatusLabel")
|
||||
invert_green_checkbox = dialog.find_child("InvertGreenChannelCheckBox")
|
||||
|
||||
editor_interface = plugin.get_editor_interface()
|
||||
_init_file_dialogs()
|
||||
editor_interface.popup_dialog_centered(dialog)
|
||||
|
||||
_init_texture_picker(dialog.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
||||
_init_texture_picker(dialog.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(dialog.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(dialog.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
window = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
window.close_requested.connect(_on_close_requested)
|
||||
window.window_input.connect(func(event:InputEvent):
|
||||
if event is InputEventKey:
|
||||
if event.pressed and event.keycode == KEY_ESCAPE:
|
||||
_on_close_requested()
|
||||
)
|
||||
window.find_child("CloseButton").pressed.connect(_on_close_requested)
|
||||
|
||||
status_label = window.find_child("StatusLabel") as Label
|
||||
invert_green_checkbox = window.find_child("InvertGreenChannelCheckBox") as CheckBox
|
||||
invert_smooth_checkbox = window.find_child("InvertSmoothCheckBox") as CheckBox
|
||||
invert_height_checkbox = window.find_child("ConvertDepthToHeight") as CheckBox
|
||||
lumin_height_button = window.find_child("LuminanceAsHeightButton") as Button
|
||||
generate_mipmaps_checkbox = window.find_child("GenerateMipmapsCheckBox") as CheckBox
|
||||
high_quality_checkbox = window.find_child("HighQualityCheckBox") as CheckBox
|
||||
align_normals_checkbox = window.find_child("AlignNormalsCheckBox") as CheckBox
|
||||
resize_toggle_checkbox = window.find_child("ResizeToggle") as CheckBox
|
||||
resize_option_box = window.find_child("ResizeOptionButton") as SpinBox
|
||||
height_channel = [
|
||||
window.find_child("HeightChannelR") as Button,
|
||||
window.find_child("HeightChannelG") as Button,
|
||||
window.find_child("HeightChannelB") as Button,
|
||||
window.find_child("HeightChannelA") as Button
|
||||
]
|
||||
roughness_channel = [
|
||||
window.find_child("RoughnessChannelR") as Button,
|
||||
window.find_child("RoughnessChannelG") as Button,
|
||||
window.find_child("RoughnessChannelB") as Button,
|
||||
window.find_child("RoughnessChannelA") as Button
|
||||
]
|
||||
|
||||
height_channel[0].pressed.connect(func() -> void: height_channel_selected = 0)
|
||||
height_channel[1].pressed.connect(func() -> void: height_channel_selected = 1)
|
||||
height_channel[2].pressed.connect(func() -> void: height_channel_selected = 2)
|
||||
height_channel[3].pressed.connect(func() -> void: height_channel_selected = 3)
|
||||
|
||||
roughness_channel[0].pressed.connect(func() -> void: roughness_channel_selected = 0)
|
||||
roughness_channel[1].pressed.connect(func() -> void: roughness_channel_selected = 1)
|
||||
roughness_channel[2].pressed.connect(func() -> void: roughness_channel_selected = 2)
|
||||
roughness_channel[3].pressed.connect(func() -> void: roughness_channel_selected = 3)
|
||||
|
||||
plugin.add_child(window)
|
||||
_init_file_dialogs()
|
||||
|
||||
# the dialog disables the parent window "on top" so, restore it after 1 frame to alow the dialog to clear.
|
||||
var set_on_top_fn: Callable = func(_file: String = "") -> void:
|
||||
await RenderingServer.frame_post_draw
|
||||
window.always_on_top = true
|
||||
save_file_dialog.file_selected.connect(set_on_top_fn)
|
||||
save_file_dialog.canceled.connect(set_on_top_fn)
|
||||
open_file_dialog.file_selected.connect(set_on_top_fn)
|
||||
open_file_dialog.canceled.connect(set_on_top_fn)
|
||||
|
||||
_init_texture_picker(window.find_child("AlbedoVBox"), IMAGE_ALBEDO)
|
||||
_init_texture_picker(window.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
|
||||
var pack_button_path: String = "Panel/MarginContainer/VBoxContainer/PackButton"
|
||||
(dialog.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
(window.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
|
||||
|
||||
func _on_close_requested() -> void:
|
||||
last_file_selected_fn = no_op
|
||||
images = [null, null, null, null]
|
||||
dialog.queue_free()
|
||||
dialog = null
|
||||
window.queue_free()
|
||||
window = null
|
||||
|
||||
|
||||
func _init_file_dialogs() -> void:
|
||||
save_file_dialog = FileDialog.new()
|
||||
save_file_dialog = EditorFileDialog.new()
|
||||
save_file_dialog.set_filters(PackedStringArray(["*.png"]))
|
||||
save_file_dialog.set_file_mode(FileDialog.FILE_MODE_SAVE_FILE)
|
||||
save_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
save_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_SAVE_FILE)
|
||||
save_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
save_file_dialog.file_selected.connect(_on_save_file_selected)
|
||||
|
||||
open_file_dialog = FileDialog.new()
|
||||
open_file_dialog.set_filters(PackedStringArray(["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", ".ktx"]))
|
||||
open_file_dialog.set_file_mode(FileDialog.FILE_MODE_OPEN_FILE)
|
||||
open_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
|
||||
dialog.add_child(save_file_dialog)
|
||||
dialog.add_child(open_file_dialog)
|
||||
save_file_dialog.ok_button_text = "Save"
|
||||
save_file_dialog.size = Vector2i(550, 550)
|
||||
#save_file_dialog.transient = false
|
||||
#save_file_dialog.exclusive = false
|
||||
#save_file_dialog.popup_window = true
|
||||
|
||||
open_file_dialog = EditorFileDialog.new()
|
||||
open_file_dialog.set_filters(PackedStringArray(
|
||||
["*.png", "*.bmp", "*.exr", "*.hdr", "*.jpg", "*.jpeg", "*.tga", "*.svg", "*.webp", "*.ktx", "*.dds"]))
|
||||
open_file_dialog.set_file_mode(EditorFileDialog.FILE_MODE_OPEN_FILE)
|
||||
open_file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
open_file_dialog.ok_button_text = "Open"
|
||||
open_file_dialog.size = Vector2i(550, 550)
|
||||
#open_file_dialog.transient = false
|
||||
#open_file_dialog.exclusive = false
|
||||
#open_file_dialog.popup_window = true
|
||||
|
||||
window.add_child(save_file_dialog)
|
||||
window.add_child(open_file_dialog)
|
||||
|
||||
|
||||
func _init_texture_picker(p_parent: Node, p_image_index: int) -> void:
|
||||
var line_edit: LineEdit = p_parent.find_child("LineEdit")
|
||||
var file_pick_button: Button = p_parent.find_child("PickButton")
|
||||
var clear_button: Button = p_parent.find_child("ClearButton")
|
||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect")
|
||||
var texture_button: Button = p_parent.find_child("TextureButton")
|
||||
|
||||
var line_edit: LineEdit = p_parent.find_child("LineEdit") as LineEdit
|
||||
var file_pick_button: Button = p_parent.find_child("PickButton") as Button
|
||||
var clear_button: Button = p_parent.find_child("ClearButton") as Button
|
||||
var texture_rect: TextureRect = p_parent.find_child("TextureRect") as TextureRect
|
||||
var texture_button: Button = p_parent.find_child("TextureButton") as Button
|
||||
texture_button.set_script(load(DRAG_DROP_SCRIPT) as GDScript)
|
||||
|
||||
var set_channel_fn: Callable = func(used_channels: int) -> void:
|
||||
var channel_count: int = 4
|
||||
# enum Image.UsedChannels
|
||||
match used_channels:
|
||||
Image.USED_CHANNELS_L, Image.USED_CHANNELS_R: channel_count = 1
|
||||
Image.USED_CHANNELS_LA, Image.USED_CHANNELS_RG: channel_count = 2
|
||||
Image.USED_CHANNELS_RGB: channel_count = 3
|
||||
Image.USED_CHANNELS_RGBA: channel_count = 4
|
||||
if p_image_index == IMAGE_HEIGHT:
|
||||
for i in 4:
|
||||
height_channel[i].visible = i < channel_count
|
||||
height_channel[0].button_pressed = true
|
||||
height_channel[0].pressed.emit()
|
||||
elif p_image_index == IMAGE_ROUGHNESS:
|
||||
for i in 4:
|
||||
roughness_channel[i].visible = i < channel_count
|
||||
roughness_channel[0].button_pressed = true
|
||||
roughness_channel[0].pressed.emit()
|
||||
|
||||
var load_image_fn: Callable = func(path: String):
|
||||
var image: Image = Image.new()
|
||||
var error: int = OK
|
||||
# Special case for dds files
|
||||
if path.get_extension() == "dds":
|
||||
image = ResourceLoader.load(path).get_image()
|
||||
if not image.is_empty():
|
||||
# if the dds file is loaded, we must clear any mipmaps and
|
||||
# decompress if needed in order to do per pixel operations.
|
||||
image.clear_mipmaps()
|
||||
image.decompress()
|
||||
else:
|
||||
error = FAILED
|
||||
else:
|
||||
error = image.load(path)
|
||||
if error != OK:
|
||||
_show_message(ERROR, "Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
_show_message(INFO, "Loaded texture '" + path + "'")
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
_set_wh_labels(p_image_index, image.get_width(), image.get_height())
|
||||
if p_image_index == IMAGE_NORMAL:
|
||||
_set_normal_vector(image)
|
||||
if p_image_index == IMAGE_HEIGHT or p_image_index == IMAGE_ROUGHNESS:
|
||||
set_channel_fn.call(image.detect_used_channels())
|
||||
|
||||
var os_drop_fn: Callable = func(files: PackedStringArray) -> void:
|
||||
# OS drag drop holds mouse focus until released,
|
||||
# Get mouse pos and check directly if inside texture_rect
|
||||
var rect = texture_button.get_global_rect()
|
||||
var mouse_position = texture_button.get_global_mouse_position()
|
||||
if rect.has_point(mouse_position):
|
||||
if files.size() != 1:
|
||||
_show_message(ERROR, "Cannot load multiple files")
|
||||
else:
|
||||
line_edit.text = files[0]
|
||||
load_image_fn.call(files[0])
|
||||
|
||||
var godot_drop_fn: Callable = func(path: String) -> void:
|
||||
path = ProjectSettings.globalize_path(path)
|
||||
line_edit.text = path
|
||||
load_image_fn.call(path)
|
||||
|
||||
var open_fn: Callable = func() -> void:
|
||||
open_file_dialog.current_path = last_opened_directory
|
||||
if last_file_selected_fn != no_op:
|
||||
open_file_dialog.file_selected.disconnect(last_file_selected_fn)
|
||||
last_file_selected_fn = func(path: String) -> void:
|
||||
line_edit.text = path
|
||||
line_edit.caret_column = path.length()
|
||||
last_opened_directory = path.get_base_dir() + "/"
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
_show_success("Loaded texture '" + path + "'")
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
load_image_fn.call(path)
|
||||
open_file_dialog.file_selected.connect(last_file_selected_fn)
|
||||
open_file_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
var line_edit_submit_fn: Callable = func(path: String) -> void:
|
||||
line_edit.text = path
|
||||
load_image_fn.call(path)
|
||||
|
||||
var clear_fn: Callable = func() -> void:
|
||||
line_edit.text = ""
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
|
||||
# allow user to edit textbox and press enter because Godot's file picker doesn't work 100% of the time
|
||||
var line_edit_submit_fn: Callable = func(path: String) -> void:
|
||||
var image: Image = Image.new()
|
||||
var code: int = image.load(path)
|
||||
if code != OK:
|
||||
_show_error("Failed to load texture '" + path + "'")
|
||||
texture_rect.texture = null
|
||||
images[p_image_index] = null
|
||||
else:
|
||||
texture_rect.texture = ImageTexture.create_from_image(image)
|
||||
images[p_image_index] = image
|
||||
|
||||
_set_wh_labels(p_image_index, -1, -1)
|
||||
|
||||
line_edit.text_submitted.connect(line_edit_submit_fn)
|
||||
file_pick_button.pressed.connect(open_fn)
|
||||
texture_button.pressed.connect(open_fn)
|
||||
clear_button.pressed.connect(clear_fn)
|
||||
_set_button_icon(file_pick_button, "Folder")
|
||||
_set_button_icon(clear_button, "Remove")
|
||||
texture_button.dropped.connect(godot_drop_fn)
|
||||
window.files_dropped.connect(os_drop_fn)
|
||||
|
||||
if p_image_index == IMAGE_HEIGHT:
|
||||
var lumin_fn: Callable = func() -> void:
|
||||
if !images[IMAGE_ALBEDO]:
|
||||
_show_message(ERROR, "Albedo Image Required for Operation")
|
||||
else:
|
||||
line_edit.text = "Generated Height"
|
||||
var height_texture: Image = Terrain3DUtil.luminance_to_height(images[IMAGE_ALBEDO])
|
||||
if height_texture.is_empty():
|
||||
_show_message(ERROR, "Height Texture Generation error")
|
||||
# blur the image by resizing down and back..
|
||||
var w: int = height_texture.get_width()
|
||||
var h: int = height_texture.get_height()
|
||||
height_texture.resize(w / 4, h / 4)
|
||||
height_texture.resize(w, h, Image.INTERPOLATE_CUBIC)
|
||||
# "Load" the height texture
|
||||
images[IMAGE_HEIGHT] = height_texture
|
||||
texture_rect.texture = ImageTexture.create_from_image(images[IMAGE_HEIGHT])
|
||||
_set_wh_labels(IMAGE_HEIGHT, height_texture.get_width(), height_texture.get_height())
|
||||
set_channel_fn.call(Image.USED_CHANNELS_R)
|
||||
_show_message(INFO, "Height Texture generated sucsessfully")
|
||||
lumin_height_button.pressed.connect(lumin_fn)
|
||||
plugin.ui.set_button_editor_icon(file_pick_button, "Folder")
|
||||
plugin.ui.set_button_editor_icon(clear_button, "Remove")
|
||||
|
||||
|
||||
func _set_button_icon(p_button: Button, p_icon_name: String) -> void:
|
||||
var editor_base: Control = editor_interface.get_base_control()
|
||||
var icon: Texture2D = editor_base.get_theme_icon(p_icon_name, "EditorIcons")
|
||||
p_button.icon = icon
|
||||
func _set_wh_labels(p_image_index: int, width: int, height: int) -> void:
|
||||
var w: String = ""
|
||||
var h: String = ""
|
||||
if width > 0 and height > 0:
|
||||
w = "w: " + str(width)
|
||||
h = "h: " + str(height)
|
||||
match p_image_index:
|
||||
0:
|
||||
window.find_child("AlbedoW").text = w
|
||||
window.find_child("AlbedoH").text = h
|
||||
1:
|
||||
window.find_child("HeightW").text = w
|
||||
window.find_child("HeightH").text = h
|
||||
2:
|
||||
window.find_child("NormalW").text = w
|
||||
window.find_child("NormalH").text = h
|
||||
3:
|
||||
window.find_child("RoughnessW").text = w
|
||||
window.find_child("RoughnessH").text = h
|
||||
|
||||
|
||||
func _show_error(p_text: String) -> void:
|
||||
push_error("Terrain3DChannelPacker: " + p_text)
|
||||
func _show_message(p_level: int, p_text: String) -> void:
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
||||
|
||||
|
||||
func _show_success(p_text: String) -> void:
|
||||
print("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.text = p_text
|
||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||
match p_level:
|
||||
INFO:
|
||||
print("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0, 0.82, 0.14))
|
||||
WARN:
|
||||
push_warning("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0.9, 0))
|
||||
ERROR,_:
|
||||
push_error("Terrain3DChannelPacker: " + p_text)
|
||||
status_label.add_theme_color_override("font_color", Color(0.9, 0, 0))
|
||||
|
||||
|
||||
func _create_import_file(png_path: String) -> void:
|
||||
var dst_import_path: String = png_path + ".import"
|
||||
|
||||
var file: FileAccess = FileAccess.open(TEMPLATE_PATH, FileAccess.READ)
|
||||
var template_content: String = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var import_content: String = template_content.replace("$SOURCE_FILE", png_path)
|
||||
template_content = template_content.replace(
|
||||
"$SOURCE_FILE", png_path).replace(
|
||||
"$HIGH_QUALITY", str(high_quality_checkbox.button_pressed)).replace(
|
||||
"$GENERATE_MIPMAPS", str(generate_mipmaps_checkbox.button_pressed)
|
||||
)
|
||||
var import_content: String = template_content
|
||||
file = FileAccess.open(dst_import_path, FileAccess.WRITE)
|
||||
file.store_string(import_content)
|
||||
file.close()
|
||||
|
|
@ -159,11 +326,10 @@ func _create_import_file(png_path: String) -> void:
|
|||
func _on_pack_button_pressed() -> void:
|
||||
packing_albedo = images[IMAGE_ALBEDO] != null and images[IMAGE_HEIGHT] != null
|
||||
var packing_normal_roughness: bool = images[IMAGE_NORMAL] != null and images[IMAGE_ROUGHNESS] != null
|
||||
|
||||
|
||||
if not packing_albedo and not packing_normal_roughness:
|
||||
_show_error("Please select an albedo and height texture or a normal and roughness texture.")
|
||||
_show_message(WARN, "Please select an albedo and height texture or a normal and roughness texture")
|
||||
return
|
||||
|
||||
if packing_albedo:
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_albedo_height"
|
||||
save_file_dialog.title = "Save Packed Albedo/Height Texture"
|
||||
|
|
@ -179,34 +345,119 @@ func _on_pack_button_pressed() -> void:
|
|||
|
||||
func _on_save_file_selected(p_dst_path) -> void:
|
||||
last_saved_directory = p_dst_path.get_base_dir() + "/"
|
||||
var error: int
|
||||
if packing_albedo:
|
||||
_pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false)
|
||||
error = _pack_textures(images[IMAGE_ALBEDO], images[IMAGE_HEIGHT], p_dst_path, false,
|
||||
invert_height_checkbox.button_pressed, false, height_channel_selected)
|
||||
else:
|
||||
_pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path, invert_green_checkbox.button_pressed)
|
||||
error = _pack_textures(images[IMAGE_NORMAL], images[IMAGE_ROUGHNESS], p_dst_path,
|
||||
invert_green_checkbox.button_pressed, invert_smooth_checkbox.button_pressed,
|
||||
align_normals_checkbox.button_pressed, roughness_channel_selected)
|
||||
|
||||
if error == OK:
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
if window.visible:
|
||||
window.hide()
|
||||
await EditorInterface.get_resource_filesystem().resources_reimported
|
||||
# wait 1 extra frame, to ensure the UI is responsive.
|
||||
await RenderingServer.frame_post_draw
|
||||
window.show()
|
||||
|
||||
if queue_pack_normal_roughness:
|
||||
queue_pack_normal_roughness = false
|
||||
packing_albedo = false
|
||||
save_file_dialog.current_path = last_saved_directory + "packed_normal_roughness"
|
||||
save_file_dialog.title = "Save Packed Normal/Roughness Texture"
|
||||
|
||||
save_file_dialog.call_deferred("popup_centered_ratio")
|
||||
save_file_dialog.call_deferred("move_to_foreground")
|
||||
|
||||
|
||||
func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool) -> void:
|
||||
func _alignment_basis(normal: Vector3) -> Basis:
|
||||
var up: Vector3 = Vector3(0, 0, 1)
|
||||
var v: Vector3 = normal.cross(up)
|
||||
var c: float = normal.dot(up)
|
||||
var k: float = 1.0 / (1.0 + c)
|
||||
|
||||
var vxy: float = v.x * v.y * k
|
||||
var vxz: float = v.x * v.z * k
|
||||
var vyz: float = v.y * v.z * k
|
||||
|
||||
return Basis(Vector3(v.x * v.x * k + c, vxy - v.z, vxz + v.y),
|
||||
Vector3(vxy + v.z, v.y * v.y * k + c, vyz - v.x),
|
||||
Vector3(vxz - v.y, vyz + v.x, v.z * v.z * k + c)
|
||||
)
|
||||
|
||||
|
||||
func _set_normal_vector(source: Image, quiet: bool = false) -> void:
|
||||
# Calculate texture normal sum direction
|
||||
var normal: Image = source
|
||||
var sum: Color = Color(0.0, 0.0, 0.0, 0.0)
|
||||
for x in normal.get_height():
|
||||
for y in normal.get_width():
|
||||
sum += normal.get_pixel(x, y)
|
||||
var div: float = normal.get_height() * normal.get_width()
|
||||
sum /= Color(div, div, div)
|
||||
sum *= 2.0
|
||||
sum -= Color(1.0, 1.0, 1.0)
|
||||
normal_vector = Vector3(sum.r, sum.g, sum.b).normalized()
|
||||
if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && !quiet:
|
||||
_show_message(WARN, "Normal Texture Not Orthoganol to UV plane.\nFor Compatability with Detiling and Rotation, Select Orthoganolize Normals")
|
||||
|
||||
|
||||
func _align_normals(source: Image, iteration: int = 0) -> void:
|
||||
# generate matrix to re-align the normalmap
|
||||
var mat3: Basis = _alignment_basis(normal_vector)
|
||||
# re-align the normal map pixels
|
||||
for x in source.get_height():
|
||||
for y in source.get_width():
|
||||
var old_pixel: Color = source.get_pixel(x, y)
|
||||
var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b)
|
||||
vector_pixel *= 2.0
|
||||
vector_pixel -= Vector3.ONE
|
||||
vector_pixel = vector_pixel.normalized()
|
||||
vector_pixel = vector_pixel * mat3
|
||||
vector_pixel += Vector3.ONE
|
||||
vector_pixel *= 0.5
|
||||
var new_pixel: Color = Color(vector_pixel.x, vector_pixel.y, vector_pixel.z, old_pixel.a)
|
||||
source.set_pixel(x, y, new_pixel)
|
||||
_set_normal_vector(source, true)
|
||||
if normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999 && iteration < 3:
|
||||
++iteration
|
||||
_align_normals(source, iteration)
|
||||
|
||||
|
||||
func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_invert_green: bool,
|
||||
p_invert_smooth: bool, p_align_normals : bool, p_alpha_channel: int) -> Error:
|
||||
if p_rgb_image and p_a_image:
|
||||
if p_rgb_image.get_size() != p_a_image.get_size():
|
||||
_show_error("Textures must be the same size.")
|
||||
return
|
||||
|
||||
var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image, p_invert_green)
|
||||
|
||||
if p_rgb_image.get_size() != p_a_image.get_size() and !resize_toggle_checkbox.button_pressed:
|
||||
_show_message(ERROR, "Textures must be the same size.\nEnable resize to override image dimensions")
|
||||
return FAILED
|
||||
|
||||
if resize_toggle_checkbox.button_pressed:
|
||||
var size: int = max(128, resize_option_box.value)
|
||||
p_rgb_image.resize(size, size, Image.INTERPOLATE_CUBIC)
|
||||
p_a_image.resize(size, size, Image.INTERPOLATE_CUBIC)
|
||||
|
||||
if p_align_normals and normal_vector.dot(Vector3(0.0, 0.0, 1.0)) < 0.999:
|
||||
_align_normals(p_rgb_image)
|
||||
elif p_align_normals:
|
||||
_show_message(INFO, "Alignment OK, skipping Normal Orthogonalization")
|
||||
|
||||
var output_image: Image = Terrain3DUtil.pack_image(p_rgb_image, p_a_image,
|
||||
p_invert_green, p_invert_smooth, p_alpha_channel)
|
||||
|
||||
if not output_image:
|
||||
_show_error("Failed to pack textures.")
|
||||
return
|
||||
|
||||
_show_message(ERROR, "Failed to pack textures")
|
||||
return FAILED
|
||||
if output_image.detect_used_channels() != 5:
|
||||
_show_message(ERROR, "Packing Error, Alpha Channel empty")
|
||||
return FAILED
|
||||
|
||||
output_image.save_png(p_dst_path)
|
||||
editor_interface.get_resource_filesystem().scan_sources()
|
||||
_create_import_file(p_dst_path)
|
||||
_show_success("Packed to " + p_dst_path + ".")
|
||||
_show_message(INFO, "Packed to " + p_dst_path + ".")
|
||||
return OK
|
||||
else:
|
||||
_show_error("Failed to load one or more textures.")
|
||||
_show_message(ERROR, "Failed to load one or more textures")
|
||||
return FAILED
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://nud6dwjcnj5v"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://nud6dwjcnj5v"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
|
||||
bg_color = Color(0.211765, 0.239216, 0.290196, 1)
|
||||
|
|
@ -30,21 +30,21 @@ corner_radius_bottom_left = 5
|
|||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"]
|
||||
|
||||
[node name="AcceptDialog" type="AcceptDialog"]
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"]
|
||||
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_bs6ki"]
|
||||
|
||||
[node name="Window" type="Window"]
|
||||
title = "Terrain3D Channel Packer"
|
||||
initial_position = 1
|
||||
size = Vector2i(660, 900)
|
||||
visible = true
|
||||
ok_button_text = "Close"
|
||||
size = Vector2i(680, 835)
|
||||
unresizable = true
|
||||
always_on_top = true
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
|
||||
|
|
@ -54,10 +54,10 @@ layout_mode = 1
|
|||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = -1.0
|
||||
offset_bottom = -53.0
|
||||
offset_left = 5.0
|
||||
offset_top = 5.0
|
||||
offset_right = -5.0
|
||||
offset_bottom = 5.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
|
|
@ -67,12 +67,12 @@ theme_override_constants/margin_bottom = 5
|
|||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="AlbedoHeightPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 250)
|
||||
custom_minimum_size = Vector2(0, 290)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
|
||||
|
|
@ -147,6 +147,27 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="AlbedoWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AlbedoW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="AlbedoH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="LuminanceAsHeightButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Generate Height from Luminance"
|
||||
icon_alignment = 2
|
||||
|
||||
[node name="HeightVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
|
@ -204,8 +225,63 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="HeightWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="HeightW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HeightH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ConvertDepthToHeight" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Convert Depth to Height"
|
||||
icon_alignment = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="HeightChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Source Channel: "
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="HeightChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "R"
|
||||
|
||||
[node name="HeightChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "G"
|
||||
|
||||
[node name="HeightChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "B"
|
||||
|
||||
[node name="HeightChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_wnxik")
|
||||
text = "A"
|
||||
|
||||
[node name="NormalRoughnessPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 280)
|
||||
custom_minimum_size = Vector2(0, 290)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
|
||||
|
||||
|
|
@ -281,9 +357,33 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
[node name="NormalWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
text = "Convert DirectX to OpenGL"
|
||||
alignment = 1
|
||||
|
||||
[node name="NormalW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="NormalH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Convert DirectX to OpenGL"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AlignNormalsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Orthoganolise Normals"
|
||||
|
||||
[node name="RoughnessVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
|
@ -342,18 +442,112 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
|
||||
|
||||
[node name="NormalRoughnessHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
[node name="RoughnessWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="RoughnessW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="RoughnessH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
|
||||
layout_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="InvertSmoothCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = " Convert Smoothness to Roughness"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="RoughnessChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = " Source Channel: "
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="RoughnessChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "R"
|
||||
|
||||
[node name="RoughnessChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "G"
|
||||
|
||||
[node name="RoughnessChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "B"
|
||||
|
||||
[node name="RoughnessChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_bs6ki")
|
||||
text = "A"
|
||||
|
||||
[node name="GeneralOptionsLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "General Options"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="GeneralOptionsHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 35)
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ResizeToggle" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
text = " Resize Packed Image"
|
||||
|
||||
[node name="ResizeOptionButton" type="SpinBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
tooltip_text = "A value of 0 disables resizing."
|
||||
min_value = 128.0
|
||||
max_value = 4096.0
|
||||
step = 128.0
|
||||
value = 1024.0
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GenerateMipmapsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
button_pressed = true
|
||||
text = "Generate Mipmaps"
|
||||
|
||||
[node name="HighQualityCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
|
||||
layout_mode = 2
|
||||
text = "Import High Quality"
|
||||
|
||||
[node name="PackButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Pack textures as..."
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
custom_minimum_size = Vector2(0, 60)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
text = "Use this to create a packed Albedo + Height texture and/or a packed Normal + Roughness texture.
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 3
|
||||
|
||||
You can then use these textures with Terrain3D."
|
||||
autowrap_mode = 2
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="CloseButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[connection signal="toggled" from="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeToggle" to="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeOptionButton" method="set_visible"]
|
||||
|
|
|
|||
15
addons/terrain_3d/src/channel_packer_dragdrop.gd
Normal file
15
addons/terrain_3d/src/channel_packer_dragdrop.gd
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@tool
|
||||
extends Button
|
||||
|
||||
signal dropped
|
||||
|
||||
func _can_drop_data(p_position, p_data) -> bool:
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
if p_data.files.size() == 1:
|
||||
match p_data.files[0].get_extension():
|
||||
"png", "bmp", "exr", "hdr", "jpg", "jpeg", "tga", "svg", "webp", "ktx", "dds":
|
||||
return true
|
||||
return false
|
||||
|
||||
func _drop_data(p_position, p_data) -> void:
|
||||
dropped.emit(p_data.files[0])
|
||||
|
|
@ -14,12 +14,12 @@ source_file="$SOURCE_FILE"
|
|||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/high_quality=$HIGH_QUALITY
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=2
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=$GENERATE_MIPMAPS
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
@ -29,4 +29,4 @@ process/normal_map_invert_y=false
|
|||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
|||
116
addons/terrain_3d/src/directory_setup.gd
Normal file
116
addons/terrain_3d/src/directory_setup.gd
Normal file
|
|
@ -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()
|
||||
66
addons/terrain_3d/src/directory_setup.tscn
Normal file
66
addons/terrain_3d/src/directory_setup.tscn
Normal file
|
|
@ -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
|
||||
124
addons/terrain_3d/src/double_slider.gd
Normal file
124
addons/terrain_3d/src/double_slider.gd
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
@tool
|
||||
class_name DoubleSlider
|
||||
extends Control
|
||||
|
||||
signal value_changed(Vector2)
|
||||
var label: Label
|
||||
var suffix: String
|
||||
var grabbed_handle: int = 0 # -1 left, 0 none, 1 right
|
||||
var min_value: float = 0.0
|
||||
var max_value: float = 100.0
|
||||
var step: float = 1.0
|
||||
var range := Vector2(0, 100)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
update_label()
|
||||
|
||||
|
||||
func set_min(p_value: float) -> void:
|
||||
min_value = p_value
|
||||
if range.x <= min_value:
|
||||
range.x = min_value
|
||||
set_value(range)
|
||||
update_label()
|
||||
|
||||
|
||||
func get_min() -> float:
|
||||
return min_value
|
||||
|
||||
|
||||
func set_max(p_value: float) -> void:
|
||||
max_value = p_value
|
||||
if range.y == 0 or range.y >= max_value:
|
||||
range.y = max_value
|
||||
set_value(range)
|
||||
update_label()
|
||||
|
||||
|
||||
func get_max() -> float:
|
||||
return max_value
|
||||
|
||||
|
||||
func set_step(p_step: float) -> void:
|
||||
step = p_step
|
||||
|
||||
|
||||
func get_step() -> float:
|
||||
return step
|
||||
|
||||
|
||||
func set_value(p_range: Vector2) -> void:
|
||||
range.x = clamp(p_range.x, min_value, max_value)
|
||||
range.y = clamp(p_range.y, min_value, max_value)
|
||||
if range.y < range.x:
|
||||
var tmp: float = range.x
|
||||
range.x = range.y
|
||||
range.y = tmp
|
||||
|
||||
update_label()
|
||||
emit_signal("value_changed", Vector2(range.x, range.y))
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func get_value() -> Vector2:
|
||||
return range
|
||||
|
||||
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix)
|
||||
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
if p_event.is_pressed():
|
||||
var mid_point = (range.x + range.y) / 2.0
|
||||
var xpos: float = p_event.get_position().x * 2.0
|
||||
if xpos >= mid_point:
|
||||
grabbed_handle = 1
|
||||
else:
|
||||
grabbed_handle = -1
|
||||
set_slider(p_event.get_position().x)
|
||||
else:
|
||||
grabbed_handle = 0
|
||||
|
||||
if p_event is InputEventMouseMotion:
|
||||
if grabbed_handle != 0:
|
||||
set_slider(p_event.get_position().x)
|
||||
|
||||
|
||||
func set_slider(p_xpos: float) -> void:
|
||||
if grabbed_handle == 0:
|
||||
return
|
||||
var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value)
|
||||
if(grabbed_handle < 0):
|
||||
range.x = xpos_step
|
||||
else:
|
||||
range.y = xpos_step
|
||||
set_value(range)
|
||||
|
||||
|
||||
func _notification(p_what: int) -> void:
|
||||
if p_what == NOTIFICATION_DRAW:
|
||||
# Draw background bar
|
||||
var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
|
||||
var bg_height: float = bg.get_minimum_size().y
|
||||
var mid_y: float = (size.y - bg_height) / 2.0
|
||||
draw_style_box(bg, Rect2(Vector2(0, mid_y), Vector2(size.x, bg_height)))
|
||||
|
||||
# Draw foreground bar
|
||||
var handle: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - handle.get_size().y / 2
|
||||
var startx: float = (range.x / max_value) * size.x
|
||||
var endx: float = (range.y / max_value) * size.x #- startx
|
||||
draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height)))
|
||||
|
||||
# Draw handles, slightly in so they don't get on the outside edges
|
||||
var handle_pos: Vector2
|
||||
handle_pos.x = clamp(startx - handle.get_size().x/2, -5, size.x)
|
||||
handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10)
|
||||
draw_texture(handle, Vector2(handle_pos.x, -mid_y))
|
||||
draw_texture(handle, Vector2(handle_pos.y, -mid_y))
|
||||
|
|
@ -5,7 +5,6 @@ signal pressed
|
|||
signal value_changed
|
||||
|
||||
|
||||
const ICON_PICKER: String = "res://addons/terrain_3d/icons/picker.svg"
|
||||
const ICON_PICKER_CHECKED: String = "res://addons/terrain_3d/icons/picker_checked.svg"
|
||||
const MAX_POINTS: int = 2
|
||||
|
||||
|
|
@ -16,8 +15,8 @@ var points: PackedVector3Array
|
|||
var picking_index: int = -1
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
icon_picker = load(ICON_PICKER)
|
||||
func _enter_tree() -> void:
|
||||
icon_picker = get_theme_icon("ColorPick", "EditorIcons")
|
||||
icon_picker_checked = load(ICON_PICKER_CHECKED)
|
||||
|
||||
points.resize(MAX_POINTS)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func _redraw() -> void:
|
|||
|
||||
if show_rect:
|
||||
var modulate: Color = main_color if !use_secondary_color else secondary_color
|
||||
if abs(region_position.x) > 8 or abs(region_position.y) > 8:
|
||||
if abs(region_position.x) > Terrain3DData.REGION_MAP_SIZE*.5 or abs(region_position.y) > Terrain3DData.REGION_MAP_SIZE*.5:
|
||||
modulate = Color.GRAY
|
||||
draw_rect(Vector2(region_size,region_size)*.5 + rect_position, region_size, selection_material, modulate)
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ func _redraw() -> void:
|
|||
|
||||
draw_rect(Vector2(region_size,region_size)*.5 + grid_tile_position, region_size, material, grid_color)
|
||||
|
||||
draw_rect(Vector2.ZERO, region_size * 16.0, material, border_color)
|
||||
draw_rect(Vector2.ZERO, region_size * Terrain3DData.REGION_MAP_SIZE, material, border_color)
|
||||
|
||||
|
||||
func draw_rect(p_pos: Vector2, p_size: float, p_material: StandardMaterial3D, p_modulate: Color) -> void:
|
||||
|
|
|
|||
|
|
@ -1,68 +1,72 @@
|
|||
extends HBoxContainer
|
||||
|
||||
|
||||
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd")
|
||||
const DirectoryWizard: Script = preload("res://addons/terrain_3d/src/directory_setup.gd")
|
||||
const Packer: Script = preload("res://addons/terrain_3d/src/channel_packer.gd")
|
||||
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd")
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var menu_button: MenuButton = MenuButton.new()
|
||||
var baker: Baker = Baker.new()
|
||||
var directory_setup: DirectoryWizard = DirectoryWizard.new()
|
||||
var packer: Packer = Packer.new()
|
||||
var baker: Baker = Baker.new()
|
||||
|
||||
# These are IDs and order must be consistent with add_item and set_disabled IDs
|
||||
enum {
|
||||
MENU_DIRECTORY_SETUP,
|
||||
MENU_PACK_TEXTURES,
|
||||
MENU_SEPARATOR,
|
||||
MENU_BAKE_ARRAY_MESH,
|
||||
MENU_BAKE_OCCLUDER,
|
||||
MENU_BAKE_NAV_MESH,
|
||||
MENU_SEPARATOR,
|
||||
MENU_SEPARATOR2,
|
||||
MENU_SET_UP_NAVIGATION,
|
||||
MENU_PACK_TEXTURES,
|
||||
MENU_BAKE_NAV_MESH,
|
||||
}
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
baker.plugin = plugin
|
||||
directory_setup.plugin = plugin
|
||||
packer.plugin = plugin
|
||||
|
||||
baker.plugin = plugin
|
||||
add_child(directory_setup)
|
||||
add_child(baker)
|
||||
|
||||
menu_button.text = "Terrain3D Tools"
|
||||
menu_button.get_popup().add_item("Bake ArrayMesh", MENU_BAKE_ARRAY_MESH)
|
||||
menu_button.get_popup().add_item("Bake Occluder3D", MENU_BAKE_OCCLUDER)
|
||||
menu_button.get_popup().add_item("Bake NavMesh", MENU_BAKE_NAV_MESH)
|
||||
menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP)
|
||||
menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
||||
menu_button.get_popup().add_item("Set up Navigation", MENU_SET_UP_NAVIGATION)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
||||
menu_button.get_popup().add_item("Pack Textures", MENU_PACK_TEXTURES)
|
||||
menu_button.get_popup().add_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH)
|
||||
menu_button.get_popup().add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR2)
|
||||
menu_button.get_popup().add_item("Set up Navigation...", MENU_SET_UP_NAVIGATION)
|
||||
menu_button.get_popup().add_item("Bake NavMesh...", MENU_BAKE_NAV_MESH)
|
||||
|
||||
menu_button.get_popup().id_pressed.connect(_on_menu_pressed)
|
||||
menu_button.about_to_popup.connect(_on_menu_about_to_popup)
|
||||
add_child(menu_button)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# TODO: If packer isn't freed, Godot complains about ObjectDB instances leaked and
|
||||
# resources still in use at exit. Figure out why.
|
||||
packer.free()
|
||||
|
||||
|
||||
func _on_menu_pressed(p_id: int) -> void:
|
||||
match p_id:
|
||||
MENU_DIRECTORY_SETUP:
|
||||
directory_setup.directory_setup_popup()
|
||||
MENU_PACK_TEXTURES:
|
||||
packer.pack_textures_popup()
|
||||
MENU_BAKE_ARRAY_MESH:
|
||||
baker.bake_mesh_popup()
|
||||
MENU_BAKE_OCCLUDER:
|
||||
baker.bake_occluder_popup()
|
||||
MENU_BAKE_NAV_MESH:
|
||||
baker.bake_nav_mesh()
|
||||
MENU_SET_UP_NAVIGATION:
|
||||
baker.set_up_navigation_popup()
|
||||
MENU_PACK_TEXTURES:
|
||||
packer.pack_textures_popup()
|
||||
MENU_BAKE_NAV_MESH:
|
||||
baker.bake_nav_mesh()
|
||||
|
||||
|
||||
func _on_menu_about_to_popup() -> void:
|
||||
menu_button.get_popup().set_item_disabled(MENU_DIRECTORY_SETUP, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_BAKE_ARRAY_MESH, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_BAKE_OCCLUDER, not plugin.terrain)
|
||||
menu_button.get_popup().set_item_disabled(MENU_PACK_TEXTURES, not plugin.terrain)
|
||||
|
||||
if plugin.terrain:
|
||||
var nav_regions: Array[NavigationRegion3D] = baker.find_terrain_nav_regions(plugin.terrain)
|
||||
|
|
@ -17,13 +17,14 @@ enum SettingType {
|
|||
PICKER,
|
||||
MULTI_PICKER,
|
||||
SLIDER,
|
||||
LABEL,
|
||||
TYPE_MAX,
|
||||
}
|
||||
|
||||
const MultiPicker: Script = preload("res://addons/terrain_3d/src/multi_picker.gd")
|
||||
const DEFAULT_BRUSH: String = "circle0.exr"
|
||||
const BRUSH_PATH: String = "res://addons/terrain_3d/brushes"
|
||||
const PICKER_ICON: String = "res://addons/terrain_3d/icons/picker.svg"
|
||||
const ES_TOOL_SETTINGS: String = "terrain3d/tool_settings/"
|
||||
|
||||
# Add settings flags
|
||||
const NONE: int = 0x0
|
||||
|
|
@ -31,13 +32,14 @@ const ALLOW_LARGER: int = 0x1
|
|||
const ALLOW_SMALLER: int = 0x2
|
||||
const ALLOW_OUT_OF_BOUNDS: int = 0x3 # LARGER|SMALLER
|
||||
const NO_LABEL: int = 0x4
|
||||
const ADD_SEPARATOR: int = 0x8
|
||||
const ADD_SPACER: int = 0x10
|
||||
const ADD_SEPARATOR: int = 0x8 # Add a vertical line before this entry
|
||||
const ADD_SPACER: int = 0x10 # Add a space before this entry
|
||||
const NO_SAVE: int = 0x20 # Don't save this in EditorSettings
|
||||
|
||||
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||
var brush_preview_material: ShaderMaterial
|
||||
var select_brush_button: Button
|
||||
|
||||
var main_list: HBoxContainer
|
||||
var main_list: HFlowContainer
|
||||
var advanced_list: VBoxContainer
|
||||
var height_list: VBoxContainer
|
||||
var scale_list: VBoxContainer
|
||||
|
|
@ -47,21 +49,26 @@ var settings: Dictionary = {}
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
main_list = HBoxContainer.new()
|
||||
# Remove old editor settings
|
||||
for setting in ["lift_floor", "flatten_peaks", "lift_flatten", "automatic_regions"]:
|
||||
plugin.erase_setting(ES_TOOL_SETTINGS + setting)
|
||||
|
||||
# Setup buttons
|
||||
main_list = HFlowContainer.new()
|
||||
add_child(main_list, true)
|
||||
|
||||
## Common Settings
|
||||
add_brushes(main_list)
|
||||
|
||||
add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":50, "unit":"m",
|
||||
"range":Vector3(2, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
|
||||
add_setting({ "name":"instructions", "label":"Click the terrain to add a region. CTRL+Click to remove. Or select another tool on the left.",
|
||||
"type":SettingType.LABEL, "list":main_list, "flags":NO_LABEL|NO_SAVE })
|
||||
|
||||
add_setting({ "name":"size", "type":SettingType.SLIDER, "list":main_list, "default":20, "unit":"m",
|
||||
"range":Vector3(0.1, 200, 1), "flags":ALLOW_LARGER|ADD_SPACER })
|
||||
|
||||
add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":10,
|
||||
add_setting({ "name":"strength", "type":SettingType.SLIDER, "list":main_list, "default":33,
|
||||
"unit":"%", "range":Vector3(1, 100, 1), "flags":ALLOW_LARGER })
|
||||
|
||||
add_setting({ "name":"enable", "type":SettingType.CHECKBOX, "list":main_list, "default":true })
|
||||
|
||||
add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":50,
|
||||
add_setting({ "name":"height", "type":SettingType.SLIDER, "list":main_list, "default":20,
|
||||
"unit":"m", "range":Vector3(-500, 500, 0.1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"height_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.HEIGHT, "flags":NO_LABEL })
|
||||
|
|
@ -71,14 +78,21 @@ func _ready() -> void:
|
|||
add_setting({ "name":"color_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.COLOR, "flags":NO_LABEL })
|
||||
|
||||
add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
add_setting({ "name":"roughness", "type":SettingType.SLIDER, "list":main_list, "default":-65,
|
||||
"unit":"%", "range":Vector3(-100, 100, 1), "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"roughness_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.ROUGHNESS, "flags":NO_LABEL })
|
||||
|
||||
add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true })
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
|
||||
add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
"unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
|
||||
# Slope painting filter
|
||||
add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list, "default":Vector2(0, 90),
|
||||
"unit":"°", "range":Vector3(0, 90, 1), "flags":ADD_SEPARATOR })
|
||||
|
||||
add_setting({ "name":"enable_angle", "label":"Angle", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"angle", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
|
|
@ -88,18 +102,16 @@ func _ready() -> void:
|
|||
add_setting({ "name":"dynamic_angle", "label":"Dynamic", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":false, "flags":ADD_SPACER })
|
||||
|
||||
add_setting({ "name":"enable_scale", "label":"Scale ±", "type":SettingType.CHECKBOX,
|
||||
add_setting({ "name":"enable_scale", "label":"Scale", "type":SettingType.CHECKBOX,
|
||||
"list":main_list, "default":true, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"scale", "label":"±", "type":SettingType.SLIDER, "list":main_list, "default":0,
|
||||
"unit":"%", "range":Vector3(-60, 80, 20), "flags":NO_LABEL })
|
||||
add_setting({ "name":"scale_picker", "type":SettingType.PICKER, "list":main_list,
|
||||
"default":Terrain3DEditor.SCALE, "flags":NO_LABEL })
|
||||
|
||||
## Slope
|
||||
add_setting({ "name":"slope", "type":SettingType.DOUBLE_SLIDER, "list":main_list,
|
||||
"default":0, "unit":"°", "range":Vector3(0, 180, 1) })
|
||||
## Slope sculpting brush
|
||||
add_setting({ "name":"gradient_points", "type":SettingType.MULTI_PICKER, "label":"Points",
|
||||
"list":main_list, "default":Terrain3DEditor.HEIGHT, "flags":ADD_SEPARATOR })
|
||||
"list":main_list, "default":Terrain3DEditor.SCULPT, "flags":ADD_SEPARATOR })
|
||||
add_setting({ "name":"drawable", "type":SettingType.CHECKBOX, "list":main_list, "default":false,
|
||||
"flags":ADD_SEPARATOR })
|
||||
settings["drawable"].toggled.connect(_on_drawable_toggled)
|
||||
|
|
@ -123,9 +135,9 @@ func _ready() -> void:
|
|||
"default":0, "unit":"°", "range":Vector3(0, 360, 1) })
|
||||
add_setting({ "name":"random_spin", "type":SettingType.SLIDER, "list":rotation_list, "default":360,
|
||||
"unit":"°", "range":Vector3(0, 360, 1) })
|
||||
add_setting({ "name":"fixed_angle", "label":"Fixed Angle (From Y)", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
add_setting({ "name":"fixed_tilt", "label":"Fixed Tilt", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
"default":0, "unit":"°", "range":Vector3(-85, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"random_angle", "label":"Random Angle ±", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
add_setting({ "name":"random_tilt", "label":"Random Tilt ±", "type":SettingType.SLIDER, "list":rotation_list,
|
||||
"default":10, "unit":"°", "range":Vector3(0, 85, 1), "flags":ALLOW_OUT_OF_BOUNDS })
|
||||
add_setting({ "name":"align_to_normal", "type":SettingType.CHECKBOX, "list":rotation_list, "default":false })
|
||||
|
||||
|
|
@ -139,14 +151,17 @@ func _ready() -> void:
|
|||
#add_setting({ "name":"blend_mode", "type":SettingType.OPTION, "list":color_list, "default":0,
|
||||
#"range":Vector3(0, 3, 1) })
|
||||
|
||||
if DisplayServer.is_touchscreen_available():
|
||||
add_setting({ "name":"remove", "label":"Invert", "type":SettingType.CHECKBOX, "list":main_list, "default":false, "flags":ADD_SEPARATOR })
|
||||
|
||||
var spacer: Control = Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_list.add_child(spacer, true)
|
||||
|
||||
## Advanced Settings Menu
|
||||
advanced_list = create_submenu(main_list, "Advanced", Layout.VERTICAL)
|
||||
add_setting({ "name":"automatic_regions", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
"default":true })
|
||||
advanced_list = create_submenu(main_list, "", Layout.VERTICAL, false)
|
||||
add_setting({ "name":"auto_regions", "label":"Add regions while sculpting", "type":SettingType.CHECKBOX,
|
||||
"list":advanced_list, "default":true })
|
||||
add_setting({ "name":"align_to_view", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
"default":true })
|
||||
add_setting({ "name":"show_cursor_while_painting", "type":SettingType.CHECKBOX, "list":advanced_list,
|
||||
|
|
@ -158,23 +173,44 @@ func _ready() -> void:
|
|||
"unit":"%", "range":Vector3(0, 100, 1) })
|
||||
|
||||
|
||||
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout) -> Container:
|
||||
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container:
|
||||
var menu_button: Button = Button.new()
|
||||
menu_button.set_text(p_button_name)
|
||||
if p_button_name.is_empty():
|
||||
menu_button.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons")
|
||||
else:
|
||||
menu_button.set_text(p_button_name)
|
||||
menu_button.set_toggle_mode(true)
|
||||
menu_button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
menu_button.toggled.connect(_on_show_submenu.bind(menu_button))
|
||||
|
||||
var submenu: PopupPanel = PopupPanel.new()
|
||||
submenu.popup_hide.connect(menu_button.set_pressed_no_signal.bind(false))
|
||||
submenu.popup_hide.connect(menu_button.set_pressed.bind(false))
|
||||
var panel_style: StyleBox = get_theme_stylebox("panel", "PopupMenu").duplicate()
|
||||
panel_style.set_content_margin_all(10)
|
||||
submenu.set("theme_override_styles/panel", panel_style)
|
||||
submenu.add_to_group("terrain3d_submenus")
|
||||
|
||||
# Pop up menu on hover, hide on exit
|
||||
menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
|
||||
submenu.mouse_exited.connect(_on_show_submenu.bind(false, menu_button))
|
||||
if p_hover_pop:
|
||||
menu_button.mouse_entered.connect(_on_show_submenu.bind(true, menu_button))
|
||||
|
||||
submenu.mouse_entered.connect(func(): submenu.set_meta("mouse_entered", true))
|
||||
|
||||
submenu.mouse_exited.connect(func():
|
||||
# On mouse_exit, hide popup unless LineEdit focused
|
||||
var focused_element: Control = submenu.gui_get_focus_owner()
|
||||
if not focused_element is LineEdit:
|
||||
_on_show_submenu(false, menu_button)
|
||||
submenu.set_meta("mouse_entered", false)
|
||||
return
|
||||
|
||||
focused_element.focus_exited.connect(func():
|
||||
# Close submenu once lineedit loses focus
|
||||
if not submenu.get_meta("mouse_entered"):
|
||||
_on_show_submenu(false, menu_button)
|
||||
submenu.set_meta("mouse_entered", false)
|
||||
)
|
||||
)
|
||||
|
||||
var sublist: Container
|
||||
match(p_layout):
|
||||
|
|
@ -200,20 +236,21 @@ func _on_show_submenu(p_toggled: bool, p_button: Button) -> void:
|
|||
# Hide menu if mouse is not in button or panel
|
||||
var button_rect: Rect2 = Rect2(p_button.get_screen_transform().origin, p_button.get_global_rect().size)
|
||||
var in_button: bool = button_rect.has_point(DisplayServer.mouse_get_position())
|
||||
var panel: PopupPanel = p_button.get_child(0)
|
||||
var panel_rect: Rect2 = Rect2(panel.position, panel.size)
|
||||
var in_panel: bool = panel_rect.has_point(DisplayServer.mouse_get_position())
|
||||
if not p_toggled and ( in_button or in_panel ):
|
||||
var popup: PopupPanel = p_button.get_child(0)
|
||||
var popup_rect: Rect2 = Rect2(popup.position, popup.size)
|
||||
var in_popup: bool = popup_rect.has_point(DisplayServer.mouse_get_position())
|
||||
if not p_toggled and ( in_button or in_popup ):
|
||||
return
|
||||
|
||||
# Hide all submenus before possibly enabling the current one
|
||||
get_tree().call_group("terrain3d_submenus", "set_visible", false)
|
||||
var popup: PopupPanel = p_button.get_child(0)
|
||||
var popup_pos: Vector2 = p_button.get_screen_transform().origin
|
||||
popup.set_visible(p_toggled)
|
||||
popup_pos.y -= popup.get_size().y
|
||||
var popup_pos: Vector2 = p_button.get_screen_transform().origin
|
||||
popup_pos.y -= popup.size.y
|
||||
if popup.get_child_count()>0 and popup.get_child(0) == advanced_list:
|
||||
popup_pos.x -= popup.size.x - p_button.size.x
|
||||
popup.set_position(popup_pos)
|
||||
|
||||
|
||||
|
||||
func add_brushes(p_parent: Control) -> void:
|
||||
var brush_list: GridContainer = create_submenu(p_parent, "Brush", Layout.GRID)
|
||||
|
|
@ -231,6 +268,8 @@ func add_brushes(p_parent: Control) -> void:
|
|||
if !dir.current_is_dir() and file_name.ends_with(".exr"):
|
||||
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
||||
img = Terrain3DUtil.black_to_alpha(img)
|
||||
if img.get_width() < 1024 and img.get_height() < 1024:
|
||||
img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
|
||||
var tex: ImageTexture = ImageTexture.create_from_image(img)
|
||||
|
||||
var btn: Button = Button.new()
|
||||
|
|
@ -270,10 +309,11 @@ func add_brushes(p_parent: Control) -> void:
|
|||
|
||||
select_brush_button = brush_list.get_parent().get_parent()
|
||||
# Optionally erase the main brush button text and replace it with the texture
|
||||
# select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
|
||||
# select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
||||
# select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
# select_brush_button.set_expand_icon(true)
|
||||
select_brush_button.set_text("")
|
||||
select_brush_button.set_button_icon(default_brush_btn.get_button_icon())
|
||||
select_brush_button.set_custom_minimum_size(Vector2.ONE * 36)
|
||||
select_brush_button.set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER)
|
||||
select_brush_button.set_expand_icon(true)
|
||||
|
||||
|
||||
func _on_brush_hover(p_hovering: bool, p_button: Button) -> void:
|
||||
|
|
@ -307,13 +347,12 @@ func _on_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position:
|
|||
|
||||
|
||||
func _on_point_pick(p_type: Terrain3DEditor.Tool, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
assert(p_type == Terrain3DEditor.SCULPT)
|
||||
emit_signal("picking", p_type, _on_point_picked.bind(p_name))
|
||||
|
||||
|
||||
func _on_point_picked(p_type: Terrain3DEditor.Tool, p_color: Color, p_global_position: Vector3, p_name: String) -> void:
|
||||
assert(p_type == Terrain3DEditor.HEIGHT)
|
||||
|
||||
assert(p_type == Terrain3DEditor.SCULPT)
|
||||
var point: Vector3 = p_global_position
|
||||
point.y = p_color.r
|
||||
settings[p_name].add_point(point)
|
||||
|
|
@ -342,9 +381,22 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
var pending_children: Array[Control]
|
||||
|
||||
match p_type:
|
||||
SettingType.LABEL:
|
||||
var label := Label.new()
|
||||
label.set_text(p_label)
|
||||
pending_children.push_back(label)
|
||||
control = label
|
||||
|
||||
SettingType.CHECKBOX:
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.set_pressed_no_signal(p_default)
|
||||
if !(p_flags & NO_SAVE):
|
||||
checkbox.set_pressed_no_signal(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
checkbox.toggled.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
checkbox.set_pressed_no_signal(p_default)
|
||||
checkbox.pressed.connect(_on_setting_changed)
|
||||
pending_children.push_back(checkbox)
|
||||
control = checkbox
|
||||
|
|
@ -352,19 +404,24 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
SettingType.COLOR_SELECT:
|
||||
var picker := ColorPickerButton.new()
|
||||
picker.set_custom_minimum_size(Vector2(100, 25))
|
||||
picker.color = Color.WHITE
|
||||
picker.edit_alpha = false
|
||||
picker.get_picker().set_color_mode(ColorPicker.MODE_HSV)
|
||||
if !(p_flags & NO_SAVE):
|
||||
picker.set_pick_color(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
picker.color_changed.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
picker.set_pick_color(p_default)
|
||||
picker.color_changed.connect(_on_setting_changed)
|
||||
var popup: PopupPanel = picker.get_popup()
|
||||
popup.mouse_exited.connect(Callable(func(p): p.hide()).bind(popup))
|
||||
pending_children.push_back(picker)
|
||||
control = picker
|
||||
|
||||
SettingType.PICKER:
|
||||
var button := Button.new()
|
||||
button.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
button.icon = load(PICKER_ICON)
|
||||
button.icon = get_theme_icon("ColorPick", "EditorIcons")
|
||||
button.tooltip_text = "Pick value from the Terrain"
|
||||
button.pressed.connect(_on_pick.bind(p_default))
|
||||
pending_children.push_back(button)
|
||||
|
|
@ -397,10 +454,9 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
spin_slider.set_max(p_maximum)
|
||||
spin_slider.set_min(p_minimum)
|
||||
spin_slider.set_step(p_step)
|
||||
spin_slider.set_value(p_default)
|
||||
spin_slider.set_suffix(p_suffix)
|
||||
spin_slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
spin_slider.set_custom_minimum_size(Vector2(75, 0))
|
||||
spin_slider.set_custom_minimum_size(Vector2(65, 0))
|
||||
|
||||
# Create horizontal slider linked to the above box
|
||||
slider = HSlider.new()
|
||||
|
|
@ -409,27 +465,38 @@ func add_setting(p_args: Dictionary) -> void:
|
|||
slider.set_allow_greater(true)
|
||||
if p_flags & ALLOW_SMALLER:
|
||||
slider.set_allow_lesser(true)
|
||||
|
||||
pending_children.push_back(slider)
|
||||
pending_children.push_back(spin_slider)
|
||||
control = spin_slider
|
||||
|
||||
|
||||
else: # DOUBLE_SLIDER
|
||||
var label := Label.new()
|
||||
label.set_custom_minimum_size(Vector2(75, 0))
|
||||
label.set_custom_minimum_size(Vector2(60, 0))
|
||||
label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT)
|
||||
slider = DoubleSlider.new()
|
||||
slider.label = label
|
||||
slider.suffix = p_suffix
|
||||
slider.setting_changed.connect(_on_setting_changed)
|
||||
slider.value_changed.connect(_on_setting_changed)
|
||||
pending_children.push_back(slider)
|
||||
pending_children.push_back(label)
|
||||
control = slider
|
||||
|
||||
slider.set_max(p_maximum)
|
||||
slider.set_min(p_minimum)
|
||||
slider.set_max(p_maximum)
|
||||
slider.set_step(p_step)
|
||||
slider.set_value(p_default)
|
||||
slider.set_v_size_flags(SIZE_SHRINK_CENTER)
|
||||
slider.set_custom_minimum_size(Vector2(60, 10))
|
||||
slider.set_custom_minimum_size(Vector2(50, 10))
|
||||
|
||||
if !(p_flags & NO_SAVE):
|
||||
slider.set_value(plugin.get_setting(ES_TOOL_SETTINGS + p_name, p_default))
|
||||
slider.value_changed.connect( (
|
||||
func(value, path):
|
||||
plugin.set_setting(path, value)
|
||||
).bind(ES_TOOL_SETTINGS + p_name) )
|
||||
else:
|
||||
slider.set_value(p_default)
|
||||
|
||||
control.name = p_name.to_pascal_case()
|
||||
settings[p_name] = control
|
||||
|
|
@ -491,8 +558,8 @@ func get_setting(p_setting: String) -> Variant:
|
|||
var width: float = clamp( (1 + count_digits(value)) * 19., 50, 80) * clamp(EditorInterface.get_editor_scale(), .9, 2)
|
||||
object.set_custom_minimum_size(Vector2(width, 0))
|
||||
elif object is DoubleSlider:
|
||||
value = Vector2(object.get_min_value(), object.get_max_value())
|
||||
elif object is ButtonGroup:
|
||||
value = object.get_value()
|
||||
elif object is ButtonGroup: # "brush"
|
||||
var img: Image = object.get_pressed_button().get_meta("image")
|
||||
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
||||
value = [ img, tex ]
|
||||
|
|
@ -509,11 +576,10 @@ func get_setting(p_setting: String) -> Variant:
|
|||
|
||||
func set_setting(p_setting: String, p_value: Variant) -> void:
|
||||
var object: Object = settings.get(p_setting)
|
||||
if object is Range:
|
||||
if object is DoubleSlider: # Expects p_value is Vector2
|
||||
object.set_value(p_value)
|
||||
elif object is Range:
|
||||
object.set_value(p_value)
|
||||
elif object is DoubleSlider: # Expects p_value is Vector2
|
||||
object.set_min_value(p_value.x)
|
||||
object.set_max_value(p_value.y)
|
||||
elif object is ButtonGroup: # Expects p_value is Array [ "button name", boolean ]
|
||||
if p_value is Array and p_value.size() == 2:
|
||||
for button in object.get_buttons():
|
||||
|
|
@ -523,6 +589,7 @@ func set_setting(p_setting: String, p_value: Variant) -> void:
|
|||
object.button_pressed = p_value
|
||||
elif object is ColorPickerButton:
|
||||
object.color = p_value
|
||||
plugin.set_setting(ES_TOOL_SETTINGS + p_setting, p_value) # Signal doesn't fire on CPB
|
||||
elif object is MultiPicker: # Expects p_value is PackedVector3Array
|
||||
object.points = p_value
|
||||
_on_setting_changed(object)
|
||||
|
|
@ -548,13 +615,12 @@ func _on_setting_changed(p_data: Variant = null) -> void:
|
|||
if p_data is Button and p_data.get_parent().get_parent() is PopupPanel:
|
||||
if p_data.get_parent().name == "BrushList":
|
||||
# Optionally Set selected brush texture in main brush button
|
||||
# p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
# Hide popup
|
||||
p_data.get_parent().get_parent().set_visible(false)
|
||||
# Hide label
|
||||
if p_data.get_child_count() > 0:
|
||||
p_data.get_child(0).visible = false
|
||||
|
||||
emit_signal("setting_changed")
|
||||
|
||||
|
||||
|
|
@ -567,7 +633,6 @@ func _get_brush_preview_material() -> ShaderMaterial:
|
|||
if !brush_preview_material:
|
||||
brush_preview_material = ShaderMaterial.new()
|
||||
var shader: Shader = Shader.new()
|
||||
|
||||
var code: String = "shader_type canvas_item;\n"
|
||||
code += "varying vec4 v_vertex_color;\n"
|
||||
code += "void vertex() {\n"
|
||||
|
|
@ -578,13 +643,11 @@ func _get_brush_preview_material() -> ShaderMaterial:
|
|||
code += " COLOR.a *= pow(tex.r, 0.666);\n"
|
||||
code += " COLOR.rgb = v_vertex_color.rgb;\n"
|
||||
code += "}\n"
|
||||
|
||||
shader.set_code(code)
|
||||
brush_preview_material.set_shader(shader)
|
||||
return brush_preview_material
|
||||
|
||||
|
||||
|
||||
# Counts digits of a number including negative sign, decimal points, and up to 3 decimals
|
||||
func count_digits(p_value: float) -> int:
|
||||
var count: int = 1
|
||||
|
|
@ -605,75 +668,3 @@ func count_digits(p_value: float) -> int:
|
|||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
#### Sub Class DoubleSlider
|
||||
|
||||
class DoubleSlider extends Range:
|
||||
signal setting_changed(Vector2)
|
||||
var label: Label
|
||||
var suffix: String
|
||||
var grabbed: bool = false
|
||||
var _max_value: float
|
||||
# TODO Needs to clamp min and max values. Currently allows max slider to go negative.
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.get_button_index() == MOUSE_BUTTON_LEFT:
|
||||
grabbed = p_event.is_pressed()
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
if p_event is InputEventMouseMotion:
|
||||
if grabbed:
|
||||
set_min_max(p_event.get_position().x)
|
||||
|
||||
|
||||
func _notification(p_what: int) -> void:
|
||||
if p_what == NOTIFICATION_RESIZED:
|
||||
pass
|
||||
if p_what == NOTIFICATION_DRAW:
|
||||
var bg: StyleBox = get_theme_stylebox("slider", "HSlider")
|
||||
var bg_height: float = bg.get_minimum_size().y
|
||||
draw_style_box(bg, Rect2(Vector2(0, (size.y - bg_height) / 2), Vector2(size.x, bg_height)))
|
||||
|
||||
var grabber: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - grabber.get_size().y / 2
|
||||
|
||||
var minpos: Vector2 = Vector2((min_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
var maxpos: Vector2 = Vector2((max_value / _max_value) * size.x - grabber.get_size().x / 2, h)
|
||||
|
||||
draw_style_box(area, Rect2(Vector2(minpos.x + grabber.get_size().x / 2, (size.y - bg_height) / 2), Vector2(maxpos.x - minpos.x, bg_height)))
|
||||
|
||||
draw_texture(grabber, minpos)
|
||||
draw_texture(grabber, maxpos)
|
||||
|
||||
|
||||
func set_max(p_value: float) -> void:
|
||||
max_value = p_value
|
||||
if _max_value == 0:
|
||||
_max_value = max_value
|
||||
update_label()
|
||||
|
||||
|
||||
func set_min_max(p_xpos: float) -> void:
|
||||
var mid_value_normalized: float = ((max_value + min_value) / 2.0) / _max_value
|
||||
var mid_value: float = size.x * mid_value_normalized
|
||||
var min_active: bool = p_xpos < mid_value
|
||||
var xpos_ranged: float = snappedf((p_xpos / size.x) * _max_value, step)
|
||||
|
||||
if min_active:
|
||||
min_value = xpos_ranged
|
||||
else:
|
||||
max_value = xpos_ranged
|
||||
|
||||
min_value = clamp(min_value, 0, max_value - 10)
|
||||
max_value = clamp(max_value, min_value + 10, _max_value)
|
||||
|
||||
update_label()
|
||||
emit_signal("setting_changed", Vector2(min_value, max_value))
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
label.set_text(str(min_value) + suffix + "/" + str(max_value) + suffix)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
extends VBoxContainer
|
||||
|
||||
|
||||
extends VFlowContainer
|
||||
|
||||
signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation)
|
||||
|
||||
const ICON_REGION_ADD: String = "res://addons/terrain_3d/icons/region_add.svg"
|
||||
const ICON_REGION_REMOVE: String = "res://addons/terrain_3d/icons/region_remove.svg"
|
||||
const ICON_HEIGHT_ADD: String = "res://addons/terrain_3d/icons/height_add.svg"
|
||||
const ICON_HEIGHT_SUB: String = "res://addons/terrain_3d/icons/height_sub.svg"
|
||||
const ICON_HEIGHT_MUL: String = "res://addons/terrain_3d/icons/height_mul.svg"
|
||||
const ICON_HEIGHT_DIV: String = "res://addons/terrain_3d/icons/height_div.svg"
|
||||
const ICON_HEIGHT_FLAT: String = "res://addons/terrain_3d/icons/height_flat.svg"
|
||||
const ICON_HEIGHT_SLOPE: String = "res://addons/terrain_3d/icons/height_slope.svg"
|
||||
const ICON_HEIGHT_SMOOTH: String = "res://addons/terrain_3d/icons/height_smooth.svg"
|
||||
|
|
@ -21,57 +18,126 @@ const ICON_HOLES: String = "res://addons/terrain_3d/icons/holes.svg"
|
|||
const ICON_NAVIGATION: String = "res://addons/terrain_3d/icons/navigation.svg"
|
||||
const ICON_INSTANCER: String = "res://addons/terrain_3d/icons/multimesh.svg"
|
||||
|
||||
var tool_group: ButtonGroup = ButtonGroup.new()
|
||||
var add_tool_group: ButtonGroup = ButtonGroup.new()
|
||||
var sub_tool_group: ButtonGroup = ButtonGroup.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
set_custom_minimum_size(Vector2(20, 0))
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
tool_group.connect("pressed", _on_tool_selected)
|
||||
add_tool_group.connect("pressed", _on_tool_selected)
|
||||
sub_tool_group.connect("pressed", _on_tool_selected)
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.REGION,
|
||||
"add_text":"Add Region", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD,
|
||||
"sub_text":"Remove Region", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_REGION_REMOVE })
|
||||
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.ADD, "Add Region", load(ICON_REGION_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.REGION, Terrain3DEditor.SUBTRACT, "Remove Region", load(ICON_REGION_REMOVE), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.ADD, "Raise", load(ICON_HEIGHT_ADD), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.SUBTRACT, "Lower", load(ICON_HEIGHT_SUB), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.MULTIPLY, "Expand (Away from 0)", load(ICON_HEIGHT_MUL), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.DIVIDE, "Reduce (Towards 0)", load(ICON_HEIGHT_DIV), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.REPLACE, "Flatten", load(ICON_HEIGHT_FLAT), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.GRADIENT, "Slope", load(ICON_HEIGHT_SLOPE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.HEIGHT, Terrain3DEditor.AVERAGE, "Smooth", load(ICON_HEIGHT_SMOOTH), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE, "Paint Base Texture", load(ICON_PAINT_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.TEXTURE, Terrain3DEditor.ADD, "Spray Overlay Texture", load(ICON_SPRAY_TEXTURE), tool_group)
|
||||
add_tool_button(Terrain3DEditor.AUTOSHADER, Terrain3DEditor.REPLACE, "Autoshader", load(ICON_AUTOSHADER), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.COLOR, Terrain3DEditor.REPLACE, "Paint Color", load(ICON_COLOR), tool_group)
|
||||
add_tool_button(Terrain3DEditor.ROUGHNESS, Terrain3DEditor.REPLACE, "Paint Wetness", load(ICON_WETNESS), tool_group)
|
||||
add_child(HSeparator.new())
|
||||
add_tool_button(Terrain3DEditor.HOLES, Terrain3DEditor.REPLACE, "Create Holes", load(ICON_HOLES), tool_group)
|
||||
add_tool_button(Terrain3DEditor.NAVIGATION, Terrain3DEditor.REPLACE, "Paint Navigable Area", load(ICON_NAVIGATION), tool_group)
|
||||
add_tool_button(Terrain3DEditor.INSTANCER, Terrain3DEditor.ADD, "Instance Meshes", load(ICON_INSTANCER), tool_group)
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Raise", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_ADD,
|
||||
"sub_text":"Lower", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_SUB })
|
||||
|
||||
var buttons: Array[BaseButton] = tool_group.get_buttons()
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Smooth", "add_op":Terrain3DEditor.AVERAGE, "add_icon":ICON_HEIGHT_SMOOTH })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.HEIGHT,
|
||||
"add_text":"Height", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HEIGHT_FLAT,
|
||||
"sub_text":"Zero", "sub_op":Terrain3DEditor.SUBTRACT, "sub_icon":ICON_HEIGHT_FLAT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.SCULPT,
|
||||
"add_text":"Slope", "add_op":Terrain3DEditor.GRADIENT, "add_icon":ICON_HEIGHT_SLOPE })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
|
||||
"add_text":"Paint Base Texture", "add_op":Terrain3DEditor.REPLACE, "add_icon":ICON_PAINT_TEXTURE })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.TEXTURE,
|
||||
"add_text":"Spray Overlay Texture", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_SPRAY_TEXTURE })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.AUTOSHADER,
|
||||
"add_text":"Enable Autoshader", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_AUTOSHADER,
|
||||
"sub_text":"Disable Autoshader", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.COLOR,
|
||||
"add_text":"Paint Color", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_COLOR,
|
||||
"sub_text":"Remove Color", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.ROUGHNESS,
|
||||
"add_text":"Paint Wetness", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_WETNESS,
|
||||
"sub_text":"Remove Wetness", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_child(HSeparator.new())
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.HOLES,
|
||||
"add_text":"Add Holes", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_HOLES,
|
||||
"sub_text":"Remove Holes", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.NAVIGATION,
|
||||
"add_text":"Paint Navigable Area", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_NAVIGATION,
|
||||
"sub_text":"Remove Navigable Area", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.INSTANCER,
|
||||
"add_text":"Instance Meshes", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_INSTANCER,
|
||||
"sub_text":"Remove Meshes", "sub_op":Terrain3DEditor.SUBTRACT })
|
||||
|
||||
# Select first button
|
||||
var buttons: Array[BaseButton] = add_tool_group.get_buttons()
|
||||
buttons[0].set_pressed(true)
|
||||
show_add_buttons(true)
|
||||
|
||||
|
||||
func add_tool_button(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation,
|
||||
p_tip: String, p_icon: Texture2D, p_group: ButtonGroup) -> void:
|
||||
|
||||
var button: Button = Button.new()
|
||||
button.set_name(p_tip.to_pascal_case())
|
||||
button.set_meta("Tool", p_tool)
|
||||
button.set_meta("Operation", p_operation)
|
||||
button.set_tooltip_text(p_tip)
|
||||
button.set_button_icon(p_icon)
|
||||
button.set_button_group(p_group)
|
||||
func add_tool_button(p_params: Dictionary) -> void:
|
||||
# Additive button
|
||||
var button := Button.new()
|
||||
button.set_name(p_params.get("add_text", "blank").to_pascal_case())
|
||||
button.set_meta("Tool", p_params.get("tool", 0))
|
||||
button.set_meta("Operation", p_params.get("add_op", 0))
|
||||
button.set_meta("ID", add_tool_group.get_buttons().size() + 1)
|
||||
button.set_tooltip_text(p_params.get("add_text", "blank"))
|
||||
button.set_button_icon(load(p_params.get("add_icon")))
|
||||
button.set_flat(true)
|
||||
button.set_toggle_mode(true)
|
||||
button.set_h_size_flags(SIZE_SHRINK_END)
|
||||
add_child(button)
|
||||
button.set_button_group(p_params.get("group", add_tool_group))
|
||||
add_child(button, true)
|
||||
|
||||
# Subtractive button
|
||||
var button2: Button
|
||||
if p_params.has("sub_text"):
|
||||
button2 = Button.new()
|
||||
button2.set_name(p_params.get("sub_text", "blank").to_pascal_case())
|
||||
button2.set_meta("Tool", p_params.get("tool", 0))
|
||||
button2.set_meta("Operation", p_params.get("sub_op", 0))
|
||||
button2.set_meta("ID", button.get_meta("ID"))
|
||||
button2.set_tooltip_text(p_params.get("sub_text", "blank"))
|
||||
button2.set_button_icon(load(p_params.get("sub_icon", p_params.get("add_icon"))))
|
||||
button2.set_flat(true)
|
||||
button2.set_toggle_mode(true)
|
||||
button2.set_h_size_flags(SIZE_SHRINK_END)
|
||||
else:
|
||||
button2 = button.duplicate()
|
||||
button2.set_button_group(p_params.get("group", sub_tool_group))
|
||||
add_child(button2, true)
|
||||
|
||||
|
||||
func show_add_buttons(p_enable: bool) -> void:
|
||||
for button in add_tool_group.get_buttons():
|
||||
button.visible = p_enable
|
||||
for button in sub_tool_group.get_buttons():
|
||||
button.visible = !p_enable
|
||||
|
||||
|
||||
func _on_tool_selected(p_button: BaseButton) -> void:
|
||||
emit_signal("tool_changed", p_button.get_meta("Tool", -1), p_button.get_meta("Operation", -1))
|
||||
# Select same tool on negative bar
|
||||
var group: ButtonGroup = p_button.get_button_group()
|
||||
var change_group: ButtonGroup = add_tool_group if group == sub_tool_group else sub_tool_group
|
||||
var id: int = p_button.get_meta("ID", -2)
|
||||
for button in change_group.get_buttons():
|
||||
button.set_pressed_no_signal(button.get_meta("ID", -1) == id)
|
||||
|
||||
emit_signal("tool_changed", p_button.get_meta("Tool", Terrain3DEditor.TOOL_MAX), p_button.get_meta("Operation", Terrain3DEditor.OP_MAX))
|
||||
|
|
|
|||
|
|
@ -5,34 +5,38 @@ extends Node
|
|||
# Includes
|
||||
const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
|
||||
const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd")
|
||||
const TerrainTools: Script = preload("res://addons/terrain_3d/src/terrain_tools.gd")
|
||||
const TerrainMenu: Script = preload("res://addons/terrain_3d/src/terrain_menu.gd")
|
||||
const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd")
|
||||
const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
|
||||
const COLOR_RAISE := Color.WHITE
|
||||
const COLOR_LOWER := Color.BLACK
|
||||
const COLOR_LOWER := Color(.02, .02, .02)
|
||||
const COLOR_SMOOTH := Color(0.5, 0, .1)
|
||||
const COLOR_EXPAND := Color.ORANGE
|
||||
const COLOR_REDUCE := Color.BLUE_VIOLET
|
||||
const COLOR_FLATTEN := Color(0., 0.32, .4)
|
||||
const COLOR_LIFT := Color.ORANGE
|
||||
const COLOR_FLATTEN := Color.BLUE_VIOLET
|
||||
const COLOR_HEIGHT := Color(0., 0.32, .4)
|
||||
const COLOR_SLOPE := Color.YELLOW
|
||||
const COLOR_PAINT := Color.FOREST_GREEN
|
||||
const COLOR_SPRAY := Color.SEA_GREEN
|
||||
const COLOR_PAINT := Color.DARK_GREEN
|
||||
const COLOR_SPRAY := Color.PALE_GREEN
|
||||
const COLOR_ROUGHNESS := Color.ROYAL_BLUE
|
||||
const COLOR_AUTOSHADER := Color.DODGER_BLUE
|
||||
const COLOR_HOLES := Color.BLACK
|
||||
const COLOR_NAVIGATION := Color.REBECCA_PURPLE
|
||||
const COLOR_NAVIGATION := Color(.15, .0, .255)
|
||||
const COLOR_INSTANCER := Color.CRIMSON
|
||||
const COLOR_PICK_COLOR := Color.WHITE
|
||||
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
||||
const COLOR_PICK_ROUGH := Color.ROYAL_BLUE
|
||||
|
||||
const OP_NONE: int = 0x0
|
||||
const OP_POSITIVE_ONLY: int = 0x01
|
||||
const OP_NEGATIVE_ONLY: int = 0x02
|
||||
|
||||
const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr"
|
||||
@onready var ring_texture := ImageTexture.create_from_image(Terrain3DUtil.black_to_alpha(Image.load_from_file(RING1)))
|
||||
|
||||
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
|
||||
var toolbar: Toolbar
|
||||
var toolbar_settings: ToolSettings
|
||||
var terrain_tools: TerrainTools
|
||||
var tool_settings: ToolSettings
|
||||
var terrain_menu: TerrainMenu
|
||||
var setting_has_changed: bool = false
|
||||
var visible: bool = false
|
||||
var picking: int = Terrain3DEditor.TOOL_MAX
|
||||
|
|
@ -42,6 +46,16 @@ var decal_timer: Timer
|
|||
var gradient_decals: Array[Decal]
|
||||
var brush_data: Dictionary
|
||||
var operation_builder: OperationBuilder
|
||||
var last_tool: Terrain3DEditor.Tool
|
||||
var last_operation: Terrain3DEditor.Operation
|
||||
var last_rmb_time: int = 0 # Set in editor.gd
|
||||
|
||||
# Compatibility decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
|
||||
var editor_decal_position: Array[Vector2]
|
||||
var editor_decal_rotation: Array[float]
|
||||
var editor_decal_size: Array[float]
|
||||
var editor_decal_color: Array[Color]
|
||||
var editor_decal_visible: Array[bool]
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
|
|
@ -49,18 +63,19 @@ func _enter_tree() -> void:
|
|||
toolbar.hide()
|
||||
toolbar.connect("tool_changed", _on_tool_changed)
|
||||
|
||||
toolbar_settings = ToolSettings.new()
|
||||
toolbar_settings.connect("setting_changed", _on_setting_changed)
|
||||
toolbar_settings.connect("picking", _on_picking)
|
||||
toolbar_settings.hide()
|
||||
tool_settings = ToolSettings.new()
|
||||
tool_settings.connect("setting_changed", _on_setting_changed)
|
||||
tool_settings.connect("picking", _on_picking)
|
||||
tool_settings.plugin = plugin
|
||||
tool_settings.hide()
|
||||
|
||||
terrain_tools = TerrainTools.new()
|
||||
terrain_tools.plugin = plugin
|
||||
terrain_tools.hide()
|
||||
terrain_menu = TerrainMenu.new()
|
||||
terrain_menu.plugin = plugin
|
||||
terrain_menu.hide()
|
||||
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_tools)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
|
||||
plugin.add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, terrain_menu)
|
||||
|
||||
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
|
||||
|
||||
|
|
@ -77,10 +92,10 @@ func _enter_tree() -> void:
|
|||
|
||||
func _exit_tree() -> void:
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, toolbar)
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, toolbar_settings)
|
||||
plugin.remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_BOTTOM, tool_settings)
|
||||
toolbar.queue_free()
|
||||
toolbar_settings.queue_free()
|
||||
terrain_tools.queue_free()
|
||||
tool_settings.queue_free()
|
||||
terrain_menu.queue_free()
|
||||
decal.queue_free()
|
||||
decal_timer.queue_free()
|
||||
for gradient_decal in gradient_decals:
|
||||
|
|
@ -88,14 +103,27 @@ func _exit_tree() -> void:
|
|||
gradient_decals.clear()
|
||||
|
||||
|
||||
func set_visible(p_visible: bool) -> void:
|
||||
visible = p_visible
|
||||
terrain_tools.set_visible(p_visible)
|
||||
toolbar.set_visible(p_visible)
|
||||
toolbar_settings.set_visible(p_visible)
|
||||
update_decal()
|
||||
func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
|
||||
terrain_menu.set_visible(p_visible)
|
||||
|
||||
if p_menu_only:
|
||||
toolbar.set_visible(false)
|
||||
tool_settings.set_visible(false)
|
||||
else:
|
||||
visible = p_visible
|
||||
toolbar.set_visible(p_visible)
|
||||
tool_settings.set_visible(p_visible)
|
||||
update_decal()
|
||||
|
||||
if(plugin.editor):
|
||||
if(p_visible):
|
||||
await get_tree().create_timer(.01).timeout # Won't work, otherwise.
|
||||
_on_tool_changed(last_tool, last_operation)
|
||||
else:
|
||||
plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX)
|
||||
plugin.editor.set_operation(Terrain3DEditor.OP_MAX)
|
||||
|
||||
|
||||
func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
|
||||
if p_list:
|
||||
p_list.get_parent().get_parent().visible = p_visible
|
||||
|
|
@ -103,33 +131,45 @@ func set_menu_visibility(p_list: Control, p_visible: bool) -> void:
|
|||
|
||||
func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) -> void:
|
||||
clear_picking()
|
||||
set_menu_visibility(toolbar_settings.advanced_list, true)
|
||||
set_menu_visibility(toolbar_settings.scale_list, false)
|
||||
set_menu_visibility(toolbar_settings.rotation_list, false)
|
||||
set_menu_visibility(toolbar_settings.height_list, false)
|
||||
set_menu_visibility(toolbar_settings.color_list, false)
|
||||
set_menu_visibility(tool_settings.advanced_list, true)
|
||||
set_menu_visibility(tool_settings.scale_list, false)
|
||||
set_menu_visibility(tool_settings.rotation_list, false)
|
||||
set_menu_visibility(tool_settings.height_list, false)
|
||||
set_menu_visibility(tool_settings.color_list, false)
|
||||
|
||||
# Select which settings to show. Options in tool_settings.gd:_ready
|
||||
var to_show: PackedStringArray = []
|
||||
|
||||
match p_tool:
|
||||
Terrain3DEditor.REGION:
|
||||
to_show.push_back("instructions")
|
||||
to_show.push_back("remove")
|
||||
set_menu_visibility(tool_settings.advanced_list, false)
|
||||
|
||||
Terrain3DEditor.SCULPT:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
if p_operation in [Terrain3DEditor.ADD, Terrain3DEditor.SUBTRACT]:
|
||||
to_show.push_back("remove")
|
||||
elif p_operation == Terrain3DEditor.GRADIENT:
|
||||
to_show.push_back("gradient_points")
|
||||
to_show.push_back("drawable")
|
||||
|
||||
Terrain3DEditor.HEIGHT:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
if p_operation == Terrain3DEditor.REPLACE:
|
||||
to_show.push_back("height")
|
||||
to_show.push_back("height_picker")
|
||||
if p_operation == Terrain3DEditor.GRADIENT:
|
||||
to_show.push_back("gradient_points")
|
||||
to_show.push_back("drawable")
|
||||
|
||||
Terrain3DEditor.TEXTURE:
|
||||
to_show.push_back("height")
|
||||
to_show.push_back("height_picker")
|
||||
|
||||
Terrain3DEditor.TEXTURE:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("enable_texture")
|
||||
if p_operation == Terrain3DEditor.ADD:
|
||||
to_show.push_back("strength")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_angle")
|
||||
to_show.push_back("angle")
|
||||
to_show.push_back("angle_picker")
|
||||
|
|
@ -144,6 +184,10 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
to_show.push_back("strength")
|
||||
to_show.push_back("color")
|
||||
to_show.push_back("color_picker")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_texture")
|
||||
to_show.push_back("margin")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
to_show.push_back("brush")
|
||||
|
|
@ -151,52 +195,59 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
to_show.push_back("strength")
|
||||
to_show.push_back("roughness")
|
||||
to_show.push_back("roughness_picker")
|
||||
to_show.push_back("slope")
|
||||
to_show.push_back("enable_texture")
|
||||
to_show.push_back("margin")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.AUTOSHADER, Terrain3DEditor.HOLES, Terrain3DEditor.NAVIGATION:
|
||||
to_show.push_back("brush")
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("enable")
|
||||
to_show.push_back("remove")
|
||||
|
||||
Terrain3DEditor.INSTANCER:
|
||||
to_show.push_back("size")
|
||||
to_show.push_back("strength")
|
||||
to_show.push_back("enable")
|
||||
set_menu_visibility(toolbar_settings.height_list, true)
|
||||
to_show.push_back("slope")
|
||||
set_menu_visibility(tool_settings.height_list, true)
|
||||
to_show.push_back("height_offset")
|
||||
to_show.push_back("random_height")
|
||||
set_menu_visibility(toolbar_settings.scale_list, true)
|
||||
set_menu_visibility(tool_settings.scale_list, true)
|
||||
to_show.push_back("fixed_scale")
|
||||
to_show.push_back("random_scale")
|
||||
set_menu_visibility(toolbar_settings.rotation_list, true)
|
||||
set_menu_visibility(tool_settings.rotation_list, true)
|
||||
to_show.push_back("fixed_spin")
|
||||
to_show.push_back("random_spin")
|
||||
to_show.push_back("fixed_angle")
|
||||
to_show.push_back("random_angle")
|
||||
to_show.push_back("fixed_tilt")
|
||||
to_show.push_back("random_tilt")
|
||||
to_show.push_back("align_to_normal")
|
||||
set_menu_visibility(toolbar_settings.color_list, true)
|
||||
set_menu_visibility(tool_settings.color_list, true)
|
||||
to_show.push_back("vertex_color")
|
||||
to_show.push_back("random_darken")
|
||||
to_show.push_back("random_hue")
|
||||
to_show.push_back("remove")
|
||||
|
||||
_:
|
||||
pass
|
||||
|
||||
# Advanced menu settings
|
||||
to_show.push_back("automatic_regions")
|
||||
to_show.push_back("auto_regions")
|
||||
to_show.push_back("align_to_view")
|
||||
to_show.push_back("show_cursor_while_painting")
|
||||
to_show.push_back("gamma")
|
||||
to_show.push_back("jitter")
|
||||
toolbar_settings.show_settings(to_show)
|
||||
tool_settings.show_settings(to_show)
|
||||
|
||||
operation_builder = null
|
||||
if p_operation == Terrain3DEditor.GRADIENT:
|
||||
operation_builder = GradientOperationBuilder.new()
|
||||
operation_builder.tool_settings = toolbar_settings
|
||||
operation_builder.tool_settings = tool_settings
|
||||
|
||||
if plugin.editor:
|
||||
plugin.editor.set_tool(p_tool)
|
||||
plugin.editor.set_operation(p_operation)
|
||||
plugin.editor.set_operation(_modify_operation(p_operation))
|
||||
last_tool = p_tool
|
||||
last_operation = p_operation
|
||||
|
||||
_on_setting_changed()
|
||||
plugin.update_region_grid()
|
||||
|
|
@ -205,31 +256,67 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
|
|||
func _on_setting_changed() -> void:
|
||||
if not plugin.asset_dock:
|
||||
return
|
||||
brush_data = toolbar_settings.get_settings()
|
||||
brush_data = tool_settings.get_settings()
|
||||
brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
|
||||
update_decal()
|
||||
plugin.editor.set_brush_data(brush_data)
|
||||
plugin.editor.set_operation(_modify_operation(plugin.editor.get_operation()))
|
||||
|
||||
|
||||
func update_modifiers() -> void:
|
||||
toolbar.show_add_buttons(not plugin.modifier_ctrl)
|
||||
|
||||
if plugin.modifier_shift and not plugin.modifier_ctrl:
|
||||
plugin.editor.set_tool(Terrain3DEditor.SCULPT)
|
||||
plugin.editor.set_operation(Terrain3DEditor.AVERAGE)
|
||||
else:
|
||||
plugin.editor.set_tool(last_tool)
|
||||
if plugin.modifier_ctrl:
|
||||
plugin.editor.set_operation(_modify_operation(last_operation))
|
||||
else:
|
||||
plugin.editor.set_operation(last_operation)
|
||||
|
||||
|
||||
func _modify_operation(p_operation: Terrain3DEditor.Operation) -> Terrain3DEditor.Operation:
|
||||
var remove_checked: bool = false
|
||||
if DisplayServer.is_touchscreen_available():
|
||||
var removable_tools := [Terrain3DEditor.REGION, Terrain3DEditor.SCULPT, Terrain3DEditor.HEIGHT, Terrain3DEditor.AUTOSHADER,
|
||||
Terrain3DEditor.HOLES, Terrain3DEditor.INSTANCER, Terrain3DEditor.NAVIGATION,
|
||||
Terrain3DEditor.COLOR, Terrain3DEditor.ROUGHNESS]
|
||||
remove_checked = brush_data.get("remove", false) && plugin.editor.get_tool() in removable_tools
|
||||
|
||||
if plugin.modifier_ctrl or remove_checked:
|
||||
return _invert_operation(p_operation, OP_NEGATIVE_ONLY)
|
||||
return _invert_operation(p_operation, OP_POSITIVE_ONLY)
|
||||
|
||||
|
||||
func _invert_operation(p_operation: Terrain3DEditor.Operation, flags: int = OP_NONE) -> Terrain3DEditor.Operation:
|
||||
if p_operation == Terrain3DEditor.ADD and ! (flags & OP_POSITIVE_ONLY):
|
||||
return Terrain3DEditor.SUBTRACT
|
||||
elif p_operation == Terrain3DEditor.SUBTRACT and ! (flags & OP_NEGATIVE_ONLY):
|
||||
return Terrain3DEditor.ADD
|
||||
return p_operation
|
||||
|
||||
|
||||
func update_decal() -> void:
|
||||
var mouse_buttons: int = Input.get_mouse_button_mask()
|
||||
# If not a state that should show the decal, hide everything and return
|
||||
if not visible or \
|
||||
not plugin.terrain or \
|
||||
brush_data.is_empty() or \
|
||||
mouse_buttons & MOUSE_BUTTON_RIGHT or \
|
||||
(mouse_buttons & MOUSE_BUTTON_LEFT and not brush_data["show_cursor_while_painting"]) or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION:
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
return
|
||||
else:
|
||||
# Wait for cursor to recenter after right-click before revealing
|
||||
not plugin.terrain or \
|
||||
plugin._input_mode < 0 or \
|
||||
# Wait for cursor to recenter after moving camera before revealing
|
||||
# See https://github.com/godotengine/godot/issues/70098
|
||||
await get_tree().create_timer(.05).timeout
|
||||
decal.visible = true
|
||||
Time.get_ticks_msec() - last_rmb_time <= 30 or \
|
||||
brush_data.is_empty() or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION or \
|
||||
(plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]):
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
return
|
||||
|
||||
decal.size = Vector3.ONE * brush_data["size"]
|
||||
decal.position = plugin.mouse_global_position
|
||||
decal.visible = true
|
||||
decal.size = Vector3.ONE * maxf(brush_data["size"], .5)
|
||||
if brush_data["align_to_view"]:
|
||||
var cam: Camera3D = plugin.terrain.get_camera();
|
||||
if (cam):
|
||||
|
|
@ -240,7 +327,7 @@ func update_decal() -> void:
|
|||
# Set texture and color
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_PICK_HEIGHT
|
||||
|
|
@ -252,62 +339,66 @@ func update_decal() -> void:
|
|||
else:
|
||||
decal.texture_albedo = brush_data["brush"][1]
|
||||
match plugin.editor.get_tool():
|
||||
Terrain3DEditor.HEIGHT:
|
||||
Terrain3DEditor.SCULPT:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_RAISE
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_LIFT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
else:
|
||||
decal.modulate = COLOR_RAISE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_LOWER
|
||||
Terrain3DEditor.MULTIPLY:
|
||||
decal.modulate = COLOR_EXPAND
|
||||
Terrain3DEditor.DIVIDE:
|
||||
decal.modulate = COLOR_REDUCE
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
else:
|
||||
decal.modulate = COLOR_LOWER
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .5
|
||||
Terrain3DEditor.AVERAGE:
|
||||
decal.modulate = COLOR_SMOOTH
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .2
|
||||
Terrain3DEditor.GRADIENT:
|
||||
decal.modulate = COLOR_SLOPE
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_HEIGHT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.TEXTURE:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .7
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_SPRAY
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()*.5
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()
|
||||
decal.modulate.a *= clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_ROUGHNESS
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.AUTOSHADER:
|
||||
decal.modulate = COLOR_AUTOSHADER
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .7
|
||||
Terrain3DEditor.HOLES:
|
||||
decal.modulate = COLOR_HOLES
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .85
|
||||
Terrain3DEditor.NAVIGATION:
|
||||
decal.modulate = COLOR_NAVIGATION
|
||||
decal.modulate.a = 1.0
|
||||
decal.modulate.a = .85
|
||||
Terrain3DEditor.INSTANCER:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.modulate = COLOR_INSTANCER
|
||||
decal.modulate.a = 1.0
|
||||
_:
|
||||
decal.modulate = Color.WHITE
|
||||
decal.modulate.a = max(.3, brush_data["strength"] * .01)
|
||||
decal.size.y = max(1000, decal.size.y)
|
||||
decal.albedo_mix = 1.0
|
||||
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
||||
decal_timer.start()
|
||||
|
||||
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
|
||||
|
|
@ -320,6 +411,8 @@ func update_decal() -> void:
|
|||
point_decal.position = point
|
||||
index += 1
|
||||
|
||||
update_compatibility_decal()
|
||||
|
||||
|
||||
func _get_gradient_decal(index: int) -> Decal:
|
||||
if gradient_decals.size() > index:
|
||||
|
|
@ -329,7 +422,7 @@ func _get_gradient_decal(index: int) -> Decal:
|
|||
gradient_decal = Decal.new()
|
||||
gradient_decal.texture_albedo = ring_texture
|
||||
gradient_decal.modulate = COLOR_SLOPE
|
||||
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_mesh_vertex_spacing()
|
||||
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
|
||||
gradient_decal.size.y = 1000.
|
||||
gradient_decal.cull_mask = decal.cull_mask
|
||||
add_child(gradient_decal)
|
||||
|
|
@ -338,6 +431,61 @@ func _get_gradient_decal(index: int) -> Decal:
|
|||
return gradient_decal
|
||||
|
||||
|
||||
func update_compatibility_decal() -> void:
|
||||
if not plugin.terrain.is_compatibility_mode():
|
||||
return
|
||||
|
||||
# Verify setup
|
||||
if editor_decal_position.size() != 3:
|
||||
editor_decal_position.resize(3)
|
||||
editor_decal_rotation.resize(3)
|
||||
editor_decal_size.resize(3)
|
||||
editor_decal_color.resize(3)
|
||||
editor_decal_visible.resize(3)
|
||||
decal_timer.timeout.connect(func():
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
editor_decal_visible[0] = false
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
)
|
||||
|
||||
# Update compatibility decal
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
if decal.visible:
|
||||
editor_decal_position[0] = Vector2(decal.global_position.x, decal.global_position.z)
|
||||
editor_decal_rotation[0] = decal.rotation.y
|
||||
editor_decal_size[0] = brush_data.get("size")
|
||||
editor_decal_color[0] = decal.modulate
|
||||
editor_decal_visible[0] = decal.visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_0", decal.texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 1:
|
||||
editor_decal_position[1] = Vector2(gradient_decals[0].global_position.x,
|
||||
gradient_decals[0].global_position.z)
|
||||
editor_decal_rotation[1] = gradient_decals[0].rotation.y
|
||||
editor_decal_size[1] = 10.0
|
||||
editor_decal_color[1] = gradient_decals[0].modulate
|
||||
editor_decal_visible[1] = gradient_decals[0].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_1", gradient_decals[0].texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 2:
|
||||
editor_decal_position[2] = Vector2(gradient_decals[1].global_position.x,
|
||||
gradient_decals[1].global_position.z)
|
||||
editor_decal_rotation[2] = gradient_decals[1].rotation.y
|
||||
editor_decal_size[2] = 10.0
|
||||
editor_decal_color[2] = gradient_decals[1].modulate
|
||||
editor_decal_visible[2] = gradient_decals[1].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_2", gradient_decals[1].texture_albedo.get_rid()
|
||||
)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
|
||||
|
||||
func set_decal_rotation(p_rot: float) -> void:
|
||||
decal.rotation.y = p_rot
|
||||
|
||||
|
|
@ -366,16 +514,16 @@ func pick(p_global_position: Vector3) -> void:
|
|||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
var color: Color
|
||||
match picking:
|
||||
Terrain3DEditor.HEIGHT:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_HEIGHT, p_global_position)
|
||||
Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT:
|
||||
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_HEIGHT, p_global_position)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
color = plugin.terrain.get_storage().get_pixel(Terrain3DStorage.TYPE_COLOR, p_global_position)
|
||||
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position)
|
||||
Terrain3DEditor.COLOR:
|
||||
color = plugin.terrain.get_storage().get_color(p_global_position)
|
||||
color = plugin.terrain.data.get_color(p_global_position)
|
||||
Terrain3DEditor.ANGLE:
|
||||
color = Color(plugin.terrain.get_storage().get_angle(p_global_position), 0., 0., 1.)
|
||||
color = Color(plugin.terrain.data.get_control_angle(p_global_position), 0., 0., 1.)
|
||||
Terrain3DEditor.SCALE:
|
||||
color = Color(plugin.terrain.get_storage().get_scale(p_global_position), 0., 0., 1.)
|
||||
color = Color(plugin.terrain.data.get_control_scale(p_global_position), 0., 0., 1.)
|
||||
_:
|
||||
push_error("Unsupported picking type: ", picking)
|
||||
return
|
||||
|
|
@ -385,3 +533,6 @@ func pick(p_global_position: Vector3) -> void:
|
|||
elif operation_builder and operation_builder.is_picking():
|
||||
operation_builder.pick(p_global_position, plugin.terrain)
|
||||
|
||||
|
||||
func set_button_editor_icon(p_button: Button, p_icon_name: String) -> void:
|
||||
p_button.icon = EditorInterface.get_base_control().get_theme_icon(p_icon_name, "EditorIcons")
|
||||
|
|
|
|||
|
|
@ -12,64 +12,83 @@ func reset_settings(p_value) -> void:
|
|||
height_file_name = ""
|
||||
control_file_name = ""
|
||||
color_file_name = ""
|
||||
import_position = Vector3.ZERO
|
||||
import_offset = 0.0
|
||||
destination_directory = ""
|
||||
import_position = Vector2i.ZERO
|
||||
height_offset = 0.0
|
||||
import_scale = 1.0
|
||||
r16_range = Vector2(0, 1)
|
||||
r16_size = Vector2i(1024, 1024)
|
||||
storage = null
|
||||
material = null
|
||||
assets = null
|
||||
|
||||
|
||||
func reset_terrain(p_value) -> void:
|
||||
if p_value:
|
||||
storage = null
|
||||
data_directory = ""
|
||||
|
||||
|
||||
func update_heights(p_value) -> void:
|
||||
if p_value and storage:
|
||||
storage.update_height_range()
|
||||
if p_value and data:
|
||||
data.update_height_range()
|
||||
|
||||
|
||||
@export_group("Import File")
|
||||
@export_global_file var height_file_name: String = ""
|
||||
@export_global_file var control_file_name: String = ""
|
||||
@export_global_file var color_file_name: String = ""
|
||||
@export var import_position: Vector3 = Vector3.ZERO
|
||||
@export var import_position: Vector2i = Vector2i(0, 0) : set = set_import_position
|
||||
@export var import_scale: float = 1.0
|
||||
@export var import_offset: float = 0.0
|
||||
@export var height_offset: float = 0.0
|
||||
@export var r16_range: Vector2 = Vector2(0, 1)
|
||||
@export var r16_size: Vector2i = Vector2i(1024, 1024)
|
||||
@export var r16_size: Vector2i = Vector2i(1024, 1024) : set = set_r16_size
|
||||
@export var run_import: bool = false : set = start_import
|
||||
|
||||
@export_dir var destination_directory: String = ""
|
||||
@export var save_to_disk: bool = false : set = save_data
|
||||
|
||||
|
||||
func set_import_position(p_value: Vector2i) -> void:
|
||||
import_position.x = clamp(p_value.x, -8192, 8192)
|
||||
import_position.y = clamp(p_value.y, -8192, 8192)
|
||||
|
||||
|
||||
func set_r16_size(p_value: Vector2i) -> void:
|
||||
r16_size.x = clamp(p_value.x, 0, 16384)
|
||||
r16_size.y = clamp(p_value.y, 0, 16384)
|
||||
|
||||
|
||||
func start_import(p_value: bool) -> void:
|
||||
if p_value:
|
||||
print("Terrain3DImporter: Importing files:\n\t%s\n\t%s\n\t%s" % [ height_file_name, control_file_name, color_file_name])
|
||||
if not storage:
|
||||
storage = Terrain3DStorage.new()
|
||||
|
||||
var imported_images: Array[Image]
|
||||
imported_images.resize(Terrain3DStorage.TYPE_MAX)
|
||||
imported_images.resize(Terrain3DRegion.TYPE_MAX)
|
||||
var min_max := Vector2(0, 1)
|
||||
var img: Image
|
||||
if height_file_name:
|
||||
img = Terrain3DUtil.load_image(height_file_name, ResourceLoader.CACHE_MODE_IGNORE, r16_range, r16_size)
|
||||
min_max = Terrain3DUtil.get_min_max(img)
|
||||
imported_images[Terrain3DStorage.TYPE_HEIGHT] = img
|
||||
imported_images[Terrain3DRegion.TYPE_HEIGHT] = img
|
||||
if control_file_name:
|
||||
img = Terrain3DUtil.load_image(control_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
||||
imported_images[Terrain3DStorage.TYPE_CONTROL] = img
|
||||
imported_images[Terrain3DRegion.TYPE_CONTROL] = img
|
||||
if color_file_name:
|
||||
img = Terrain3DUtil.load_image(color_file_name, ResourceLoader.CACHE_MODE_IGNORE)
|
||||
imported_images[Terrain3DStorage.TYPE_COLOR] = img
|
||||
imported_images[Terrain3DRegion.TYPE_COLOR] = img
|
||||
if assets.get_texture_count() == 0:
|
||||
material.show_checkered = false
|
||||
material.show_colormap = true
|
||||
storage.import_images(imported_images, import_position, import_offset, import_scale)
|
||||
var pos := Vector3(import_position.x, 0, import_position.y)
|
||||
data.import_images(imported_images, pos, height_offset, import_scale)
|
||||
print("Terrain3DImporter: Import finished")
|
||||
|
||||
|
||||
func save_data(p_value: bool) -> void:
|
||||
if destination_directory.is_empty():
|
||||
push_error("Set destination directory first")
|
||||
return
|
||||
data.save_directory(destination_directory)
|
||||
|
||||
|
||||
@export_group("Export File")
|
||||
enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
||||
@export_enum("Height:0", "Control:1", "Color:2") var map_type: int = TYPE_HEIGHT
|
||||
|
|
@ -77,6 +96,6 @@ enum { TYPE_HEIGHT, TYPE_CONTROL, TYPE_COLOR }
|
|||
@export var run_export: bool = false : set = start_export
|
||||
|
||||
func start_export(p_value: bool) -> void:
|
||||
var err: int = storage.export_image(file_name_out, map_type)
|
||||
var err: int = data.export_image(file_name_out, map_type)
|
||||
print("Terrain3DImporter: Export error status: ", err, " ", error_string(err))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,63 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://blaieaqp413k7"]
|
||||
[gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"]
|
||||
|
||||
[sub_resource type="Terrain3DStorage" id="Terrain3DStorage_rmuvl"]
|
||||
[sub_resource type="Gradient" id="Gradient_88f3t"]
|
||||
offsets = PackedFloat32Array(0.2, 1)
|
||||
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_cjpaa"]
|
||||
[sub_resource type="FastNoiseLite" id="FastNoiseLite_muvel"]
|
||||
noise_type = 2
|
||||
frequency = 0.03
|
||||
cellular_jitter = 3.0
|
||||
cellular_return_type = 0
|
||||
domain_warp_enabled = true
|
||||
domain_warp_type = 1
|
||||
domain_warp_amplitude = 50.0
|
||||
domain_warp_fractal_type = 2
|
||||
domain_warp_fractal_lacunarity = 1.5
|
||||
domain_warp_fractal_gain = 1.0
|
||||
|
||||
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_ve0yk"]
|
||||
seamless = true
|
||||
color_ramp = SubResource("Gradient_88f3t")
|
||||
noise = SubResource("FastNoiseLite_muvel")
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_p55u0"]
|
||||
_shader_parameters = {
|
||||
"blend_sharpness": 0.87,
|
||||
"height_blending": true,
|
||||
"macro_variation1": Color(1, 1, 1, 1),
|
||||
"macro_variation2": Color(1, 1, 1, 1),
|
||||
"noise1_angle": 0.0,
|
||||
"noise1_offset": Vector2(0.5, 0.5),
|
||||
"noise1_scale": 0.04,
|
||||
"noise2_scale": 0.076,
|
||||
"noise3_scale": 0.225,
|
||||
"noise_texture": SubResource("NoiseTexture2D_ve0yk"),
|
||||
"vertex_normals_distance": 128.0
|
||||
}
|
||||
show_checkered = true
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_gbxcd"]
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8rvqy"]
|
||||
cull_mode = 2
|
||||
vertex_color_use_as_albedo = true
|
||||
backlight_enabled = true
|
||||
backlight = Color(0.5, 0.5, 0.5, 1)
|
||||
|
||||
[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_7je72"]
|
||||
height_offset = 0.5
|
||||
density = 10.0
|
||||
material_override = SubResource("StandardMaterial3D_8rvqy")
|
||||
generated_type = 1
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_op32e"]
|
||||
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_7je72")])
|
||||
|
||||
[node name="Importer" type="Terrain3D"]
|
||||
storage = SubResource("Terrain3DStorage_rmuvl")
|
||||
material = SubResource("Terrain3DMaterial_cjpaa")
|
||||
assets = SubResource("Terrain3DAssets_gbxcd")
|
||||
material = SubResource("Terrain3DMaterial_p55u0")
|
||||
assets = SubResource("Terrain3DAssets_op32e")
|
||||
mesh_lods = 8
|
||||
top_level = true
|
||||
script = ExtResource("1_60b8f")
|
||||
metadata/_edit_lock_ = true
|
||||
|
|
|
|||
|
|
@ -42,22 +42,22 @@ func editor_setup(p_plugin) -> void:
|
|||
func get_terrain() -> Terrain3D:
|
||||
var terrain := instance_from_id(_terrain_id) as Terrain3D
|
||||
if not terrain or terrain.is_queued_for_deletion() or not terrain.is_inside_tree():
|
||||
var terrains: Array[Node] = EditorInterface.get_edited_scene_root().find_children("", "Terrain3D")
|
||||
var terrains: Array[Node] = Engine.get_singleton(&"EditorInterface").get_edited_scene_root().find_children("", "Terrain3D")
|
||||
if terrains.size() > 0:
|
||||
terrain = terrains[0]
|
||||
_terrain_id = terrain.get_instance_id() if terrain else 0
|
||||
|
||||
if terrain and terrain.storage and not terrain.storage.maps_edited.is_connected(_on_maps_edited):
|
||||
terrain.storage.maps_edited.connect(_on_maps_edited)
|
||||
if terrain and terrain.data and not terrain.data.maps_edited.is_connected(_on_maps_edited):
|
||||
terrain.data.maps_edited.connect(_on_maps_edited)
|
||||
|
||||
return terrain
|
||||
|
||||
|
||||
func _get_terrain_height(p_global_position: Vector3) -> float:
|
||||
var terrain: Terrain3D = get_terrain()
|
||||
if not terrain or not terrain.storage:
|
||||
if not terrain or not terrain.data:
|
||||
return 0.0
|
||||
var height: float = terrain.storage.get_height(p_global_position)
|
||||
var height: float = terrain.data.get_height(p_global_position)
|
||||
if is_nan(height):
|
||||
return 0.0
|
||||
return height
|
||||
|
|
@ -105,7 +105,7 @@ func _on_child_exiting_tree(p_node: Node) -> void:
|
|||
|
||||
|
||||
func _is_node_selected(p_node: Node) -> bool:
|
||||
var editor_sel = EditorInterface.get_selection()
|
||||
var editor_sel = Engine.get_singleton(&"EditorInterface").get_selection()
|
||||
return editor_sel.get_transformable_selected_nodes().has(p_node)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,4 +46,3 @@ func _release(inherited_velocity : Vector3 = Vector3.ZERO, _force: int = throw_f
|
|||
_flag = null
|
||||
# hide carried flag mesh
|
||||
hide()
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ continuous_cd = true
|
|||
script = ExtResource("1_y7d3d")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0.00601196)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = ExtResource("2_ig160")
|
||||
|
||||
[node name="GripArea" type="Area3D" parent="."]
|
||||
|
|
|
|||
|
|
@ -37,18 +37,19 @@ signal respawned(player: Player)
|
|||
|
||||
@export_category("Parameters")
|
||||
@export var ground_speed:float = 48 / 3.6 # m/s
|
||||
@export var aerial_control_force:int = 400
|
||||
@export var aerial_control_force:int = 320
|
||||
@export var jump_height:float = 1.0
|
||||
@export var max_floor_angle:float = 60
|
||||
|
||||
@export_group("Jetpack")
|
||||
@export var energy: float = 100.0
|
||||
@export var energy_charge_rate:float = 25 # energy per second
|
||||
@export var energy_drain_rate:float = 25 # energy per second
|
||||
@export var energy_max:float = 100.
|
||||
@export var energy: float = energy_max
|
||||
@export var energy_charge_rate:float = 25 # energy per second
|
||||
@export var energy_drain_rate:float = 30 # energy per second
|
||||
@export var jetpack_force_factor:float = 2.
|
||||
@export var jetpack_horizontal_force:float = 600
|
||||
@export var jetpack_vertical_force:float = 1200
|
||||
@export var jetpack_horizontal_force:float = 700
|
||||
@export var jetpack_vertical_force:float = 900
|
||||
@export_range(0., 1., .01) var stutter_treshold:float = .3
|
||||
|
||||
@export_group("State")
|
||||
@export var input: PlayerInputController
|
||||
|
|
@ -60,6 +61,8 @@ signal respawned(player: Player)
|
|||
var g:float = ProjectSettings.get_setting("physics/3d/default_gravity") # in m/s²
|
||||
var gravity:Vector3 = g * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
||||
var _jumping:bool = false
|
||||
var _stutter:bool = false
|
||||
var _stutter_treshold:int = int(energy_max * .3)
|
||||
|
||||
static var pawn_player:Player
|
||||
|
||||
|
|
@ -79,9 +82,9 @@ func set_username(new_username:String) -> void:
|
|||
username_changed.emit(username)
|
||||
|
||||
func set_peer_id(new_peer_id:int) -> void:
|
||||
remove_from_group(str(peer_id))
|
||||
#remove_from_group(str(peer_id))
|
||||
peer_id = new_peer_id
|
||||
add_to_group(str(peer_id))
|
||||
#add_to_group(str(peer_id))
|
||||
peer_id_changed.emit(peer_id)
|
||||
|
||||
# Maximum duration for pumping force when throwing a flag, in seconds.
|
||||
|
|
@ -130,6 +133,9 @@ func _ready() -> void:
|
|||
func(_source: Node, target: Node, amount: int) -> void:
|
||||
target.health.damage(amount, 0))
|
||||
|
||||
input.camera_rotation.x = rotation.x
|
||||
input.camera_rotation.y = rotation.y
|
||||
|
||||
if _is_pawn():
|
||||
pawn_player = self
|
||||
camera.current = true
|
||||
|
|
@ -165,12 +171,12 @@ func _process(_delta:float) -> void:
|
|||
|
||||
if not is_alive():
|
||||
return
|
||||
|
||||
|
||||
# compute target rotation from input.camera_rotation
|
||||
var target_euler := Vector3(input.camera_rotation.x, input.camera_rotation.y, 0.0)
|
||||
# smoothly interpolate rotation of pivot node towards target rotation
|
||||
pivot.global_transform.basis = pivot.global_transform.basis.slerp(Basis.from_euler(target_euler), .6)
|
||||
|
||||
|
||||
if not _is_pawn():
|
||||
# compute target rotation from input.camera_rotation
|
||||
var tp_target_euler := Vector3(.0, input.camera_rotation.y + PI, 0.0)
|
||||
|
|
@ -193,7 +199,8 @@ func _is_pawn() -> bool:
|
|||
return true
|
||||
|
||||
func _on_throw(pressed: bool) -> void:
|
||||
if pressed:
|
||||
var flag: Flag = flag_carry_component._flag
|
||||
if pressed and flag and flag.state == flag.FlagState.TAKEN:
|
||||
throw_timer.start(throw_duration_max)
|
||||
hud.throw_progress.visible = true
|
||||
else:
|
||||
|
|
@ -230,40 +237,46 @@ func _handle_aerial_control(direction:Vector3) -> void:
|
|||
apply_force(direction * aerial_control_force)
|
||||
|
||||
func _handle_jetpack(direction:Vector3) -> void:
|
||||
if input.jetting and energy > 0:
|
||||
if input.jetting and energy > 0 and not _stutter:
|
||||
var up_vector:Vector3 = Vector3.UP * jetpack_vertical_force * jetpack_force_factor
|
||||
var side_vector:Vector3 = direction * jetpack_horizontal_force * jetpack_force_factor
|
||||
apply_force(up_vector + side_vector)
|
||||
for particle: GPUParticles3D in _jetpack_particles:
|
||||
particle.emitting = true
|
||||
|
||||
func _handle_ski() -> void:
|
||||
# set ski state
|
||||
physics_material_override.friction = !input.skiing
|
||||
# zero-damping ski
|
||||
linear_damp_mode = DAMP_MODE_REPLACE if is_on_floor() and input.skiing else DAMP_MODE_COMBINE
|
||||
|
||||
func _update_jetpack_energy(delta:float) -> void:
|
||||
if input.jetting and energy > 0:
|
||||
energy -= energy_drain_rate * delta
|
||||
if input.jetting:
|
||||
if energy == 0:
|
||||
_stutter = true
|
||||
elif energy > _stutter_treshold:
|
||||
_stutter = false
|
||||
|
||||
if not _stutter:
|
||||
energy -= energy_drain_rate * delta
|
||||
else:
|
||||
energy += energy_charge_rate * delta
|
||||
else:
|
||||
energy += energy_charge_rate * delta
|
||||
|
||||
energy = clamp(energy, 0, energy_max)
|
||||
hud.energy_bar.value = energy
|
||||
|
||||
func _integrate_forces(_state:PhysicsDirectBodyState3D) -> void:
|
||||
# skip if player is dead
|
||||
if not is_alive():
|
||||
return
|
||||
# retrieve user's direction vector
|
||||
var _input_dir:Vector2 = input.direction
|
||||
# compute direction in local space
|
||||
var _direction:Vector3 = (transform.basis * Vector3(_input_dir.x, 0, _input_dir.y)).normalized()
|
||||
var _direction:Vector3 = (transform.basis * Vector3(
|
||||
input.direction.x, 0, input.direction.y)).normalized()
|
||||
|
||||
# adjust direction based on pivot rotation
|
||||
_direction = _direction.rotated(Vector3.UP, pivot.rotation.y)
|
||||
|
||||
# zero-damping ski
|
||||
if is_on_floor() and input.skiing:
|
||||
linear_damp = lerp(linear_damp, .0, .1)
|
||||
else:
|
||||
linear_damp = lerp(
|
||||
linear_damp,
|
||||
ProjectSettings.get_setting("physics/3d/default_linear_damp") + .05,
|
||||
.2)
|
||||
|
||||
if is_on_floor():
|
||||
if not _direction.is_zero_approx() and not input.skiing:
|
||||
|
|
@ -284,11 +297,9 @@ func _integrate_forces(_state:PhysicsDirectBodyState3D) -> void:
|
|||
|
||||
_jumping = false
|
||||
|
||||
# set ski state
|
||||
physics_material_override.friction = !input.skiing
|
||||
|
||||
_handle_aerial_control(_direction)
|
||||
_handle_jetpack(_direction)
|
||||
_handle_ski()
|
||||
|
||||
func _update_third_person_animations() -> void:
|
||||
if not is_alive():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=44 format=3 uid="uid://cbhx1xme0sb7k"]
|
||||
[gd_scene load_steps=43 format=3 uid="uid://cbhx1xme0sb7k"]
|
||||
|
||||
[ext_resource type="Script" path="res://entities/player/player.gd" id="1_y2i7h"]
|
||||
[ext_resource type="PackedScene" uid="uid://bbeecp3jusppn" path="res://interfaces/hud/iffs/IFF.tscn" id="2_s5wgp"]
|
||||
|
|
@ -22,35 +22,8 @@ resource_local_to_scene = true
|
|||
bounce = 1.0
|
||||
absorbent = true
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_h0a20"]
|
||||
radius = 0.25
|
||||
|
||||
[sub_resource type="Animation" id="Animation_eoxkv"]
|
||||
length = 0.001
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Pivot:position")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector3(0, 0.625, 0)]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("Pivot:rotation")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [Vector3(0, 0, 0)]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_yqgrk"]
|
||||
resource_name = "death"
|
||||
|
|
@ -237,7 +210,6 @@ properties/3/replication_mode = 2
|
|||
radius = 0.2
|
||||
|
||||
[node name="Player" type="RigidBody3D" node_paths=PackedStringArray("iff", "health", "flag_carry_component", "walkable_surface_sensor", "hud", "inventory", "tp_mesh", "third_person", "pivot", "camera", "input")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
collision_mask = 2147483649
|
||||
collision_priority = 9.0
|
||||
axis_lock_angular_x = true
|
||||
|
|
@ -245,11 +217,10 @@ axis_lock_angular_y = true
|
|||
axis_lock_angular_z = true
|
||||
mass = 80.0
|
||||
physics_material_override = SubResource("PhysicsMaterial_clur0")
|
||||
gravity_scale = 1.2
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector3(0, 1, 0)
|
||||
can_sleep = false
|
||||
continuous_cd = true
|
||||
linear_damp_mode = 1
|
||||
linear_damp = 0.15
|
||||
script = ExtResource("1_y2i7h")
|
||||
iff = NodePath("IFF")
|
||||
health = NodePath("Health")
|
||||
|
|
@ -265,30 +236,23 @@ max_floor_angle = 80.0
|
|||
input = NodePath("Inputs")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
shape = ExtResource("4_8kvcy")
|
||||
|
||||
[node name="IFF" parent="." instance=ExtResource("2_s5wgp")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.1, 0)
|
||||
fill = Color(0, 1, 0, 1)
|
||||
|
||||
[node name="Health" type="Area3D" parent="." node_paths=PackedStringArray("collider")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
script = ExtResource("4_55muf")
|
||||
collider = NodePath("CollisionShape3D")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Health"]
|
||||
shape = SubResource("CapsuleShape3D_h0a20")
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("3_ccety")]
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_hg307")
|
||||
}
|
||||
autoplay = "RESET"
|
||||
playback_default_blend_time = 0.05
|
||||
shape = ExtResource("4_8kvcy")
|
||||
|
||||
[node name="Pivot" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.625, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.75, 0)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="Pivot"]
|
||||
fov = 90.0
|
||||
|
|
@ -318,7 +282,17 @@ mesh = NodePath("FlagMesh")
|
|||
|
||||
[node name="FlagMesh" parent="Pivot/FlagCarryComponent" instance=ExtResource("18_7nkei")]
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("3_ccety")]
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
libraries = {
|
||||
"": SubResource("AnimationLibrary_hg307")
|
||||
}
|
||||
autoplay = "RESET"
|
||||
playback_default_blend_time = 0.05
|
||||
|
||||
[node name="ThirdPerson" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
visible = false
|
||||
|
||||
[node name="Mesh" parent="ThirdPerson" node_paths=PackedStringArray("spine_ik_target_attachment") instance=ExtResource("8_eiy7q")]
|
||||
|
|
@ -335,39 +309,42 @@ bones/8/rotation = Quaternion(0.675761, -0.399581, 0.409695, 0.464577)
|
|||
bones/10/rotation = Quaternion(0.212344, -0.0870591, -0.287653, 0.929831)
|
||||
bones/12/rotation = Quaternion(0.0811501, 0.206806, -0.754068, 0.618084)
|
||||
bones/14/rotation = Quaternion(0.00943715, 0.0971687, -0.145046, 0.984597)
|
||||
bones/20/rotation = Quaternion(-0.123455, 0.0248346, 0.23344, 0.964183)
|
||||
bones/24/rotation = Quaternion(0.0450683, -0.000817796, 0.0508488, 0.997689)
|
||||
bones/26/rotation = Quaternion(0.100545, -1.16532e-07, 0.00792588, 0.994901)
|
||||
bones/28/rotation = Quaternion(0.288532, -1.83796e-07, 0.0227447, 0.9572)
|
||||
bones/32/rotation = Quaternion(0.00674081, -0.0316083, 0.105474, 0.993897)
|
||||
bones/34/rotation = Quaternion(0.780684, 4.23384e-08, 0.0615407, 0.621889)
|
||||
bones/36/rotation = Quaternion(0.580978, -1.56224e-07, 0.0457976, 0.81263)
|
||||
bones/40/rotation = Quaternion(0.0913739, 0.113691, 0.100265, 0.984211)
|
||||
bones/42/rotation = Quaternion(0.836841, 4.40546e-07, 0.0659669, 0.543457)
|
||||
bones/44/rotation = Quaternion(0.633142, 6.48257e-09, 0.04991, 0.772425)
|
||||
bones/50/rotation = Quaternion(0.729888, -4.88266e-08, 0.0575362, 0.681141)
|
||||
bones/52/rotation = Quaternion(0.624011, -9.63141e-08, 0.04919, 0.779865)
|
||||
bones/16/rotation = Quaternion(0.202944, -0.189284, -0.192306, 0.941278)
|
||||
bones/18/rotation = Quaternion(0.00767626, 0.0143343, 0.192225, 0.981216)
|
||||
bones/20/rotation = Quaternion(-0.123455, 0.0248345, 0.23344, 0.964183)
|
||||
bones/24/rotation = Quaternion(0.0450684, -0.000817573, 0.0508488, 0.997689)
|
||||
bones/26/rotation = Quaternion(0.100545, -2.54424e-07, 0.00792588, 0.994901)
|
||||
bones/28/rotation = Quaternion(0.288532, -7.96814e-08, 0.0227446, 0.9572)
|
||||
bones/32/rotation = Quaternion(0.0067407, -0.0316083, 0.105474, 0.993897)
|
||||
bones/34/rotation = Quaternion(0.780684, 6.72079e-08, 0.0615403, 0.621889)
|
||||
bones/36/rotation = Quaternion(0.580978, -9.89914e-08, 0.0457976, 0.81263)
|
||||
bones/40/rotation = Quaternion(0.091374, 0.113691, 0.100265, 0.984211)
|
||||
bones/42/rotation = Quaternion(0.836841, 2.02021e-07, 0.0659672, 0.543457)
|
||||
bones/44/rotation = Quaternion(0.633142, -1.16317e-07, 0.0499098, 0.772425)
|
||||
bones/48/rotation = Quaternion(0.0427938, 0.15521, 0.085814, 0.983216)
|
||||
bones/50/rotation = Quaternion(0.729888, -5.53666e-08, 0.0575362, 0.681141)
|
||||
bones/52/rotation = Quaternion(0.624012, -7.97555e-08, 0.0491901, 0.779865)
|
||||
bones/56/rotation = Quaternion(-0.0254885, 0.0439755, -0.00910637, 0.998666)
|
||||
bones/58/rotation = Quaternion(-0.0119802, 0.289956, -0.0527053, 0.955513)
|
||||
bones/58/rotation = Quaternion(-0.0119802, 0.289956, -0.0527054, 0.955513)
|
||||
bones/62/rotation = Quaternion(0.689943, 0.375565, -0.410267, 0.463261)
|
||||
bones/64/rotation = Quaternion(0.337746, -0.290519, 0.159479, 0.880961)
|
||||
bones/66/rotation = Quaternion(0.540371, -0.580679, 0.406779, 0.453147)
|
||||
bones/68/position = Vector3(-1.77636e-17, 0.240718, 0)
|
||||
bones/68/rotation = Quaternion(0.0150529, -0.289001, 0.0720108, 0.954498)
|
||||
bones/70/rotation = Quaternion(0.155965, 0.0109114, -0.00107202, 0.987702)
|
||||
bones/72/rotation = Quaternion(0.563923, 4.19095e-08, -0.0577906, 0.823803)
|
||||
bones/72/rotation = Quaternion(0.563923, 3.95812e-08, -0.0577906, 0.823803)
|
||||
bones/74/rotation = Quaternion(0.285209, 0.0197164, -0.0936782, 0.953673)
|
||||
bones/78/rotation = Quaternion(0.057484, -0.0698946, -0.0100642, 0.995846)
|
||||
bones/80/rotation = Quaternion(0.433127, 5.44824e-08, -0.0443866, 0.900239)
|
||||
bones/82/rotation = Quaternion(0.274138, -1.97906e-08, -0.0280937, 0.96128)
|
||||
bones/80/rotation = Quaternion(0.433127, 5.45988e-08, -0.0443866, 0.900239)
|
||||
bones/82/rotation = Quaternion(0.274138, -1.89757e-08, -0.0280937, 0.96128)
|
||||
bones/86/rotation = Quaternion(0.244152, 0.0521336, 0.176446, 0.952123)
|
||||
bones/88/rotation = Quaternion(0.0146391, -1.48637e-07, -0.121951, 0.992428)
|
||||
bones/88/rotation = Quaternion(0.0146391, -1.48753e-07, -0.121951, 0.992428)
|
||||
bones/90/rotation = Quaternion(-0.147655, -0.0737763, 0.197847, 0.966236)
|
||||
bones/94/rotation = Quaternion(0.180682, 0.0832945, -0.00387314, 0.980001)
|
||||
bones/96/rotation = Quaternion(0.245651, -6.35919e-08, -0.0251742, 0.969032)
|
||||
bones/98/rotation = Quaternion(0.246432, 7.6252e-08, -0.0252543, 0.968831)
|
||||
bones/94/rotation = Quaternion(0.180682, 0.0832945, -0.00387313, 0.980001)
|
||||
bones/96/rotation = Quaternion(0.245651, -6.3621e-08, -0.0251742, 0.969032)
|
||||
bones/98/rotation = Quaternion(0.246432, 7.61938e-08, -0.0252543, 0.968831)
|
||||
bones/102/rotation = Quaternion(0.179829, 0.0890365, -0.000307644, 0.97966)
|
||||
bones/104/rotation = Quaternion(0.388149, 1.28057e-07, -0.0397774, 0.920738)
|
||||
bones/104/rotation = Quaternion(0.388149, 1.27126e-07, -0.0397774, 0.920738)
|
||||
bones/106/rotation = Quaternion(0.372324, -1.37021e-07, -0.0381557, 0.927318)
|
||||
bones/110/rotation = Quaternion(-0.167577, 0.223934, 0.958827, 0.0492099)
|
||||
bones/112/rotation = Quaternion(-0.466474, -0.0088339, -0.0232928, 0.884184)
|
||||
|
|
@ -375,11 +352,11 @@ bones/114/rotation = Quaternion(0.575696, 0.0793941, -0.0250592, 0.813414)
|
|||
bones/116/rotation = Quaternion(0.355062, 0.0493655, 0.0246355, 0.933213)
|
||||
bones/120/rotation = Quaternion(0.115252, 0.282473, 0.945749, -0.111737)
|
||||
bones/122/rotation = Quaternion(-0.494906, -0.0647935, 0.0183973, 0.866332)
|
||||
bones/124/rotation = Quaternion(0.417677, -0.0431149, 0.00625689, 0.90755)
|
||||
bones/124/rotation = Quaternion(0.417677, -0.0431149, 0.0062569, 0.90755)
|
||||
bones/126/rotation = Quaternion(0.397818, -0.0427722, -0.00601182, 0.916447)
|
||||
|
||||
[node name="HandAttachment" parent="ThirdPerson/Mesh/Node/Skeleton3D" index="0"]
|
||||
transform = Transform3D(-0.152214, 0.0548832, 0.986823, 0.933991, 0.334546, 0.125459, -0.323252, 0.94078, -0.102183, -0.261612, 1.14328, 0.0896011)
|
||||
transform = Transform3D(-0.152214, 0.0548831, 0.986823, 0.933991, 0.334546, 0.125459, -0.323252, 0.94078, -0.102183, -0.261612, 1.14328, 0.0896011)
|
||||
|
||||
[node name="grip" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/SpaceGun/Mesh/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.6068e-14, -1.19209e-07, 9.53674e-07)
|
||||
|
|
@ -399,20 +376,32 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.6068e-14, -1.19209e-07, 9.5
|
|||
[node name="sides" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/SpaceGun/Mesh/Armature/Skeleton3D/sides" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="BarrelsInner" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="BarrelsInner" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D/BarrelsInner" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="BarrelsOuter" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="BarrelsOuter" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D/BarrelsOuter" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="Base" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Base" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D/Base" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="Grip" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D" index="3"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.692504, 1.30946)
|
||||
|
||||
[node name="Grip" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun/Armature/Skeleton3D/Grip" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="Barrels" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/ChainGun" index="2"]
|
||||
transform = Transform3D(-1, 0, 8.9407e-08, 8.47504e-08, 0, 1, 0, 1, -3.72529e-09, -1.19209e-07, 4.76837e-07, -0.55315)
|
||||
transform = Transform3D(-1, -7.45058e-09, 8.98726e-08, 8.42847e-08, 0, 1, 0, 1, 0, 1.19209e-07, 4.76837e-07, -0.55315)
|
||||
|
||||
[node name="Skeleton3D" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature" index="0"]
|
||||
bones/0/rotation = Quaternion(0, 0.707107, 0.707107, 0)
|
||||
|
|
@ -420,12 +409,21 @@ bones/1/rotation = Quaternion(0, 0.707107, 0.707107, 0)
|
|||
bones/2/rotation = Quaternion(0, 0.707107, 0.707107, 0)
|
||||
bones/3/rotation = Quaternion(0, 0.707107, 0.707107, 0)
|
||||
|
||||
[node name="barrel" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19209e-07, 0)
|
||||
|
||||
[node name="barrel" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D/barrel" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="grip" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="1"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.082471, -0.0653242)
|
||||
|
||||
[node name="grip" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D/grip" index="0"]
|
||||
layers = 2
|
||||
|
||||
[node name="main" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D" index="2"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.19209e-07, 0)
|
||||
|
||||
[node name="main" parent="ThirdPerson/Mesh/Node/Skeleton3D/HandAttachment/GrenadeLauncher/Armature/Skeleton3D/main" index="0"]
|
||||
layers = 2
|
||||
|
||||
|
|
@ -478,10 +476,9 @@ script = ExtResource("18_v4iu1")
|
|||
replication_config = SubResource("SceneReplicationConfig_5j4ew")
|
||||
|
||||
[node name="WalkableSurfaceSensor" type="ShapeCast3D" parent="."]
|
||||
transform = Transform3D(1.05, 0, 0, 0, 1.05, 0, 0, 0, 1.05, 0, -0.85, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.085, 0)
|
||||
shape = SubResource("SphereShape3D_hwe6e")
|
||||
target_position = Vector3(0, 0, 0)
|
||||
max_results = 16
|
||||
collision_mask = 2147483648
|
||||
|
||||
[connection signal="child_entered_tree" from="Pivot/Inventory" to="Pivot/Inventory" method="_on_child_entered_tree"]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ speed = 78.4
|
|||
lifespan = 6.0
|
||||
|
||||
[node name="ShapeCast3D" type="ShapeCast3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.2, 0, 0, 0, 1, 0, 0, 0)
|
||||
shape = ExtResource("3_j3hyk")
|
||||
target_position = Vector3(0, 0, 0)
|
||||
collision_mask = 2147483649
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ func _process(_delta : float) -> void:
|
|||
var viewport_render_size : Vector2i = get_viewport().size * get_viewport().scaling_3d_scale
|
||||
text += \"3D viewport resolution: %d × %d (%d%%)\\n\" \\
|
||||
% [viewport_render_size.x, viewport_render_size.y, round(get_viewport().scaling_3d_scale * 100)]
|
||||
text += \"health: %d\" % player.health.value
|
||||
text += \"health: %d\\n\" % player.health.value
|
||||
text += \"energy: %d\" % player.energy
|
||||
"
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_f23s3"]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0)
|
|||
offset = Vector2(0, 64)
|
||||
billboard = 1
|
||||
fixed_size = true
|
||||
text = "Username"
|
||||
text = "Newblood"
|
||||
font_size = 24
|
||||
|
||||
[node name="ProgressBar3D" parent="." instance=ExtResource("2_h0pl8")]
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,12 +1,17 @@
|
|||
[gd_scene load_steps=15 format=3 uid="uid://rqirbdfmt2g6"]
|
||||
[gd_scene load_steps=20 format=3 uid="uid://rqirbdfmt2g6"]
|
||||
|
||||
[ext_resource type="Terrain3DStorage" uid="uid://ba71qjh77d5xe" path="res://maps/blaze/resources/storage.res" id="1_c5mnl"]
|
||||
[ext_resource type="Script" path="res://maps/map.gd" id="1_mnbe8"]
|
||||
[ext_resource type="Script" path="res://maps/spawns.gd" id="2_6f0my"]
|
||||
[ext_resource type="Terrain3DMaterial" uid="uid://by5l8p52isb5w" path="res://maps/blaze/resources/material.tres" id="2_swjtx"]
|
||||
[ext_resource type="Texture2D" uid="uid://cyw6oism6152n" path="res://maps/blaze/assets/textures/Ground074_1K-PNG/Ground074_1K-PNG_Color.png" id="4_g7p33"]
|
||||
[ext_resource type="Texture2D" uid="uid://b3hocnuaq1y5g" path="res://maps/blaze/assets/textures/Ground068_1K-PNG/Ground068_1K-PNG_Color.png" id="5_ako74"]
|
||||
[ext_resource type="Texture2D" uid="uid://c43blkvrhgves" path="res://maps/blaze/assets/textures/Ground068_1K-PNG/Ground068_1K-PNG_NormalDX.png" id="6_7no1c"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_5nwqp"]
|
||||
script = ExtResource("2_6f0my")
|
||||
players = Array[NodePath]([NodePath("Spawns/PlayerSpawn")])
|
||||
objectives = Array[NodePath]([NodePath("Spawns/NeutralObjectiveSpawn")])
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_8mbvu"]
|
||||
sky_top_color = Color(0, 0.584314, 0.670588, 1)
|
||||
sky_horizon_color = Color(0.819608, 0.592157, 0.556863, 1)
|
||||
|
|
@ -50,6 +55,7 @@ name = "Rock"
|
|||
albedo_color = Color(0.898039, 0.760784, 0.905882, 1)
|
||||
albedo_texture = ExtResource("4_g7p33")
|
||||
normal_texture = ExtResource("6_7no1c")
|
||||
detiling = 1.0
|
||||
|
||||
[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_wpn65"]
|
||||
name = "Ground"
|
||||
|
|
@ -57,15 +63,28 @@ id = 1
|
|||
albedo_color = Color(0.882353, 0.764706, 0.87451, 1)
|
||||
albedo_texture = ExtResource("5_ako74")
|
||||
normal_texture = ExtResource("6_7no1c")
|
||||
detiling = 1.0
|
||||
|
||||
[sub_resource type="Terrain3DAssets" id="Terrain3DAssets_rej0g"]
|
||||
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_rwwg2")])
|
||||
texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_ukab7"), SubResource("Terrain3DTextureAsset_wpn65")])
|
||||
|
||||
[node name="Blaze" type="Node3D" node_paths=PackedStringArray("players", "objectives")]
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_7eeae"]
|
||||
plane = Plane(1, 0, 0, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_2xje2"]
|
||||
plane = Plane(-1, 0, 0, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_3mpfb"]
|
||||
plane = Plane(0, 0, 1, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_ol8ht"]
|
||||
plane = Plane(0, 0, -1, 0)
|
||||
|
||||
[node name="Blaze" type="Node3D" node_paths=PackedStringArray("terrain")]
|
||||
script = ExtResource("1_mnbe8")
|
||||
players = [NodePath("Spawns/PlayerSpawn")]
|
||||
objectives = [NodePath("Spawns/NeutralObjectiveSpawn")]
|
||||
terrain = NodePath("Terrain3D")
|
||||
spawns = SubResource("Resource_5nwqp")
|
||||
|
||||
[node name="Sunlight" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.204973, 0.153214, -0.966701, 0.88677, 0.388975, 0.249674, 0.414276, -0.908418, -0.0561367, 0, 0, 0)
|
||||
|
|
@ -75,15 +94,38 @@ shadow_enabled = true
|
|||
environment = SubResource("Environment_3veb0")
|
||||
|
||||
[node name="Terrain3D" type="Terrain3D" parent="."]
|
||||
storage = ExtResource("1_c5mnl")
|
||||
data_directory = "res://maps/blaze/data"
|
||||
material = ExtResource("2_swjtx")
|
||||
assets = SubResource("Terrain3DAssets_rej0g")
|
||||
collision_layer = 2147483649
|
||||
collision_mask = 9
|
||||
collision_priority = 9.0
|
||||
cast_shadows = 0
|
||||
top_level = true
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Spawns" type="Node" parent="."]
|
||||
|
||||
[node name="PlayerSpawn" type="Node3D" parent="Spawns"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 84.3897, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 96.8228, 0)
|
||||
|
||||
[node name="NeutralObjectiveSpawn" type="Node3D" parent="Spawns"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -383.22, 116.253, -62.8721)
|
||||
|
||||
[node name="Boundaries" type="StaticBody3D" parent="."]
|
||||
|
||||
[node name="East" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -921.6, 0, 0)
|
||||
shape = SubResource("WorldBoundaryShape3D_7eeae")
|
||||
|
||||
[node name="West" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 921.6, 0, 0)
|
||||
shape = SubResource("WorldBoundaryShape3D_2xje2")
|
||||
|
||||
[node name="South" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -921.6)
|
||||
shape = SubResource("WorldBoundaryShape3D_3mpfb")
|
||||
|
||||
[node name="North" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 921.6)
|
||||
shape = SubResource("WorldBoundaryShape3D_ol8ht")
|
||||
|
|
|
|||
BIN
maps/blaze/data/terrain3d-01-01.res
Normal file
BIN
maps/blaze/data/terrain3d-01-01.res
Normal file
Binary file not shown.
BIN
maps/blaze/data/terrain3d-01_00.res
Normal file
BIN
maps/blaze/data/terrain3d-01_00.res
Normal file
Binary file not shown.
BIN
maps/blaze/data/terrain3d_00-01.res
Normal file
BIN
maps/blaze/data/terrain3d_00-01.res
Normal file
Binary file not shown.
BIN
maps/blaze/data/terrain3d_00_00.res
Normal file
BIN
maps/blaze/data/terrain3d_00_00.res
Normal file
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Terrain3DMaterial" load_steps=5 format=3 uid="uid://by5l8p52isb5w"]
|
||||
[gd_resource type="Terrain3DMaterial" load_steps=4 format=3 uid="uid://by5l8p52isb5w"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_b6eo8"]
|
||||
offsets = PackedFloat32Array(0.2, 1)
|
||||
|
|
@ -22,602 +22,37 @@ seamless_blend_skirt = 1.0
|
|||
color_ramp = SubResource("Gradient_b6eo8")
|
||||
noise = SubResource("FastNoiseLite_mcady")
|
||||
|
||||
[sub_resource type="Shader" id="Shader_ypt1p"]
|
||||
code = "shader_type spatial;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
|
||||
|
||||
/* This shader is generated based upon the debug views you have selected.
|
||||
* The terrain function depends on this shader. So don't change:
|
||||
* - vertex positioning in vertex()
|
||||
* - terrain normal calculation in fragment()
|
||||
* - the last function being fragment() as the editor injects code before the closing }
|
||||
*
|
||||
* Most will only want to customize the material calculation and PBR application in fragment()
|
||||
*
|
||||
* Uniforms that begin with _ are private and will not display in the inspector. However,
|
||||
* you can set them via code. You are welcome to create more of your own hidden uniforms.
|
||||
*
|
||||
* This system only supports albedo, height, normal, roughness. Most textures don't need the other
|
||||
* PBR channels. Height can be used as an approximation for AO. For the rare textures do need
|
||||
* additional channels, you can add maps for that one texture. e.g. an emissive map for lava.
|
||||
*
|
||||
*/
|
||||
|
||||
// Private uniforms
|
||||
|
||||
uniform float _region_size = 1024.0;
|
||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||
uniform float _mesh_vertex_spacing = 1.0;
|
||||
uniform float _mesh_vertex_density = 1.0; // = 1/_mesh_vertex_spacing
|
||||
uniform int _region_map_size = 16;
|
||||
uniform int _region_map[256];
|
||||
uniform vec2 _region_offsets[256];
|
||||
uniform sampler2DArray _height_maps : repeat_disable;
|
||||
uniform usampler2DArray _control_maps : repeat_disable;
|
||||
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
||||
uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
|
||||
uniform float _texture_uv_scale_array[32];
|
||||
uniform float _texture_detile_array[32];
|
||||
uniform vec4 _texture_color_array[32];
|
||||
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
||||
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
||||
|
||||
// Public uniforms
|
||||
uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
|
||||
uniform bool height_blending = true;
|
||||
uniform float blend_sharpness : hint_range(0, 1) = 0.87;
|
||||
uniform float auto_slope : hint_range(0, 10) = 1.0;
|
||||
uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
|
||||
uniform int auto_base_texture : hint_range(0, 31) = 0;
|
||||
uniform int auto_overlay_texture : hint_range(0, 31) = 1;
|
||||
|
||||
uniform int dual_scale_texture : hint_range(0,31) = 0;
|
||||
uniform float dual_scale_reduction : hint_range(0.001,1) = 0.3;
|
||||
uniform float tri_scale_reduction : hint_range(0.001,1) = 0.3;
|
||||
uniform float dual_scale_far : hint_range(0,1000) = 170.0;
|
||||
uniform float dual_scale_near : hint_range(0,1000) = 100.0;
|
||||
|
||||
uniform vec3 macro_variation1 : source_color = vec3(1.);
|
||||
uniform vec3 macro_variation2 : source_color = vec3(1.);
|
||||
// Generic noise at 3 scales, which can be used for anything
|
||||
uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
|
||||
uniform float noise1_angle : hint_range(0, 6.283) = 0.;
|
||||
uniform vec2 noise1_offset = vec2(0.5);
|
||||
uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x
|
||||
uniform float noise3_scale : hint_range(0.001, 1.) = 0.225; // Used for texture blending edge.
|
||||
|
||||
// Varyings & Types
|
||||
|
||||
struct Material {
|
||||
vec4 alb_ht;
|
||||
vec4 nrm_rg;
|
||||
int base;
|
||||
int over;
|
||||
float blend;
|
||||
};
|
||||
|
||||
varying flat vec3 v_vertex; // World coordinate vertex location
|
||||
varying flat vec3 v_camera_pos;
|
||||
varying float v_vertex_xz_dist;
|
||||
varying flat ivec3 v_region;
|
||||
varying flat vec2 v_uv_offset;
|
||||
varying flat vec2 v_uv2_offset;
|
||||
varying vec3 v_normal;
|
||||
varying float v_region_border_mask;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
////////////////////////
|
||||
|
||||
// Takes in UV world space coordinates, returns ivec3 with:
|
||||
// XY: (0 to _region_size) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
ivec3 get_region_uv(vec2 uv) {
|
||||
uv *= _region_texel_size;
|
||||
ivec2 pos = ivec2(floor(uv)) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x >= 0 && pos.x < _region_map_size && pos.y >= 0 && pos.y < _region_map_size);
|
||||
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);
|
||||
}
|
||||
|
||||
// Takes in UV2 region space coordinates, returns vec3 with:
|
||||
// XY: (0 to 1) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
vec3 get_region_uv2(vec2 uv) {
|
||||
// Vertex function added half a texel to UV2, to center the UV's. vertex(), fragment() and get_height()
|
||||
// call this with reclaimed versions of UV2, so to keep the last row/column within the correct
|
||||
// window, take back the half pixel before the floor().
|
||||
ivec2 pos = ivec2(floor(uv - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(pos.x >= 0 && pos.x < _region_map_size && pos.y >= 0 && pos.y < _region_map_size);
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
// The return value is still texel-centered.
|
||||
return vec3(uv - _region_offsets[layer_index], float(layer_index));
|
||||
}
|
||||
|
||||
// World Noise
|
||||
|
||||
uniform sampler2D _region_blend_map : hint_default_black, filter_linear, repeat_disable;
|
||||
uniform int world_noise_max_octaves : hint_range(0, 15) = 4;
|
||||
uniform int world_noise_min_octaves : hint_range(0, 15) = 2;
|
||||
uniform float world_noise_lod_distance : hint_range(0, 40000, 1) = 7500.;
|
||||
uniform float world_noise_scale : hint_range(0.25, 20, 0.01) = 5.0;
|
||||
uniform float world_noise_height : hint_range(0, 1000, 0.1) = 64.0;
|
||||
uniform vec3 world_noise_offset = vec3(0.0);
|
||||
uniform float world_noise_blend_near : hint_range(0, .95, 0.01) = 0.5;
|
||||
uniform float world_noise_blend_far : hint_range(.05, 1, 0.01) = 1.0;
|
||||
|
||||
float hashf(float f) {
|
||||
return fract(sin(f) * 1e4);
|
||||
}
|
||||
|
||||
float hashv2(vec2 v) {
|
||||
return fract(1e4 * sin(17.0 * v.x + v.y * 0.1) * (0.1 + abs(sin(v.y * 13.0 + v.x))));
|
||||
}
|
||||
|
||||
// https://iquilezles.org/articles/morenoise/
|
||||
vec3 noise2D(vec2 x) {
|
||||
vec2 f = fract(x);
|
||||
// Quintic Hermine Curve. Similar to SmoothStep()
|
||||
vec2 u = f*f*f*(f*(f*6.0-15.0)+10.0);
|
||||
vec2 du = 30.0*f*f*(f*(f-2.0)+1.0);
|
||||
|
||||
vec2 p = floor(x);
|
||||
|
||||
// Four corners in 2D of a tile
|
||||
float a = hashv2( p+vec2(0,0) );
|
||||
float b = hashv2( p+vec2(1,0) );
|
||||
float c = hashv2( p+vec2(0,1) );
|
||||
float d = hashv2( p+vec2(1,1) );
|
||||
|
||||
// Mix 4 corner percentages
|
||||
float k0 = a;
|
||||
float k1 = b - a;
|
||||
float k2 = c - a;
|
||||
float k3 = a - b - c + d;
|
||||
return vec3( k0 + k1 * u.x + k2 * u.y + k3 * u.x * u.y,
|
||||
du * ( vec2(k1, k2) + k3 * u.yx) );
|
||||
}
|
||||
|
||||
float world_noise(vec2 p) {
|
||||
float a = 0.0;
|
||||
float b = 1.0;
|
||||
vec2 d = vec2(0.0);
|
||||
|
||||
int octaves = int( clamp(
|
||||
float(world_noise_max_octaves) - floor(v_vertex_xz_dist/(world_noise_lod_distance)),
|
||||
float(world_noise_min_octaves), float(world_noise_max_octaves)) );
|
||||
|
||||
for( int i=0; i < octaves; i++ ) {
|
||||
vec3 n = noise2D(p);
|
||||
d += n.yz;
|
||||
a += b * n.x / (1.0 + dot(d,d));
|
||||
b *= 0.5;
|
||||
p = mat2( vec2(0.8, -0.6), vec2(0.6, 0.8) ) * p * 2.0;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// World Noise end
|
||||
|
||||
// 1 lookup
|
||||
float get_height(vec2 uv) {
|
||||
highp float height = 0.0;
|
||||
vec3 region = get_region_uv2(uv);
|
||||
if (region.z >= 0.) {
|
||||
height = texture(_height_maps, region).r;
|
||||
}
|
||||
// World Noise
|
||||
if (_background_mode == 2u) {
|
||||
float weight = texture(_region_blend_map, (uv / float(_region_map_size)) + 0.5).r;
|
||||
float rmap_half_size = float(_region_map_size) * .5;
|
||||
if (abs(uv.x) > rmap_half_size + .5 || abs(uv.y) > rmap_half_size + .5) {
|
||||
weight = 0.;
|
||||
} else {
|
||||
if (abs(uv.x) > rmap_half_size - .5) {
|
||||
weight = mix(weight, 0., abs(uv.x) - (rmap_half_size-.5));
|
||||
}
|
||||
if (abs(uv.y) > rmap_half_size - .5) {
|
||||
weight = mix(weight, 0., abs(uv.y) - (rmap_half_size-.5));
|
||||
}
|
||||
}
|
||||
height = mix(height, world_noise((uv + world_noise_offset.xz) * world_noise_scale * .1) *
|
||||
world_noise_height * 10. + world_noise_offset.y * 100.,
|
||||
clamp(smoothstep(world_noise_blend_near, world_noise_blend_far, 1.0 - weight), 0.0, 1.0));
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
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
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
v_vertex_xz_dist = length(v_vertex.xz - v_camera_pos.xz);
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = round(v_vertex.xz * _mesh_vertex_density);
|
||||
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
UV2 = (UV + vec2(0.5)) * _region_texel_size;
|
||||
|
||||
// Discard vertices for Holes. 1 lookup
|
||||
v_region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, v_region, 0).r;
|
||||
bool hole = bool(control >>2u & 0x1u);
|
||||
|
||||
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||
(hole || (_background_mode == 0u && v_region.z < 0)) ) {
|
||||
VERTEX.x = 0. / 0.;
|
||||
} else {
|
||||
// Set final vertex height & calculate vertex normals. 3 lookups.
|
||||
VERTEX.y = get_height(UV2);
|
||||
v_vertex.y = VERTEX.y;
|
||||
v_normal = vec3(
|
||||
v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
|
||||
_mesh_vertex_spacing,
|
||||
v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
|
||||
);
|
||||
// Due to a bug caused by the GPUs linear interpolation across edges of region maps,
|
||||
// mask region edges and use vertex normals only across region boundaries.
|
||||
v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
|
||||
}
|
||||
|
||||
// Transform UVs to local to avoid poor precision during varying interpolation.
|
||||
v_uv_offset = MODEL_MATRIX[3].xz * _mesh_vertex_density;
|
||||
UV -= v_uv_offset;
|
||||
v_uv2_offset = v_uv_offset * _region_texel_size;
|
||||
UV2 -= v_uv2_offset;
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Fragment
|
||||
////////////////////////
|
||||
|
||||
// 0 - 3 lookups
|
||||
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
|
||||
float u, v, height;
|
||||
vec3 normal;
|
||||
// Use vertex normals within radius of vertex_normals_distance, and along region borders.
|
||||
if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
|
||||
normal = normalize(v_normal);
|
||||
} else {
|
||||
height = get_height(uv);
|
||||
u = height - get_height(uv + vec2(_region_texel_size, 0));
|
||||
v = height - get_height(uv + vec2(0, _region_texel_size));
|
||||
normal = normalize(vec3(u, _mesh_vertex_spacing, v));
|
||||
}
|
||||
tangent = cross(normal, vec3(0, 0, 1));
|
||||
binormal = cross(normal, tangent);
|
||||
return normal;
|
||||
}
|
||||
|
||||
vec3 unpack_normal(vec4 rgba) {
|
||||
vec3 n = rgba.xzy * 2.0 - vec3(1.0);
|
||||
n.z *= -1.0;
|
||||
return n;
|
||||
}
|
||||
|
||||
vec4 pack_normal(vec3 n, float a) {
|
||||
n.z *= -1.0;
|
||||
return vec4((n.xzy + vec3(1.0)) * 0.5, a);
|
||||
}
|
||||
|
||||
float random(in vec2 xy) {
|
||||
return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
vec2 rotate(vec2 v, float cosa, float sina) {
|
||||
return vec2(cosa * v.x - sina * v.y, sina * v.x + cosa * v.y);
|
||||
}
|
||||
|
||||
// Moves a point around a pivot point.
|
||||
vec2 rotate_around(vec2 point, vec2 pivot, float angle){
|
||||
float x = pivot.x + (point.x - pivot.x) * cos(angle) - (point.y - pivot.y) * sin(angle);
|
||||
float y = pivot.y + (point.x - pivot.x) * sin(angle) + (point.y - pivot.y) * cos(angle);
|
||||
return vec2(x, y);
|
||||
}
|
||||
|
||||
vec4 height_blend(vec4 a_value, float a_height, vec4 b_value, float b_height, float blend) {
|
||||
if(height_blending) {
|
||||
float ma = max(a_height + (1.0 - blend), b_height + blend) - (1.001 - blend_sharpness);
|
||||
float b1 = max(a_height + (1.0 - blend) - ma, 0.0);
|
||||
float b2 = max(b_height + blend - ma, 0.0);
|
||||
return (a_value * b1 + b_value * b2) / (b1 + b2);
|
||||
} else {
|
||||
float contrast = 1.0 - blend_sharpness;
|
||||
float factor = (blend - contrast) / contrast;
|
||||
return mix(a_value, b_value, clamp(factor, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
vec2 detiling(vec2 uv, vec2 uv_center, int mat_id, inout float normal_rotation){
|
||||
if (_texture_detile_array[mat_id] >= 0.001){
|
||||
uv_center = floor(uv_center) + 0.5;
|
||||
float detile = (random(uv_center) - 0.5) * 2.0 * TAU * _texture_detile_array[mat_id]; // -180deg to 180deg
|
||||
uv = rotate_around(uv, uv_center, detile);
|
||||
// Accumulate total rotation for normal rotation
|
||||
normal_rotation += detile;
|
||||
}
|
||||
return uv;
|
||||
}
|
||||
|
||||
vec2 rotate_normal(vec2 normal, float angle) {
|
||||
angle += PI * 0.5;
|
||||
float new_y = dot(vec2(cos(angle), sin(angle)), normal);
|
||||
angle -= PI * 0.5;
|
||||
float new_x = dot(vec2(cos(angle) ,sin(angle)) ,normal);
|
||||
return vec2(new_x, new_y);
|
||||
}
|
||||
|
||||
// 2-4 lookups
|
||||
void get_material(vec2 base_uv, uint control, ivec3 iuv_center, vec3 normal, out Material out_mat) {
|
||||
out_mat = Material(vec4(0.), vec4(0.), 0, 0, 0.0);
|
||||
vec2 uv_center = vec2(iuv_center.xy);
|
||||
int region = iuv_center.z;
|
||||
|
||||
// Enable Autoshader if outside regions or painted in regions, otherwise manual painted
|
||||
bool auto_shader = region < 0 || bool(control & 0x1u);
|
||||
out_mat.base = int(auto_shader) * auto_base_texture + int(!auto_shader) * int(control >>27u & 0x1Fu);
|
||||
out_mat.over = int(auto_shader) * auto_overlay_texture + int(!auto_shader) * int(control >> 22u & 0x1Fu);
|
||||
out_mat.blend = float(auto_shader) * clamp(
|
||||
dot(vec3(0., 1., 0.), normal * auto_slope * 2. - (auto_slope * 2. - 1.))
|
||||
- auto_height_reduction * .01 * v_vertex.y // Reduce as vertices get higher
|
||||
, 0., 1.) +
|
||||
float(!auto_shader) * float(control >>14u & 0xFFu) * 0.003921568627450; // 1./255.0
|
||||
|
||||
// Control map scale & rotation, apply to both base and
|
||||
// uv_center. Translate uv center to the current region.
|
||||
uv_center += _region_offsets[region] * _region_size;
|
||||
// Define base scale from control map value as array index. 0.5 as baseline.
|
||||
float[8] scale_array = { 0.5, 0.4, 0.3, 0.2, 0.1, 0.8, 0.7, 0.6};
|
||||
float control_scale = scale_array[(control >>7u & 0x7u)];
|
||||
base_uv *= control_scale;
|
||||
uv_center *= control_scale;
|
||||
// calculate baseline derivatives
|
||||
vec2 ddx = dFdxCoarse(base_uv);
|
||||
vec2 ddy = dFdyCoarse(base_uv);
|
||||
// Apply global uv rotation from control map.
|
||||
float uv_rotation = float(control >>10u & 0xFu) / 16. * TAU;
|
||||
base_uv = rotate_around(base_uv, vec2(0), uv_rotation);
|
||||
uv_center = rotate_around(uv_center, vec2(0), uv_rotation);
|
||||
|
||||
vec2 matUV = base_uv;
|
||||
vec4 albedo_ht = vec4(0.);
|
||||
vec4 normal_rg = vec4(0.5f, 0.5f, 1.0f, 1.0f);
|
||||
vec4 albedo_far = vec4(0.);
|
||||
vec4 normal_far = vec4(0.5f, 0.5f, 1.0f, 1.0f);
|
||||
float mat_scale = _texture_uv_scale_array[out_mat.base];
|
||||
float normal_angle = uv_rotation;
|
||||
vec2 ddx1 = ddx;
|
||||
vec2 ddy1 = ddy;
|
||||
|
||||
// If dual scaling, apply to base texture
|
||||
if(region < 0) {
|
||||
mat_scale *= tri_scale_reduction;
|
||||
}
|
||||
//each time we change scale, recalculate antitiling from baseline to maintain continuity.
|
||||
matUV = detiling(base_uv * mat_scale, uv_center * mat_scale, out_mat.base, normal_angle);
|
||||
ddx1 *= mat_scale;
|
||||
ddy1 *= mat_scale;
|
||||
albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
|
||||
normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), ddx1, ddy1);
|
||||
|
||||
// Unpack & rotate base normal for blending
|
||||
normal_rg.xz = unpack_normal(normal_rg).xz;
|
||||
normal_rg.xz = rotate_normal(normal_rg.xz, normal_angle);
|
||||
|
||||
if(out_mat.base == dual_scale_texture || out_mat.over == dual_scale_texture) {
|
||||
mat_scale *= dual_scale_reduction;
|
||||
ddx1 *= dual_scale_reduction;
|
||||
ddy1 *= dual_scale_reduction;
|
||||
float dual_scale_normal = uv_rotation; //do not add near & far rotations
|
||||
// Do not apply detiling if tri-scale reduction occurs.
|
||||
matUV = region < 0 ? base_uv * mat_scale : detiling(base_uv * mat_scale, uv_center * mat_scale, dual_scale_texture, dual_scale_normal);
|
||||
albedo_far = textureGrad(_texture_array_albedo, vec3(matUV, float(dual_scale_texture)), ddx1, ddy1);
|
||||
normal_far = textureGrad(_texture_array_normal, vec3(matUV, float(dual_scale_texture)), ddx1, ddy1);
|
||||
|
||||
// Unpack & rotate dual scale normal for blending
|
||||
normal_far.xz = unpack_normal(normal_far).xz;
|
||||
normal_far.xz = rotate_normal(normal_far.xz, dual_scale_normal);
|
||||
}
|
||||
|
||||
float far_factor = clamp(smoothstep(dual_scale_near, dual_scale_far, length(v_vertex - v_camera_pos)), 0.0, 1.0);
|
||||
if(out_mat.base == dual_scale_texture) {
|
||||
albedo_ht = mix(albedo_ht, albedo_far, far_factor);
|
||||
normal_rg = mix(normal_rg, normal_far, far_factor);
|
||||
}
|
||||
|
||||
// Apply color to base
|
||||
albedo_ht.rgb *= _texture_color_array[out_mat.base].rgb;
|
||||
|
||||
// Setup overlay texture to blend
|
||||
float mat_scale2 = _texture_uv_scale_array[out_mat.over];
|
||||
float normal_angle2 = uv_rotation;
|
||||
vec2 matUV2 = detiling(base_uv * mat_scale2, uv_center * mat_scale2, out_mat.over, normal_angle2);
|
||||
vec2 ddx2 = ddx * mat_scale2;
|
||||
vec2 ddy2 = ddy * mat_scale2;
|
||||
vec4 albedo_ht2 = textureGrad(_texture_array_albedo, vec3(matUV2, float(out_mat.over)), ddx2, ddy2);
|
||||
vec4 normal_rg2 = textureGrad(_texture_array_normal, vec3(matUV2, float(out_mat.over)), ddx2, ddy2);
|
||||
|
||||
// Though it would seem having the above lookups in this block, or removing the branch would
|
||||
// be more optimal, the first introduces artifacts #276, and the second is noticably slower.
|
||||
// It seems the branching off dual scaling and the color array lookup is more optimal.
|
||||
if (out_mat.blend > 0.f) {
|
||||
// Unpack & rotate overlay normal for blending
|
||||
normal_rg2.xz = unpack_normal(normal_rg2).xz;
|
||||
normal_rg2.xz = rotate_normal(normal_rg2.xz, normal_angle2);
|
||||
|
||||
// If dual scaling, apply to overlay texture
|
||||
if(out_mat.over == dual_scale_texture) {
|
||||
albedo_ht2 = mix(albedo_ht2, albedo_far, far_factor);
|
||||
normal_rg2 = mix(normal_rg2, normal_far, far_factor);
|
||||
}
|
||||
|
||||
// Apply color to overlay
|
||||
albedo_ht2.rgb *= _texture_color_array[out_mat.over].rgb;
|
||||
|
||||
// Blend overlay and base
|
||||
albedo_ht = height_blend(albedo_ht, albedo_ht.a, albedo_ht2, albedo_ht2.a, out_mat.blend);
|
||||
normal_rg = height_blend(normal_rg, albedo_ht.a, normal_rg2, albedo_ht2.a, out_mat.blend);
|
||||
}
|
||||
|
||||
// Repack normals and return material
|
||||
normal_rg = pack_normal(normal_rg.xyz, normal_rg.a);
|
||||
out_mat.alb_ht = albedo_ht;
|
||||
out_mat.nrm_rg = normal_rg;
|
||||
return;
|
||||
}
|
||||
|
||||
float blend_weights(float weight, float detail) {
|
||||
weight = smoothstep(0.0, 1.0, weight);
|
||||
weight = sqrt(weight * 0.5);
|
||||
float result = max(0.1 * weight, 10.0 * (weight + detail) + 1.0f - (detail + 10.0));
|
||||
return result;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Recover UVs
|
||||
vec2 uv = UV + v_uv_offset;
|
||||
vec2 uv2 = UV2 + v_uv2_offset;
|
||||
|
||||
// Calculate Terrain Normals. 4 lookups
|
||||
vec3 w_tangent, w_binormal;
|
||||
vec3 w_normal = get_normal(uv2, w_tangent, w_binormal);
|
||||
NORMAL = mat3(VIEW_MATRIX) * w_normal;
|
||||
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
|
||||
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
|
||||
|
||||
// Idenfity 4 vertices surrounding this pixel
|
||||
vec2 texel_pos = uv;
|
||||
highp vec2 texel_pos_floor = floor(uv);
|
||||
|
||||
// Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY
|
||||
vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0);
|
||||
// And the opposite grid in ZW
|
||||
mirror.zw = vec2(1.0) - mirror.xy;
|
||||
|
||||
// Get the region and control map ID for the vertices
|
||||
ivec3 indexUV[4] = {
|
||||
get_region_uv(texel_pos_floor + mirror.xy),
|
||||
get_region_uv(texel_pos_floor + mirror.xw),
|
||||
get_region_uv(texel_pos_floor + mirror.zy),
|
||||
get_region_uv(texel_pos_floor + mirror.zw)
|
||||
};
|
||||
|
||||
// Lookup adjacent vertices. 4 lookups
|
||||
uint control[4] = {
|
||||
texelFetch(_control_maps, indexUV[0], 0).r,
|
||||
texelFetch(_control_maps, indexUV[1], 0).r,
|
||||
texelFetch(_control_maps, indexUV[2], 0).r,
|
||||
texelFetch(_control_maps, indexUV[3], 0).r
|
||||
};
|
||||
|
||||
// Get the textures for each vertex. 8-16 lookups (2-4 ea)
|
||||
Material mat[4];
|
||||
get_material(uv, control[0], indexUV[0], w_normal, mat[0]);
|
||||
get_material(uv, control[1], indexUV[1], w_normal, mat[1]);
|
||||
get_material(uv, control[2], indexUV[2], w_normal, mat[2]);
|
||||
get_material(uv, control[3], indexUV[3], w_normal, mat[3]);
|
||||
|
||||
// Macro variation. 2 Lookups
|
||||
float noise1 = texture(noise_texture, rotate(uv * noise1_scale * .1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r;
|
||||
float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
|
||||
vec3 macrov = mix(macro_variation1, vec3(1.), clamp(noise1 + v_vertex_xz_dist * .0002, 0., 1.));
|
||||
macrov *= mix(macro_variation2, vec3(1.), clamp(noise2 + v_vertex_xz_dist * .0002, 0., 1.));
|
||||
|
||||
|
||||
// Calculate weight for the pixel position between the vertices
|
||||
// Bilinear interpolation of difference of uv and floor(uv)
|
||||
vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1);
|
||||
weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy);
|
||||
vec2 weights0 = vec2(1.0) - weights1;
|
||||
// Adjust final weights by texture's height/depth + noise. 1 lookup
|
||||
float noise3 = texture(noise_texture, uv * noise3_scale).r;
|
||||
vec4 weights;
|
||||
weights.x = blend_weights(weights0.x * weights0.y, clamp(mat[0].alb_ht.a + noise3, 0., 1.));
|
||||
weights.y = blend_weights(weights0.x * weights1.y, clamp(mat[1].alb_ht.a + noise3, 0., 1.));
|
||||
weights.z = blend_weights(weights1.x * weights0.y, clamp(mat[2].alb_ht.a + noise3, 0., 1.));
|
||||
weights.w = blend_weights(weights1.x * weights1.y, clamp(mat[3].alb_ht.a + noise3, 0., 1.));
|
||||
float weight_sum = weights.x + weights.y + weights.z + weights.w;
|
||||
float weight_inv = 1.0 / weight_sum;
|
||||
|
||||
// Weighted average of albedo & height
|
||||
vec4 albedo_height = weight_inv * (
|
||||
mat[0].alb_ht * weights.x +
|
||||
mat[1].alb_ht * weights.y +
|
||||
mat[2].alb_ht * weights.z +
|
||||
mat[3].alb_ht * weights.w );
|
||||
|
||||
// Weighted average of normal & rough
|
||||
vec4 normal_rough = weight_inv * (
|
||||
mat[0].nrm_rg * weights.x +
|
||||
mat[1].nrm_rg * weights.y +
|
||||
mat[2].nrm_rg * weights.z +
|
||||
mat[3].nrm_rg * weights.w );
|
||||
|
||||
// Determine if we're in a region or not (region_uv.z>0)
|
||||
vec3 region_uv = get_region_uv2(uv2);
|
||||
|
||||
// Colormap. 1 lookup
|
||||
vec4 color_map = vec4(1., 1., 1., .5);
|
||||
if (region_uv.z >= 0.) {
|
||||
float lod = textureQueryLod(_color_maps, uv2.xy).y;
|
||||
color_map = textureLod(_color_maps, region_uv, lod);
|
||||
}
|
||||
|
||||
// Wetness/roughness modifier, converting 0-1 range to -1 to 1 range
|
||||
float roughness = fma(color_map.a - 0.5, 2.0, normal_rough.a);
|
||||
|
||||
// Apply PBR
|
||||
ALBEDO = albedo_height.rgb * color_map.rgb * macrov;
|
||||
ROUGHNESS = roughness;
|
||||
SPECULAR = 1. - normal_rough.a;
|
||||
NORMAL_MAP = normal_rough.rgb;
|
||||
NORMAL_MAP_DEPTH = 1.0;
|
||||
|
||||
}
|
||||
"
|
||||
|
||||
[resource]
|
||||
_shader_parameters = {
|
||||
"auto_base_texture": 0,
|
||||
"auto_height_reduction": 0.15,
|
||||
"auto_height_reduction": 0.0,
|
||||
"auto_overlay_texture": 1,
|
||||
"auto_slope": 1.25,
|
||||
"blend_sharpness": 0.7,
|
||||
"dual_scale_far": 170.0,
|
||||
"auto_slope": 1.0,
|
||||
"blend_sharpness": 0.5,
|
||||
"dual_scale_far": 200.0,
|
||||
"dual_scale_near": 100.0,
|
||||
"dual_scale_reduction": 0.2,
|
||||
"dual_scale_reduction": 0.3,
|
||||
"dual_scale_texture": 1,
|
||||
"height_blending": true,
|
||||
"macro_variation1": Color(0.8, 1, 0.8, 1),
|
||||
"macro_variation2": Color(0.6, 0.75, 0.6, 1),
|
||||
"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.165,
|
||||
"noise1_scale": 0.04,
|
||||
"noise2_scale": 0.076,
|
||||
"noise3_scale": 0.225,
|
||||
"noise_texture": SubResource("NoiseTexture2D_r2cfv"),
|
||||
"tri_scale_reduction": 0.125,
|
||||
"tri_scale_reduction": 0.3,
|
||||
"vertex_normals_distance": 128.0,
|
||||
"world_noise_blend_far": 1.0,
|
||||
"world_noise_blend_near": 0.5,
|
||||
"world_noise_height": 24.0,
|
||||
"world_noise_height": 12.0,
|
||||
"world_noise_lod_distance": 7500.0,
|
||||
"world_noise_max_octaves": 4,
|
||||
"world_noise_min_octaves": 2,
|
||||
"world_noise_offset": Vector3(0, 0, 0),
|
||||
"world_noise_region_blend": 0.05,
|
||||
"world_noise_scale": 16.0
|
||||
}
|
||||
world_background = 2
|
||||
texture_filtering = 1
|
||||
auto_shader = true
|
||||
dual_scaling = true
|
||||
shader_override_enabled = true
|
||||
shader_override = SubResource("Shader_ypt1p")
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1,44 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://b40ts3kdv4in0"
|
||||
path="res://.godot/imported/boundaries.glb-55bd84dc617756c38b0a32e84b5db1c6.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://maps/desert/assets/boundaries.glb"
|
||||
dest_files=["res://.godot/imported/boundaries.glb-55bd84dc617756c38b0a32e84b5db1c6.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
_subresources={
|
||||
"nodes": {
|
||||
"PATH:Cube-concave": {
|
||||
"generate/physics": true,
|
||||
"physics/mask": 9,
|
||||
"physics/shape_type": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
gltf/naming_version=1
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
maps/desert/data/terrain3d-01-01.res
Normal file
BIN
maps/desert/data/terrain3d-01-01.res
Normal file
Binary file not shown.
BIN
maps/desert/data/terrain3d-01_00.res
Normal file
BIN
maps/desert/data/terrain3d-01_00.res
Normal file
Binary file not shown.
BIN
maps/desert/data/terrain3d_00-01.res
Normal file
BIN
maps/desert/data/terrain3d_00-01.res
Normal file
Binary file not shown.
BIN
maps/desert/data/terrain3d_00_00.res
Normal file
BIN
maps/desert/data/terrain3d_00_00.res
Normal file
Binary file not shown.
|
|
@ -1,23 +1,35 @@
|
|||
[gd_scene load_steps=8 format=3 uid="uid://btlkog4b87p4x"]
|
||||
[gd_scene load_steps=12 format=3 uid="uid://btlkog4b87p4x"]
|
||||
|
||||
[ext_resource type="Terrain3DStorage" uid="uid://wgmg245njt8e" path="res://maps/desert/resources/storage.res" id="1_lmk23"]
|
||||
[ext_resource type="Script" path="res://maps/spawns.gd" id="2_kdipq"]
|
||||
[ext_resource type="Terrain3DMaterial" uid="uid://c3isipd4wqxpk" path="res://maps/desert/resources/material.tres" id="2_n44fh"]
|
||||
[ext_resource type="Terrain3DAssets" uid="uid://d1j24k8sq8qpj" path="res://maps/desert/resources/textures.tres" id="3_w6mwl"]
|
||||
[ext_resource type="Environment" uid="uid://nw62ce5cglvs" path="res://maps/desert/resources/env.tres" id="4_m7p64"]
|
||||
[ext_resource type="Script" path="res://maps/map.gd" id="4_o1mfe"]
|
||||
[ext_resource type="PackedScene" uid="uid://b40ts3kdv4in0" path="res://maps/desert/assets/boundaries.glb" id="5_dyuqn"]
|
||||
|
||||
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_wu2oa"]
|
||||
data = PackedVector3Array(-1024, -1024, 1024, -1024, 1024, -1024, -1024, 1024, 1024, -1024, -1024, 1024, -1024, -1024, -1024, -1024, 1024, -1024, -1024, -1024, -1024, 1024, 1024, -1024, -1024, 1024, -1024, -1024, -1024, -1024, 1024, -1024, -1024, 1024, 1024, -1024, 1024, -1024, -1024, 1024, 1024, 1024, 1024, 1024, -1024, 1024, -1024, -1024, 1024, -1024, 1024, 1024, 1024, 1024, 1024, -1024, 1024, -1024, 1024, 1024, 1024, 1024, 1024, 1024, -1024, 1024, -1024, -1024, 1024, -1024, 1024, 1024, -1024, -1024, -1024, 1024, -1024, 1024, 1024, -1024, -1024, -1024, -1024, -1024, -1024, -1024, 1024, 1024, -1024, 1024, 1024, 1024, -1024, -1024, 1024, 1024, -1024, 1024, -1024, 1024, 1024, -1024, 1024, 1024, 1024, -1024, 1024, 1024)
|
||||
backface_collision = true
|
||||
[sub_resource type="Resource" id="Resource_ti0hi"]
|
||||
script = ExtResource("2_kdipq")
|
||||
players = Array[NodePath]([NodePath("PlayerSpawns/Spawn1"), NodePath("PlayerSpawns/Spawn2")])
|
||||
objectives = Array[NodePath]([NodePath("FlagStand")])
|
||||
|
||||
[node name="Desert" type="Terrain3D"]
|
||||
storage = ExtResource("1_lmk23")
|
||||
material = ExtResource("2_n44fh")
|
||||
assets = ExtResource("3_w6mwl")
|
||||
collision_layer = 2147483648
|
||||
collision_mask = 2147483648
|
||||
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_p8eda"]
|
||||
bounce = 0.5
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1y05l"]
|
||||
plane = Plane(1, 0, 0, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_r685f"]
|
||||
plane = Plane(-1, 0, 0, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_yhtlb"]
|
||||
plane = Plane(0, 0, 1, 0)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_e5ob2"]
|
||||
plane = Plane(0, 0, -1, 0)
|
||||
|
||||
[node name="Desert" type="Node3D" node_paths=PackedStringArray("terrain")]
|
||||
script = ExtResource("4_o1mfe")
|
||||
terrain = NodePath("Terrain3D")
|
||||
spawns = SubResource("Resource_ti0hi")
|
||||
|
||||
[node name="Sunlight" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.5, 0.55667, -0.663414, 0, 0.766044, 0.642788, 0.866025, -0.321394, 0.383022, -1613.38, 2730.74, 446.64)
|
||||
|
|
@ -26,25 +38,43 @@ shadow_enabled = true
|
|||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = ExtResource("4_m7p64")
|
||||
|
||||
[node name="Terrain3D" type="Terrain3D" parent="."]
|
||||
data_directory = "res://maps/desert/data"
|
||||
material = ExtResource("2_n44fh")
|
||||
assets = ExtResource("3_w6mwl")
|
||||
collision_layer = 2147483649
|
||||
collision_mask = 9
|
||||
collision_priority = 9.0
|
||||
cast_shadows = 0
|
||||
top_level = true
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="PlayerSpawns" type="Node" parent="."]
|
||||
|
||||
[node name="Spawn1" type="Marker3D" parent="PlayerSpawns"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1024, 172.13, 1024)
|
||||
transform = Transform3D(0.998641, 7.63963e-05, 0.0521093, -2.81251e-11, 0.999999, -0.00146608, -0.0521094, 0.00146408, 0.99864, 0, 59.1821, 0)
|
||||
|
||||
[node name="Spawn2" type="Marker3D" parent="PlayerSpawns"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1161.42, 174.535, 909.901)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 137.42, 75.6649, -114.099)
|
||||
|
||||
[node name="FlagStand" type="Marker3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 895.356, 145.367, 888.261)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -128.644, 66.6749, -135.739)
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(-0.642788, 0.586824, -0.492404, 0, 0.642788, 0.766044, 0.766044, 0.492404, -0.413176, 800, 320, 846.595)
|
||||
[node name="Boundaries" type="StaticBody3D" parent="."]
|
||||
physics_material_override = SubResource("PhysicsMaterial_p8eda")
|
||||
|
||||
[node name="Boundaries" parent="." instance=ExtResource("5_dyuqn")]
|
||||
transform = Transform3D(0.99, 0, 0, 0, 0.99, 0, 0, 0, 0.99, 1024, 1024, 1024)
|
||||
visible = false
|
||||
[node name="East" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -921.6, 0, 0)
|
||||
shape = SubResource("WorldBoundaryShape3D_1y05l")
|
||||
|
||||
[node name="CollisionShape3D" parent="Boundaries/Cube-concave/StaticBody3D" index="0"]
|
||||
shape = SubResource("ConcavePolygonShape3D_wu2oa")
|
||||
[node name="West" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 921.6, 0, 0)
|
||||
shape = SubResource("WorldBoundaryShape3D_r685f")
|
||||
|
||||
[editable path="Boundaries"]
|
||||
[node name="South" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -921.6)
|
||||
shape = SubResource("WorldBoundaryShape3D_yhtlb")
|
||||
|
||||
[node name="North" type="CollisionShape3D" parent="Boundaries"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 921.6)
|
||||
shape = SubResource("WorldBoundaryShape3D_e5ob2")
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ noise = SubResource("FastNoiseLite_e3unr")
|
|||
_shader_parameters = {
|
||||
"_mouse_layer": 2147483648,
|
||||
"auto_base_texture": 0,
|
||||
"auto_height_reduction": 0.085,
|
||||
"auto_height_reduction": 0.1,
|
||||
"auto_overlay_texture": 1,
|
||||
"auto_slope": 1.0,
|
||||
"blend_sharpness": 0.85,
|
||||
"auto_slope": 2.25,
|
||||
"blend_sharpness": 0.69,
|
||||
"dual_scale_far": 100.0,
|
||||
"dual_scale_near": 0.0,
|
||||
"dual_scale_reduction": 0.3,
|
||||
|
|
@ -43,15 +43,14 @@ _shader_parameters = {
|
|||
"noise3_scale": 0.225,
|
||||
"noise_texture": SubResource("NoiseTexture2D_esvkc"),
|
||||
"tri_scale_reduction": 0.075,
|
||||
"vertex_normals_distance": 0.0,
|
||||
"world_noise_blend_far": 1.0,
|
||||
"world_noise_blend_near": 0.75,
|
||||
"world_noise_height": 50.0,
|
||||
"world_noise_lod_distance": 500.0,
|
||||
"world_noise_max_octaves": 6,
|
||||
"world_noise_min_octaves": 5,
|
||||
"vertex_normals_distance": 128.0,
|
||||
"world_noise_height": 64.0,
|
||||
"world_noise_lod_distance": 7500.0,
|
||||
"world_noise_max_octaves": 4,
|
||||
"world_noise_min_octaves": 2,
|
||||
"world_noise_offset": Vector3(0, 0, 0),
|
||||
"world_noise_scale": 7.5
|
||||
"world_noise_region_blend": 0.05,
|
||||
"world_noise_scale": 5.0
|
||||
}
|
||||
world_background = 2
|
||||
texture_filtering = 1
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
33
maps/map.gd
33
maps/map.gd
|
|
@ -1,13 +1,30 @@
|
|||
class_name Map extends Node
|
||||
# This file is part of open-fpsz.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
class_name Map extends Node3D
|
||||
|
||||
@export_group("Spawns")
|
||||
@export var players : Array[Node]
|
||||
@export var objectives : Array[Node]
|
||||
@export var terrain : Terrain3D
|
||||
@export var spawns : MapSpawns
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
#pass
|
||||
func get_objective_spawn() -> Node3D:
|
||||
var random_index : int = randi() % spawns.objectives.size()
|
||||
return get_node(spawns.objectives[random_index])
|
||||
|
||||
func get_player_spawn() -> Node3D:
|
||||
var random_index : int = randi() % spawns.players.size()
|
||||
return get_node(spawns.players[random_index])
|
||||
|
|
|
|||
18
maps/spawns.gd
Normal file
18
maps/spawns.gd
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# This file is part of open-fpsz.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
class_name MapSpawns extends Resource
|
||||
|
||||
@export var players : Array[NodePath] = []
|
||||
@export var objectives : Array[NodePath] = []
|
||||
|
|
@ -29,6 +29,7 @@ gdscript/warnings/unused_variable=2
|
|||
gdscript/warnings/unused_local_constant=2
|
||||
gdscript/warnings/unused_private_class_variable=2
|
||||
gdscript/warnings/unused_parameter=2
|
||||
gdscript/warnings/unused_signal=0
|
||||
gdscript/warnings/shadowed_variable=2
|
||||
gdscript/warnings/shadowed_variable_base_class=2
|
||||
gdscript/warnings/shadowed_global_identifier=2
|
||||
|
|
@ -141,6 +142,5 @@ respawn={
|
|||
|
||||
[physics]
|
||||
|
||||
common/physics_jitter_fix=0.0
|
||||
3d/physics_engine="JoltPhysics3D"
|
||||
3d/default_gravity=19.6
|
||||
|
|
|
|||
|
|
@ -19,19 +19,8 @@ extends Node
|
|||
@export var maps : Array[PackedScene]
|
||||
|
||||
## @experimental
|
||||
var current_map : Terrain3D = null:
|
||||
var current_map : Node3D = null:
|
||||
set(new_map):
|
||||
if current_map != null:
|
||||
current_map.queue_free()
|
||||
current_map = new_map
|
||||
|
||||
## @experimental
|
||||
func get_player_spawn() -> Node3D:
|
||||
if not current_map or not current_map.has_node("PlayerSpawns"):
|
||||
return null
|
||||
var spawns : Node = current_map.get_node("PlayerSpawns")
|
||||
if spawns:
|
||||
var child_count : int = spawns.get_child_count()
|
||||
return spawns.get_child(
|
||||
randi_range(0, child_count - 1 if child_count else 0))
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func test_that_flag_grab_hides_flag() -> void:
|
|||
|
||||
func test_grab_taken_flag() -> void:
|
||||
test_grab_flag()
|
||||
var _new_carry = FlagCarryComponent.new()
|
||||
var _new_carry : FlagCarryComponent = FlagCarryComponent.new()
|
||||
add_child_autofree(_new_carry)
|
||||
_new_carry.grab(_subject)
|
||||
assert_null(_new_carry._flag)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ enum Mode {
|
|||
## The multiplayer mode.
|
||||
@export var mode : Mode = Mode.FREE_FOR_ALL
|
||||
## The current map node.
|
||||
@export var map : Node: set = set_map
|
||||
@export var map : Node
|
||||
## The maximum number of clients.
|
||||
@export var MAX_CLIENTS := 24
|
||||
## The time it takes for a player to respawn when killed, secconds (s).
|
||||
|
|
@ -79,8 +79,8 @@ func is_peer_connected() -> bool:
|
|||
|
||||
func set_map(new_map: Node) -> void:
|
||||
map = new_map
|
||||
map_root.add_child(map)
|
||||
MapsManager.current_map = map
|
||||
map_root.add_child(map)
|
||||
|
||||
## This method starts a server.
|
||||
func start_server(port : int, map_scene : PackedScene, _mode: Mode = mode, username : String = "mercury") -> void:
|
||||
|
|
@ -96,6 +96,8 @@ func start_server(port : int, map_scene : PackedScene, _mode: Mode = mode, usern
|
|||
add_child(timer)
|
||||
|
||||
map = map_scene.instantiate()
|
||||
map_root.add_child(map)
|
||||
|
||||
mode = _mode
|
||||
|
||||
ping.synchronized.connect(scoreboard._on_ping_sync)
|
||||
|
|
@ -103,14 +105,13 @@ func start_server(port : int, map_scene : PackedScene, _mode: Mode = mode, usern
|
|||
match mode:
|
||||
Mode.RABBIT:
|
||||
var flag : Flag = _FLAG.instantiate()
|
||||
var flagstand : Marker3D = map.get_node("FlagStand")
|
||||
objectives.add_child(flag)
|
||||
if flagstand:
|
||||
flag.global_position = flagstand.global_position
|
||||
var spawn : Node3D = map.get_objective_spawn()
|
||||
flag.global_position = spawn.global_position
|
||||
flag.respawn_timer.timeout.connect(func() -> void:
|
||||
if flagstand:
|
||||
if spawn:
|
||||
flag.waypoint.text = ""
|
||||
flag.global_position = flagstand.global_position
|
||||
flag.global_position = spawn.global_position
|
||||
flag.state = flag.FlagState.ON_STAND
|
||||
)
|
||||
|
||||
|
|
@ -191,9 +192,8 @@ func _start_match() -> void:
|
|||
if player.has_flag():
|
||||
var flag: Flag = player.flag_carry_component._flag
|
||||
player.flag_carry_component.drop(player.linear_velocity)
|
||||
var flagstand : Marker3D = map.get_node("FlagStand")
|
||||
if flagstand:
|
||||
flag.global_position = flagstand.global_position
|
||||
var spawn : Node3D = map.get_objective_spawn()
|
||||
flag.global_position = spawn.global_position
|
||||
scoreboard.reset_scores()
|
||||
timer.start(MATCH_DURATION) # restart timer with match duration
|
||||
players.respawn() # respawn everyone
|
||||
|
|
@ -211,9 +211,8 @@ func _on_post_match() -> void:
|
|||
if player.has_flag():
|
||||
var flag: Flag = player.flag_carry_component._flag
|
||||
player.flag_carry_component.drop(player.linear_velocity)
|
||||
var flagstand : Marker3D = map.get_node("FlagStand")
|
||||
if flagstand:
|
||||
flag.global_position = flagstand.global_position
|
||||
var spawn : Node3D = map.get_objective_spawn()
|
||||
flag.global_position = spawn.global_position
|
||||
# restart match
|
||||
_start_match()
|
||||
|
||||
|
|
@ -224,6 +223,8 @@ func join_server(host : String, port : int, username : String) -> void:
|
|||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
multiplayer.multiplayer_peer = peer
|
||||
if map_root.get_child_count() > 0:
|
||||
map = map_root.get_child(0)
|
||||
|
||||
func _on_server_disconnected() -> void:
|
||||
Game.exit_pressed.emit()
|
||||
|
|
@ -242,7 +243,7 @@ func add_player(_peer_id : int, username : String) -> void:
|
|||
# @NOTE: player scene requires both params prior being added to the tree
|
||||
players.add_child(player)
|
||||
# @TODO: get spawn location randomly instead of using points
|
||||
player.global_position = MapsManager.get_player_spawn().global_position
|
||||
player.global_position = Game.type.map.get_player_spawn().global_position
|
||||
player.killed.connect(_on_player_killed)
|
||||
|
||||
## This method switch a [param peer_id] to the specified team along with its score.
|
||||
|
|
@ -289,7 +290,7 @@ func _on_connection_failed() -> void:
|
|||
|
||||
func _on_player_killed(victim: Player, _killer:int) -> void:
|
||||
await get_tree().create_timer(VICTIM_RESPAWN_TIME).timeout
|
||||
var spawn: Node3D = MapsManager.get_player_spawn()
|
||||
var spawn: Node3D = Game.type.map.get_player_spawn()
|
||||
victim.respawn.rpc_id(1, spawn.global_position)
|
||||
|
||||
# This method notifies the server that a player wants to join the match. It
|
||||
|
|
@ -318,8 +319,8 @@ func _cleanup_peer() -> void:
|
|||
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
queue_free()
|
||||
|
||||
func _exit_tree() -> void:
|
||||
#func _exit_tree() -> void:
|
||||
# @NOTE: The `is_multiplayer_authority` method in `_extree` push an error
|
||||
# about the multiplayer instance not being the currently active one.it_
|
||||
# see https://github.com/godotengine/godot/issues/77723
|
||||
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
#multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ script = ExtResource("5_op0if")
|
|||
[node name="Map" type="Node" parent="."]
|
||||
|
||||
[node name="MapSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://maps/desert/desert.tscn")
|
||||
_spawnable_scenes = PackedStringArray("res://maps/desert/desert.tscn", "res://maps/blaze/blaze.tscn")
|
||||
spawn_path = NodePath("../Map")
|
||||
spawn_limit = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ extends Node
|
|||
|
||||
func respawn() -> void:
|
||||
for player : Player in get_children():
|
||||
var spawn : Node3D = MapsManager.get_player_spawn()
|
||||
var spawn : Node3D = Game.type.map.get_player_spawn()
|
||||
if spawn:
|
||||
player.respawn.rpc_id(get_multiplayer_authority(), spawn.global_position)
|
||||
|
|
|
|||
|
|
@ -19,19 +19,15 @@ class_name Singleplayer extends Node
|
|||
@onready var map : Node = $Blaze
|
||||
|
||||
func _ready() -> void:
|
||||
MapsManager.current_map = map
|
||||
player.health.exhausted.connect(_on_player_dead)
|
||||
var flag_spawn : Node = map.objectives.front()
|
||||
flag.global_position = flag_spawn.global_position
|
||||
#var flagstand : Marker3D = map.get_node("FlagStand")
|
||||
#if flagstand:
|
||||
#flag.global_position = flagstand.position
|
||||
var spawn : Node = map.get_objective_spawn()
|
||||
flag.global_position = spawn.global_position
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("exit"):
|
||||
queue_free()
|
||||
|
||||
func _on_player_dead(_player : Player) -> void:
|
||||
var spawns : Node = map.get_node("PlayerSpawns")
|
||||
var spawn : Marker3D = spawns.get_child(
|
||||
randi_range(0, spawns.get_child_count() - 1))
|
||||
var spawn : Node3D = map.get_player_spawn()
|
||||
player.respawn(spawn.global_position)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue