added terrain3d

This commit is contained in:
derek
2025-03-31 14:14:50 -05:00
parent 27175618c0
commit bd767d2927
148 changed files with 2602 additions and 1381 deletions

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 Cory Petkovsek, Roope Palmroos, and Contributors. Copyright (c) 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -3,34 +3,26 @@
# Terrain3D # Terrain3D
A high performance, editable terrain system for Godot 4. A high performance, editable terrain system for Godot 4.
## Features ## Features
* Written in C++ as a GDExtension addon, which works with official engine builds * Written in C++ as a GDExtension addon, which works with official builds of Godot Engine
* Can be accessed by GDScript, C#, and any language Godot supports * Can be accessed by GDScript, C#, and any language Godot supports
* Geometric Clipmap Mesh Terrain, as used in The Witcher 3. See [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html) * Geomorphing Geometric Clipmap Mesh Terrain, as used in The Witcher 3. See [System Architecture](https://terrain3d.readthedocs.io/en/stable/docs/system_architecture.html)
* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in variable sized regions * Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in variable sized regions
* Up to 32 textures * Up to 32 textures
* Up to 10 levels of detail * Up to 10 levels of detail for the terrain mesh
* Foliage instancing * Foliage instancing, with up to 10 levels of detail, and a shadow impostor
* Sculpting, holes, texture painting, texture detiling, painting colors and wetness * 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) * Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), Gaea, World Creator, World Machine, Unity, Unreal and any tool that can export a heightmap. See [heightmaps](https://terrain3d.readthedocs.io/en/stable/docs/heightmaps.html)
See [Project Status](https://terrain3d.readthedocs.io/en/stable/docs/project_status.html) for details.
## Getting Started ## Getting Started
1. Read the [Installation & Upgrades](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions. 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). 2. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) and join our [Discord server](https://tokisan.com/discord).
3. Watch the tutorial videos: 3. Watch the [tutorial videos](https://terrain3d.readthedocs.io/en/stable/docs/tutorial_videos.html).
**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 ## Credit
@@ -41,9 +33,8 @@ 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)| | **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)| | **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). And other contributors displayed on the right of the github page and in [AUTHORS.md](https://terrain3d.readthedocs.io/en/stable/docs/authors.html).
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 ## Contributing

Binary file not shown.

View File

@@ -1 +0,0 @@
uid://cuhwgfm8bkknk

View File

@@ -0,0 +1,67 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
// This shader snippet draws a hex grid
// To use it, add this line to the top of your shader:
// #include "res://addons/terrain_3d/extras/hex_grid.gdshaderinc"
// And this line at the bottom of your shader:
// draw_hex_grid(uv2, _region_texel_size, w_normal, ALBEDO);
mat2 rotate2d(float _angle) {
return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle)));
}
void draw_hex_grid(vec2 uv, float texel_size, vec3 normal, inout vec3 albedo) {
float hex_size = 0.02;
float line_thickness = 0.04;
vec2 guv = (uv - vec2(0.5 * texel_size)) / hex_size;
// Convert UV to axial hex coordinates
float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y);
float r = (2.0 / 3.0 * guv.y);
// Cube coordinates for the hex (q, r, -q-r)
float x = q;
float z = r;
float y = -x - z;
// Round to the nearest hex center
vec3 rounded = round(vec3(x, y, z));
vec3 diff = abs(vec3(x, y, z) - rounded);
// Fix rounding errors
if (diff.x > diff.y && diff.x > diff.z) {
rounded.x = -rounded.y - rounded.z;
} else if (diff.y > diff.z) {
rounded.y = -rounded.x - rounded.z;
} else {
rounded.z = -rounded.x - rounded.y;
}
// Find the hex center in UV space
vec2 hex_center = vec2(
sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z,
3.0 / 2.0 * rounded.z
);
// Relative position within the hex
vec2 local_pos = guv - hex_center;
vec2 lines_uv = local_pos;
float line = 1.0;
for (int i = 0; i < 6; i++) {
vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0));
float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0)));
line = min(line, dist);
}
// Filter lines by slope
float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement
float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (normal - 1.) + 1.), 0., 1.);
// Draw hex grid
albedo = mix(albedo, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor);
// Draw Hex center dot
albedo = mix(albedo, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor);
}

View File

@@ -0,0 +1 @@
uid://mri8pfoj2mfk

View File

@@ -1,4 +1,5 @@
## Import From SimpleGrassTextured # Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Import From SimpleGrassTextured
# #
# This script demonstrates how to import transforms from SimpleGrassTextured. To use it: # This script demonstrates how to import transforms from SimpleGrassTextured. To use it:
# #

View File

@@ -1 +1 @@
uid://bm8g6gkvrerqy uid://bllcuwetve45k

View File

@@ -0,0 +1,400 @@
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
/* This is an example stripped down shader with maximum performance in mind.
* Only Autoshader/Base/Over/Blend/Holes/Colormap are supported.
* All terrain normal calculations take place in vetex() as well as control map reads
* for the bilinear blend, when not skippable have moved to vertex() too.
*
* A single controlmap lookup in fragment is added at distances where the vertices spread too wide.
*/
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms
uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/1024
uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform float _texture_normal_depth_array[32];
uniform float _texture_ao_strength_array[32];
uniform float _texture_roughness_mod_array[32];
uniform float _texture_uv_scale_array[32];
uniform vec4 _texture_color_array[32];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap, repeat_disable;
uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap, repeat_enable;
uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap, repeat_enable;
// Public uniforms
uniform float auto_slope : hint_range(0, 10) = 1.0;
uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
uniform int auto_base_texture : hint_range(0, 31) = 0;
uniform int auto_overlay_texture : hint_range(0, 31) = 1;
uniform bool height_blending = true;
uniform bool world_space_normal_blend = true;
uniform float blend_sharpness : hint_range(0, 1) = 0.87;
// Varyings & Types
struct Material {
vec4 alb_ht;
vec4 nrm_rg;
int base;
int over;
float blend;
float nrm_depth;
float ao_str;
};
varying vec3 v_vertex;
varying vec3 v_normal;
varying flat uint v_control[4];
varying flat int v_lerp;
varying mat3 v_tbn;
////////////////////////
// Vertex
////////////////////////
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) 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_index_uv(const vec2 uv2) {
ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
}
void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane
float v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
const vec3 offsets = vec3(0, 1, 2);
ivec3 indexUV[4];
// control map lookups in vertex, used for bilinear blend in fragment.
indexUV[0] = get_index_coord(start_pos + offsets.xy, VERTEX_PASS);
indexUV[1] = get_index_coord(start_pos + offsets.yy, VERTEX_PASS);
indexUV[2] = get_index_coord(start_pos + offsets.yx, VERTEX_PASS);
indexUV[3] = get_index_coord(start_pos + offsets.xx, VERTEX_PASS);
// Mask off Scale/Rotation/Navigation bits to 0, as they are not used.
#define CONTROL_MASK 0xFFFFC07Du
v_control[0] = floatBitsToUint(texelFetch(_control_maps, indexUV[0], 0)).r & CONTROL_MASK;
v_control[1] = floatBitsToUint(texelFetch(_control_maps, indexUV[1], 0)).r & CONTROL_MASK;
v_control[2] = floatBitsToUint(texelFetch(_control_maps, indexUV[2], 0)).r & CONTROL_MASK;
v_control[3] = floatBitsToUint(texelFetch(_control_maps, indexUV[3], 0)).r & CONTROL_MASK;
bool full_auto = !bool((v_control[0] & v_control[1] & v_control[2] & v_control[3]) & 0x1u);
bool identical = !(
(v_control[0] == v_control[1]) &&
(v_control[1] == v_control[2]) &&
(v_control[2] == v_control[3]));
// Verticies are close enough, full auto shader, or all 4 indicies match, skip bilinear blend in fragment.
v_lerp = scale < _vertex_spacing + 1e-3 && vertex_lerp < 1e-3 && (full_auto || identical) ? 1 : 0;
// Discard vertices for Holes. 1 lookup
bool hole = bool(v_control[3] >>2u & 0x1u);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && indexUV[3].z == -1))) {
v_vertex.x = 0. / 0.;
} else {
// Set final vertex height & calculate vertex normals. 3 lookups
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
float h = mix(texelFetch(_height_maps, uv_a, 0).r,texelFetch(_height_maps, uv_b, 0).r,vertex_lerp);
float u = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(1,0), VERTEX_PASS), 0).r,
texelFetch(_height_maps, get_index_coord(end_pos + vec2(1,0), VERTEX_PASS), 0).r, vertex_lerp);
float v = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(0,1), VERTEX_PASS), 0).r,
texelFetch(_height_maps, get_index_coord(end_pos + vec2(0,1), VERTEX_PASS), 0).r, vertex_lerp);
v_vertex.y = h;
v_normal = vec3(h - u, _vertex_spacing, h - v);
}
// Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
// Apply terrain normals
vec3 w_normal = normalize(v_normal);
vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
vec3 w_binormal = normalize(cross(w_normal, w_tangent));
v_tbn = mat3(w_tangent, w_normal, w_binormal);
NORMAL = normalize((VIEW_MATRIX * vec4(w_normal, 0.0)).xyz);
BINORMAL = normalize((VIEW_MATRIX * vec4(w_binormal, 0.0)).xyz);
TANGENT = normalize((VIEW_MATRIX * vec4(w_tangent, 0.0)).xyz);
}
////////////////////////
// Fragment
////////////////////////
vec3 unpack_normal(vec4 rgba) {
return fma(rgba.xzy, vec3(2.0), vec3(-1.0));
}
vec3 pack_normal(vec3 n) {
return fma(normalize(n.xzy), vec3(0.5), vec3(0.5));
}
vec4 height_blend4(vec4 a_value, float a_height, vec4 b_value, float b_height, float blend) {
if(height_blending) {
float ma = max(a_height + (1.0 - blend), b_height + blend) - (1.001 - blend_sharpness);
float b1 = max(a_height + (1.0 - blend) - ma, 0.0);
float b2 = max(b_height + blend - ma, 0.0);
return (a_value * b1 + b_value * b2) / (b1 + b2);
} else {
float contrast = 1.0 - blend_sharpness;
float factor = (blend - contrast) / contrast;
return mix(a_value, b_value, clamp(factor, 0.0, 1.0));
}
}
float height_blend1(float a_value, float a_height, float b_value, float b_height, float blend) {
if(height_blending) {
float ma = max(a_height + (1.0 - blend), b_height + blend) - (1.001 - blend_sharpness);
float b1 = max(a_height + (1.0 - blend) - ma, 0.0);
float b2 = max(b_height + blend - ma, 0.0);
return (a_value * b1 + b_value * b2) / (b1 + b2);
} else {
float contrast = 1.0 - blend_sharpness;
float factor = (blend - contrast) / contrast;
return mix(a_value, b_value, clamp(factor, 0.0, 1.0));
}
}
// 2-4 lookups ( 2-6 with dual scaling )
void get_material(vec4 ddxy, uint control, vec3 iuv_center, out Material out_mat) {
out_mat = Material(vec4(0.), vec4(0.), 0, 0, 0.0, 0.0, 0.0);
int region = int(iuv_center.z);
vec2 base_uv = v_vertex.xz * 0.5;
ddxy *= 0.5;
// Enable Autoshader if outside regions or painted in regions, otherwise manual painted
bool auto_shader = region < 0 || bool(control & 0x1u);
out_mat.base = int(auto_shader) * auto_base_texture + int(!auto_shader) * int(control >>27u & 0x1Fu);
out_mat.over = int(auto_shader) * auto_overlay_texture + int(!auto_shader) * int(control >> 22u & 0x1Fu);
out_mat.blend = float(auto_shader) * clamp(
(auto_slope * 2. * ( v_tbn[1].y - 1.) + 1.)
- auto_height_reduction * .01 * v_vertex.y // Reduce as vertices get higher
, 0., 1.) +
float(!auto_shader) * float(control >>14u & 0xFFu) * 0.003921568627450; // 1./255.0
out_mat.nrm_depth = _texture_normal_depth_array[out_mat.base];
out_mat.ao_str = _texture_ao_strength_array[out_mat.base];
vec2 matUV = base_uv;
vec4 albedo_ht = vec4(0.);
vec4 normal_rg = vec4(0.5, 0.5, 1.0, 1.0);
vec4 albedo_far = vec4(0.);
vec4 normal_far = vec4(0.5, 0.5, 1.0, 1.0);
float mat_scale = _texture_uv_scale_array[out_mat.base];
vec4 base_dd = ddxy;
if (out_mat.blend < 1.0) {
// 2 lookups
//each time we change scale, recalculate antitiling from baseline to maintain continuity.
matUV = base_uv * mat_scale;
base_dd *= mat_scale;
albedo_ht = textureGrad(_texture_array_albedo, vec3(matUV, float(out_mat.base)), base_dd.xy, base_dd.zw);
normal_rg = textureGrad(_texture_array_normal, vec3(matUV, float(out_mat.base)), base_dd.xy, base_dd.zw);
// Unpack & rotate base normal for blending
normal_rg.xyz = unpack_normal(normal_rg);
}
// Apply color to base
albedo_ht.rgb *= _texture_color_array[out_mat.base].rgb;
// Apply Roughness modifier to base
normal_rg.a = clamp(normal_rg.a + _texture_roughness_mod_array[out_mat.base], 0., 1.);
out_mat.alb_ht = albedo_ht;
out_mat.nrm_rg = normal_rg;
if (out_mat.blend > 0.) {
// 2 lookups
// Setup overlay texture to blend
float mat_scale2 = _texture_uv_scale_array[out_mat.over];
vec2 matUV2 = base_uv * mat_scale2;
vec4 over_dd = ddxy * mat_scale2;
vec4 albedo_ht2 = textureGrad(_texture_array_albedo, vec3(matUV2, float(out_mat.over)), over_dd.xy, over_dd.zw);
vec4 normal_rg2 = textureGrad(_texture_array_normal, vec3(matUV2, float(out_mat.over)), over_dd.xy, over_dd.zw);
// Unpack & rotate overlay normal for blending
normal_rg2.xyz = unpack_normal(normal_rg2);
// Apply color to overlay
albedo_ht2.rgb *= _texture_color_array[out_mat.over].rgb;
// Apply Roughness modifier to overlay
normal_rg2.a = clamp(normal_rg2.a + _texture_roughness_mod_array[out_mat.over], 0., 1.);
// apply world space normal weighting from base, to overlay layer
// Its a matrix Mult, but the value is rather high, so not cutting this one.
if (world_space_normal_blend) {
albedo_ht2.a *= bool(control >>3u & 0x1u) ? 1.0 : clamp((v_tbn * normal_rg.xyz).y, 0.0, 1.0);
}
// Blend overlay and base
out_mat.alb_ht = height_blend4(albedo_ht, albedo_ht.a, albedo_ht2, albedo_ht2.a, out_mat.blend);
out_mat.nrm_rg = height_blend4(normal_rg, albedo_ht.a, normal_rg2, albedo_ht2.a, out_mat.blend);
out_mat.nrm_depth = height_blend1(_texture_normal_depth_array[out_mat.base], albedo_ht.a,
_texture_normal_depth_array[out_mat.over], albedo_ht2.a, out_mat.blend);
out_mat.ao_str = height_blend1(_texture_ao_strength_array[out_mat.base], albedo_ht.a,
_texture_ao_strength_array[out_mat.over], albedo_ht2.a, out_mat.blend);
}
return;
}
void fragment() {
// Recover UVs
vec2 uv = UV;
vec2 uv2 = UV2;
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
// Colormap. 1 lookup
// For speed sake, we'll live with cross region artifacts.
#define COLOR_MAP vec4(1.0, 1.0, 1.0, 0.5)
vec3 region_uv = get_index_uv(uv2);
vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP;
Material mat[4];
uint control = floatBitsToUint(texelFetch(_control_maps, get_index_coord(floor(uv), FRAGMENT_PASS), 0)).r;
get_material(base_derivatives, control, region_uv, mat[3]);
vec4 albedo_height = mat[3].alb_ht;
vec4 normal_rough = mat[3].nrm_rg;
float normal_map_depth = mat[3].nrm_depth;
float ao_strength = mat[3].ao_str;
// Only do blend if we really have to.
if (v_lerp == 1) {
get_material(base_derivatives, v_control[0], region_uv, mat[0]);
get_material(base_derivatives, v_control[1], region_uv, mat[1]);
get_material(base_derivatives, v_control[2], region_uv, mat[2]);
// we dont need weights before this point when using vertex normals.
vec2 weight = fract(uv);
vec2 invert = 1.0 - weight;
vec4 weights = vec4(
invert.x * weight.y, // 0
weight.x * weight.y, // 1
weight.x * invert.y, // 2
invert.x * invert.y // 3
);
// Interpolate Albedo/Height/Normal/Roughness
albedo_height =
mat[0].alb_ht * weights[0] +
mat[1].alb_ht * weights[1] +
mat[2].alb_ht * weights[2] +
mat[3].alb_ht * weights[3] ;
normal_rough =
mat[0].nrm_rg * weights[0] +
mat[1].nrm_rg * weights[1] +
mat[2].nrm_rg * weights[2] +
mat[3].nrm_rg * weights[3] ;
normal_map_depth =
mat[0].nrm_depth * weights[0] +
mat[1].nrm_depth * weights[1] +
mat[2].nrm_depth * weights[2] +
mat[3].nrm_depth * weights[3] ;
ao_strength =
mat[0].ao_str * weights[0] +
mat[1].ao_str * weights[1] +
mat[2].ao_str * weights[2] +
mat[3].ao_str * weights[3] ;
}
// Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range
float roughness = fma(color_map.a - 0.5, 2.0, normal_rough.a);
// Apply PBR
ALBEDO = albedo_height.rgb * color_map.rgb;
ROUGHNESS = roughness;
SPECULAR = 1. - normal_rough.a;
NORMAL_MAP = pack_normal(normal_rough.rgb);
NORMAL_MAP_DEPTH = normal_map_depth;
// Higher and/or facing up, less occluded.
// This is also virtually free.
float ao = (1.0 - (albedo_height.a * log(2.1 - ao_strength))) * (1.0 - normal_rough.y);
AO = clamp(1.0 - ao * ao_strength, albedo_height.a, 1.0);
AO_LIGHT_AFFECT = albedo_height.a;
}

