generated from krampus/template-godot4
Added Terrain3D addon
This commit is contained in:
parent
3558e0ab52
commit
557ede92e2
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,56 @@
|
|||
<img src="doc/docs/images/terrain3d.png">
|
||||
|
||||
# Terrain3D
|
||||
A high performance, editable terrain system for Godot 4.
|
||||
|
||||
## Features
|
||||
* 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)
|
||||
* Up to 32 textures
|
||||
* Up to 10 levels of detail
|
||||
* Foliage instancing
|
||||
* Sculpting, holes, texture painting, texture detiling, painting colors and wetness
|
||||
* Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), WorldMachine, Unity, Unreal and any tool that can export a heightmap (raw/r16/exr/+). See [importing data](https://terrain3d.readthedocs.io/en/stable/docs/import_export.html)
|
||||
|
||||
See [Project Status](https://terrain3d.readthedocs.io/en/stable/docs/project_status.html) for details.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Read the [Installation & Upgrades](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions.
|
||||
|
||||
2. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) or join our [Discord server](https://tokisan.com/discord).
|
||||
|
||||
3. Watch the tutorial videos:
|
||||
|
||||
**Installation, Setup, Basic Usage**
|
||||
|
||||
[![Using Terrain3D - Part 1](https://i.ytimg.com/vi/oV8c9alXVwU/hqdefault.jpg)](https://youtu.be/oV8c9alXVwU)
|
||||
|
||||
**Texture Painting, Holes, Navigation, Advanced Usage**
|
||||
|
||||
[![Using Terrain3D - Part 2](https://i.ytimg.com/vi/YtiAI2F6Xkk/hqdefault.jpg)](https://youtu.be/YtiAI2F6Xkk)
|
||||
|
||||
|
||||
## Credit
|
||||
Developed for the Godot community by:
|
||||
|
||||
|||
|
||||
|--|--|
|
||||
| **Cory Petkovsek, Tokisan Games** | [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true" width="24"/>](https://twitter.com/TokisanGames) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true" width="24"/>](https://github.com/TokisanGames) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true" width="24"/>](https://tokisan.com/) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/discord.png?raw=true" width="24"/>](https://tokisan.com/discord) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true" width="24"/>](https://www.youtube.com/@TokisanGames)|
|
||||
| **Roope Palmroos, Outobugi Games** | [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true" width="24"/>](https://twitter.com/outobugi) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true" width="24"/>](https://github.com/outobugi) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true" width="24"/>](https://outobugi.com/) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true" width="24"/>](https://www.youtube.com/@outobugi)|
|
||||
|
||||
And other contributors displayed on the right of the github page and in [AUTHORS.md](https://github.com/TokisanGames/Terrain3D/blob/main/AUTHORS.md).
|
||||
|
||||
Geometry clipmap mesh code created by [Mike J. Savage](https://mikejsavage.co.uk/blog/geometry-clipmaps.html). Blog and repository code released under the MIT license per email communication with Mike.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md](https://github.com/TokisanGames/Terrain3D/blob/main/CONTRIBUTING.md) if you would like to help make Terrain3D the best terrain system for Godot.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This addon has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt).
|
||||
|
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.
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.
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.
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.
|
@ -0,0 +1,299 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
#class_name Terrain3DEditorPlugin Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# Includes
|
||||
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 terrain: Terrain3D
|
||||
var _last_terrain: Terrain3D
|
||||
var nav_region: NavigationRegion3D
|
||||
|
||||
var editor: Terrain3DEditor
|
||||
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
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
editor = Terrain3DEditor.new()
|
||||
ui = UI.new()
|
||||
ui.plugin = self
|
||||
add_child(ui)
|
||||
|
||||
region_gizmo = RegionGizmo.new()
|
||||
|
||||
scene_changed.connect(_on_scene_changed)
|
||||
|
||||
asset_dock = load(ASSET_DOCK).instantiate()
|
||||
asset_dock.initialize(self)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
asset_dock.remove_dock(true)
|
||||
asset_dock.queue_free()
|
||||
ui.queue_free()
|
||||
editor.free()
|
||||
|
||||
scene_changed.disconnect(_on_scene_changed)
|
||||
|
||||
|
||||
func _handles(p_object: Object) -> bool:
|
||||
if p_object is Terrain3D:
|
||||
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:
|
||||
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 _edit(p_object: Object) -> void:
|
||||
if !p_object:
|
||||
_clear()
|
||||
|
||||
if p_object is Terrain3D:
|
||||
if p_object == terrain:
|
||||
return
|
||||
terrain = p_object
|
||||
_last_terrain = terrain
|
||||
editor.set_terrain(terrain)
|
||||
region_gizmo.set_node_3d(terrain)
|
||||
terrain.add_gizmo(region_gizmo)
|
||||
terrain.set_plugin(self)
|
||||
|
||||
# Connect to new Assets resource
|
||||
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()
|
||||
else:
|
||||
_clear()
|
||||
|
||||
if is_instance_valid(_last_terrain) and _last_terrain.is_inside_tree():
|
||||
if p_object is NavigationRegion3D:
|
||||
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)
|
||||
|
||||
terrain.clear_gizmos()
|
||||
terrain = null
|
||||
editor.set_terrain(null)
|
||||
|
||||
ui.clear_picking()
|
||||
|
||||
region_gizmo.clear()
|
||||
|
||||
|
||||
func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> int:
|
||||
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
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
if _prev_enable_state >= 0 and not Input.is_key_pressed(KEY_CTRL):
|
||||
_negative_input = false
|
||||
ui.toolbar_settings.set_setting("enable", bool(_prev_enable_state))
|
||||
_prev_enable_state = -1
|
||||
|
||||
## Setup for active camera & viewport
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
# Detect if viewport is set to half_resolution
|
||||
# Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D
|
||||
var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
|
||||
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
|
||||
|
||||
## Get mouse location on terrain
|
||||
|
||||
# Project 2D mouse position to 3D position and direction
|
||||
var mouse_pos: Vector2 = p_event.position if full_resolution else p_event.position/2
|
||||
var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
|
||||
var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
|
||||
|
||||
# If region tool, grab mouse position without considering height
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
|
||||
mouse_global_position = (camera_pos + t * camera_dir)
|
||||
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
|
||||
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)
|
||||
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_STOP
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
func _load_storage() -> void:
|
||||
if terrain:
|
||||
update_region_grid()
|
||||
|
||||
|
||||
func update_region_grid() -> void:
|
||||
if not region_gizmo:
|
||||
return
|
||||
|
||||
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()
|
||||
|
||||
terrain.update_gizmos()
|
||||
return
|
||||
|
||||
region_gizmo.show_rect = false
|
||||
region_gizmo.region_size = 1024
|
||||
region_gizmo.grid = [Vector2i.ZERO]
|
||||
|
||||
|
||||
func _on_scene_changed(scene_root: Node) -> void:
|
||||
if not scene_root:
|
||||
return
|
||||
|
||||
for node in scene_root.find_children("", "Terrain3DObjects"):
|
||||
node.editor_setup(self)
|
||||
|
||||
asset_dock.update_assets()
|
||||
await get_tree().create_timer(2).timeout
|
||||
asset_dock.update_thumbnails()
|
||||
|
||||
|
||||
func is_terrain_valid(p_terrain: Terrain3D = null) -> bool:
|
||||
var t: Terrain3D
|
||||
if p_terrain:
|
||||
t = p_terrain
|
||||
else:
|
||||
t = terrain
|
||||
if is_instance_valid(t) and t.is_inside_tree() and t.get_storage():
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func is_selected() -> bool:
|
||||
var selected: Array[Node] = get_editor_interface().get_selection().get_selected_nodes()
|
||||
for node in selected:
|
||||
if node.get_instance_id() == _last_terrain.get_instance_id():
|
||||
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()
|
||||
es.clear()
|
||||
es.add_node(_last_terrain)
|
|
@ -0,0 +1,41 @@
|
|||
## Import From SimpleGrassTextured
|
||||
#
|
||||
# This script demonstrates how to import transforms from SimpleGrassTextured. To use it:
|
||||
#
|
||||
# 1. Setup the mesh asset you wish to use in the asset dock.
|
||||
# 1. Select your Terrain3D node.
|
||||
# 1. In the inspector, click Script (very bottom) and Quick Load import_sgt.gd.
|
||||
# 1. At the very top, assign your SimpleGrassTextured node.
|
||||
# 1. Input the desired mesh asset ID.
|
||||
# 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.
|
||||
#
|
||||
# Use clear_instances to erase all instances that match the assign_mesh_id.
|
||||
#
|
||||
# The add_transforms function (called by add_multimesh) applies the height_offset specified in the
|
||||
# Terrain3DMeshAsset.
|
||||
# Once the transforms are imported, you can reassign any mesh you like into this mesh slot.
|
||||
|
||||
@tool
|
||||
extends Terrain3D
|
||||
|
||||
@export var simple_grass_textured: MultiMeshInstance3D
|
||||
@export var assign_mesh_id: int
|
||||
@export var import: bool = false : set = import_sgt
|
||||
@export var clear_instances: bool = false : set = clear_multimeshes
|
||||
|
||||
|
||||
func clear_multimeshes(value: bool) -> void:
|
||||
get_instancer().clear_by_mesh(assign_mesh_id)
|
||||
|
||||
|
||||
func import_sgt(value: bool) -> void:
|
||||
var sgt_mm: MultiMesh = simple_grass_textured.multimesh
|
||||
var global_xform: Transform3D = simple_grass_textured.global_transform
|
||||
print("Starting to import %d instances from SimpleGrassTextured using mesh id %d" % [ sgt_mm.instance_count, assign_mesh_id])
|
||||
var time: int = Time.get_ticks_msec()
|
||||
get_instancer().add_multimesh(assign_mesh_id, sgt_mm, simple_grass_textured.global_transform)
|
||||
print("Import complete in %.2f seconds" % [ float(Time.get_ticks_msec() - time)/1000. ])
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// 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;
|
||||
|
||||
// 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 float _texture_uv_scale_array[32];
|
||||
uniform float _texture_uv_rotation_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
|
||||
|
||||
varying flat vec2 v_uv_offset;
|
||||
varying flat vec2 v_uv2_offset;
|
||||
|
||||
////////////////////////
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
vec3 vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = round(vertex.xz * _mesh_vertex_density);
|
||||
|
||||
// Discard vertices for Holes. 1 lookup
|
||||
ivec3 region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, 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
|
||||
VERTEX.y = get_height(UV2);
|
||||
}
|
||||
|
||||
// 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
|
||||
////////////////////////
|
||||
|
||||
// 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;
|
||||
} else {
|
||||
u = height - get_height(uv + vec2(_region_texel_size, 0));
|
||||
}
|
||||
|
||||
vec3 normal = vec3(u, _mesh_vertex_spacing, v);
|
||||
normal = normalize(normal);
|
||||
tangent = cross(normal, vec3(0, 0, 1));
|
||||
binormal = cross(normal, tangent);
|
||||
return normal;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Apply PBR
|
||||
ALBEDO=vec3(.2);
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
|
||||
# It provides a `Project on Terrain3D` modifier, which allows Scatter
|
||||
# to detect the terrain height from Terrain3D without using collision.
|
||||
# Copy this file into /addons/proton_scatter/src/modifiers
|
||||
# Then uncomment everything below
|
||||
# In the editor, add this modifier to Scatter, then set your Terrain3D node
|
||||
|
||||
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
|
||||
# It allows Scatter to detect the terrain height from Terrain3D
|
||||
# Copy this file into /addons/proton_scatter/src/modifiers
|
||||
# Then uncomment everything below (select, press CTRL+K)
|
||||
# In the editor, add this modifier, then set your Terrain3D node
|
||||
|
||||
#@tool
|
||||
#extends "base_modifier.gd"
|
||||
#
|
||||
#
|
||||
#signal projection_completed
|
||||
#
|
||||
#
|
||||
#@export var terrain_node : NodePath
|
||||
#@export var align_with_collision_normal := false
|
||||
#
|
||||
#var _terrain: Terrain3D
|
||||
#
|
||||
#
|
||||
#func _init() -> void:
|
||||
#display_name = "Project On Terrain3D"
|
||||
#category = "Edit"
|
||||
#can_restrict_height = false
|
||||
#global_reference_frame_available = true
|
||||
#local_reference_frame_available = true
|
||||
#individual_instances_reference_frame_available = true
|
||||
#use_global_space_by_default()
|
||||
#
|
||||
#documentation.add_paragraph(
|
||||
#"This is a duplicate of `Project on Colliders` that queries the terrain system
|
||||
#for height and sets the transform height appropriately.
|
||||
#
|
||||
#This modifier must have terrain_node set to a Terrain3D node.")
|
||||
#
|
||||
#var p := documentation.add_parameter("Terrain Node")
|
||||
#p.set_type("NodePath")
|
||||
#p.set_description("Set your Terrain3D node.")
|
||||
#
|
||||
#p = documentation.add_parameter("Align with collision normal")
|
||||
#p.set_type("bool")
|
||||
#p.set_description(
|
||||
#"Rotate the transform to align it with the collision normal in case
|
||||
#the ray cast hit a collider.")
|
||||
#
|
||||
#
|
||||
#func _process_transforms(transforms, domain, _seed) -> void:
|
||||
#if transforms.is_empty():
|
||||
#return
|
||||
#
|
||||
#if terrain_node:
|
||||
#_terrain = domain.get_root().get_node_or_null(terrain_node)
|
||||
#
|
||||
#if not _terrain:
|
||||
#warning += """No Terrain3D node found"""
|
||||
#return
|
||||
#
|
||||
#if not _terrain.storage:
|
||||
#warning += """Terrain3D storage is not initialized"""
|
||||
#return
|
||||
#
|
||||
## Get global transform
|
||||
#var gt: Transform3D = domain.get_global_transform()
|
||||
#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)
|
||||
#
|
||||
#if align_with_collision_normal and not is_nan(normal.x):
|
||||
#transforms.list[i].basis.y = normal
|
||||
#transforms.list[i].basis.x = -transforms.list[i].basis.z.cross(normal)
|
||||
#transforms.list[i].basis = transforms.list[i].basis.orthonormalized()
|
||||
#
|
||||
#transforms.list[i].origin.y = gt.origin.y if is_nan(height) else height - gt.origin.y
|
||||
#
|
||||
#if transforms.is_empty():
|
||||
#warning += """Every point has been removed. Possible reasons include: \n
|
||||
#+ No collider is close enough to the shapes.
|
||||
#+ Ray length is too short.
|
||||
#+ Ray direction is incorrect.
|
||||
#+ Collision mask is not set properly.
|
||||
#+ Max slope is too low.
|
||||
#"""
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bdwolwswwy8wr"
|
||||
path="res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/autoshader.svg"
|
||||
dest_files=["res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://krrmpalen8xu"
|
||||
path="res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/color_paint.svg"
|
||||
dest_files=["res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bcmbqryggekg1"
|
||||
path="res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_add.svg"
|
||||
dest_files=["res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://danh7tb2v6rx7"
|
||||
path="res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_div.svg"
|
||||
dest_files=["res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://crj0xfyiyr45u"
|
||||
path="res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_flat.svg"
|
||||
dest_files=["res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bu3q0645kb3el"
|
||||
path="res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_mul.svg"
|
||||
dest_files=["res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://0cd7so4kw7da"
|
||||
path="res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_slope.svg"
|
||||
dest_files=["res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://chrbx4xnxyiel"
|
||||
path="res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_smooth.svg"
|
||||
dest_files=["res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://mo3hnbk3ffjs"
|
||||
path="res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/height_sub.svg"
|
||||
dest_files=["res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bsmaxekrmnuy2"
|
||||
path="res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/holes.svg"
|
||||
dest_files=["res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cs1la1mashf2e"
|
||||
path="res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/layers.svg"
|
||||
dest_files=["res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cjlcl5lf20ve0"
|
||||
path="res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/multimesh.svg"
|
||||
dest_files=["res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://f3po5pogkv2b"
|
||||
path="res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/navigation.svg"
|
||||
dest_files=["res://.godot/imported/navigation.svg-1e4cf210c589be8d2911c522d4a17d78.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bg8x6o32ggt88"
|
||||
path="res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/picker_checked.svg"
|
||||
dest_files=["res://.godot/imported/picker_checked.svg-81f35b6ae38bccc8aa9e7ae22b530168.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c0tn453fsckv5"
|
||||
path="res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/region_add.svg"
|
||||
dest_files=["res://.godot/imported/region_add.svg-a05dc161a452dd3e024f9835a737d9f0.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cbpo5eamf3bx2"
|
||||
path="res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/region_remove.svg"
|
||||
dest_files=["res://.godot/imported/region_remove.svg-5710e8aeb34f1eaa06e637634f4a7d16.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bnsydn4jkyeyn"
|
||||
path="res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/terrain3d.svg"
|
||||
dest_files=["res://.godot/imported/terrain3d.svg-eb45756f1a003759fda81eaa1db10769.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://duo8valena3a2"
|
||||
path="res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/texture_paint.svg"
|
||||
dest_files=["res://.godot/imported/texture_paint.svg-72da4fd2096377e625a8fe09cdacb0e4.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://16yfxe7xe703"
|
||||
path="res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/texture_spray.svg"
|
||||
dest_files=["res://.godot/imported/texture_spray.svg-326fee11cf418653e621bc222a470861.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
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bcgg0srmqsh3n"
|
||||
path="res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/terrain_3d/icons/wetness.svg"
|
||||
dest_files=["res://.godot/imported/wetness.svg-9b2ddec096ab7734492b77b20c75c82b.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
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Terrain3D"
|
||||
description="A high performance, editable terrain system for Godot 4."
|
||||
author="Cory Petkovsek & Roope Palmroos"
|
||||
version="0.9.2"
|
||||
script="editor.gd"
|
|
@ -0,0 +1,832 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
#class_name Terrain3DAssetDock
|
||||
|
||||
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"
|
||||
|
||||
var texture_list: ListContainer
|
||||
var mesh_list: ListContainer
|
||||
var _current_list: ListContainer
|
||||
var _last_thumb_update_time: int = 0
|
||||
const MAX_UPDATE_TIME: int = 1000
|
||||
|
||||
var placement_opt: OptionButton
|
||||
var floating_btn: Button
|
||||
var pinned_btn: Button
|
||||
var size_slider: HSlider
|
||||
var box: BoxContainer
|
||||
var buttons: BoxContainer
|
||||
var textures_btn: Button
|
||||
var meshes_btn: Button
|
||||
var asset_container: ScrollContainer
|
||||
var confirm_dialog: ConfirmationDialog
|
||||
var _confirmed: bool = false
|
||||
|
||||
# Used only for editor, so change to single visible/hiddden
|
||||
enum {
|
||||
HIDDEN = -1,
|
||||
SIDEBAR = 0,
|
||||
BOTTOM = 1,
|
||||
WINDOWED = 2,
|
||||
}
|
||||
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,
|
||||
POS_LEFT_UR = 2,
|
||||
POS_LEFT_BR = 3,
|
||||
POS_RIGHT_UL = 4,
|
||||
POS_RIGHT_BL = 5,
|
||||
POS_RIGHT_UR = 6,
|
||||
POS_RIGHT_BR = 7,
|
||||
POS_BOTTOM = 8,
|
||||
POS_MAX = 9,
|
||||
}
|
||||
var slot: int = POS_RIGHT_BR
|
||||
var _initialized: bool = false
|
||||
var plugin: EditorPlugin
|
||||
var editor_settings: EditorSettings
|
||||
|
||||
|
||||
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
|
||||
|
||||
placement_opt = $Box/Buttons/PlacementOpt
|
||||
pinned_btn = $Box/Buttons/Pinned
|
||||
floating_btn = $Box/Buttons/Floating
|
||||
floating_btn.owner = null
|
||||
size_slider = $Box/Buttons/SizeSlider
|
||||
size_slider.owner = null
|
||||
box = $Box
|
||||
buttons = $Box/Buttons
|
||||
textures_btn = $Box/Buttons/TexturesBtn
|
||||
meshes_btn = $Box/Buttons/MeshesBtn
|
||||
asset_container = $Box/ScrollContainer
|
||||
|
||||
texture_list = ListContainer.new()
|
||||
texture_list.plugin = plugin
|
||||
texture_list.type = Terrain3DAssets.TYPE_TEXTURE
|
||||
asset_container.add_child(texture_list)
|
||||
mesh_list = ListContainer.new()
|
||||
mesh_list.plugin = plugin
|
||||
mesh_list.type = Terrain3DAssets.TYPE_MESH
|
||||
mesh_list.visible = false
|
||||
asset_container.add_child(mesh_list)
|
||||
_current_list = texture_list
|
||||
|
||||
editor_settings = EditorInterface.get_editor_settings()
|
||||
load_editor_settings()
|
||||
|
||||
# Connect signals
|
||||
resized.connect(update_layout)
|
||||
textures_btn.pressed.connect(_on_textures_pressed)
|
||||
meshes_btn.pressed.connect(_on_meshes_pressed)
|
||||
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
|
||||
size_slider.value_changed.connect(_on_slider_changed)
|
||||
plugin.ui.toolbar.tool_changed.connect(_on_tool_changed)
|
||||
|
||||
meshes_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
|
||||
textures_btn.add_theme_font_size_override("font_size", 16 * EditorInterface.get_editor_scale())
|
||||
|
||||
_initialized = true
|
||||
update_dock(plugin.visible)
|
||||
update_layout()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
|
||||
# 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:
|
||||
pinned_btn.icon = get_theme_icon("Pin", "EditorIcons")
|
||||
pinned_btn.text = ""
|
||||
floating_btn.icon = get_theme_icon("MakeFloating", "EditorIcons")
|
||||
floating_btn.text = ""
|
||||
|
||||
update_thumbnails()
|
||||
confirm_dialog = ConfirmationDialog.new()
|
||||
add_child(confirm_dialog)
|
||||
confirm_dialog.hide()
|
||||
confirm_dialog.confirmed.connect(func(): _confirmed = true; \
|
||||
emit_signal("confirmation_closed"); \
|
||||
emit_signal("confirmation_confirmed") )
|
||||
confirm_dialog.canceled.connect(func(): _confirmed = false; \
|
||||
emit_signal("confirmation_closed"); \
|
||||
emit_signal("confirmation_canceled") )
|
||||
|
||||
|
||||
func get_current_list() -> ListContainer:
|
||||
return _current_list
|
||||
|
||||
|
||||
## Dock placement
|
||||
|
||||
func set_slot(p_slot: int) -> void:
|
||||
p_slot = clamp(p_slot, 0, POS_MAX-1)
|
||||
|
||||
if slot != p_slot:
|
||||
slot = p_slot
|
||||
placement_opt.selected = slot
|
||||
save_editor_settings()
|
||||
plugin.select_terrain()
|
||||
update_dock(plugin.visible)
|
||||
|
||||
|
||||
func remove_dock(p_force: bool = false) -> void:
|
||||
if state == SIDEBAR:
|
||||
plugin.remove_control_from_docks(self)
|
||||
state = HIDDEN
|
||||
|
||||
elif state == BOTTOM:
|
||||
plugin.remove_control_from_bottom_panel(self)
|
||||
state = HIDDEN
|
||||
|
||||
# If windowed and destination is not window or final exit, otherwise leave
|
||||
elif state == WINDOWED and p_force:
|
||||
if not window:
|
||||
return
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
func update_dock(p_visible: bool) -> void:
|
||||
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
|
||||
|
||||
remove_dock()
|
||||
# Add dock to new destination
|
||||
# Sidebar
|
||||
if slot < POS_BOTTOM:
|
||||
state = SIDEBAR
|
||||
plugin.add_control_to_dock(slot, self)
|
||||
elif slot == POS_BOTTOM:
|
||||
state = BOTTOM
|
||||
plugin.add_control_to_bottom_panel(self, "Terrain3D")
|
||||
if p_visible:
|
||||
plugin.make_bottom_panel_item_visible(self)
|
||||
|
||||
|
||||
func update_layout() -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
|
||||
# Detect if we have a new window from Make floating, grab it so we can free it properly
|
||||
if not window and get_parent() and get_parent().get_parent() is Window:
|
||||
window = get_parent().get_parent()
|
||||
make_dock_float()
|
||||
return # Will call this function again upon display
|
||||
|
||||
var size_parent: Control = size_slider.get_parent()
|
||||
# Vertical layout in window / sidebar
|
||||
if window or slot < POS_BOTTOM:
|
||||
box.vertical = true
|
||||
buttons.vertical = false
|
||||
|
||||
if size.x >= 500 and size_parent != buttons:
|
||||
size_slider.reparent(buttons)
|
||||
buttons.move_child(size_slider, 3)
|
||||
elif size.x < 500 and size_parent != box:
|
||||
size_slider.reparent(box)
|
||||
box.move_child(size_slider, 1)
|
||||
floating_btn.reparent(buttons)
|
||||
buttons.move_child(floating_btn, 4)
|
||||
|
||||
# Wide layout on bottom bar
|
||||
else:
|
||||
size_slider.reparent(buttons)
|
||||
buttons.move_child(size_slider, 3)
|
||||
floating_btn.reparent(box)
|
||||
box.vertical = false
|
||||
buttons.vertical = true
|
||||
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func update_thumbnails() -> void:
|
||||
if not is_instance_valid(plugin.terrain):
|
||||
return
|
||||
if _current_list.type == Terrain3DAssets.TYPE_MESH and \
|
||||
Time.get_ticks_msec() - _last_thumb_update_time > MAX_UPDATE_TIME:
|
||||
plugin.terrain.assets.create_mesh_thumbnails()
|
||||
_last_thumb_update_time = Time.get_ticks_msec()
|
||||
for mesh_asset in mesh_list.entries:
|
||||
mesh_asset.queue_redraw()
|
||||
## Dock Button handlers
|
||||
|
||||
|
||||
func _on_pin_changed(toggled: bool) -> void:
|
||||
if window:
|
||||
window.always_on_top = pinned_btn.button_pressed
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_slider_changed(value: float) -> void:
|
||||
if texture_list:
|
||||
texture_list.set_entry_width(value)
|
||||
if mesh_list:
|
||||
mesh_list.set_entry_width(value)
|
||||
save_editor_settings()
|
||||
|
||||
|
||||
func _on_textures_pressed() -> void:
|
||||
_current_list = texture_list
|
||||
texture_list.update_asset_list()
|
||||
texture_list.visible = true
|
||||
mesh_list.visible = false
|
||||
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)
|
||||
|
||||
|
||||
func _on_meshes_pressed() -> void:
|
||||
_current_list = mesh_list
|
||||
mesh_list.update_asset_list()
|
||||
mesh_list.visible = true
|
||||
texture_list.visible = false
|
||||
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)
|
||||
update_thumbnails()
|
||||
|
||||
|
||||
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:
|
||||
_on_textures_pressed()
|
||||
|
||||
|
||||
## Update Dock Contents
|
||||
|
||||
|
||||
func update_assets() -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
|
||||
# Verify signals to individual lists
|
||||
if plugin.is_terrain_valid() and plugin.terrain.assets:
|
||||
if not plugin.terrain.assets.textures_changed.is_connected(texture_list.update_asset_list):
|
||||
plugin.terrain.assets.textures_changed.connect(texture_list.update_asset_list)
|
||||
if not plugin.terrain.assets.meshes_changed.is_connected(mesh_list.update_asset_list):
|
||||
plugin.terrain.assets.meshes_changed.connect(mesh_list.update_asset_list)
|
||||
|
||||
_current_list.update_asset_list()
|
||||
|
||||
## Window Management
|
||||
|
||||
|
||||
func make_dock_float() -> void:
|
||||
# If already created (eg from editor Make Floating)
|
||||
if not window:
|
||||
remove_dock()
|
||||
create_window()
|
||||
|
||||
state = WINDOWED
|
||||
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()
|
||||
|
||||
|
||||
func create_window() -> void:
|
||||
window = Window.new()
|
||||
window.wrap_controls = true
|
||||
var mc := MarginContainer.new()
|
||||
mc.set_anchors_preset(PRESET_FULL_RECT, false)
|
||||
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)))
|
||||
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 _on_window_input(event: InputEvent) -> void:
|
||||
# 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()
|
||||
|
||||
|
||||
func _on_godot_window_entered() -> void:
|
||||
if is_instance_valid(window) and window.has_focus():
|
||||
_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:
|
||||
window.show()
|
||||
_godot_last_state = _godot_editor_window.mode
|
||||
_godot_editor_window.grab_focus()
|
||||
|
||||
|
||||
func _on_godot_focus_exited() -> void:
|
||||
if is_instance_valid(window) and _godot_editor_window.mode == Window.MODE_MINIMIZED:
|
||||
window.hide()
|
||||
_godot_last_state = _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))
|
||||
_on_slider_changed(size_slider.value)
|
||||
# Window pos/size set on window creation in update_dock
|
||||
update_dock(plugin.visible)
|
||||
|
||||
|
||||
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)
|
||||
if window:
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_SIZE, window.size)
|
||||
editor_settings.set_setting(PS_DOCK_WINDOW_POSITION, window.position)
|
||||
|
||||
|
||||
##############################################################
|
||||
## class ListContainer
|
||||
##############################################################
|
||||
|
||||
|
||||
class ListContainer extends Container:
|
||||
var plugin: EditorPlugin
|
||||
var type := Terrain3DAssets.TYPE_TEXTURE
|
||||
var entries: Array[ListEntry]
|
||||
var selected_id: int = 0
|
||||
var height: float = 0
|
||||
var width: float = 83
|
||||
var focus_style: StyleBox
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_v_size_flags(SIZE_EXPAND_FILL)
|
||||
set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
focus_style = get_theme_stylebox("focus", "Button").duplicate()
|
||||
focus_style.set_border_width_all(2)
|
||||
focus_style.set_border_color(Color(1, 1, 1, .67))
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
for e in entries:
|
||||
e.get_parent().remove_child(e)
|
||||
e.queue_free()
|
||||
entries.clear()
|
||||
|
||||
|
||||
func update_asset_list() -> void:
|
||||
clear()
|
||||
|
||||
# Grab terrain
|
||||
var t: Terrain3D
|
||||
if plugin.is_terrain_valid():
|
||||
t = plugin.terrain
|
||||
elif is_instance_valid(plugin._last_terrain) and plugin.is_terrain_valid(plugin._last_terrain):
|
||||
t = plugin._last_terrain
|
||||
else:
|
||||
return
|
||||
|
||||
if not t.assets:
|
||||
return
|
||||
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
var texture_count: int = t.assets.get_texture_count()
|
||||
for i in texture_count:
|
||||
var texture: Terrain3DTextureAsset = t.assets.get_texture(i)
|
||||
add_item(texture)
|
||||
if texture_count < Terrain3DAssets.MAX_TEXTURES:
|
||||
add_item()
|
||||
else:
|
||||
var mesh_count: int = t.assets.get_mesh_count()
|
||||
for i in mesh_count:
|
||||
var mesh: Terrain3DMeshAsset = t.assets.get_mesh_asset(i)
|
||||
add_item(mesh, t.assets)
|
||||
if mesh_count < Terrain3DAssets.MAX_MESHES:
|
||||
add_item()
|
||||
if selected_id >= mesh_count or selected_id < 0:
|
||||
set_selected_id(0)
|
||||
|
||||
|
||||
func add_item(p_resource: Resource = null, p_assets: Terrain3DAssets = null) -> void:
|
||||
var entry: ListEntry = ListEntry.new()
|
||||
entry.focus_style = focus_style
|
||||
var id: int = entries.size()
|
||||
|
||||
entry.set_edited_resource(p_resource)
|
||||
entry.hovered.connect(_on_resource_hovered.bind(id))
|
||||
entry.selected.connect(set_selected_id.bind(id))
|
||||
entry.inspected.connect(_on_resource_inspected)
|
||||
entry.changed.connect(_on_resource_changed.bind(id))
|
||||
entry.type = type
|
||||
entry.asset_list = p_assets
|
||||
add_child(entry)
|
||||
entries.push_back(entry)
|
||||
|
||||
if p_resource:
|
||||
entry.set_selected(id == selected_id)
|
||||
if not p_resource.id_changed.is_connected(set_selected_after_swap):
|
||||
p_resource.id_changed.connect(set_selected_after_swap)
|
||||
|
||||
|
||||
func _on_resource_hovered(p_id: int):
|
||||
if type == Terrain3DAssets.TYPE_MESH:
|
||||
if plugin.terrain:
|
||||
plugin.terrain.assets.create_mesh_thumbnails(p_id)
|
||||
|
||||
|
||||
func set_selected_after_swap(p_type: Terrain3DAssets.AssetType, p_old_id: int, p_new_id: int) -> void:
|
||||
set_selected_id(clamp(p_new_id, 0, entries.size() - 2))
|
||||
|
||||
|
||||
func set_selected_id(p_id: int) -> void:
|
||||
selected_id = p_id
|
||||
|
||||
for i in entries.size():
|
||||
var entry: ListEntry = entries[i]
|
||||
entry.set_selected(i == selected_id)
|
||||
|
||||
plugin.select_terrain()
|
||||
|
||||
# Select Paint tool if clicking a texture
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE and plugin.editor.get_tool() != Terrain3DEditor.TEXTURE:
|
||||
var paint_btn: Button = plugin.ui.toolbar.get_node_or_null("PaintBaseTexture")
|
||||
if paint_btn:
|
||||
paint_btn.set_pressed(true)
|
||||
plugin.ui._on_tool_changed(Terrain3DEditor.TEXTURE, Terrain3DEditor.REPLACE)
|
||||
|
||||
elif type == Terrain3DAssets.TYPE_MESH and plugin.editor.get_tool() != Terrain3DEditor.INSTANCER:
|
||||
var instancer_btn: Button = plugin.ui.toolbar.get_node_or_null("InstanceMeshes")
|
||||
if instancer_btn:
|
||||
instancer_btn.set_pressed(true)
|
||||
plugin.ui._on_tool_changed(Terrain3DEditor.INSTANCER, Terrain3DEditor.ADD)
|
||||
|
||||
# Update editor with selected brush
|
||||
plugin.ui._on_setting_changed()
|
||||
|
||||
|
||||
func _on_resource_inspected(p_resource: Resource) -> void:
|
||||
await get_tree().create_timer(.01).timeout
|
||||
plugin.get_editor_interface().edit_resource(p_resource)
|
||||
|
||||
|
||||
func _on_resource_changed(p_resource: Resource, p_id: int) -> void:
|
||||
if not p_resource:
|
||||
var asset_dock: Control = get_parent().get_parent().get_parent()
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this texture?"
|
||||
else:
|
||||
asset_dock.confirm_dialog.dialog_text = "Are you sure you want to clear this mesh and delete all instances?"
|
||||
asset_dock.confirm_dialog.popup_centered()
|
||||
await asset_dock.confirmation_closed
|
||||
if not asset_dock._confirmed:
|
||||
update_asset_list()
|
||||
return
|
||||
|
||||
if not plugin.is_terrain_valid():
|
||||
plugin.select_terrain()
|
||||
await get_tree().create_timer(.01).timeout
|
||||
|
||||
if plugin.is_terrain_valid():
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
plugin.terrain.get_assets().set_texture(p_id, p_resource)
|
||||
else:
|
||||
plugin.terrain.get_assets().set_mesh_asset(p_id, p_resource)
|
||||
await get_tree().create_timer(.01).timeout
|
||||
plugin.terrain.assets.create_mesh_thumbnails(p_id)
|
||||
|
||||
# If removing an entry, clear inspector
|
||||
if not p_resource:
|
||||
plugin.get_editor_interface().inspect_object(null)
|
||||
|
||||
# If null resource, remove last
|
||||
if not p_resource:
|
||||
var last_offset: int = 2
|
||||
if p_id == entries.size()-2:
|
||||
last_offset = 3
|
||||
set_selected_id(clamp(selected_id, 0, entries.size() - last_offset))
|
||||
|
||||
# Update editor with selected brush
|
||||
plugin.ui._on_setting_changed()
|
||||
|
||||
|
||||
func get_selected_id() -> int:
|
||||
return selected_id
|
||||
|
||||
|
||||
|
||||
func set_entry_width(value: float) -> void:
|
||||
width = clamp(value, 56, 230)
|
||||
redraw()
|
||||
|
||||
|
||||
func get_entry_width() -> float:
|
||||
return width
|
||||
|
||||
|
||||
func redraw() -> void:
|
||||
height = 0
|
||||
var id: int = 0
|
||||
var separation: float = 4
|
||||
var columns: int = 3
|
||||
columns = clamp(size.x / width, 1, 100)
|
||||
|
||||
for c in get_children():
|
||||
if is_instance_valid(c):
|
||||
c.size = Vector2(width, width) - Vector2(separation, separation)
|
||||
c.position = Vector2(id % columns, id / columns) * width + \
|
||||
Vector2(separation / columns, separation / columns)
|
||||
height = max(height, c.position.y + width)
|
||||
id += 1
|
||||
|
||||
|
||||
# Needed to enable ScrollContainer scroll bar
|
||||
func _get_minimum_size() -> Vector2:
|
||||
return Vector2(0, height)
|
||||
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
if p_what == NOTIFICATION_SORT_CHILDREN:
|
||||
redraw()
|
||||
|
||||
|
||||
##############################################################
|
||||
## class ListEntry
|
||||
##############################################################
|
||||
|
||||
|
||||
class ListEntry extends VBoxContainer:
|
||||
signal hovered()
|
||||
signal selected()
|
||||
signal changed(resource: Resource)
|
||||
signal inspected(resource: Resource)
|
||||
|
||||
var resource: Resource
|
||||
var type := Terrain3DAssets.TYPE_TEXTURE
|
||||
var _thumbnail: Texture2D
|
||||
var drop_data: bool = false
|
||||
var is_hovered: bool = false
|
||||
var is_selected: bool = false
|
||||
var asset_list: Terrain3DAssets
|
||||
|
||||
var button_clear: TextureButton
|
||||
var button_edit: TextureButton
|
||||
var name_label: Label
|
||||
|
||||
@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")
|
||||
@onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons")
|
||||
@onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons")
|
||||
@onready var background: StyleBox = get_theme_stylebox("pressed", "Button")
|
||||
var focus_style: StyleBox
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var icon_size: Vector2 = Vector2(12, 12)
|
||||
|
||||
button_clear = TextureButton.new()
|
||||
button_clear.set_texture_normal(clear_icon)
|
||||
button_clear.set_custom_minimum_size(icon_size)
|
||||
button_clear.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_clear.set_visible(resource != null)
|
||||
button_clear.pressed.connect(clear)
|
||||
add_child(button_clear)
|
||||
|
||||
button_edit = TextureButton.new()
|
||||
button_edit.set_texture_normal(edit_icon)
|
||||
button_edit.set_custom_minimum_size(icon_size)
|
||||
button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_edit.set_visible(resource != null)
|
||||
button_edit.pressed.connect(edit)
|
||||
add_child(button_edit)
|
||||
|
||||
name_label = Label.new()
|
||||
add_child(name_label, true)
|
||||
name_label.visible = false
|
||||
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||
name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
name_label.add_theme_color_override("font_shadow_color", Color.BLACK)
|
||||
name_label.add_theme_constant_override("shadow_offset_x", 1)
|
||||
name_label.add_theme_constant_override("shadow_offset_y", 1)
|
||||
name_label.add_theme_font_size_override("font_size", 15)
|
||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
name_label.text = "Add Texture"
|
||||
else:
|
||||
name_label.text = "Add Mesh"
|
||||
|
||||
|
||||
func _notification(p_what) -> void:
|
||||
match p_what:
|
||||
NOTIFICATION_DRAW:
|
||||
var rect: Rect2 = Rect2(Vector2.ZERO, get_size())
|
||||
if !resource:
|
||||
draw_style_box(background, rect)
|
||||
draw_texture(add_icon, (get_size() / 2) - (add_icon.get_size() / 2))
|
||||
else:
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
name_label.text = (resource as Terrain3DTextureAsset).get_name()
|
||||
self_modulate = resource.get_albedo_color()
|
||||
_thumbnail = resource.get_albedo_texture()
|
||||
if _thumbnail:
|
||||
draw_texture_rect(_thumbnail, rect, false)
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST_WITH_MIPMAPS
|
||||
else:
|
||||
name_label.text = (resource as Terrain3DMeshAsset).get_name()
|
||||
var id: int = (resource as Terrain3DMeshAsset).get_id()
|
||||
_thumbnail = resource.get_thumbnail()
|
||||
if _thumbnail:
|
||||
draw_texture_rect(_thumbnail, rect, false)
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||
else:
|
||||
draw_rect(rect, Color(.15, .15, .15, 1.))
|
||||
name_label.add_theme_font_size_override("font_size", 4 + rect.size.x/10)
|
||||
if drop_data:
|
||||
draw_style_box(focus_style, rect)
|
||||
if is_hovered:
|
||||
draw_rect(rect, Color(1, 1, 1, 0.2))
|
||||
if is_selected:
|
||||
draw_style_box(focus_style, rect)
|
||||
NOTIFICATION_MOUSE_ENTER:
|
||||
is_hovered = true
|
||||
name_label.visible = true
|
||||
emit_signal("hovered")
|
||||
queue_redraw()
|
||||
NOTIFICATION_MOUSE_EXIT:
|
||||
is_hovered = false
|
||||
name_label.visible = false
|
||||
drop_data = false
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _gui_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton:
|
||||
if p_event.is_pressed():
|
||||
match p_event.get_button_index():
|
||||
MOUSE_BUTTON_LEFT:
|
||||
# If `Add new` is clicked
|
||||
if !resource:
|
||||
if type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
set_edited_resource(Terrain3DTextureAsset.new(), false)
|
||||
else:
|
||||
set_edited_resource(Terrain3DMeshAsset.new(), false)
|
||||
edit()
|
||||
else:
|
||||
emit_signal("selected")
|
||||
MOUSE_BUTTON_RIGHT:
|
||||
if resource:
|
||||
edit()
|
||||
MOUSE_BUTTON_MIDDLE:
|
||||
if resource:
|
||||
clear()
|
||||
|
||||
|
||||
func _can_drop_data(p_at_position: Vector2, p_data: Variant) -> bool:
|
||||
drop_data = false
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
if p_data.files.size() == 1:
|
||||
queue_redraw()
|
||||
drop_data = true
|
||||
return drop_data
|
||||
|
||||
|
||||
func _drop_data(p_at_position: Vector2, p_data: Variant) -> void:
|
||||
if typeof(p_data) == TYPE_DICTIONARY:
|
||||
var res: Resource = load(p_data.files[0])
|
||||
if res is Texture2D and type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
var ta := Terrain3DTextureAsset.new()
|
||||
if resource is Terrain3DTextureAsset:
|
||||
ta.id = resource.id
|
||||
ta.set_albedo_texture(res)
|
||||
set_edited_resource(ta, false)
|
||||
resource = ta
|
||||
elif res is Terrain3DTextureAsset and type == Terrain3DAssets.TYPE_TEXTURE:
|
||||
if resource is Terrain3DTextureAsset:
|
||||
res.id = resource.id
|
||||
set_edited_resource(res, false)
|
||||
elif res is PackedScene and type == Terrain3DAssets.TYPE_MESH:
|
||||
var ma := Terrain3DMeshAsset.new()
|
||||
if resource is Terrain3DMeshAsset:
|
||||
ma.id = resource.id
|
||||
ma.set_scene_file(res)
|
||||
set_edited_resource(ma, false)
|
||||
resource = ma
|
||||
elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH:
|
||||
if resource is Terrain3DMeshAsset:
|
||||
res.id = resource.id
|
||||
set_edited_resource(res, false)
|
||||
emit_signal("selected")
|
||||
emit_signal("inspected", resource)
|
||||
|
||||
|
||||
|
||||
func set_edited_resource(p_res: Resource, p_no_signal: bool = true) -> void:
|
||||
resource = p_res
|
||||
if resource:
|
||||
resource.setting_changed.connect(_on_resource_changed)
|
||||
resource.file_changed.connect(_on_resource_changed)
|
||||
|
||||
if button_clear:
|
||||
button_clear.set_visible(resource != null)
|
||||
|
||||
queue_redraw()
|
||||
if !p_no_signal:
|
||||
emit_signal("changed", resource)
|
||||
|
||||
|
||||
func _on_resource_changed() -> void:
|
||||
emit_signal("changed", resource)
|
||||
|
||||
|
||||
func set_selected(value: bool) -> void:
|
||||
is_selected = value
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
if resource:
|
||||
set_edited_resource(null, false)
|
||||
|
||||
|
||||
func edit() -> void:
|
||||
emit_signal("selected")
|
||||
emit_signal("inspected", resource)
|
|
@ -0,0 +1,93 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dkb6hii5e48m2"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/terrain_3d/src/asset_dock.gd" id="1_e23pg"]
|
||||
|
||||
[node name="Terrain3D" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(256, 95)
|
||||
offset_right = 766.0
|
||||
offset_bottom = 100.0
|
||||
script = ExtResource("1_e23pg")
|
||||
|
||||
[node name="Box" type="BoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
vertical = true
|
||||
|
||||
[node name="Buttons" type="BoxContainer" parent="Box"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TexturesBtn" type="Button" parent="Box/Buttons"]
|
||||
custom_minimum_size = Vector2(80, 30)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
theme_override_font_sizes/font_size = 16
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
text = "Textures"
|
||||
|
||||
[node name="MeshesBtn" type="Button" parent="Box/Buttons"]
|
||||
custom_minimum_size = Vector2(80, 30)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
theme_override_font_sizes/font_size = 16
|
||||
toggle_mode = true
|
||||
text = "Meshes"
|
||||
|
||||
[node name="PlacementOpt" type="OptionButton" parent="Box/Buttons"]
|
||||
custom_minimum_size = Vector2(80, 30)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
item_count = 9
|
||||
selected = 7
|
||||
popup/item_0/text = "Left_UL"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Left_BL"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Left_UR"
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = "Left_BR"
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Right_UL"
|
||||
popup/item_4/id = 4
|
||||
popup/item_5/text = "Right_BL "
|
||||
popup/item_5/id = 5
|
||||
popup/item_6/text = "Right_UR"
|
||||
popup/item_6/id = 6
|
||||
popup/item_7/text = "Right_BR"
|
||||
popup/item_7/id = 7
|
||||
popup/item_8/text = "Bottom"
|
||||
popup/item_8/id = 8
|
||||
|
||||
[node name="SizeSlider" type="HSlider" parent="Box/Buttons"]
|
||||
custom_minimum_size = Vector2(80, 10)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 56.0
|
||||
max_value = 230.0
|
||||
value = 83.0
|
||||
|
||||
[node name="Floating" type="Button" parent="Box/Buttons"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
tooltip_text = "Pop this dock out to a floating window."
|
||||
toggle_mode = true
|
||||
text = "F"
|
||||
flat = true
|
||||
|
||||
[node name="Pinned" type="Button" parent="Box/Buttons"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
tooltip_text = "Make this window \"Always on top\"."
|
||||
toggle_mode = true
|
||||
text = "P"
|
||||
flat = true
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Box"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
|
@ -0,0 +1,28 @@
|
|||
@tool
|
||||
extends ConfirmationDialog
|
||||
|
||||
var lod: int = 0
|
||||
var description: String = ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_unparent_when_invisible(true)
|
||||
about_to_popup.connect(_on_about_to_popup)
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
%LodBox.value_changed.connect(_on_lod_box_value_changed)
|
||||
|
||||
|
||||
func _on_about_to_popup() -> void:
|
||||
lod = %LodBox.value
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
# Change text on the autowrap label only when the popup is visible.
|
||||
# Works around Godot issue #47005:
|
||||
# https://github.com/godotengine/godot/issues/47005
|
||||
if visible:
|
||||
%DescriptionLabel.text = description
|
||||
|
||||
|
||||
func _on_lod_box_value_changed(p_value: float) -> void:
|
||||
lod = %LodBox.value
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue