Added Terrain3D addon

This commit is contained in:
Rob Kelly 2024-10-20 15:47:57 -06:00
parent 3558e0ab52
commit 557ede92e2
119 changed files with 5286 additions and 3 deletions

View File

@ -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.

View File

@ -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.

View File

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.

299
addons/terrain_3d/editor.gd Normal file
View File

@ -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)

View File

@ -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. ])

View File

@ -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);
}

View File

@ -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.
#"""

BIN
addons/terrain_3d/icons/autoshader.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/color_paint.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_add.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_div.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_flat.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_mul.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_slope.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_smooth.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/height_sub.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/holes.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/layers.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/multimesh.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/navigation.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/picker.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/picker_checked.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/region_add.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/region_remove.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/terrain3d.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/texture_paint.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/texture_spray.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

BIN
addons/terrain_3d/icons/wetness.svg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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