View File

@@ -0,0 +1 @@
uid://bbx2xhanpq5l3

View File

@@ -0,0 +1,163 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
// This is an example of a minimal, low-poly style shader colored by the color map and wetness tools.
// No textures are needed or used in this shader.
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms
uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/1024
uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
uniform highp sampler2DArray _color_maps : source_color, filter_nearest_mipmap, repeat_disable;
// Public uniforms
uniform vec3 default_albedo : source_color = vec3(.38, .35, .3);
uniform float default_roughness : hint_range(0.0, 1.0, 0.01) = 0.8;
// Varyings & Types
// Some are required for editor functions
varying float v_vertex_xz_dist;
varying vec3 v_vertex;
////////////////////////
// Vertex
////////////////////////
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) 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_index_uv(const vec2 uv2) {
ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
}
void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane
v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
ivec3 region = get_index_coord(start_pos, VERTEX_PASS);
uint control = floatBitsToUint(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))) {
v_vertex.x = 0. / 0.;
} else {
// Interpolate Geomorph Start & End, set height. 2 Lookups.
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
v_vertex.y = h;
}
// Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
}
////////////////////////
// Fragment
////////////////////////
void fragment() {
// Recover UVs
vec2 uv = UV;
vec2 uv2 = UV2;
// Apply terrain normals
vec3 ddx = dFdxCoarse(VERTEX);
vec3 ddy = dFdyCoarse(VERTEX);
NORMAL = normalize(cross(ddy, ddx));
TANGENT = normalize(cross(NORMAL, vec3(0.0, 0.0, 1.0)));
BINORMAL = normalize(cross(NORMAL, TANGENT));
// Determine if we're in a region or not (region_uv.z>0)
vec3 region_uv = get_index_uv(uv2);
// Colormap. 1 lookup
float lod = log2(max(length(ddx.xz), length(ddy.xz)) * _vertex_density);
vec4 color_map = region_uv.z > -1.0 ?
textureLod(_color_maps, region_uv, lod) : vec4(1., 1., 1., .5);
// Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range
float roughness = fma(color_map.a - 0.5, 2.0, default_roughness);
// Apply PBR
ALBEDO = default_albedo * color_map.rgb;
ROUGHNESS = roughness;
SPECULAR = 1.0 - roughness;
}

View File

@@ -0,0 +1 @@
uid://bda7fq1rh3nmv

View File

@@ -0,0 +1,134 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
// This shader is a version of minimum.gdshader with flat normals for a low poly look.
// Increase vertex_spacing for a better result.
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms
uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/1024
uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
// Varyings & Types
// Some are required for editor functions
varying float v_vertex_xz_dist;
varying vec3 v_vertex;
////////////////////////
// Vertex
////////////////////////
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane
v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
ivec3 region = get_index_coord(start_pos, VERTEX_PASS);
uint control = floatBitsToUint(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))) {
v_vertex.x = 0. / 0.;
} else {
// Interpolate Geomorph Start & End, set height. 2 Lookups.
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
v_vertex.y = h;
}
// Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
}
////////////////////////
// Fragment
////////////////////////
void fragment() {
// Recover UVs
vec2 uv = UV;
vec2 uv2 = UV2;
// Apply terrain normals
NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
TANGENT = normalize(cross(NORMAL, vec3(0.0, 0.0, 1.0)));
BINORMAL = normalize(cross(NORMAL, TANGENT));
// Apply PBR
ALBEDO = vec3(.2);
ROUGHNESS = .7;
}

View File

@@ -0,0 +1 @@
uid://x11v7w7v8hqa

View File

@@ -1,125 +1,120 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
// This shader is the minimum needed to allow the terrain to function, without any texturing. // This shader is the minimum needed to allow the terrain to function, without any texturing.
shader_type spatial; shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform; render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms // Private uniforms
uniform float _region_size = 1024.0; // Commented uniforms aren't needed for this shader, but are available for your own needs.
uniform float _region_texel_size = 0.0009765625; // = 1/1024 uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0; uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/1024
uniform int _region_map_size = 32; uniform int _region_map_size = 32;
uniform int _region_map[1024]; uniform int _region_map[1024];
uniform vec2 _region_locations[1024]; uniform vec2 _region_locations[1024];
uniform sampler2DArray _height_maps : repeat_disable; //uniform float _texture_uv_scale_array[32];
uniform usampler2DArray _control_maps : repeat_disable; //uniform float _texture_detile_array[32];
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable; //uniform vec4 _texture_color_array[32];
uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable; uniform highp sampler2DArray _height_maps : repeat_disable;
uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable; uniform highp sampler2DArray _control_maps : repeat_disable;
uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable; //uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
uniform float _texture_uv_scale_array[32]; //uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
uniform float _texture_detile_array[32];
uniform vec4 _texture_color_array[32];
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
// Public uniforms
uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
// Varyings & Types // Varyings & Types
varying flat vec3 v_vertex; // World coordinate vertex location // Some are required for editor functions
varying flat vec3 v_camera_pos;
varying float v_vertex_xz_dist; varying float v_vertex_xz_dist;
varying flat ivec3 v_region; varying vec3 v_vertex;
varying flat vec2 v_uv_offset;
varying flat vec2 v_uv2_offset;
varying vec3 v_normal;
varying float v_region_border_mask;
//////////////////////// ////////////////////////
// Vertex // Vertex
//////////////////////// ////////////////////////
// Takes in UV world space coordinates, returns ivec3 with: // Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// XY: (0 to _region_size) coordinates within a region // Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region // Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_region_uv(const vec2 uv) { ivec3 get_index_coord(const vec2 uv, const int search) {
ivec2 pos = ivec2(floor(uv * _region_texel_size)) + (_region_map_size / 2); vec2 r_uv = round(uv);
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); vec2 o_uv = mod(r_uv,_region_size);
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1; ivec2 pos;
return ivec3(ivec2(mod(uv,_region_size)), layer_index); int bounds, layer_index = -1;
} for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
// Takes in UV2 region space coordinates, returns vec3 with: r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
// XY: (0 to 1) coordinates within a region pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
// Z: layer index used for texturearrays, -1 if not in a region bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
vec3 get_region_uv2(const vec2 uv2) { layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
// Remove Texel Offset to ensure correct region index.
ivec2 pos = ivec2(floor(uv2 - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
return vec3(uv2 - _region_locations[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; }
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
} }
void vertex() { void vertex() {
// Get camera pos in world vertex coords
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
// Get vertex of flat plane in world coordinates and set world UV // Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane // Camera distance to vertex on flat plane
v_vertex_xz_dist = length(v_vertex.xz - v_camera_pos.xz); v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions // UV coordinates in world space. Values are 0 to _region_size within regions
UV = round(v_vertex.xz * _vertex_density); UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions // UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size)); UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup // Discard vertices for Holes. 1 lookup
v_region = get_region_uv(UV); ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
uint control = texelFetch(_control_maps, v_region, 0).r; uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = bool(control >>2u & 0x1u); bool hole = bool(control >>2u & 0x1u);
// Show holes to all cameras except mouse camera (on exactly 1 layer) // Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) && if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) { (hole || (_background_mode == 0u && v_region.z < 0))) {
VERTEX.x = 0. / 0.; v_vertex.x = 0. / 0.;
} else { } else {
// Set final vertex height & calculate vertex normals. 3 lookups. // Interpolate Geomorph Start & End, set height. 2 Lookups.
VERTEX.y = get_height(UV2); ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
v_vertex.y = VERTEX.y; ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
v_normal = vec3( float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)), v_vertex.y = h;
_vertex_spacing,
v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
);
// Due to a bug caused by the GPUs linear interpolation across edges of region maps,
// mask region edges and use vertex normals only across region boundaries.
v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
} }
// Transform UVs to local to avoid poor precision during varying interpolation.
v_uv_offset = MODEL_MATRIX[3].xz * _vertex_density;
UV -= v_uv_offset;
v_uv2_offset = v_uv_offset * _region_texel_size;
UV2 -= v_uv2_offset;
// Convert model space to view space w/ skip_vertex_transform render mode // Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz); BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz); TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
@@ -129,36 +124,85 @@ void vertex() {
// Fragment // Fragment
//////////////////////// ////////////////////////
// 0 - 3 lookups
vec3 get_normal(vec2 uv, out vec3 tangent, out vec3 binormal) {
float u, v, height;
vec3 normal;
// Use vertex normals within radius of vertex_normals_distance, and along region borders.
if (v_region_border_mask > 0.5 || v_vertex_xz_dist < vertex_normals_distance) {
normal = normalize(v_normal);
} else {
height = get_height(uv);
u = height - get_height(uv + vec2(_region_texel_size, 0));
v = height - get_height(uv + vec2(0, _region_texel_size));
normal = normalize(vec3(u, _vertex_spacing, v));
}
tangent = cross(normal, vec3(0, 0, 1));
binormal = cross(normal, tangent);
return normal;
}
void fragment() { void fragment() {
// Recover UVs // Recover UVs
vec2 uv = UV + v_uv_offset; vec2 uv = UV;
vec2 uv2 = UV2 + v_uv2_offset; vec2 uv2 = UV2;
// Calculate Terrain Normals. 4 lookups // Lookup offsets, ID and blend weight
vec3 w_tangent, w_binormal; const vec3 offsets = vec3(0, 1, 2);
vec3 w_normal = get_normal(uv2, w_tangent, w_binormal); vec2 index_id = floor(uv);
vec2 weight = fract(uv);
vec2 invert = 1.0 - weight;
vec4 weights = vec4(
invert.x * weight.y, // 0
weight.x * weight.y, // 1
weight.x * invert.y, // 2
invert.x * invert.y // 3
);
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
// Calculate the effective mipmap for regionspace, and if less than 0,
// skip all extra lookups required for bilinear blend.
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
bool bilerp = region_mip < 0.0;
ivec3 indexUV[4];
// control map lookups, used for some normal lookups as well
indexUV[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
indexUV[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
indexUV[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
indexUV[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
// Terrain normals
vec3 index_normal[4];
float h[8];
// allows additional derivatives, eg world noise, brush previews etc
float u = 0.0;
float v = 0.0;
// Re-use the indexUVs for the first lookups, skipping some math. 3 lookups
h[3] = texelFetch(_height_maps, indexUV[3], 0).r; // 0 (0,0)
h[2] = texelFetch(_height_maps, indexUV[2], 0).r; // 1 (1,0)
h[0] = texelFetch(_height_maps, indexUV[0], 0).r; // 2 (0,1)
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
// Set flat world normal - overriden if bilerp is true
vec3 w_normal = index_normal[3];
// Branching smooth normals must be done seperatley for correct normals at all 4 index ids
if (bilerp) {
// 5 lookups
// Fetch the additional required height values for smooth normals
h[1] = texelFetch(_height_maps, indexUV[1], 0).r; // 3 (1,1)
h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
// Calculate the normal for the remaining index ids.
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h[7] + v));
index_normal[1] = normalize(vec3(h[1] - h[5] + u, _vertex_spacing, h[1] - h[4] + v));
index_normal[2] = normalize(vec3(h[2] - h[6] + u, _vertex_spacing, h[2] - h[1] + v));
// Set interpolated world normal
w_normal =
index_normal[0] * weights[0] +
index_normal[1] * weights[1] +
index_normal[2] * weights[2] +
index_normal[3] * weights[3] ;
}
// Apply terrain normals
vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
vec3 w_binormal = normalize(cross(w_normal, w_tangent));
NORMAL = mat3(VIEW_MATRIX) * w_normal; NORMAL = mat3(VIEW_MATRIX) * w_normal;
TANGENT = mat3(VIEW_MATRIX) * w_tangent; TANGENT = mat3(VIEW_MATRIX) * w_tangent;
BINORMAL = mat3(VIEW_MATRIX) * w_binormal; BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
// Apply PBR // Apply PBR
ALBEDO=vec3(.2); ALBEDO = vec3(.2);
ROUGHNESS = .7;
} }

View File

@@ -1 +1 @@
uid://c8qog5mlaoeno uid://01qauauvd8aa

View File

@@ -1,15 +1,11 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter # This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
# It provides a `Project on Terrain3D` modifier, which allows Scatter # It provides a `Project on Terrain3D` modifier, which allows Scatter
# to detect the terrain height from Terrain3D without using collision. # 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 # Copy this file into /addons/proton_scatter/src/modifiers
# Then uncomment everything below (select, press CTRL+K) # Then uncomment everything below (select, press CTRL+K)
# In the editor, add this modifier, then set your Terrain3D node # In the editor, add this modifier to Scatter, then set your Terrain3D node
#@tool #@tool
#extends "base_modifier.gd" #extends "base_modifier.gd"
@@ -20,6 +16,7 @@
# #
#@export var terrain_node : NodePath #@export var terrain_node : NodePath
#@export var align_with_collision_normal := false #@export var align_with_collision_normal := false
#@export_range(0.0, 90.0, 0.1) var max_slope = 90.0
# #
#var _terrain: Terrain3D #var _terrain: Terrain3D
# #
@@ -68,20 +65,29 @@
## Get global transform ## Get global transform
#var gt: Transform3D = domain.get_global_transform() #var gt: Transform3D = domain.get_global_transform()
#var gt_inverse := gt.affine_inverse() #var gt_inverse := gt.affine_inverse()
#var new_transforms_array: Array[Transform3D] = []
#var remapped_max_slope: float = remap(max_slope, 0.0, 90.0, 0.0, 1.0)
#for i in transforms.list.size(): #for i in transforms.list.size():
#var location: Vector3 = (gt * transforms.list[i]).origin #var t: Transform3D = transforms.list[i]
#
#var location: Vector3 = (gt * t).origin
#var height: float = _terrain.data.get_height(location) #var height: float = _terrain.data.get_height(location)
#var normal: Vector3 = _terrain.data.get_normal(location) #var normal: Vector3 = _terrain.data.get_normal(location)
# #
#if align_with_collision_normal and not is_nan(normal.x): #if align_with_collision_normal and not is_nan(normal.x):
#transforms.list[i].basis.y = normal #t.basis.y = normal
#transforms.list[i].basis.x = -transforms.list[i].basis.z.cross(normal) #t.basis.x = -t.basis.z.cross(normal)
#transforms.list[i].basis = transforms.list[i].basis.orthonormalized() #t.basis = t.basis.orthonormalized()
# #
#transforms.list[i].origin.y = gt.origin.y if is_nan(height) else height - gt.origin.y #if abs(Vector3.UP.dot(normal)) >= (1.0 - remapped_max_slope):
#t.origin.y = gt.origin.y if is_nan(height) else height - gt.origin.y
#new_transforms_array.push_back(t)
#
#transforms.list.clear()
#transforms.list.append_array(new_transforms_array)
# #
#if transforms.is_empty(): #if transforms.is_empty():
#warning += """Every point has been removed. Possible reasons include: \n #warning += """All transforms have been removed. Possible reasons include: \n
#+ No collider is close enough to the shapes. #+ No collider is close enough to the shapes.
#+ Ray length is too short. #+ Ray length is too short.
#+ Ray direction is incorrect. #+ Ray direction is incorrect.

View File

@@ -1 +1 @@
uid://ce403ehalp57b uid://g3opjh3m3iww

View File

@@ -0,0 +1,52 @@
# This script can be used to move your regions by an offset.
# Eventually this tool will find its way into a built in UI
#
# Attach it to your Terrain3D node
# Save and reload your scene
# Select your Terrain3D node
# Enter a valid `offset` where all regions will be within -16, +15
# Run it
# It should unload the regions, rename files, and reload them
# Clear the script and resave your scene
@tool
extends Terrain3D
@export var offset: Vector2i
@export var run: bool = false : set = start_rename
func start_rename(val: bool = false) -> void:
if val == false or offset == Vector2i.ZERO:
return
var dir_name: String = data_directory
data_directory = ""
var dir := DirAccess.open(dir_name)
if not dir:
print("An error occurred when trying to access the path: ", data_directory)
return
var affected_files: PackedStringArray
var files: PackedStringArray = dir.get_files()
for file_name in files:
if file_name.match("terrain3d*.res") and not dir.current_is_dir():
var region_loc: Vector2i = Terrain3DUtil.filename_to_location(file_name)
var new_loc: Vector2i = region_loc + offset
if new_loc.x < -16 or new_loc.x > 15 or new_loc.y < -16 or new_loc.y > 15:
push_error("New location %.0v out of bounds for region %.0v. Aborting" % [ new_loc, region_loc ])
return
var new_name: String = "tmp_" + Terrain3DUtil.location_to_filename(new_loc)
dir.rename(file_name, new_name)
affected_files.push_back(new_name)
print("File: %s renamed to: %s" % [ file_name, new_name ])
for file_name in affected_files:
var new_name: String = file_name.trim_prefix("tmp_")
dir.rename(file_name, new_name)
print("File: %s renamed to: %s" % [ file_name, new_name ])
data_directory = dir_name
EditorInterface.get_resource_filesystem().scan()

View File

@@ -0,0 +1 @@
uid://bngnvtbm6ifkk

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Bake LOD Dialog for Terrain3D
@tool @tool
extends ConfirmationDialog extends ConfirmationDialog

View File

@@ -0,0 +1 @@
uid://cqmt8f5x5c2ad

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bhvrrmb8bk1bt"] [gd_scene load_steps=2 format=3 uid="uid://bhvrrmb8bk1bt"]
[ext_resource type="Script" uid="uid://dft2g3p2pjw12" path="res://addons/terrain_3d/src/bake_lod_dialog.gd" id="1_sf76d"] [ext_resource type="Script" path="res://addons/terrain_3d/menu/bake_lod_dialog.gd" id="1_sf76d"]
[node name="bake_lod_dialog" type="ConfirmationDialog"] [node name="bake_lod_dialog" type="ConfirmationDialog"]
title = "Bake Terrain3D Mesh" title = "Bake Terrain3D Mesh"

View File

@@ -1,6 +1,8 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Baker for Terrain3D
extends Node extends Node
const BakeLodDialog: PackedScene = preload("res://addons/terrain_3d/src/bake_lod_dialog.tscn") const BakeLodDialog: PackedScene = preload("res://addons/terrain_3d/menu/bake_lod_dialog.tscn")
const BAKE_MESH_DESCRIPTION: String = "This will create a child MeshInstance3D. LOD4+ is recommended. LOD0 is slow and dense with vertices every 1 unit. It is not an optimal mesh." const BAKE_MESH_DESCRIPTION: String = "This will create a child MeshInstance3D. LOD4+ is recommended. LOD0 is slow and dense with vertices every 1 unit. It is not an optimal mesh."
const BAKE_OCCLUDER_DESCRIPTION: String = "This will create a child OccluderInstance3D. LOD4+ is recommended and will take 5+ seconds per region to generate. LOD0 is unnecessarily dense and slow." const BAKE_OCCLUDER_DESCRIPTION: String = "This will create a child OccluderInstance3D. LOD4+ is recommended and will take 5+ seconds per region to generate. LOD0 is unnecessarily dense and slow."
const SET_UP_NAVIGATION_DESCRIPTION: String = "This operation will: const SET_UP_NAVIGATION_DESCRIPTION: String = "This operation will:
@@ -194,7 +196,7 @@ func _bake_nav_region_nav_mesh(p_nav_region: NavigationRegion3D) -> void:
assert(nav_mesh != null) assert(nav_mesh != null)
var source_geometry_data := NavigationMeshSourceGeometryData3D.new() var source_geometry_data := NavigationMeshSourceGeometryData3D.new()
NavigationMeshGenerator.parse_source_geometry_data(nav_mesh, source_geometry_data, p_nav_region) NavigationServer3D.parse_source_geometry_data(nav_mesh, source_geometry_data, p_nav_region)
for terrain in find_nav_region_terrains(p_nav_region): for terrain in find_nav_region_terrains(p_nav_region):
var aabb: AABB = nav_mesh.filter_baking_aabb var aabb: AABB = nav_mesh.filter_baking_aabb
@@ -204,7 +206,7 @@ func _bake_nav_region_nav_mesh(p_nav_region: NavigationRegion3D) -> void:
if not faces.is_empty(): if not faces.is_empty():
source_geometry_data.add_faces(faces, Transform3D.IDENTITY) source_geometry_data.add_faces(faces, Transform3D.IDENTITY)
NavigationMeshGenerator.bake_from_source_geometry_data(nav_mesh, source_geometry_data) NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry_data)
_postprocess_nav_mesh(nav_mesh) _postprocess_nav_mesh(nav_mesh)
@@ -374,10 +376,8 @@ func _do_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3
var index: int = p_terrain.get_index() var index: int = p_terrain.get_index()
var t_owner: Node = p_terrain.owner var t_owner: Node = p_terrain.owner
parent.remove_child(p_terrain)
p_nav_region.add_child(p_terrain)
parent.add_child(p_nav_region, true) parent.add_child(p_nav_region, true)
p_terrain.reparent(p_nav_region)
parent.move_child(p_nav_region, index) parent.move_child(p_nav_region, index)
p_nav_region.owner = t_owner p_nav_region.owner = t_owner
@@ -391,10 +391,8 @@ func _undo_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrai
var index: int = p_nav_region.get_index() var index: int = p_nav_region.get_index()
var t_owner: Node = p_nav_region.get_owner() var t_owner: Node = p_nav_region.get_owner()
p_terrain.reparent(parent)
parent.remove_child(p_nav_region) parent.remove_child(p_nav_region)
p_nav_region.remove_child(p_terrain)
parent.add_child(p_terrain, true)
parent.move_child(p_terrain, index) parent.move_child(p_terrain, index)
p_terrain.owner = t_owner p_terrain.owner = t_owner

View File

@@ -0,0 +1 @@
uid://4ulaeevj5jvi

View File

@@ -1,8 +1,10 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Channel Packer for Terrain3D
extends RefCounted extends RefCounted
const WINDOW_SCENE: String = "res://addons/terrain_3d/src/channel_packer.tscn" const WINDOW_SCENE: String = "res://addons/terrain_3d/menu/channel_packer.tscn"
const TEMPLATE_PATH: String = "res://addons/terrain_3d/src/channel_packer_import_template.txt" const TEMPLATE_PATH: String = "res://addons/terrain_3d/menu/channel_packer_import_template.txt"
const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/src/channel_packer_dragdrop.gd" const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/menu/channel_packer_dragdrop.gd"
enum { enum {
INFO, INFO,
WARN, WARN,
@@ -47,7 +49,7 @@ var normal_vector: Vector3
func pack_textures_popup() -> void: func pack_textures_popup() -> void:
if window != null: if window != null:
window.show() window.show()
window.move_to_foreground() window.grab_focus()
window.move_to_center() window.move_to_center()
return return
window = (load(WINDOW_SCENE) as PackedScene).instantiate() window = (load(WINDOW_SCENE) as PackedScene).instantiate()
@@ -109,8 +111,7 @@ func pack_textures_popup() -> void:
_init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL) _init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL)
_init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS) _init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
var pack_button_path: String = "Panel/MarginContainer/VBoxContainer/PackButton" (window.find_child("PackButton") as Button).pressed.connect(_on_pack_button_pressed)
(window.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
func _on_close_requested() -> void: func _on_close_requested() -> void:
@@ -370,7 +371,7 @@ func _on_save_file_selected(p_dst_path) -> void:
save_file_dialog.title = "Save Packed Normal/Roughness Texture" save_file_dialog.title = "Save Packed Normal/Roughness Texture"
save_file_dialog.call_deferred("popup_centered_ratio") save_file_dialog.call_deferred("popup_centered_ratio")
save_file_dialog.call_deferred("move_to_foreground") save_file_dialog.call_deferred("grab_focus")
func _alignment_basis(normal: Vector3) -> Basis: func _alignment_basis(normal: Vector3) -> Basis:
@@ -393,8 +394,8 @@ func _set_normal_vector(source: Image, quiet: bool = false) -> void:
# Calculate texture normal sum direction # Calculate texture normal sum direction
var normal: Image = source var normal: Image = source
var sum: Color = Color(0.0, 0.0, 0.0, 0.0) var sum: Color = Color(0.0, 0.0, 0.0, 0.0)
for x in normal.get_height(): for x in normal.get_width():
for y in normal.get_width(): for y in normal.get_height():
sum += normal.get_pixel(x, y) sum += normal.get_pixel(x, y)
var div: float = normal.get_height() * normal.get_width() var div: float = normal.get_height() * normal.get_width()
sum /= Color(div, div, div) sum /= Color(div, div, div)
@@ -409,8 +410,8 @@ func _align_normals(source: Image, iteration: int = 0) -> void:
# generate matrix to re-align the normalmap # generate matrix to re-align the normalmap
var mat3: Basis = _alignment_basis(normal_vector) var mat3: Basis = _alignment_basis(normal_vector)
# re-align the normal map pixels # re-align the normal map pixels
for x in source.get_height(): for x in source.get_width():
for y in source.get_width(): for y in source.get_height():
var old_pixel: Color = source.get_pixel(x, y) var old_pixel: Color = source.get_pixel(x, y)
var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b) var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b)
vector_pixel *= 2.0 vector_pixel *= 2.0
@@ -450,9 +451,8 @@ func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_
if not output_image: if not output_image:
_show_message(ERROR, "Failed to pack textures") _show_message(ERROR, "Failed to pack textures")
return FAILED return FAILED
if output_image.detect_used_channels() != 5: if output_image.detect_alpha() != Image.ALPHA_BLEND:
_show_message(ERROR, "Packing Error, Alpha Channel empty") _show_message(WARN, "Warning, Alpha channel empty")
return FAILED
output_image.save_png(p_dst_path) output_image.save_png(p_dst_path)
_create_import_file(p_dst_path) _create_import_file(p_dst_path)

View File

@@ -0,0 +1 @@
uid://bwldx4itd58o7

View File

@@ -0,0 +1,530 @@
[gd_scene load_steps=7 format=3 uid="uid://nud6dwjcnj5v"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
bg_color = Color(0.211765, 0.239216, 0.290196, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lcvna"]
bg_color = Color(0.168627, 0.211765, 0.266667, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.270588, 0.435294, 0.580392, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cb0xf"]
bg_color = Color(0.137255, 0.137255, 0.137255, 1)
draw_center = false
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.784314, 0.784314, 0.784314, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"]
[sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"]
[sub_resource type="ButtonGroup" id="ButtonGroup_bs6ki"]
[node name="Window" type="Window"]
title = "Terrain3D Channel Packer"
initial_position = 1
size = Vector2i(583, 856)
wrap_controls = true
always_on_top = true
[node name="PanelContainer" type="PanelContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="AlbedoHeightPanel" type="PanelContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer"]
layout_mode = 2
[node name="AlbedoVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="AlbedoLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
text = "Albedo texture"
[node name="AlbedoHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="AlbedoWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
alignment = 1
[node name="AlbedoW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="AlbedoH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
alignment = 1
[node name="LuminanceAsHeightButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/HBoxContainer2"]
layout_mode = 2
text = " Generate Height from Luminance"
icon_alignment = 2
[node name="HeightVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="HeightLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
text = "Height texture"
[node name="HeightHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="HeightWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="HeightW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HeightH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="ConvertDepthToHeight" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer2"]
layout_mode = 2
text = " Convert Depth to Height"
icon_alignment = 2
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="HeightChannelLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
text = " Source Channel: "
horizontal_alignment = 2
[node name="HeightChannelR" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_pressed = true
button_group = SubResource("ButtonGroup_wnxik")
text = "R"
[node name="HeightChannelB" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "G"
[node name="HeightChannelG" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "B"
[node name="HeightChannelA" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "A"
[node name="NormalRoughnessPanel" type="PanelContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer"]
layout_mode = 2
[node name="NormalVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="NormalLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
text = "Normal texture"
[node name="NormalHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="NormalWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="NormalW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="NormalH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer"]
layout_mode = 2
text = " Convert DirectX to OpenGL"
[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="AlignNormalsCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer2"]
layout_mode = 2
text = " Orthoganolise Normals"
[node name="RoughnessVBox" type="VBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="RoughnessLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
text = "Roughness texture"
[node name="RoughnessHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="RoughnessWHHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="RoughnessW" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="RoughnessH" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="InvertSmoothCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer2"]
layout_mode = 2
text = " Convert Smoothness to Roughness"
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="RoughnessChannelLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
text = " Source Channel: "
horizontal_alignment = 2
[node name="RoughnessChannelR" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_pressed = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "R"
[node name="RoughnessChannelG" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "G"
[node name="RoughnessChannelB" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "B"
[node name="RoughnessChannelA" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "A"
[node name="GeneralOptionsLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "General Options"
horizontal_alignment = 1
vertical_alignment = 1
[node name="GeneralOptionsHBox" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="ResizeToggle" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
text = " Resize Packed Image"
[node name="ResizeOptionButton" type="SpinBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
visible = false
layout_mode = 2
tooltip_text = "A value of 0 disables resizing."
min_value = 128.0
max_value = 4096.0
step = 128.0
value = 1024.0
[node name="VSeparator" type="VSeparator" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
[node name="GenerateMipmapsCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
button_pressed = true
text = "Generate Mipmaps"
[node name="HighQualityCheckBox" type="CheckBox" parent="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
text = "Import High Quality"
[node name="PackButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Pack textures as..."
[node name="StatusLabel" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 3
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="CloseButton" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Close"
[connection signal="toggled" from="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeToggle" to="PanelContainer/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeOptionButton" method="set_visible"]

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Channel Packer Dragdropper for Terrain3D
@tool @tool
extends Button extends Button

View File

@@ -0,0 +1 @@
uid://br45krrqbw8bg

View File

@@ -1,13 +1,13 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Directory Setup for Terrain3D
extends Node extends Node
const DIRECTORY_SETUP: String = "res://addons/terrain_3d/src/directory_setup.tscn" const DIRECTORY_SETUP: String = "res://addons/terrain_3d/menu/directory_setup.tscn"
var plugin: EditorPlugin var plugin: EditorPlugin
var dialog: ConfirmationDialog var dialog: ConfirmationDialog
var select_dir_btn: Button var select_dir_btn: Button
var selected_dir_le: LineEdit var selected_dir_le: LineEdit
var select_upg_btn: Button
var upgrade_file_le: LineEdit
var editor_file_dialog: EditorFileDialog var editor_file_dialog: EditorFileDialog
@@ -18,7 +18,6 @@ func _init() -> void:
editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
editor_file_dialog.ok_button_text = "Open" editor_file_dialog.ok_button_text = "Open"
editor_file_dialog.title = "Open a folder or file" editor_file_dialog.title = "Open a folder or file"
editor_file_dialog.file_selected.connect(_on_file_selected)
editor_file_dialog.dir_selected.connect(_on_dir_selected) editor_file_dialog.dir_selected.connect(_on_dir_selected)
editor_file_dialog.size = Vector2i(850, 550) editor_file_dialog.size = Vector2i(850, 550)
editor_file_dialog.transient = false editor_file_dialog.transient = false
@@ -34,22 +33,14 @@ func directory_setup_popup() -> void:
# Nodes # Nodes
select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir") select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir")
selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit") selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit")
select_upg_btn = dialog.get_node("Margin/VBox/UpgradeHBox/SelectResFile")
upgrade_file_le = dialog.get_node("Margin/VBox/UpgradeHBox/LineEdit")
upgrade_file_le.text = ""
if plugin.terrain.data_directory: if plugin.terrain.data_directory:
selected_dir_le.text = plugin.terrain.data_directory selected_dir_le.text = plugin.terrain.data_directory
if plugin.terrain.storage:
upgrade_file_le.text = plugin.terrain.storage.get_path()
# Icons # Icons
plugin.ui.set_button_editor_icon(select_upg_btn, "Folder")
plugin.ui.set_button_editor_icon(select_dir_btn, "Folder") plugin.ui.set_button_editor_icon(select_dir_btn, "Folder")
#Signals #Signals
select_upg_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_FILE))
select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR)) select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR))
dialog.confirmed.connect(_on_close_requested) dialog.confirmed.connect(_on_close_requested)
dialog.canceled.connect(_on_close_requested) dialog.canceled.connect(_on_close_requested)
@@ -73,10 +64,6 @@ func _on_dir_selected(path: String) -> void:
selected_dir_le.text = path selected_dir_le.text = path
func _on_file_selected(path: String) -> void:
upgrade_file_le.text = path
func _on_ok_pressed() -> void: func _on_ok_pressed() -> void:
if not plugin.terrain: if not plugin.terrain:
push_error("Not connected terrain. Click the Terrain3D node first") push_error("Not connected terrain. Click the Terrain3D node first")
@@ -97,20 +84,3 @@ func _on_ok_pressed() -> void:
print("Setting terrain directory: ", selected_dir_le.text) print("Setting terrain directory: ", selected_dir_le.text)
plugin.terrain.data_directory = selected_dir_le.text plugin.terrain.data_directory = selected_dir_le.text
if not upgrade_file_le.text.is_empty():
if data_found:
push_warning("Target directory already has terrain data. Specify an empty directory to upgrade")
return
if not FileAccess.file_exists(upgrade_file_le.text):
push_error("File doesn't exist: ", upgrade_file_le.text)
return
if not plugin.terrain.storage or \
( plugin.terrain.storage and plugin.terrain.storage.get_path() != upgrade_file_le.text):
print("Loading storage file: ", upgrade_file_le.text)
plugin.terrain.set_storage(load(upgrade_file_le.text))
if plugin.terrain.storage:
print("Begining upgrade of: ", upgrade_file_le.text)
plugin.terrain.split_storage()

View File

@@ -0,0 +1 @@
uid://0034ukv2mngn

View File

@@ -3,14 +3,14 @@
[node name="DirectorySetup" type="ConfirmationDialog"] [node name="DirectorySetup" type="ConfirmationDialog"]
title = "Terrain3D Data Directory Setup" title = "Terrain3D Data Directory Setup"
position = Vector2i(0, 36) position = Vector2i(0, 36)
size = Vector2i(750, 574) size = Vector2i(750, 330)
visible = true visible = true
[node name="Margin" type="MarginContainer" parent="."] [node name="Margin" type="MarginContainer" parent="."]
offset_left = 8.0 offset_left = 8.0
offset_top = 8.0 offset_top = 8.0
offset_right = 742.0 offset_right = 742.0
offset_bottom = 525.0 offset_bottom = 281.0
theme_override_constants/margin_left = 20 theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20 theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20 theme_override_constants/margin_right = 20
@@ -43,24 +43,6 @@ placeholder_text = "Data directory"
[node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"] [node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"]
layout_mode = 2 layout_mode = 2
[node name="UpgradeLabel" type="Label" parent="Margin/VBox"]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
text = "
If you wish to upgrade a storage file from v0.8.4 - v0.9.2, specify it below. Data will be stored in the directory above upon save. The original file will not be touched."
autowrap_mode = 3
[node name="UpgradeHBox" type="HBoxContainer" parent="Margin/VBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Margin/VBox/UpgradeHBox"]
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Storage .res to upgrade"
[node name="SelectResFile" type="Button" parent="Margin/VBox/UpgradeHBox"]
layout_mode = 2
[node name="Spacer" type="Control" parent="Margin/VBox"] [node name="Spacer" type="Control" parent="Margin/VBox"]
custom_minimum_size = Vector2(0, 40) custom_minimum_size = Vector2(0, 40)
layout_mode = 2 layout_mode = 2

View File

@@ -1,9 +1,11 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Menu for Terrain3D
extends HBoxContainer extends HBoxContainer
const DirectoryWizard: Script = preload("res://addons/terrain_3d/src/directory_setup.gd") const DirectoryWizard: Script = preload("res://addons/terrain_3d/menu/directory_setup.gd")
const Packer: Script = preload("res://addons/terrain_3d/src/channel_packer.gd") const Packer: Script = preload("res://addons/terrain_3d/menu/channel_packer.gd")
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd") const Baker: Script = preload("res://addons/terrain_3d/menu/baker.gd")
var plugin: EditorPlugin var plugin: EditorPlugin
var menu_button: MenuButton = MenuButton.new() var menu_button: MenuButton = MenuButton.new()
@@ -31,7 +33,7 @@ func _enter_tree() -> void:
add_child(directory_setup) add_child(directory_setup)
add_child(baker) add_child(baker)
menu_button.text = "Terrain3D Tools" menu_button.text = "Terrain3D"
menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP) menu_button.get_popup().add_item("Directory Setup...", MENU_DIRECTORY_SETUP)
menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES) menu_button.get_popup().add_item("Pack Textures...", MENU_PACK_TEXTURES)
menu_button.get_popup().add_separator("", MENU_SEPARATOR) menu_button.get_popup().add_separator("", MENU_SEPARATOR)

View File

@@ -0,0 +1 @@
uid://3gxvahogxa10

View File

@@ -3,5 +3,5 @@
name="Terrain3D" name="Terrain3D"
description="A high performance, editable terrain system for Godot 4." description="A high performance, editable terrain system for Godot 4."
author="Cory Petkovsek & Roope Palmroos" author="Cory Petkovsek & Roope Palmroos"
version="0.9.3a" version="1.0.0"
script="editor.gd" script="src/editor_plugin.gd"

View File

@@ -1,6 +1,7 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Asset Dock for Terrain3D
@tool @tool
extends PanelContainer extends PanelContainer
#class_name Terrain3DAssetDock
signal confirmation_closed signal confirmation_closed
signal confirmation_confirmed signal confirmation_confirmed
@@ -444,9 +445,6 @@ class ListContainer extends Container:
func _ready() -> void: func _ready() -> void:
set_v_size_flags(SIZE_EXPAND_FILL) set_v_size_flags(SIZE_EXPAND_FILL)
set_h_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: func clear() -> void:
@@ -588,17 +586,13 @@ class ListContainer extends Container:
last_offset = 3 last_offset = 3
set_selected_id(clamp(selected_id, 0, entries.size() - last_offset)) 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: func get_selected_id() -> int:
return selected_id return selected_id
func set_entry_width(value: float) -> void: func set_entry_width(value: float) -> void:
width = clamp(value, 56, 230) width = clamp(value, 66, 230)
redraw() redraw()
@@ -651,45 +645,86 @@ class ListEntry extends VBoxContainer:
var is_selected: bool = false var is_selected: bool = false
var asset_list: Terrain3DAssets var asset_list: Terrain3DAssets
var button_clear: TextureButton @onready var button_row := HBoxContainer.new()
var button_edit: TextureButton @onready var button_clear := TextureButton.new()
var name_label: Label @onready var button_edit := TextureButton.new()
@onready var spacer := Control.new()
@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons") @onready var button_enabled := TextureButton.new()
@onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons") @onready var clear_icon: Texture2D = get_theme_icon("Close", "EditorIcons")
@onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons") @onready var edit_icon: Texture2D = get_theme_icon("Edit", "EditorIcons")
@onready var enabled_icon: Texture2D = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
@onready var disabled_icon: Texture2D = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
var name_label: Label
@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")
@onready var background: StyleBox = get_theme_stylebox("pressed", "Button") @onready var background: StyleBox = get_theme_stylebox("pressed", "Button")
var focus_style: StyleBox @onready var focus_style: StyleBox = get_theme_stylebox("focus", "Button").duplicate()
func _ready() -> void: func _ready() -> void:
setup_buttons()
setup_label()
focus_style.set_border_width_all(2)
focus_style.set_border_color(Color(1, 1, 1, .67))
func setup_buttons() -> void:
var icon_size: Vector2 = Vector2(12, 12) var icon_size: Vector2 = Vector2(12, 12)
var margin_container := MarginContainer.new()
margin_container.mouse_filter = Control.MOUSE_FILTER_PASS
margin_container.add_theme_constant_override("margin_top", 5)
margin_container.add_theme_constant_override("margin_left", 5)
margin_container.add_theme_constant_override("margin_right", 5)
add_child(margin_container)
button_clear = TextureButton.new() button_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_clear.set_texture_normal(clear_icon) button_row.alignment = BoxContainer.ALIGNMENT_CENTER
button_clear.set_custom_minimum_size(icon_size) button_row.mouse_filter = Control.MOUSE_FILTER_PASS
button_clear.set_h_size_flags(Control.SIZE_SHRINK_END) margin_container.add_child(button_row)
button_clear.set_visible(resource != null)
button_clear.pressed.connect(clear) if type == Terrain3DAssets.TYPE_MESH:
add_child(button_clear) button_enabled.set_texture_normal(enabled_icon)
button_enabled.set_texture_pressed(disabled_icon)
button_enabled.set_custom_minimum_size(icon_size)
button_enabled.set_h_size_flags(Control.SIZE_SHRINK_END)
button_enabled.set_visible(resource != null)
button_enabled.toggle_mode = true
button_enabled.mouse_filter = Control.MOUSE_FILTER_PASS
button_enabled.pressed.connect(enable)
button_row.add_child(button_enabled)
button_edit = TextureButton.new()
button_edit.set_texture_normal(edit_icon) button_edit.set_texture_normal(edit_icon)
button_edit.set_custom_minimum_size(icon_size) button_edit.set_custom_minimum_size(icon_size)
button_edit.set_h_size_flags(Control.SIZE_SHRINK_END) button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
button_edit.set_visible(resource != null) button_edit.set_visible(resource != null)
button_edit.mouse_filter = Control.MOUSE_FILTER_PASS
button_edit.pressed.connect(edit) button_edit.pressed.connect(edit)
add_child(button_edit) button_row.add_child(button_edit)
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
spacer.mouse_filter = Control.MOUSE_FILTER_PASS
button_row.add_child(spacer)
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.mouse_filter = Control.MOUSE_FILTER_PASS
button_clear.pressed.connect(clear)
button_row.add_child(button_clear)
func setup_label() -> void:
name_label = Label.new() name_label = Label.new()
add_child(name_label, true) add_child(name_label, true)
name_label.visible = false name_label.visible = false
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
name_label.add_theme_color_override("font_color", Color.WHITE)
name_label.add_theme_color_override("font_shadow_color", Color.BLACK) 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_x", 1.)
name_label.add_theme_constant_override("shadow_offset_y", 1) name_label.add_theme_constant_override("shadow_offset_y", 1.)
name_label.add_theme_font_size_override("font_size", 15) name_label.add_theme_font_size_override("font_size", 15)
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
@@ -702,6 +737,9 @@ class ListEntry extends VBoxContainer:
func _notification(p_what) -> void: func _notification(p_what) -> void:
match p_what: match p_what:
NOTIFICATION_DRAW: NOTIFICATION_DRAW:
# Hide spacer if icons are crowding small textures
spacer.visible = size.x > 70 or type == Terrain3DAssets.TYPE_TEXTURE
var rect: Rect2 = Rect2(Vector2.ZERO, get_size()) var rect: Rect2 = Rect2(Vector2.ZERO, get_size())
if !resource: if !resource:
draw_style_box(background, rect) draw_style_box(background, rect)
@@ -723,6 +761,7 @@ class ListEntry extends VBoxContainer:
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
else: else:
draw_rect(rect, Color(.15, .15, .15, 1.)) draw_rect(rect, Color(.15, .15, .15, 1.))
button_enabled.set_pressed_no_signal(!resource.is_enabled())
name_label.add_theme_font_size_override("font_size", 4 + rect.size.x/10) name_label.add_theme_font_size_override("font_size", 4 + rect.size.x/10)
if drop_data: if drop_data:
draw_style_box(focus_style, rect) draw_style_box(focus_style, rect)
@@ -791,8 +830,8 @@ class ListEntry extends VBoxContainer:
var ma := Terrain3DMeshAsset.new() var ma := Terrain3DMeshAsset.new()
if resource is Terrain3DMeshAsset: if resource is Terrain3DMeshAsset:
ma.id = resource.id ma.id = resource.id
ma.set_scene_file(res)
set_edited_resource(ma, false) set_edited_resource(ma, false)
ma.set_scene_file(res)
resource = ma resource = ma
elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH: elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH:
if resource is Terrain3DMeshAsset: if resource is Terrain3DMeshAsset:
@@ -808,6 +847,8 @@ class ListEntry extends VBoxContainer:
if resource: if resource:
resource.setting_changed.connect(_on_resource_changed) resource.setting_changed.connect(_on_resource_changed)
resource.file_changed.connect(_on_resource_changed) resource.file_changed.connect(_on_resource_changed)
if resource is Terrain3DMeshAsset:
resource.instancer_setting_changed.connect(_on_resource_changed)
if button_clear: if button_clear:
button_clear.set_visible(resource != null) button_clear.set_visible(resource != null)
@@ -818,6 +859,7 @@ class ListEntry extends VBoxContainer:
func _on_resource_changed() -> void: func _on_resource_changed() -> void:
queue_redraw()
emit_signal("changed", resource) emit_signal("changed", resource)
@@ -834,3 +876,8 @@ class ListEntry extends VBoxContainer:
func edit() -> void: func edit() -> void:
emit_signal("selected") emit_signal("selected")
emit_signal("inspected", resource) emit_signal("inspected", resource)
func enable() -> void:
if resource is Terrain3DMeshAsset:
resource.set_enabled(!resource.is_enabled())

View File

@@ -1 +1 @@
uid://bcfr30tpffi5o uid://bgoifepft1hjw

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dkb6hii5e48m2"] [gd_scene load_steps=2 format=3 uid="uid://dkb6hii5e48m2"]
[ext_resource type="Script" uid="uid://bcfr30tpffi5o" path="res://addons/terrain_3d/src/asset_dock.gd" id="1_e23pg"] [ext_resource type="Script" path="res://addons/terrain_3d/src/asset_dock.gd" id="1_e23pg"]
[node name="Terrain3D" type="PanelContainer"] [node name="Terrain3D" type="PanelContainer"]
custom_minimum_size = Vector2(256, 95) custom_minimum_size = Vector2(256, 95)
@@ -40,10 +40,9 @@ custom_minimum_size = Vector2(80, 30)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 0 size_flags_vertical = 0
item_count = 9
selected = 7 selected = 7
item_count = 9
popup/item_0/text = "Left_UL" popup/item_0/text = "Left_UL"
popup/item_0/id = 0
popup/item_1/text = "Left_BL" popup/item_1/text = "Left_BL"
popup/item_1/id = 1 popup/item_1/id = 1
popup/item_2/text = "Left_UR" popup/item_2/text = "Left_UR"
@@ -65,7 +64,7 @@ popup/item_8/id = 8
custom_minimum_size = Vector2(80, 10) custom_minimum_size = Vector2(80, 10)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
min_value = 56.0 min_value = 66.0
max_value = 230.0 max_value = 230.0
value = 83.0 value = 83.0

View File

@@ -1 +0,0 @@
uid://dft2g3p2pjw12

View File

@@ -1 +0,0 @@
uid://byi465tyumga0

View File

@@ -1 +0,0 @@
uid://y7pm2ndr5k2m

View File

@@ -1,553 +0,0 @@
[gd_scene load_steps=7 format=3 uid="uid://nud6dwjcnj5v"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ysabf"]
bg_color = Color(0.211765, 0.239216, 0.290196, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lcvna"]
bg_color = Color(0.168627, 0.211765, 0.266667, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.270588, 0.435294, 0.580392, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cb0xf"]
bg_color = Color(0.137255, 0.137255, 0.137255, 1)
draw_center = false
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.784314, 0.784314, 0.784314, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7qdas"]
[sub_resource type="ButtonGroup" id="ButtonGroup_wnxik"]
[sub_resource type="ButtonGroup" id="ButtonGroup_bs6ki"]
[node name="Window" type="Window"]
title = "Terrain3D Channel Packer"
initial_position = 1
size = Vector2i(680, 835)
unresizable = true
always_on_top = true
[node name="Panel" type="Panel" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_ysabf")
[node name="MarginContainer" type="MarginContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 5.0
offset_top = 5.0
offset_right = -5.0
offset_bottom = 5.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="AlbedoHeightPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 290)
layout_mode = 2
mouse_filter = 1
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer"]
layout_mode = 2
[node name="AlbedoVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="AlbedoLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
text = "Albedo texture"
[node name="AlbedoHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="AlbedoWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
alignment = 1
[node name="AlbedoW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="AlbedoH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/AlbedoWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox"]
layout_mode = 2
alignment = 1
[node name="LuminanceAsHeightButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/AlbedoVBox/HBoxContainer2"]
layout_mode = 2
text = " Generate Height from Luminance"
icon_alignment = 2
[node name="HeightVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="HeightLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
text = "Height texture"
[node name="HeightHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="HeightWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="HeightW" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HeightH" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HeightWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="ConvertDepthToHeight" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer2"]
layout_mode = 2
text = " Convert Depth to Height"
icon_alignment = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox"]
layout_mode = 2
alignment = 1
[node name="HeightChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
text = " Source Channel: "
horizontal_alignment = 2
[node name="HeightChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_pressed = true
button_group = SubResource("ButtonGroup_wnxik")
text = "R"
[node name="HeightChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "G"
[node name="HeightChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "B"
[node name="HeightChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/AlbedoHeightPanel/MarginContainer/HBoxContainer/HeightVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_wnxik")
text = "A"
[node name="NormalRoughnessPanel" type="Panel" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 290)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_lcvna")
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer"]
layout_mode = 2
[node name="NormalVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="NormalLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
text = "Normal texture"
[node name="NormalHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="NormalWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="NormalW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="NormalH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/NormalWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="InvertGreenChannelCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer"]
layout_mode = 2
text = " Convert DirectX to OpenGL"
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox"]
layout_mode = 2
alignment = 1
[node name="AlignNormalsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/NormalVBox/HBoxContainer2"]
layout_mode = 2
text = " Orthoganolise Normals"
[node name="RoughnessVBox" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="RoughnessLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
text = "Roughness texture"
[node name="RoughnessHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PickButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessHBox"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
size_flags_vertical = 4
theme_override_constants/margin_top = 10
[node name="Panel" type="Panel" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer"]
custom_minimum_size = Vector2(110, 110)
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_styles/panel = SubResource("StyleBoxFlat_cb0xf")
[node name="TextureRect" type="TextureRect" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -50.0
offset_top = -50.0
offset_right = 50.0
offset_bottom = 50.0
grow_horizontal = 2
grow_vertical = 2
expand_mode = 1
[node name="TextureButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/MarginContainer/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/normal = SubResource("StyleBoxEmpty_7qdas")
[node name="RoughnessWHHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="RoughnessW" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="RoughnessH" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/RoughnessWHHBox"]
layout_mode = 2
horizontal_alignment = 1
[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="InvertSmoothCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer2"]
layout_mode = 2
text = " Convert Smoothness to Roughness"
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox"]
layout_mode = 2
alignment = 1
[node name="RoughnessChannelLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
text = " Source Channel: "
horizontal_alignment = 2
[node name="RoughnessChannelR" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_pressed = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "R"
[node name="RoughnessChannelG" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "G"
[node name="RoughnessChannelB" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "B"
[node name="RoughnessChannelA" type="Button" parent="Panel/MarginContainer/VBoxContainer/NormalRoughnessPanel/MarginContainer/HBoxContainer/RoughnessVBox/HBoxContainer"]
layout_mode = 2
toggle_mode = true
button_group = SubResource("ButtonGroup_bs6ki")
text = "A"
[node name="GeneralOptionsLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "General Options"
horizontal_alignment = 1
vertical_alignment = 1
[node name="GeneralOptionsHBox" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
alignment = 1
[node name="ResizeToggle" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
text = " Resize Packed Image"
[node name="ResizeOptionButton" type="SpinBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
visible = false
layout_mode = 2
tooltip_text = "A value of 0 disables resizing."
min_value = 128.0
max_value = 4096.0
step = 128.0
value = 1024.0
[node name="VSeparator" type="VSeparator" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
[node name="GenerateMipmapsCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
button_pressed = true
text = "Generate Mipmaps"
[node name="HighQualityCheckBox" type="CheckBox" parent="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox"]
layout_mode = 2
text = "Import High Quality"
[node name="PackButton" type="Button" parent="Panel/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Pack textures as..."
[node name="StatusLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 3
[node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="CloseButton" type="Button" parent="Panel/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Close"
[connection signal="toggled" from="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeToggle" to="Panel/MarginContainer/VBoxContainer/GeneralOptionsHBox/ResizeOptionButton" method="set_visible"]

View File

@@ -1 +0,0 @@
uid://r0eex2idvm56

View File

@@ -1 +0,0 @@
uid://dt80i8xsj8tun

View File

@@ -1,3 +1,6 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# DoubleSlider for Terrain3D
# Should work for other UIs
@tool @tool
class_name DoubleSlider class_name DoubleSlider
extends Control extends Control
@@ -10,9 +13,23 @@ var min_value: float = 0.0
var max_value: float = 100.0 var max_value: float = 100.0
var step: float = 1.0 var step: float = 1.0
var range := Vector2(0, 100) var range := Vector2(0, 100)
var display_scale: float = 1.
var position_x: float = 0.
var minimum_x: float = 60.
func _ready() -> void: func _ready() -> void:
# Setup Display Scale
# 0 auto, 1 75%, 2 100%, 3 125%, 4 150%, 5 175%, 6 200%, 7 custom
var es: EditorSettings = EditorInterface.get_editor_settings()
var ds: int = es.get_setting("interface/editor/display_scale")
if ds == 0:
ds = 2
elif ds == 7:
display_scale = es.get_setting("interface/editor/custom_display_scale")
else:
display_scale = float(ds + 2) * .25
update_label() update_label()
@@ -68,11 +85,21 @@ func get_value() -> Vector2:
func update_label() -> void: func update_label() -> void:
if label: if label:
label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix) label.set_text(str(range.x) + suffix + "/" + str(range.y) + suffix)
if position_x == 0:
position_x = label.position.x
else:
label.position.x = position_x + 5 * display_scale
label.custom_minimum_size.x = minimum_x + 5 * display_scale
func _get_handle() -> int:
return 1
func _gui_input(p_event: InputEvent) -> void: func _gui_input(p_event: InputEvent) -> void:
if p_event is InputEventMouseButton: if p_event is InputEventMouseButton:
if p_event.get_button_index() == MOUSE_BUTTON_LEFT: var button: int = p_event.get_button_index()
if button in [ MOUSE_BUTTON_LEFT, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN ]:
if p_event.is_pressed(): if p_event.is_pressed():
var mid_point = (range.x + range.y) / 2.0 var mid_point = (range.x + range.y) / 2.0
var xpos: float = p_event.get_position().x * 2.0 var xpos: float = p_event.get_position().x * 2.0
@@ -80,7 +107,13 @@ func _gui_input(p_event: InputEvent) -> void:
grabbed_handle = 1 grabbed_handle = 1
else: else:
grabbed_handle = -1 grabbed_handle = -1
match button:
MOUSE_BUTTON_LEFT:
set_slider(p_event.get_position().x) set_slider(p_event.get_position().x)
MOUSE_BUTTON_WHEEL_DOWN:
set_slider(-1., true)
MOUSE_BUTTON_WHEEL_UP:
set_slider(1., true)
else: else:
grabbed_handle = 0 grabbed_handle = 0
@@ -89,12 +122,18 @@ func _gui_input(p_event: InputEvent) -> void:
set_slider(p_event.get_position().x) set_slider(p_event.get_position().x)
func set_slider(p_xpos: float) -> void: func set_slider(p_xpos: float, p_relative: bool = false) -> void:
if grabbed_handle == 0: if grabbed_handle == 0:
return return
var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value) var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value)
if(grabbed_handle < 0): if(grabbed_handle < 0):
if p_relative:
range.x += p_xpos
else:
range.x = xpos_step range.x = xpos_step
else:
if p_relative:
range.y += p_xpos
else: else:
range.y = xpos_step range.y = xpos_step
set_value(range) set_value(range)
@@ -111,14 +150,15 @@ func _notification(p_what: int) -> void:
# Draw foreground bar # Draw foreground bar
var handle: Texture2D = get_theme_icon("grabber", "HSlider") var handle: Texture2D = get_theme_icon("grabber", "HSlider")
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider") var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
var h: float = size.y / 2 - handle.get_size().y / 2
var startx: float = (range.x / max_value) * size.x var startx: float = (range.x / max_value) * size.x
var endx: float = (range.y / max_value) * size.x #- startx var endx: float = (range.y / max_value) * size.x
draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height))) draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height)))
# Draw handles, slightly in so they don't get on the outside edges # Draw handles, slightly in so they don't get on the outside edges
var handle_pos: Vector2 var handle_pos: Vector2
handle_pos.x = clamp(startx - handle.get_size().x/2, -5, size.x) handle_pos.x = clamp(startx - handle.get_size().x/2, -10, size.x)
handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10) handle_pos.y = clamp(endx - handle.get_size().x/2, 0, size.x - 10)
draw_texture(handle, Vector2(handle_pos.x, -mid_y)) draw_texture(handle, Vector2(handle_pos.x, -mid_y - 10 * (display_scale - 1.)))
draw_texture(handle, Vector2(handle_pos.y, -mid_y)) draw_texture(handle, Vector2(handle_pos.y, -mid_y - 10 * (display_scale - 1.)))
update_label()

View File

@@ -1 +1 @@
uid://cd6164a8pp48f uid://stro0p1oawfb

View File

@@ -1,6 +1,7 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Editor Plugin for Terrain3D
@tool @tool
extends EditorPlugin extends EditorPlugin
#class_name Terrain3DEditorPlugin Cannot be named until Godot #75388
# Includes # Includes
@@ -13,6 +14,7 @@ var modifier_alt: bool
var modifier_shift: bool var modifier_shift: bool
var _last_modifiers: int = 0 var _last_modifiers: int = 0
var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
var _use_meta: bool = false
var terrain: Terrain3D var terrain: Terrain3D
var _last_terrain: Terrain3D var _last_terrain: Terrain3D
@@ -29,6 +31,9 @@ var godot_editor_window: Window # The Godot Editor window
func _init() -> void: func _init() -> void:
if OS.get_name() == "macOS":
_use_meta = true
# Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control # Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent() godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
godot_editor_window.focus_entered.connect(_on_godot_focus_entered) godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
@@ -113,10 +118,6 @@ func _edit(p_object: Object) -> void:
ui.set_visible(true) ui.set_visible(true)
terrain.set_meta("_edit_lock_", true) terrain.set_meta("_edit_lock_", true)
# Deprecated 0.9.3 - Remove 1.0
if terrain.storage:
ui.terrain_menu.directory_setup.directory_setup_popup()
# Get alerted when a new asset list is loaded # Get alerted when a new asset list is loaded
if not terrain.assets_changed.is_connected(asset_dock.update_assets): if not terrain.assets_changed.is_connected(asset_dock.update_assets):
terrain.assets_changed.connect(asset_dock.update_assets) terrain.assets_changed.connect(asset_dock.update_assets)
@@ -155,10 +156,12 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
return AFTER_GUI_INPUT_PASS return AFTER_GUI_INPUT_PASS
_read_input(p_event) _read_input(p_event)
ui.update_decal()
## Handle mouse movement
if p_event is InputEventMouseMotion:
## Setup active camera & viewport ## Setup active camera & viewport
# Always update this for all inputs, as the mouse position can move without
# necessarily being a InputEventMouseMotion object. get_intersection() also
# returns the last frame position, and should be updated more frequently.
# Snap terrain to current camera # Snap terrain to current camera
terrain.set_camera(p_viewport_camera) terrain.set_camera(p_viewport_camera)
@@ -169,9 +172,9 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
## Get mouse location on terrain ## Get mouse location on terrain
# Project 2D mouse position to 3D position and direction # 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 vp_mouse_pos: Vector2 = editor_vpc.get_local_mouse_position()
var mouse_pos: Vector2 = vp_mouse_pos if full_resolution else vp_mouse_pos / 2
var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos) var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos) var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
@@ -180,13 +183,14 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir) var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
mouse_global_position = (camera_pos + t * camera_dir) mouse_global_position = (camera_pos + t * camera_dir)
else: else:
# Else look for intersection with terrain #Else look for intersection with terrain
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir) var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir, true)
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
return AFTER_GUI_INPUT_STOP return AFTER_GUI_INPUT_PASS
mouse_global_position = intersection_point mouse_global_position = intersection_point
ui.update_decal() ## Handle mouse movement
if p_event is InputEventMouseMotion:
if _input_mode != -1: # Not cam rotation if _input_mode != -1: # Not cam rotation
## Update region highlight ## Update region highlight
@@ -205,8 +209,6 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
return AFTER_GUI_INPUT_PASS return AFTER_GUI_INPUT_PASS
ui.update_decal()
if p_event is InputEventMouseButton and _input_mode > 0: if p_event is InputEventMouseButton and _input_mode > 0:
if p_event.is_pressed(): if p_event.is_pressed():
# If picking # If picking
@@ -215,6 +217,11 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
if not ui.operation_builder or not ui.operation_builder.is_ready(): if not ui.operation_builder or not ui.operation_builder.is_ready():
return AFTER_GUI_INPUT_STOP return AFTER_GUI_INPUT_STOP
if modifier_ctrl and editor.get_tool() == Terrain3DEditor.HEIGHT:
var height: float = terrain.data.get_height(mouse_global_position)
ui.brush_data["height"] = height
ui.tool_settings.set_setting("height", height)
# If adjusting regions # If adjusting regions
if editor.get_tool() == Terrain3DEditor.REGION: if editor.get_tool() == Terrain3DEditor.REGION:
# Skip regions that already exist or don't # Skip regions that already exist or don't
@@ -273,7 +280,7 @@ func _read_input(p_event: InputEvent = null) -> void:
## Determine modifiers pressed ## Determine modifiers pressed
modifier_shift = Input.is_key_pressed(KEY_SHIFT) modifier_shift = Input.is_key_pressed(KEY_SHIFT)
modifier_ctrl = Input.is_key_pressed(KEY_CTRL) modifier_ctrl = Input.is_key_pressed(KEY_META) if _use_meta else Input.is_key_pressed(KEY_CTRL)
# Keybind enum: Alt,Space,Meta,Capslock # Keybind enum: Alt,Space,Meta,Capslock
var alt_key: int var alt_key: int
match get_setting("terrain3d/config/alt_key_bind", 0): match get_setting("terrain3d/config/alt_key_bind", 0):
@@ -370,29 +377,6 @@ func setup_editor_settings() -> void:
} }
editor_settings.add_property_info(property_info) editor_settings.add_property_info(property_info)
_cleanup_old_settings()
# Remove or rename old settings
func _cleanup_old_settings() -> void:
# Rename deprecated settings - Remove in 1.0
var value: Variant
var rename_arr := [ "terrain3d/config/dock_slot", "terrain3d/config/dock_tile_size",
"terrain3d/config/dock_floating", "terrain3d/config/dock_always_on_top",
"terrain3d/config/dock_window_size", "terrain3d/config/dock_window_position", ]
for es: String in rename_arr:
if editor_settings.has_setting(es):
value = editor_settings.get_setting(es)
editor_settings.erase(es)
editor_settings.set_setting(es.replace("/config/dock_", "/dock/"), value)
# Special handling
var es: String = "terrain3d/tool_settings/slope"
if editor_settings.has_setting(es):
value = editor_settings.get_setting(es)
if typeof(value) == TYPE_FLOAT:
editor_settings.erase(es)
func set_setting(p_str: String, p_value: Variant) -> void: func set_setting(p_str: String, p_value: Variant) -> void:
editor_settings.set_setting(p_str, p_value) editor_settings.set_setting(p_str, p_value)

View File

@@ -0,0 +1 @@
uid://bsgxo1qywjdf3

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Gradient Operation Builder for Terrain3D
extends "res://addons/terrain_3d/src/operation_builder.gd" extends "res://addons/terrain_3d/src/operation_builder.gd"
@@ -52,4 +54,3 @@ func apply_operation(p_editor: Terrain3DEditor, p_global_position: Vector3, p_ca
p_editor.stop_operation() p_editor.stop_operation()
_get_point_picker().clear() _get_point_picker().clear()

View File

@@ -1 +1 @@
uid://b1qt62vc3vnx7 uid://def7sych6dp8b

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Multipicker for Terrain3D
extends HBoxContainer extends HBoxContainer

View File

@@ -1 +1 @@
uid://dqwxbfvjra3p3 uid://dvdtoa32h6xdn

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Operation Builder for Terrain3D
extends RefCounted extends RefCounted

View File

@@ -1 +1 @@
uid://3rphgkvw2bi1 uid://bu5cm0eh052rm

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Editor Region Gizmos for Terrain3D
extends EditorNode3DGizmo extends EditorNode3DGizmo
var material: StandardMaterial3D var material: StandardMaterial3D

View File

@@ -1 +1 @@
uid://ddnxpafte8kb7 uid://bh6qwe1ok4cx3

View File

@@ -1 +0,0 @@
uid://cbtr86lrjls7a

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Tool settings bar for Terrain3D
extends PanelContainer extends PanelContainer
signal picking(type, callback) signal picking(type, callback)
@@ -39,6 +41,7 @@ const NO_SAVE: int = 0x20 # Don't save this in EditorSettings
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
var brush_preview_material: ShaderMaterial var brush_preview_material: ShaderMaterial
var select_brush_button: Button var select_brush_button: Button
var selected_brush_imgs: Array
var main_list: HFlowContainer var main_list: HFlowContainer
var advanced_list: VBoxContainer var advanced_list: VBoxContainer
var height_list: VBoxContainer var height_list: VBoxContainer
@@ -86,6 +89,9 @@ func _ready() -> void:
add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX, add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
"list":main_list, "default":true, "flags":ADD_SEPARATOR }) "list":main_list, "default":true, "flags":ADD_SEPARATOR })
add_setting({ "name":"texture_filter", "label":"Texture Filter", "type":SettingType.CHECKBOX,
"list":main_list, "default":false, "flags":ADD_SEPARATOR })
add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0, add_setting({ "name":"margin", "type":SettingType.SLIDER, "list":main_list, "default":0,
"unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS }) "unit":"", "range":Vector3(-50, 50, 1), "flags":ALLOW_OUT_OF_BOUNDS })
@@ -171,6 +177,8 @@ func _ready() -> void:
"unit":"γ", "range":Vector3(0.1, 2.0, 0.01) }) "unit":"γ", "range":Vector3(0.1, 2.0, 0.01) })
add_setting({ "name":"jitter", "type":SettingType.SLIDER, "list":advanced_list, "default":50, add_setting({ "name":"jitter", "type":SettingType.SLIDER, "list":advanced_list, "default":50,
"unit":"%", "range":Vector3(0, 100, 1) }) "unit":"%", "range":Vector3(0, 100, 1) })
add_setting({ "name":"crosshair_threshold", "type":SettingType.SLIDER, "list":advanced_list, "default":16.,
"unit":"m", "range":Vector3(0, 200, 1) })
func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container: func create_submenu(p_parent: Control, p_button_name: String, p_layout: Layout, p_hover_pop: bool = true) -> Container:
@@ -267,29 +275,33 @@ func add_brushes(p_parent: Control) -> void:
while file_name != "": while file_name != "":
if !dir.current_is_dir() and file_name.ends_with(".exr"): if !dir.current_is_dir() and file_name.ends_with(".exr"):
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name) var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
img = Terrain3DUtil.black_to_alpha(img) var thumbimg: Image = img.duplicate()
if img.get_width() < 1024 and img.get_height() < 1024: img.convert(Image.FORMAT_RF)
img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
var tex: ImageTexture = ImageTexture.create_from_image(img)
var btn: Button = Button.new() if thumbimg.get_width() != 100 and thumbimg.get_height() != 100:
btn.set_custom_minimum_size(Vector2.ONE * 100) thumbimg.resize(100, 100, Image.INTERPOLATE_CUBIC)
btn.set_button_icon(tex) thumbimg = Terrain3DUtil.black_to_alpha(thumbimg)
btn.set_meta("image", img) thumbimg.convert(Image.FORMAT_LA8)
btn.set_expand_icon(true) var thumbtex: ImageTexture = ImageTexture.create_from_image(thumbimg)
btn.set_material(_get_brush_preview_material())
btn.set_toggle_mode(true) var brush_btn: Button = Button.new()
btn.set_button_group(brush_button_group) brush_btn.set_custom_minimum_size(Vector2.ONE * 100)
btn.mouse_entered.connect(_on_brush_hover.bind(true, btn)) brush_btn.set_button_icon(thumbtex)
btn.mouse_exited.connect(_on_brush_hover.bind(false, btn)) brush_btn.set_meta("image", img)
brush_list.add_child(btn, true) brush_btn.set_expand_icon(true)
brush_btn.set_material(_get_brush_preview_material())
brush_btn.set_toggle_mode(true)
brush_btn.set_button_group(brush_button_group)
brush_btn.mouse_entered.connect(_on_brush_hover.bind(true, brush_btn))
brush_btn.mouse_exited.connect(_on_brush_hover.bind(false, brush_btn))
brush_list.add_child(brush_btn, true)
if file_name == DEFAULT_BRUSH: if file_name == DEFAULT_BRUSH:
default_brush_btn = btn default_brush_btn = brush_btn
var lbl: Label = Label.new() var lbl: Label = Label.new()
btn.name = file_name.get_basename().to_pascal_case() brush_btn.name = file_name.get_basename().to_pascal_case()
btn.add_child(lbl, true) brush_btn.add_child(lbl, true)
lbl.text = btn.name lbl.text = brush_btn.name
lbl.visible = false lbl.visible = false
lbl.position.y = 70 lbl.position.y = 70
lbl.add_theme_color_override("font_shadow_color", Color.BLACK) lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
@@ -304,6 +316,7 @@ func add_brushes(p_parent: Control) -> void:
if not default_brush_btn: if not default_brush_btn:
default_brush_btn = brush_button_group.get_buttons()[0] default_brush_btn = brush_button_group.get_buttons()[0]
default_brush_btn.set_pressed(true) default_brush_btn.set_pressed(true)
_generate_brush_texture(default_brush_btn)
settings["brush"] = brush_button_group settings["brush"] = brush_button_group
@@ -473,7 +486,7 @@ func add_setting(p_args: Dictionary) -> void:
else: # DOUBLE_SLIDER else: # DOUBLE_SLIDER
var label := Label.new() var label := Label.new()
label.set_custom_minimum_size(Vector2(60, 0)) label.set_custom_minimum_size(Vector2(60, 0))
label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT) label.set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER)
slider = DoubleSlider.new() slider = DoubleSlider.new()
slider.label = label slider.label = label
slider.suffix = p_suffix slider.suffix = p_suffix
@@ -560,9 +573,7 @@ func get_setting(p_setting: String) -> Variant:
elif object is DoubleSlider: elif object is DoubleSlider:
value = object.get_value() value = object.get_value()
elif object is ButtonGroup: # "brush" elif object is ButtonGroup: # "brush"
var img: Image = object.get_pressed_button().get_meta("image") value = selected_brush_imgs
var tex: Texture2D = object.get_pressed_button().get_button_icon()
value = [ img, tex ]
elif object is CheckBox: elif object is CheckBox:
value = object.is_pressed() value = object.is_pressed()
elif object is ColorPickerButton: elif object is ColorPickerButton:
@@ -610,20 +621,31 @@ func show_settings(p_settings: PackedStringArray) -> void:
select_brush_button.show() select_brush_button.show()
func _on_setting_changed(p_data: Variant = null) -> void: func _on_setting_changed(p_object: Variant = null) -> void:
# If a button was clicked on a submenu # If a brush was selected
if p_data is Button and p_data.get_parent().get_parent() is PopupPanel: if p_object is Button and p_object.get_parent().name == "BrushList":
if p_data.get_parent().name == "BrushList": _generate_brush_texture(p_object)
# Optionally Set selected brush texture in main brush button # Optionally Set selected brush texture in main brush button
p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon()) if select_brush_button:
select_brush_button.set_button_icon(p_object.get_button_icon())
# Hide popup # Hide popup
p_data.get_parent().get_parent().set_visible(false) p_object.get_parent().get_parent().set_visible(false)
# Hide label # Hide label
if p_data.get_child_count() > 0: if p_object.get_child_count() > 0:
p_data.get_child(0).visible = false p_object.get_child(0).visible = false
emit_signal("setting_changed") emit_signal("setting_changed")
func _generate_brush_texture(p_btn: Button) -> void:
if p_btn is Button:
var img: Image = p_btn.get_meta("image")
if img.get_width() < 1024 and img.get_height() < 1024:
img = img.duplicate()
img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
var tex: ImageTexture = ImageTexture.create_from_image(img)
selected_brush_imgs = [ img, tex ]
func _on_drawable_toggled(p_button_pressed: bool) -> void: func _on_drawable_toggled(p_button_pressed: bool) -> void:
if not p_button_pressed: if not p_button_pressed:
settings["gradient_points"].clear() settings["gradient_points"].clear()

View File

@@ -1 +1 @@
uid://b0hs7rbtc8jfy uid://ciskaaennrffu

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Toolbar for Terrain3D
extends VFlowContainer extends VFlowContainer
signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation) signal tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor.Operation)
@@ -26,8 +28,8 @@ func _init() -> void:
set_custom_minimum_size(Vector2(20, 0)) set_custom_minimum_size(Vector2(20, 0))
func _ready() -> void: func _ready() -> void:
add_tool_group.connect("pressed", _on_tool_selected) add_tool_group.pressed.connect(_on_tool_selected)
sub_tool_group.connect("pressed", _on_tool_selected) sub_tool_group.pressed.connect(_on_tool_selected)
add_tool_button({ "tool":Terrain3DEditor.REGION, add_tool_button({ "tool":Terrain3DEditor.REGION,
"add_text":"Add Region", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD, "add_text":"Add Region", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD,

View File

@@ -1 +1 @@
uid://bthdcp8t4awsk uid://b1j37u6utjbom

View File

@@ -1,16 +1,17 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# UI for Terrain3D
extends Node extends Node
#class_name Terrain3DUI Cannot be named until Godot #75388
# Includes # Includes
const TerrainMenu: Script = preload("res://addons/terrain_3d/menu/terrain_menu.gd")
const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd") const Toolbar: Script = preload("res://addons/terrain_3d/src/toolbar.gd")
const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd") const ToolSettings: Script = preload("res://addons/terrain_3d/src/tool_settings.gd")
const TerrainMenu: Script = preload("res://addons/terrain_3d/src/terrain_menu.gd")
const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd") const OperationBuilder: Script = preload("res://addons/terrain_3d/src/operation_builder.gd")
const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd") const GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
const COLOR_RAISE := Color.WHITE const COLOR_RAISE := Color.WHITE
const COLOR_LOWER := Color(.02, .02, .02) const COLOR_LOWER := Color.BLACK
const COLOR_SMOOTH := Color(0.5, 0, .1) const COLOR_SMOOTH := Color(0.5, 0, .2)
const COLOR_LIFT := Color.ORANGE const COLOR_LIFT := Color.ORANGE
const COLOR_FLATTEN := Color.BLUE_VIOLET const COLOR_FLATTEN := Color.BLUE_VIOLET
const COLOR_HEIGHT := Color(0., 0.32, .4) const COLOR_HEIGHT := Color(0., 0.32, .4)
@@ -20,7 +21,7 @@ const COLOR_SPRAY := Color.PALE_GREEN
const COLOR_ROUGHNESS := Color.ROYAL_BLUE const COLOR_ROUGHNESS := Color.ROYAL_BLUE
const COLOR_AUTOSHADER := Color.DODGER_BLUE const COLOR_AUTOSHADER := Color.DODGER_BLUE
const COLOR_HOLES := Color.BLACK const COLOR_HOLES := Color.BLACK
const COLOR_NAVIGATION := Color(.15, .0, .255) const COLOR_NAVIGATION := Color(.28, .0, .25)
const COLOR_INSTANCER := Color.CRIMSON const COLOR_INSTANCER := Color.CRIMSON
const COLOR_PICK_COLOR := Color.WHITE const COLOR_PICK_COLOR := Color.WHITE
const COLOR_PICK_HEIGHT := Color.DARK_RED const COLOR_PICK_HEIGHT := Color.DARK_RED
@@ -31,8 +32,13 @@ const OP_POSITIVE_ONLY: int = 0x01
const OP_NEGATIVE_ONLY: int = 0x02 const OP_NEGATIVE_ONLY: int = 0x02
const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr" const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr"
@onready var ring_texture := ImageTexture.create_from_image(Terrain3DUtil.black_to_alpha(Image.load_from_file(RING1))) var ring_texture : ImageTexture
@onready var region_texture := ImageTexture.new() :
set(value):
var image: Image = Image.create_empty(1, 1, false, Image.FORMAT_R8)
image.fill(Color.WHITE)
value.create_from_image(image)
region_texture = value
var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors var plugin: EditorPlugin # Actually Terrain3DEditorPlugin, but Godot still has CRC errors
var toolbar: Toolbar var toolbar: Toolbar
var tool_settings: ToolSettings var tool_settings: ToolSettings
@@ -41,31 +47,42 @@ var setting_has_changed: bool = false
var visible: bool = false var visible: bool = false
var picking: int = Terrain3DEditor.TOOL_MAX var picking: int = Terrain3DEditor.TOOL_MAX
var picking_callback: Callable var picking_callback: Callable
var decal: Decal
var decal_timer: Timer
var gradient_decals: Array[Decal]
var brush_data: Dictionary var brush_data: Dictionary
var operation_builder: OperationBuilder var operation_builder: OperationBuilder
var last_tool: Terrain3DEditor.Tool var last_tool: Terrain3DEditor.Tool
var last_operation: Terrain3DEditor.Operation var last_operation: Terrain3DEditor.Operation
var last_rmb_time: int = 0 # Set in editor.gd var last_rmb_time: int = 0 # Set in editor.gd
# Compatibility decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B # Editor decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
var editor_decal_position: Array[Vector2] var mat_rid: RID
var editor_decal_rotation: Array[float] var editor_decal_position: Array[Vector2] = [Vector2(), Vector2(), Vector2()]
var editor_decal_size: Array[float] var editor_decal_rotation: Array[float] = [float(), float(), float()]
var editor_decal_color: Array[Color] var editor_decal_size: Array[float] = [float(), float(), float()]
var editor_decal_visible: Array[bool] var editor_decal_color: Array[Color] = [Color(), Color(), Color()]
var editor_decal_visible: Array[bool] = [bool(), bool(), bool()]
var editor_brush_texture_rid: RID = RID()
var editor_decal_timer: Timer
var editor_decal_fade: float :
set(value):
editor_decal_fade = value
if editor_decal_color.size() > 0:
editor_decal_color[0].a = value
if is_shader_valid():
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
if value < 0.001:
var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
var editor_ring_texture_rid: RID
func _enter_tree() -> void: func _enter_tree() -> void:
toolbar = Toolbar.new() toolbar = Toolbar.new()
toolbar.hide() toolbar.hide()
toolbar.connect("tool_changed", _on_tool_changed) toolbar.tool_changed.connect(_on_tool_changed)
tool_settings = ToolSettings.new() tool_settings = ToolSettings.new()
tool_settings.connect("setting_changed", _on_setting_changed) tool_settings.setting_changed.connect(_on_setting_changed)
tool_settings.connect("picking", _on_picking) tool_settings.picking.connect(_on_picking)
tool_settings.plugin = plugin tool_settings.plugin = plugin
tool_settings.hide() tool_settings.hide()
@@ -79,15 +96,19 @@ func _enter_tree() -> void:
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD) _on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
decal = Decal.new() editor_decal_timer = Timer.new()
add_child(decal) editor_decal_timer.wait_time = .5
decal_timer = Timer.new() editor_decal_timer.one_shot = true
decal_timer.wait_time = .5 editor_decal_timer.timeout.connect(func():
decal_timer.one_shot = true get_tree().create_tween().tween_property(self, "editor_decal_fade", 0.0, 0.15))
decal_timer.timeout.connect(Callable(func(node): add_child(editor_decal_timer)
if node:
get_tree().create_tween().tween_property(node, "albedo_mix", 0.0, 0.15)).bind(decal))
add_child(decal_timer) func _ready() -> void:
var img: Image = Image.load_from_file(RING1)
img.convert(Image.FORMAT_R8)
ring_texture = ImageTexture.create_from_image(img)
editor_ring_texture_rid = ring_texture.get_rid()
func _exit_tree() -> void: func _exit_tree() -> void:
@@ -96,11 +117,7 @@ func _exit_tree() -> void:
toolbar.queue_free() toolbar.queue_free()
tool_settings.queue_free() tool_settings.queue_free()
terrain_menu.queue_free() terrain_menu.queue_free()
decal.queue_free() editor_decal_timer.queue_free()
decal_timer.queue_free()
for gradient_decal in gradient_decals:
gradient_decal.queue_free()
gradient_decals.clear()
func set_visible(p_visible: bool, p_menu_only: bool = false) -> void: func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
@@ -117,7 +134,7 @@ func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
if(plugin.editor): if(plugin.editor):
if(p_visible): if(p_visible):
await get_tree().create_timer(.01).timeout # Won't work, otherwise. await get_tree().create_timer(.01).timeout # Won't work, otherwise
_on_tool_changed(last_tool, last_operation) _on_tool_changed(last_tool, last_operation)
else: else:
plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX) plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX)
@@ -185,7 +202,7 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
to_show.push_back("color") to_show.push_back("color")
to_show.push_back("color_picker") to_show.push_back("color_picker")
to_show.push_back("slope") to_show.push_back("slope")
to_show.push_back("enable_texture") to_show.push_back("texture_filter")
to_show.push_back("margin") to_show.push_back("margin")
to_show.push_back("remove") to_show.push_back("remove")
@@ -196,7 +213,7 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
to_show.push_back("roughness") to_show.push_back("roughness")
to_show.push_back("roughness_picker") to_show.push_back("roughness_picker")
to_show.push_back("slope") to_show.push_back("slope")
to_show.push_back("enable_texture") to_show.push_back("texture_filter")
to_show.push_back("margin") to_show.push_back("margin")
to_show.push_back("remove") to_show.push_back("remove")
@@ -236,6 +253,7 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
to_show.push_back("show_cursor_while_painting") to_show.push_back("show_cursor_while_painting")
to_show.push_back("gamma") to_show.push_back("gamma")
to_show.push_back("jitter") to_show.push_back("jitter")
to_show.push_back("crosshair_threshold")
tool_settings.show_settings(to_show) tool_settings.show_settings(to_show)
operation_builder = null operation_builder = null
@@ -258,9 +276,9 @@ func _on_setting_changed() -> void:
return return
brush_data = tool_settings.get_settings() brush_data = tool_settings.get_settings()
brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id() brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
update_decal()
plugin.editor.set_brush_data(brush_data) plugin.editor.set_brush_data(brush_data)
plugin.editor.set_operation(_modify_operation(plugin.editor.get_operation())) plugin.editor.set_operation(_modify_operation(plugin.editor.get_operation()))
update_decal()
func update_modifiers() -> void: func update_modifiers() -> void:
@@ -299,195 +317,211 @@ func _invert_operation(p_operation: Terrain3DEditor.Operation, flags: int = OP_N
func update_decal() -> void: func update_decal() -> void:
if not plugin.terrain or brush_data.size() <= 3:
return
mat_rid = plugin.terrain.material.get_material_rid()
editor_decal_timer.start()
# If not a state that should show the decal, hide everything and return # If not a state that should show the decal, hide everything and return
if not visible or \ if not visible or \
not plugin.terrain or \
plugin._input_mode < 0 or \ plugin._input_mode < 0 or \
# Wait for cursor to recenter after moving camera before revealing # Wait for cursor to recenter after moving camera before revealing
# See https://github.com/godotengine/godot/issues/70098 # See https://github.com/godotengine/godot/issues/70098
Time.get_ticks_msec() - last_rmb_time <= 30 or \ Time.get_ticks_msec() - last_rmb_time <= 30 or \
brush_data.is_empty() or \
plugin.editor.get_tool() == Terrain3DEditor.REGION or \
(plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]): (plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]):
decal.visible = false hide_decal()
for gradient_decal in gradient_decals:
gradient_decal.visible = false
return return
decal.position = plugin.mouse_global_position reset_decal_arrays()
decal.visible = true editor_decal_position[0] = Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z)
decal.size = Vector3.ONE * maxf(brush_data["size"], .5) editor_decal_visible[0] = true
# Set region size, and modify region map for none background mode.
var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
if plugin.editor.get_tool() == Terrain3DEditor.REGION:
var r_size: float = float(plugin.terrain.get_region_size()) * plugin.terrain.get_vertex_spacing()
var map_size: int = plugin.terrain.data.REGION_MAP_SIZE
var half_r_size: float = r_size * 0.5
var pos: Vector2 = (Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z) +
Vector2(half_r_size, half_r_size)).snappedf(r_size) - Vector2(half_r_size, half_r_size)
editor_brush_texture_rid = region_texture.get_rid()
editor_decal_position[0] = pos
editor_decal_size[0] = r_size
editor_decal_rotation[0] = 0.0
var loc: Vector2i = plugin.terrain.data.get_region_location(plugin.mouse_global_position)
loc += Vector2i(map_size / 2, map_size / 2)
if !(loc.x < 0 or loc.x > map_size - 1 or loc.y < 0 or loc.y > map_size - 1):
var index: int = clampi(loc.y * map_size + loc.x, 0, map_size * map_size - 1)
if plugin.terrain.material.get_world_background() == Terrain3DMaterial.WorldBackground.NONE:
if r_map[index] == 0 and plugin.editor.get_operation() == Terrain3DEditor.ADD:
r_map[index] = -index - 1
else:
r_map[index] = r_map[index]
match plugin.editor.get_operation():
Terrain3DEditor.ADD:
if r_map[index] <= 0:
editor_decal_color[0] = Color.WHITE
editor_decal_color[0].a = 0.25
else:
hide_decal()
Terrain3DEditor.SUBTRACT:
if r_map[index] > 0:
editor_decal_color[0] = Color.WHITE * .15
editor_decal_color[0].a = 0.75
else:
hide_decal()
else:
hide_decal()
# Set texture and color
elif picking != Terrain3DEditor.TOOL_MAX:
editor_brush_texture_rid = ring_texture.get_rid()
editor_decal_size[0] = 10. * plugin.terrain.get_vertex_spacing()
match picking:
Terrain3DEditor.HEIGHT:
editor_decal_color[0] = COLOR_PICK_HEIGHT
Terrain3DEditor.COLOR:
editor_decal_color[0] = COLOR_PICK_COLOR
Terrain3DEditor.ROUGHNESS:
editor_decal_color[0] = COLOR_PICK_ROUGH
editor_decal_color[0].a = 1.0
else:
editor_brush_texture_rid = brush_data["brush"][1].get_rid()
editor_decal_size[0] = maxf(brush_data["size"], .5)
if brush_data["align_to_view"]: if brush_data["align_to_view"]:
var cam: Camera3D = plugin.terrain.get_camera(); var cam: Camera3D = plugin.terrain.get_camera();
if (cam): if (cam):
decal.rotation.y = cam.rotation.y editor_decal_rotation[0] = cam.rotation.y
else: else:
decal.rotation.y = 0 editor_decal_rotation[0] = 0.
# Set texture and color
if picking != Terrain3DEditor.TOOL_MAX:
decal.texture_albedo = ring_texture
decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
match picking:
Terrain3DEditor.HEIGHT:
decal.modulate = COLOR_PICK_HEIGHT
Terrain3DEditor.COLOR:
decal.modulate = COLOR_PICK_COLOR
Terrain3DEditor.ROUGHNESS:
decal.modulate = COLOR_PICK_ROUGH
decal.modulate.a = 1.0
else:
decal.texture_albedo = brush_data["brush"][1]
match plugin.editor.get_tool(): match plugin.editor.get_tool():
Terrain3DEditor.SCULPT: Terrain3DEditor.SCULPT:
match plugin.editor.get_operation(): match plugin.editor.get_operation():
Terrain3DEditor.ADD: Terrain3DEditor.ADD:
if plugin.modifier_alt: if plugin.modifier_alt:
decal.modulate = COLOR_LIFT editor_decal_color[0] = COLOR_LIFT
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5)
else: else:
decal.modulate = COLOR_RAISE editor_decal_color[0] = COLOR_RAISE
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5)
Terrain3DEditor.SUBTRACT: Terrain3DEditor.SUBTRACT:
if plugin.modifier_alt: if plugin.modifier_alt:
decal.modulate = COLOR_FLATTEN editor_decal_color[0] = COLOR_FLATTEN
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5) + .1
else: else:
decal.modulate = COLOR_LOWER editor_decal_color[0] = COLOR_LOWER
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .5 editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
Terrain3DEditor.AVERAGE: Terrain3DEditor.AVERAGE:
decal.modulate = COLOR_SMOOTH editor_decal_color[0] = COLOR_SMOOTH
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .2 editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
Terrain3DEditor.GRADIENT: Terrain3DEditor.GRADIENT:
decal.modulate = COLOR_SLOPE editor_decal_color[0] = COLOR_SLOPE
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .2, .4)
Terrain3DEditor.HEIGHT: Terrain3DEditor.HEIGHT:
decal.modulate = COLOR_HEIGHT editor_decal_color[0] = COLOR_HEIGHT
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
Terrain3DEditor.TEXTURE: Terrain3DEditor.TEXTURE:
match plugin.editor.get_operation(): match plugin.editor.get_operation():
Terrain3DEditor.REPLACE: Terrain3DEditor.REPLACE:
decal.modulate = COLOR_PAINT editor_decal_color[0] = COLOR_PAINT
decal.modulate.a = .7 editor_decal_color[0].a = .6
Terrain3DEditor.SUBTRACT: Terrain3DEditor.SUBTRACT:
decal.modulate = COLOR_PAINT editor_decal_color[0] = COLOR_PAINT
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
Terrain3DEditor.ADD: Terrain3DEditor.ADD:
decal.modulate = COLOR_SPRAY editor_decal_color[0] = COLOR_SPRAY
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .15, .4)
Terrain3DEditor.COLOR: Terrain3DEditor.COLOR:
decal.modulate = brush_data["color"].srgb_to_linear() editor_decal_color[0] = brush_data["color"].srgb_to_linear()
decal.modulate.a *= clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a *= clamp(brush_data["strength"], .2, .5)
Terrain3DEditor.ROUGHNESS: Terrain3DEditor.ROUGHNESS:
decal.modulate = COLOR_ROUGHNESS editor_decal_color[0] = COLOR_ROUGHNESS
decal.modulate.a = clamp(brush_data["strength"], .2, .5) editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
Terrain3DEditor.AUTOSHADER: Terrain3DEditor.AUTOSHADER:
decal.modulate = COLOR_AUTOSHADER editor_decal_color[0] = COLOR_AUTOSHADER
decal.modulate.a = .7 editor_decal_color[0].a = .6
Terrain3DEditor.HOLES: Terrain3DEditor.HOLES:
decal.modulate = COLOR_HOLES editor_decal_color[0] = COLOR_HOLES
decal.modulate.a = .85 editor_decal_color[0].a = .75
Terrain3DEditor.NAVIGATION: Terrain3DEditor.NAVIGATION:
decal.modulate = COLOR_NAVIGATION editor_decal_color[0] = COLOR_NAVIGATION
decal.modulate.a = .85 editor_decal_color[0].a = .80
Terrain3DEditor.INSTANCER: Terrain3DEditor.INSTANCER:
decal.texture_albedo = ring_texture editor_brush_texture_rid = ring_texture.get_rid()
decal.modulate = COLOR_INSTANCER editor_decal_color[0] = COLOR_INSTANCER
decal.modulate.a = 1.0 editor_decal_color[0].a = .75
decal.size.y = max(1000, decal.size.y)
decal.albedo_mix = 1.0
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
decal_timer.start()
for gradient_decal in gradient_decals: editor_decal_visible[1] = false
gradient_decal.visible = false editor_decal_visible[2] = false
if plugin.editor.get_operation() == Terrain3DEditor.GRADIENT: if plugin.editor.get_operation() == Terrain3DEditor.GRADIENT:
var index := 0 var point1: Vector3 = brush_data["gradient_points"][0]
for point in brush_data["gradient_points"]: if point1 != Vector3.ZERO:
if point != Vector3.ZERO: editor_decal_color[1] = COLOR_SLOPE
var point_decal: Decal = _get_gradient_decal(index) editor_decal_size[1] = 10. * plugin.terrain.get_vertex_spacing()
point_decal.visible = true editor_decal_visible[1] = true
point_decal.position = point editor_decal_position[1] = Vector2(point1.x, point1.z)
index += 1 var point2: Vector3 = brush_data["gradient_points"][1]
if point2 != Vector3.ZERO:
editor_decal_color[2] = COLOR_SLOPE
editor_decal_size[2] = 10. * plugin.terrain.get_vertex_spacing()
editor_decal_visible[2] = true
editor_decal_position[2] = Vector2(point2.x, point2.z)
update_compatibility_decal() if RenderingServer.get_current_rendering_method().contains("gl_compatibility"):
for i in editor_decal_color.size():
editor_decal_color[i].a = maxf(0.1, editor_decal_color[i].a - .25)
editor_decal_fade = editor_decal_color[0].a
func _get_gradient_decal(index: int) -> Decal: # Update Shader params
if gradient_decals.size() > index: if is_shader_valid():
return gradient_decals[index] RenderingServer.material_set_param(mat_rid, "_editor_brush_texture", editor_brush_texture_rid)
RenderingServer.material_set_param(mat_rid, "_editor_ring_texture", editor_ring_texture_rid)
var gradient_decal := Decal.new()
gradient_decal = Decal.new()
gradient_decal.texture_albedo = ring_texture
gradient_decal.modulate = COLOR_SLOPE
gradient_decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
gradient_decal.size.y = 1000.
gradient_decal.cull_mask = decal.cull_mask
add_child(gradient_decal)
gradient_decals.push_back(gradient_decal)
return gradient_decal
func update_compatibility_decal() -> void:
if not plugin.terrain.is_compatibility_mode():
return
# Verify setup
if editor_decal_position.size() != 3:
editor_decal_position.resize(3)
editor_decal_rotation.resize(3)
editor_decal_size.resize(3)
editor_decal_color.resize(3)
editor_decal_visible.resize(3)
decal_timer.timeout.connect(func():
var mat_rid: RID = plugin.terrain.material.get_material_rid()
editor_decal_visible[0] = false
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
)
# Update compatibility decal
var mat_rid: RID = plugin.terrain.material.get_material_rid()
if decal.visible:
editor_decal_position[0] = Vector2(decal.global_position.x, decal.global_position.z)
editor_decal_rotation[0] = decal.rotation.y
editor_decal_size[0] = brush_data.get("size")
editor_decal_color[0] = decal.modulate
editor_decal_visible[0] = decal.visible
RenderingServer.material_set_param(
mat_rid, "_editor_decal_0", decal.texture_albedo.get_rid()
)
if gradient_decals.size() >= 1:
editor_decal_position[1] = Vector2(gradient_decals[0].global_position.x,
gradient_decals[0].global_position.z)
editor_decal_rotation[1] = gradient_decals[0].rotation.y
editor_decal_size[1] = 10.0
editor_decal_color[1] = gradient_decals[0].modulate
editor_decal_visible[1] = gradient_decals[0].visible
RenderingServer.material_set_param(
mat_rid, "_editor_decal_1", gradient_decals[0].texture_albedo.get_rid()
)
if gradient_decals.size() >= 2:
editor_decal_position[2] = Vector2(gradient_decals[1].global_position.x,
gradient_decals[1].global_position.z)
editor_decal_rotation[2] = gradient_decals[1].rotation.y
editor_decal_size[2] = 10.0
editor_decal_color[2] = gradient_decals[1].modulate
editor_decal_visible[2] = gradient_decals[1].visible
RenderingServer.material_set_param(
mat_rid, "_editor_decal_2", gradient_decals[1].texture_albedo.get_rid()
)
RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position) RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation) RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size) RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color) RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible) RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
RenderingServer.material_set_param(mat_rid, "_editor_crosshair_threshold", brush_data["crosshair_threshold"] + 0.1)
RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
func is_shader_valid() -> bool:
# As long as the compiled shader contains at least 1 uniform, we can use it to check
# if the shader compilation has failed, as this will then return an empty dictionary.
if not plugin.terrain:
return false
var params = RenderingServer.get_shader_parameter_list(plugin.terrain.material.get_shader_rid())
if params.is_empty():
return false
else:
return true
func hide_decal() -> void:
editor_decal_visible = [false, false, false]
if is_shader_valid():
var r_map: PackedInt32Array = plugin.terrain.data.get_region_map()
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
RenderingServer.material_set_param(mat_rid, "_region_map", r_map)
# These array sizes are reset to 0 when closing scenes for some unknown reason, so check and reset
func reset_decal_arrays() -> void:
if editor_decal_color.size() < 3:
editor_decal_position = [Vector2(), Vector2(), Vector2()]
editor_decal_rotation = [float(), float(), float()]
editor_decal_size = [float(), float(), float()]
editor_decal_color = [Color(), Color(), Color()]
editor_decal_visible = [false, false, false]
editor_brush_texture_rid = RID()
func set_decal_rotation(p_rot: float) -> void: func set_decal_rotation(p_rot: float) -> void:
decal.rotation.y = p_rot editor_decal_rotation[0] = p_rot
if is_shader_valid():
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
func _on_picking(p_type: int, p_callback: Callable) -> void: func _on_picking(p_type: int, p_callback: Callable) -> void:
@@ -515,7 +549,7 @@ func pick(p_global_position: Vector3) -> void:
var color: Color var color: Color
match picking: match picking:
Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT: Terrain3DEditor.HEIGHT, Terrain3DEditor.SCULPT:
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_HEIGHT, p_global_position) color = Color(plugin.terrain.data.get_height(p_global_position), 0., 0., 1.)
Terrain3DEditor.ROUGHNESS: Terrain3DEditor.ROUGHNESS:
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position) color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position)
Terrain3DEditor.COLOR: Terrain3DEditor.COLOR:
@@ -527,7 +561,9 @@ func pick(p_global_position: Vector3) -> void:
_: _:
push_error("Unsupported picking type: ", picking) push_error("Unsupported picking type: ", picking)
return return
if picking_callback.is_valid():
picking_callback.call(picking, color, p_global_position) picking_callback.call(picking, color, p_global_position)
picking_callback = Callable()
picking = Terrain3DEditor.TOOL_MAX picking = Terrain3DEditor.TOOL_MAX
elif operation_builder and operation_builder.is_picking(): elif operation_builder and operation_builder.is_picking():

View File

@@ -1 +1 @@
uid://bv4lj2cvubl7p uid://bpad72s36mwkx

View File

@@ -1,7 +1,7 @@
[configuration] [configuration]
entry_symbol = "terrain_3d_init" entry_symbol = "terrain_3d_init"
compatibility_minimum = 4.2 compatibility_minimum = 4.4
[icons] [icons]
@@ -27,3 +27,6 @@ android.release.arm64 = "res://addons/terrain_3d/bin/libterrain.android.release.
ios.debug = "res://addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib" ios.debug = "res://addons/terrain_3d/bin/libterrain.ios.debug.universal.dylib"
ios.release = "res://addons/terrain_3d/bin/libterrain.ios.release.universal.dylib" ios.release = "res://addons/terrain_3d/bin/libterrain.ios.release.universal.dylib"
web.debug = "res://addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm"
web.release = "res://addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm"

View File

@@ -1 +1 @@
uid://chcl8jili5ggt uid://bdn2ygldx56a8

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Importer for Terrain3D
@tool @tool
extends Terrain3D extends Terrain3D
@@ -20,10 +22,14 @@ func reset_settings(p_value) -> void:
r16_size = Vector2i(1024, 1024) r16_size = Vector2i(1024, 1024)
material = null material = null
assets = null assets = null
reset_terrain(true)
func reset_terrain(p_value) -> void: func reset_terrain(p_value) -> void:
data_directory = "" data_directory = ""
for region:Terrain3DRegion in data.get_regions_active():
data.remove_region(region, false)
data.update_maps(Terrain3DRegion.TYPE_MAX, true, false)
func update_heights(p_value) -> void: func update_heights(p_value) -> void:

View File

@@ -1 +1 @@
uid://co2gd0uhumg34 uid://cib2rig7vup10

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"] [gd_scene load_steps=9 format=3 uid="uid://blaieaqp413k7"]
[ext_resource type="Script" uid="uid://co2gd0uhumg34" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"] [ext_resource type="Script" path="res://addons/terrain_3d/tools/importer.gd" id="1_60b8f"]
[sub_resource type="Gradient" id="Gradient_88f3t"] [sub_resource type="Gradient" id="Gradient_88f3t"]
offsets = PackedFloat32Array(0.2, 1) offsets = PackedFloat32Array(0.2, 1)

View File

@@ -1,3 +1,6 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Objects parent for Terrain3D
# Children nodes get transform updates on sculpting
@tool @tool
extends Node3D extends Node3D
class_name Terrain3DObjects class_name Terrain3DObjects
@@ -97,6 +100,7 @@ func _on_child_exiting_tree(p_node: Node) -> void:
var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH) var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH)
if helper: if helper:
if helper.transform_changed.is_connected(_on_child_transform_changed):
helper.transform_changed.disconnect(_on_child_transform_changed) helper.transform_changed.disconnect(_on_child_transform_changed)
p_node.remove_child(helper) p_node.remove_child(helper)
helper.queue_free() helper.queue_free()

View File

@@ -1 +1 @@
uid://cl5vs8p47r15v uid://dbndw8p05yam7

View File

@@ -1,3 +1,5 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Transform Changed Notifier for Terrain3D
@tool @tool
extends Node3D extends Node3D

View File

@@ -1 +1 @@
uid://drw381usdrd8b uid://claxtgppe8keq

File diff suppressed because one or more lines are too long

View File

@@ -34,7 +34,10 @@ ssao_enabled = true
ssil_enabled = true ssil_enabled = true
sdfgi_enabled = true sdfgi_enabled = true
glow_enabled = true glow_enabled = true
fog_density = 0.0005 fog_mode = 1
fog_density = 0.1
volumetric_fog_density = 0.006
volumetric_fog_albedo = Color(0.530013, 0.696854, 0.82526, 1)
[node name="Realtime Day Night Cycle" type="Node3D"] [node name="Realtime Day Night Cycle" type="Node3D"]
script = ExtResource("1_idjmm") script = ExtResource("1_idjmm")

View File

@@ -1,13 +1,11 @@
[gd_scene load_steps=8 format=3 uid="uid://cofnhdcclon1w"] [gd_scene load_steps=8 format=3 uid="uid://cofnhdcclon1w"]
[ext_resource type="Script" uid="uid://dd26e3lk8oe1l" path="res://demo/src/CodeGenerated.gd" id="1_h7vyv"] [ext_resource type="Script" uid="uid://dakis6gu8b7nm" path="res://demo/src/CodeGenerated.gd" id="1_h7vyv"]
[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="2_3v2uf"]
[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_71ikj"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_71ikj"]
[ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="4_p8qry"] [ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="4_p8qry"]
[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="4_x5ge4"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="4_x5ge4"]
[ext_resource type="Script" uid="uid://br3a8kjyuwoeg" path="res://demo/src/RuntimeNavigationBaker.gd" id="5_445ur"] [ext_resource type="Script" uid="uid://brh8x1wnycrl5" path="res://demo/src/RuntimeNavigationBaker.gd" id="5_445ur"]
[sub_resource type="Resource" id="Resource_mekfw"]
metadata/__load_path__ = "res://demo/components/Player.tscn"
[sub_resource type="NavigationMesh" id="NavigationMesh_vs6am"] [sub_resource type="NavigationMesh" id="NavigationMesh_vs6am"]
geometry_parsed_geometry_type = 1 geometry_parsed_geometry_type = 1
@@ -19,7 +17,7 @@ script = ExtResource("1_h7vyv")
[node name="Environment" parent="." instance=ExtResource("3_71ikj")] [node name="Environment" parent="." instance=ExtResource("3_71ikj")]
[node name="Player" parent="."] [node name="Player" parent="." instance=ExtResource("2_3v2uf")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 50, 0)
[node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("4_p8qry")] [node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("4_p8qry")]

View File

@@ -1,15 +1,13 @@
[gd_scene load_steps=12 format=3 uid="uid://chol2xlfbq7cu"] [gd_scene load_steps=12 format=3 uid="uid://chol2xlfbq7cu"]
[ext_resource type="Script" uid="uid://8atelq27qxnq" path="res://demo/src/DemoScene.gd" id="1_k7qca"] [ext_resource type="Script" uid="uid://chstoagn42gbr" path="res://demo/src/DemoScene.gd" id="1_k7qca"]
[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_nqak5"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="2_nqak5"]
[ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_cw38j"] [ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_cw38j"]
[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="3_ht63y"]
[ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="3_kdh0b"] [ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="3_kdh0b"]
[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_yqldq"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="3_yqldq"]
[ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="8_g2of2"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="8_g2of2"]
[sub_resource type="Resource" id="Resource_wlawv"]
metadata/__load_path__ = "res://demo/components/Player.tscn"
[sub_resource type="Gradient" id="Gradient_vr1m7"] [sub_resource type="Gradient" id="Gradient_vr1m7"]
offsets = PackedFloat32Array(0.2, 1) offsets = PackedFloat32Array(0.2, 1)
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
@@ -35,32 +33,41 @@ noise = SubResource("FastNoiseLite_d8lcj")
_shader_parameters = { _shader_parameters = {
"_mouse_layer": 2147483648, "_mouse_layer": 2147483648,
"auto_base_texture": 0, "auto_base_texture": 0,
"auto_height_reduction": 0.1, "auto_height_reduction": 0.0,
"auto_overlay_texture": 1, "auto_overlay_texture": 1,
"auto_slope": 1.0, "auto_slope": 1.0,
"bias_distance": 512.0,
"blend_sharpness": 0.87, "blend_sharpness": 0.87,
"depth_blur": 0.0,
"dual_scale_far": 170.0, "dual_scale_far": 170.0,
"dual_scale_near": 100.0, "dual_scale_near": 100.0,
"dual_scale_reduction": 0.3, "dual_scale_reduction": 0.3,
"dual_scale_texture": 0, "dual_scale_texture": 0,
"enable_macro_variation": true,
"enable_projection": true,
"height_blending": true, "height_blending": true,
"macro_variation1": Color(0.878431, 0.862745, 0.901961, 1), "macro_variation1": Color(0.855, 0.8625, 0.9, 1),
"macro_variation2": Color(0.898039, 0.898039, 0.803922, 1), "macro_variation2": Color(0.9, 0.885, 0.81, 1),
"macro_variation_slope": 0.333,
"mipmap_bias": 1.0,
"noise1_angle": 0.1, "noise1_angle": 0.1,
"noise1_offset": Vector2(0.5, 0.5), "noise1_offset": Vector2(0.5, 0.5),
"noise1_scale": 0.04, "noise1_scale": 0.04,
"noise2_scale": 0.076, "noise2_scale": 0.076,
"noise3_scale": 0.225, "noise3_scale": 0.225,
"noise_texture": SubResource("NoiseTexture2D_bov7h"), "noise_texture": SubResource("NoiseTexture2D_bov7h"),
"projection_angular_division": 1.0,
"projection_threshold": 0.8,
"tri_scale_reduction": 0.3, "tri_scale_reduction": 0.3,
"vertex_normals_distance": 128.0, "world_noise_fragment_normals": false,
"world_noise_height": 34.0, "world_noise_height": 34.0,
"world_noise_lod_distance": 7500.0, "world_noise_lod_distance": 7500.0,
"world_noise_max_octaves": 4, "world_noise_max_octaves": 4,
"world_noise_min_octaves": 2, "world_noise_min_octaves": 2,
"world_noise_offset": Vector3(2.17, -1.225, 1.9), "world_noise_offset": Vector3(2.17, -1.225, 1.9),
"world_noise_region_blend": 0.33, "world_noise_region_blend": 0.33,
"world_noise_scale": 9.85 "world_noise_scale": 9.85,
"world_space_normal_blend": true
} }
world_background = 2 world_background = 2
auto_shader = true auto_shader = true
@@ -79,7 +86,7 @@ collision_mask = 3
[node name="Tunnel" parent="." instance=ExtResource("3_kdh0b")] [node name="Tunnel" parent="." instance=ExtResource("3_kdh0b")]
[node name="Player" parent="."] [node name="Player" parent="." instance=ExtResource("3_ht63y")]
transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 223.143, 105.348, -1833.08) transform = Transform3D(0.176947, 0, -0.98422, 0, 1, 0, 0.98422, 0, 0.176947, 223.143, 105.348, -1833.08)
[node name="Terrain3D" type="Terrain3D" parent="."] [node name="Terrain3D" type="Terrain3D" parent="."]

View File

@@ -1,17 +1,15 @@
[gd_scene load_steps=14 format=3 uid="uid://x34e4v60vdmy"] [gd_scene load_steps=14 format=3 uid="uid://x34e4v60vdmy"]
[ext_resource type="Script" uid="uid://8atelq27qxnq" path="res://demo/src/DemoScene.gd" id="1_po4vw"] [ext_resource type="Script" path="res://demo/src/DemoScene.gd" id="1_po4vw"]
[ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="2_i74wg"] [ext_resource type="PackedScene" uid="uid://bb2lp50sjndus" path="res://demo/components/Environment.tscn" id="2_i74wg"]
[ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_3upu0"] [ext_resource type="PackedScene" uid="uid://dwnhqfjq7v1pq" path="res://demo/components/Borders.tscn" id="3_3upu0"]
[ext_resource type="PackedScene" uid="uid://domhm87hbhbg1" path="res://demo/components/Player.tscn" id="4_fk3ul"]
[ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="5_2lvlr"] [ext_resource type="PackedScene" uid="uid://djhl3foqkj4e2" path="res://demo/components/Tunnel.tscn" id="5_2lvlr"]
[ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="5_rc7e2"] [ext_resource type="PackedScene" uid="uid://d2jihfohphuue" path="res://demo/components/UI.tscn" id="5_rc7e2"]
[ext_resource type="NavigationMesh" uid="uid://yf3fu23666k8" path="res://demo/data/nav_mesh.res" id="8_6jfdr"] [ext_resource type="NavigationMesh" uid="uid://yf3fu23666k8" path="res://demo/data/nav_mesh.res" id="8_6jfdr"]
[ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="10_qmhh8"] [ext_resource type="Terrain3DAssets" uid="uid://dal3jhw6241qg" path="res://demo/data/assets.tres" id="10_qmhh8"]
[ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="12_b2xl8"] [ext_resource type="PackedScene" uid="uid://di5fovhcyd7re" path="res://demo/components/Enemy.tscn" id="12_b2xl8"]
[sub_resource type="Resource" id="Resource_it78x"]
metadata/__load_path__ = "res://demo/components/Player.tscn"
[sub_resource type="Gradient" id="Gradient_vr1m7"] [sub_resource type="Gradient" id="Gradient_vr1m7"]
offsets = PackedFloat32Array(0.2, 1) offsets = PackedFloat32Array(0.2, 1)
colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1)
@@ -40,22 +38,29 @@ _shader_parameters = {
"auto_height_reduction": 0.1, "auto_height_reduction": 0.1,
"auto_overlay_texture": 1, "auto_overlay_texture": 1,
"auto_slope": 1.0, "auto_slope": 1.0,
"bias_distance": 512.0,
"blend_sharpness": 0.87, "blend_sharpness": 0.87,
"depth_blur": 0.0,
"dual_scale_far": 170.0, "dual_scale_far": 170.0,
"dual_scale_near": 100.0, "dual_scale_near": 100.0,
"dual_scale_reduction": 0.3, "dual_scale_reduction": 0.3,
"dual_scale_texture": 0, "dual_scale_texture": 0,
"enable_macro_variation": true,
"enable_projection": true,
"height_blending": true, "height_blending": true,
"macro_variation1": Color(0.878431, 0.862745, 0.901961, 1), "macro_variation1": Color(0.878431, 0.862745, 0.901961, 1),
"macro_variation2": Color(0.898039, 0.898039, 0.803922, 1), "macro_variation2": Color(0.898039, 0.898039, 0.803922, 1),
"macro_variation_slope": 0.333,
"mipmap_bias": 1.0,
"noise1_angle": 0.1, "noise1_angle": 0.1,
"noise1_offset": Vector2(0.5, 0.5), "noise1_offset": Vector2(0.5, 0.5),
"noise1_scale": 0.04, "noise1_scale": 0.04,
"noise2_scale": 0.076, "noise2_scale": 0.076,
"noise3_scale": 0.225, "noise3_scale": 0.225,
"noise_texture": SubResource("NoiseTexture2D_bov7h"), "noise_texture": SubResource("NoiseTexture2D_bov7h"),
"tri_scale_reduction": 0.3, "projection_angular_division": 1.436,
"vertex_normals_distance": 128.0 "projection_threshold": 0.8,
"tri_scale_reduction": 0.3
} }
world_background = 0 world_background = 0
auto_shader = true auto_shader = true
@@ -73,7 +78,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 512, -2.5, 512)
[node name="Tunnel" parent="." instance=ExtResource("5_2lvlr")] [node name="Tunnel" parent="." instance=ExtResource("5_2lvlr")]
[node name="Player" parent="."] [node name="Player" parent="." instance=ExtResource("4_fk3ul")]
transform = Transform3D(0.0774673, 0, -0.996995, 0, 1, 0, 0.996995, 0, 0.0774673, 229.418, 104.956, -1838.16) transform = Transform3D(0.0774673, 0, -0.996995, 0, 1, 0, 0.996995, 0, 0.0774673, 229.418, 104.956, -1838.16)
[node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("12_b2xl8")] [node name="Enemy" parent="." node_paths=PackedStringArray("target") instance=ExtResource("12_b2xl8")]

View File

@@ -1,7 +1,6 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://fkhkir6t1hml"] [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://fkhkir6t1hml"]
[ext_resource type="Texture2D" uid="uid://br7bfxcxh60df" path="res://demo/assets/textures/rock030_alb_ht.png" id="1_ahc02"] [ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_3is54"]
[ext_resource type="Texture2D" uid="uid://cr72kjil43vgj" path="res://demo/assets/textures/rock030_nrm_rgh.png" id="2_glxvb"]
[resource] [resource]
transparency = 1 transparency = 1
@@ -9,19 +8,18 @@ vertex_color_use_as_albedo = true
albedo_color = Color(0.4, 1, 1, 0.647059) albedo_color = Color(0.4, 1, 1, 0.647059)
metallic_specular = 1.0 metallic_specular = 1.0
roughness = 0.2 roughness = 0.2
roughness_texture = ExtResource("2_glxvb") roughness_texture = ExtResource("2_3is54")
roughness_texture_channel = 3 roughness_texture_channel = 3
emission_enabled = true emission_enabled = true
emission = Color(0, 0.145098, 0.776471, 1) emission = Color(0, 0.145098, 0.776471, 1)
emission_energy_multiplier = 1.4 emission_energy_multiplier = 1.4
normal_enabled = true normal_enabled = true
normal_scale = 0.4 normal_scale = 0.4
normal_texture = ExtResource("2_glxvb") normal_texture = ExtResource("2_3is54")
rim_enabled = true rim_enabled = true
rim = 0.25 rim = 0.25
clearcoat_roughness = 0.0 clearcoat_roughness = 0.0
ao_light_affect = 1.0 ao_light_affect = 1.0
ao_texture = ExtResource("1_ahc02")
ao_texture_channel = 3 ao_texture_channel = 3
refraction_enabled = true refraction_enabled = true
refraction_scale = -0.1 refraction_scale = -0.1

View File

@@ -1,7 +1,6 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://cso4f2iyuxpmc"] [gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://cso4f2iyuxpmc"]
[ext_resource type="Texture2D" uid="uid://br7bfxcxh60df" path="res://demo/assets/textures/rock030_alb_ht.png" id="1_uwd12"] [ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_1t610"]
[ext_resource type="Texture2D" uid="uid://cr72kjil43vgj" path="res://demo/assets/textures/rock030_nrm_rgh.png" id="2_0wyvn"]
[resource] [resource]
transparency = 1 transparency = 1
@@ -9,18 +8,17 @@ vertex_color_use_as_albedo = true
albedo_color = Color(0.4, 0.4, 1, 0.556863) albedo_color = Color(0.4, 0.4, 1, 0.556863)
metallic_specular = 1.0 metallic_specular = 1.0
roughness = 0.2 roughness = 0.2
roughness_texture = ExtResource("2_0wyvn") roughness_texture = ExtResource("2_1t610")
roughness_texture_channel = 3 roughness_texture_channel = 3
emission_enabled = true emission_enabled = true
emission = Color(0.235294, 0.145098, 0.776471, 1) emission = Color(0.235294, 0.145098, 0.776471, 1)
normal_enabled = true normal_enabled = true
normal_scale = 0.4 normal_scale = 0.4
normal_texture = ExtResource("2_0wyvn") normal_texture = ExtResource("2_1t610")
rim_enabled = true rim_enabled = true
rim = 0.25 rim = 0.25
clearcoat_roughness = 0.0 clearcoat_roughness = 0.0
ao_light_affect = 1.0 ao_light_affect = 1.0
ao_texture = ExtResource("1_uwd12")
ao_texture_channel = 3 ao_texture_channel = 3
refraction_enabled = true refraction_enabled = true
refraction_scale = -0.1 refraction_scale = -0.1

View File

@@ -0,0 +1,24 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ickkffutwcvo"]
[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_k2yhv"]
[resource]
transparency = 1
albedo_color = Color(0.670588, 0.0588235, 0.384314, 0.509804)
metallic_specular = 1.0
roughness = 0.2
roughness_texture = ExtResource("2_k2yhv")
roughness_texture_channel = 3
emission_enabled = true
emission = Color(0.258824, 0.0823529, 0.25098, 1)
normal_enabled = true
normal_scale = 0.4
normal_texture = ExtResource("2_k2yhv")
rim_enabled = true
rim = 0.25
clearcoat_roughness = 0.0
ao_enabled = true
ao_light_affect = 1.0
ao_texture_channel = 3
refraction_enabled = true
refraction_scale = -0.1

View File

@@ -0,0 +1,17 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://d0hyi5n6ng25w"]
[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_1js8i"]
[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_snl3x"]
[resource]
albedo_color = Color(0.501406, 0.501407, 0.501406, 1)
albedo_texture = ExtResource("1_1js8i")
roughness = 0.95
roughness_texture = ExtResource("2_snl3x")
roughness_texture_channel = 3
normal_enabled = true
normal_texture = ExtResource("2_snl3x")
uv1_scale = Vector3(0.4, 0.4, 0.4)
uv1_triplanar = true
uv1_world_triplanar = true
texture_filter = 5

View File

@@ -0,0 +1,16 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://nbbdrx8vma80"]
[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="1_mek0c"]
[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="2_pp13c"]
[resource]
vertex_color_use_as_albedo = true
albedo_color = Color(0.83, 0.83, 0.83, 1)
albedo_texture = ExtResource("1_mek0c")
normal_enabled = true
normal_texture = ExtResource("2_pp13c")
uv1_scale = Vector3(0.02, 0.02, 0.02)
uv1_triplanar = true
uv1_triplanar_sharpness = 6.06286
uv1_world_triplanar = true
texture_filter = 5

View File

@@ -1,14 +0,0 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://dlmtxs0c66hj3"]
[ext_resource type="Texture2D" uid="uid://br7bfxcxh60df" path="res://demo/assets/textures/rock030_alb_ht.png" id="1_j5sdo"]
[ext_resource type="Texture2D" uid="uid://cr72kjil43vgj" path="res://demo/assets/textures/rock030_nrm_rgh.png" id="2_yyurh"]
[resource]
vertex_color_use_as_albedo = true
albedo_color = Color(1.351, 1.321, 1.27, 1)
albedo_texture = ExtResource("1_j5sdo")
normal_enabled = true
normal_texture = ExtResource("2_yyurh")
uv1_scale = Vector3(4, 4, 4)
uv1_triplanar_sharpness = 6.06286
texture_filter = 5

View File

@@ -1,15 +0,0 @@
[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://dkvsksxpkl1vj"]
[ext_resource type="Texture2D" uid="uid://br7bfxcxh60df" path="res://demo/assets/textures/rock030_alb_ht.png" id="1_j5sdo"]
[ext_resource type="Texture2D" uid="uid://cr72kjil43vgj" path="res://demo/assets/textures/rock030_nrm_rgh.png" id="2_yyurh"]
[resource]
vertex_color_use_as_albedo = true
albedo_color = Color(1.351, 1.321, 1.27, 1)
albedo_texture = ExtResource("1_j5sdo")
normal_enabled = true
normal_texture = ExtResource("2_yyurh")
uv1_scale = Vector3(0.04, 0.04, 0.04)
uv1_triplanar = true
uv1_triplanar_sharpness = 6.06286
texture_filter = 5

View File

@@ -0,0 +1,56 @@
[gd_scene load_steps=9 format=3 uid="uid://bn5nf4esciwex"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"]
albedo_color = Color(1, 0, 0, 1)
[sub_resource type="SphereMesh" id="SphereMesh_u4ac2"]
material = SubResource("StandardMaterial3D_788j8")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"]
albedo_color = Color(1, 0.540167, 0.11, 1)
[sub_resource type="BoxMesh" id="BoxMesh_xyuxq"]
material = SubResource("StandardMaterial3D_3i52q")
size = Vector3(0.85, 0.85, 0.85)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"]
albedo_color = Color(0.48, 1, 0.497333, 1)
[sub_resource type="PrismMesh" id="PrismMesh_xnm4h"]
material = SubResource("StandardMaterial3D_p0ha4")
size = Vector3(1, 1, 0.855)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4koei"]
albedo_color = Color(0.45, 0.596667, 1, 1)
[sub_resource type="CylinderMesh" id="CylinderMesh_dp7xj"]
material = SubResource("StandardMaterial3D_4koei")
top_radius = 0.0
bottom_radius = 0.34
height = 1.54
radial_segments = 4
[node name="TestMultimesh" type="Node3D"]
[node name="Node3D" type="Node3D" parent="."]
[node name="MeshInstance3D1" type="MeshInstance3D" parent="Node3D"]
mesh = SubResource("SphereMesh_u4ac2")
skeleton = NodePath("../..")
[node name="MeshInstance3D2" type="MeshInstance3D" parent="Node3D"]
visible = false
mesh = SubResource("BoxMesh_xyuxq")
skeleton = NodePath("../..")
[node name="MeshInstance3D3" type="MeshInstance3D" parent="Node3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.118133, 0)
visible = false
mesh = SubResource("PrismMesh_xnm4h")
skeleton = NodePath("../..")
[node name="MeshInstance3D4" type="MeshInstance3D" parent="Node3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.246009, 0)
visible = false
mesh = SubResource("CylinderMesh_dp7xj")
skeleton = NodePath("../..")

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More