added terrain3d
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -3,34 +3,26 @@
|
||||
# Terrain3D
|
||||
A high performance, editable terrain system for Godot 4.
|
||||
|
||||
|
||||
## 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
|
||||
* 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
|
||||
* Up to 32 textures
|
||||
* Up to 10 levels of detail
|
||||
* Foliage instancing
|
||||
* Up to 10 levels of detail for the terrain mesh
|
||||
* Foliage instancing, with up to 10 levels of detail, and a shadow impostor
|
||||
* 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
|
||||
|
||||
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:
|
||||
|
||||
**Installation, Setup, Basic Usage**
|
||||
|
||||
[](https://youtu.be/oV8c9alXVwU)
|
||||
|
||||
**Texture Painting, Holes, Navigation, Advanced Usage**
|
||||
|
||||
[](https://youtu.be/YtiAI2F6Xkk)
|
||||
3. Watch the [tutorial videos](https://terrain3d.readthedocs.io/en/stable/docs/tutorial_videos.html).
|
||||
|
||||
|
||||
## 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)|
|
||||
| **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
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm
Normal file
BIN
addons/terrain_3d/bin/libterrain.web.debug.wasm32.wasm
Normal file
Binary file not shown.
BIN
addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm
Normal file
BIN
addons/terrain_3d/bin/libterrain.web.release.wasm32.wasm
Normal file
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
uid://cuhwgfm8bkknk
|
||||
67
addons/terrain_3d/extras/hex_grid.gdshaderinc
Normal file
67
addons/terrain_3d/extras/hex_grid.gdshaderinc
Normal 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);
|
||||
}
|
||||
1
addons/terrain_3d/extras/hex_grid.gdshaderinc.uid
Normal file
1
addons/terrain_3d/extras/hex_grid.gdshaderinc.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://mri8pfoj2mfk
|
||||
@@ -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:
|
||||
#
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bm8g6gkvrerqy
|
||||
uid://bllcuwetve45k
|
||||
|
||||
400
addons/terrain_3d/extras/lightweight.gdshader
Normal file
400
addons/terrain_3d/extras/lightweight.gdshader
Normal 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;
|
||||
|
||||
}
|
||||
1
addons/terrain_3d/extras/lightweight.gdshader.uid
Normal file
1
addons/terrain_3d/extras/lightweight.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bbx2xhanpq5l3
|
||||
163
addons/terrain_3d/extras/lowpoly_colormap.gdshader
Normal file
163
addons/terrain_3d/extras/lowpoly_colormap.gdshader
Normal 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;
|
||||
}
|
||||
1
addons/terrain_3d/extras/lowpoly_colormap.gdshader.uid
Normal file
1
addons/terrain_3d/extras/lowpoly_colormap.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bda7fq1rh3nmv
|
||||
134
addons/terrain_3d/extras/lowpoly_minimum.gdshader
Normal file
134
addons/terrain_3d/extras/lowpoly_minimum.gdshader
Normal 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;
|
||||
}
|
||||
1
addons/terrain_3d/extras/lowpoly_minimum.gdshader.uid
Normal file
1
addons/terrain_3d/extras/lowpoly_minimum.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://x11v7w7v8hqa
|
||||
@@ -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.
|
||||
|
||||
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 float _region_size = 1024.0;
|
||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||
// Commented uniforms aren't needed for this shader, but are available for your own needs.
|
||||
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 sampler2DArray _height_maps : repeat_disable;
|
||||
uniform usampler2DArray _control_maps : repeat_disable;
|
||||
uniform sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
||||
uniform sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
uniform sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
|
||||
uniform float _texture_uv_scale_array[32];
|
||||
uniform float _texture_detile_array[32];
|
||||
uniform vec4 _texture_color_array[32];
|
||||
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
|
||||
uniform uint _mouse_layer = 0x80000000u; // Layer 32
|
||||
|
||||
// Public uniforms
|
||||
uniform float vertex_normals_distance : hint_range(0, 1024) = 128.0;
|
||||
//uniform float _texture_uv_scale_array[32];
|
||||
//uniform float _texture_detile_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_anisotropic, repeat_disable;
|
||||
//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
//uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
|
||||
|
||||
// Varyings & Types
|
||||
varying flat vec3 v_vertex; // World coordinate vertex location
|
||||
varying flat vec3 v_camera_pos;
|
||||
// Some are required for editor functions
|
||||
varying float v_vertex_xz_dist;
|
||||
varying flat ivec3 v_region;
|
||||
varying flat vec2 v_uv_offset;
|
||||
varying flat vec2 v_uv2_offset;
|
||||
varying vec3 v_normal;
|
||||
varying float v_region_border_mask;
|
||||
varying vec3 v_vertex;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
////////////////////////
|
||||
|
||||
// Takes in UV world space coordinates, returns ivec3 with:
|
||||
// XY: (0 to _region_size) coordinates within a region
|
||||
// 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_region_uv(const vec2 uv) {
|
||||
ivec2 pos = ivec2(floor(uv * _region_texel_size)) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
return ivec3(ivec2(mod(uv,_region_size)), layer_index);
|
||||
}
|
||||
|
||||
// Takes in UV2 region space coordinates, returns vec3 with:
|
||||
// XY: (0 to 1) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
vec3 get_region_uv2(const vec2 uv2) {
|
||||
// Remove Texel Offset to ensure correct region index.
|
||||
ivec2 pos = ivec2(floor(uv2 - vec2(_region_texel_size * 0.5))) + (_region_map_size / 2);
|
||||
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
|
||||
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
|
||||
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;
|
||||
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 height;
|
||||
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
// Get camera pos in world vertex coords
|
||||
v_camera_pos = INV_VIEW_MATRIX[3].xyz;
|
||||
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
v_vertex_xz_dist = length(v_vertex.xz - v_camera_pos.xz);
|
||||
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 = 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
|
||||
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
|
||||
|
||||
// Discard vertices for Holes. 1 lookup
|
||||
v_region = get_region_uv(UV);
|
||||
uint control = texelFetch(_control_maps, v_region, 0).r;
|
||||
ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
|
||||
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
|
||||
bool hole = bool(control >>2u & 0x1u);
|
||||
|
||||
// Show holes to all cameras except mouse camera (on exactly 1 layer)
|
||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||
(hole || (_background_mode == 0u && (get_region_uv(UV - _region_texel_size) & v_region).z < 0))) {
|
||||
VERTEX.x = 0. / 0.;
|
||||
} else {
|
||||
// Set final vertex height & calculate vertex normals. 3 lookups.
|
||||
VERTEX.y = get_height(UV2);
|
||||
v_vertex.y = VERTEX.y;
|
||||
v_normal = vec3(
|
||||
v_vertex.y - get_height(UV2 + vec2(_region_texel_size, 0)),
|
||||
_vertex_spacing,
|
||||
v_vertex.y - get_height(UV2 + vec2(0, _region_texel_size))
|
||||
);
|
||||
// Due to a bug caused by the GPUs linear interpolation across edges of region maps,
|
||||
// mask region edges and use vertex normals only across region boundaries.
|
||||
v_region_border_mask = mod(UV.x + 2.5, _region_size) - fract(UV.x) < 5.0 || mod(UV.y + 2.5, _region_size) - fract(UV.y) < 5.0 ? 1. : 0.;
|
||||
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
|
||||
(hole || (_background_mode == 0u && v_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;
|
||||
}
|
||||
|
||||
// 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
|
||||
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
VERTEX = (VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
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);
|
||||
@@ -129,36 +124,85 @@ void vertex() {
|
||||
// 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() {
|
||||
// Recover UVs
|
||||
vec2 uv = UV + v_uv_offset;
|
||||
vec2 uv2 = UV2 + v_uv2_offset;
|
||||
vec2 uv = UV;
|
||||
vec2 uv2 = UV2;
|
||||
|
||||
// Calculate Terrain Normals. 4 lookups
|
||||
vec3 w_tangent, w_binormal;
|
||||
vec3 w_normal = get_normal(uv2, w_tangent, w_binormal);
|
||||
// Lookup offsets, ID and blend weight
|
||||
const vec3 offsets = vec3(0, 1, 2);
|
||||
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;
|
||||
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
|
||||
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
|
||||
|
||||
// Apply PBR
|
||||
ALBEDO=vec3(.2);
|
||||
ALBEDO = vec3(.2);
|
||||
ROUGHNESS = .7;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c8qog5mlaoeno
|
||||
uid://01qauauvd8aa
|
||||
|
||||
@@ -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
|
||||
# It provides a `Project on Terrain3D` modifier, which allows Scatter
|
||||
# to detect the terrain height from Terrain3D without using collision.
|
||||
# Copy this file into /addons/proton_scatter/src/modifiers
|
||||
# Then uncomment everything below
|
||||
# In the editor, add this modifier to Scatter, then set your Terrain3D node
|
||||
|
||||
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
|
||||
# It allows Scatter to detect the terrain height from Terrain3D
|
||||
#
|
||||
# Copy this file into /addons/proton_scatter/src/modifiers
|
||||
# Then uncomment everything below (select, press CTRL+K)
|
||||
# In the editor, add this modifier, then set your Terrain3D node
|
||||
# In the editor, add this modifier to Scatter, then set your Terrain3D node
|
||||
|
||||
#@tool
|
||||
#extends "base_modifier.gd"
|
||||
@@ -20,6 +16,7 @@
|
||||
#
|
||||
#@export var terrain_node : NodePath
|
||||
#@export var align_with_collision_normal := false
|
||||
#@export_range(0.0, 90.0, 0.1) var max_slope = 90.0
|
||||
#
|
||||
#var _terrain: Terrain3D
|
||||
#
|
||||
@@ -68,20 +65,29 @@
|
||||
## Get global transform
|
||||
#var gt: Transform3D = domain.get_global_transform()
|
||||
#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():
|
||||
#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 normal: Vector3 = _terrain.data.get_normal(location)
|
||||
#
|
||||
#if align_with_collision_normal and not is_nan(normal.x):
|
||||
#transforms.list[i].basis.y = normal
|
||||
#transforms.list[i].basis.x = -transforms.list[i].basis.z.cross(normal)
|
||||
#transforms.list[i].basis = transforms.list[i].basis.orthonormalized()
|
||||
#t.basis.y = normal
|
||||
#t.basis.x = -t.basis.z.cross(normal)
|
||||
#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():
|
||||
#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.
|
||||
#+ Ray length is too short.
|
||||
#+ Ray direction is incorrect.
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://ce403ehalp57b
|
||||
uid://g3opjh3m3iww
|
||||
|
||||
52
addons/terrain_3d/extras/region_mover.gd
Normal file
52
addons/terrain_3d/extras/region_mover.gd
Normal 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()
|
||||
1
addons/terrain_3d/extras/region_mover.gd.uid
Normal file
1
addons/terrain_3d/extras/region_mover.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bngnvtbm6ifkk
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Bake LOD Dialog for Terrain3D
|
||||
@tool
|
||||
extends ConfirmationDialog
|
||||
|
||||
1
addons/terrain_3d/menu/bake_lod_dialog.gd.uid
Normal file
1
addons/terrain_3d/menu/bake_lod_dialog.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cqmt8f5x5c2ad
|
||||
@@ -1,6 +1,6 @@
|
||||
[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"]
|
||||
title = "Bake Terrain3D Mesh"
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Baker for Terrain3D
|
||||
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_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:
|
||||
@@ -194,7 +196,7 @@ func _bake_nav_region_nav_mesh(p_nav_region: NavigationRegion3D) -> void:
|
||||
assert(nav_mesh != null)
|
||||
|
||||
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):
|
||||
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():
|
||||
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)
|
||||
|
||||
@@ -374,10 +376,8 @@ func _do_set_up_navigation(p_nav_region: NavigationRegion3D, p_terrain: Terrain3
|
||||
var index: int = p_terrain.get_index()
|
||||
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)
|
||||
p_terrain.reparent(p_nav_region)
|
||||
parent.move_child(p_nav_region, index)
|
||||
|
||||
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 t_owner: Node = p_nav_region.get_owner()
|
||||
|
||||
p_terrain.reparent(parent)
|
||||
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)
|
||||
|
||||
p_terrain.owner = t_owner
|
||||
1
addons/terrain_3d/menu/baker.gd.uid
Normal file
1
addons/terrain_3d/menu/baker.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://4ulaeevj5jvi
|
||||
@@ -1,8 +1,10 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Channel Packer for Terrain3D
|
||||
extends RefCounted
|
||||
|
||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/src/channel_packer.tscn"
|
||||
const TEMPLATE_PATH: String = "res://addons/terrain_3d/src/channel_packer_import_template.txt"
|
||||
const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/src/channel_packer_dragdrop.gd"
|
||||
const WINDOW_SCENE: String = "res://addons/terrain_3d/menu/channel_packer.tscn"
|
||||
const TEMPLATE_PATH: String = "res://addons/terrain_3d/menu/channel_packer_import_template.txt"
|
||||
const DRAG_DROP_SCRIPT: String = "res://addons/terrain_3d/menu/channel_packer_dragdrop.gd"
|
||||
enum {
|
||||
INFO,
|
||||
WARN,
|
||||
@@ -47,7 +49,7 @@ var normal_vector: Vector3
|
||||
func pack_textures_popup() -> void:
|
||||
if window != null:
|
||||
window.show()
|
||||
window.move_to_foreground()
|
||||
window.grab_focus()
|
||||
window.move_to_center()
|
||||
return
|
||||
window = (load(WINDOW_SCENE) as PackedScene).instantiate()
|
||||
@@ -108,9 +110,8 @@ func pack_textures_popup() -> void:
|
||||
_init_texture_picker(window.find_child("HeightVBox"), IMAGE_HEIGHT)
|
||||
_init_texture_picker(window.find_child("NormalVBox"), IMAGE_NORMAL)
|
||||
_init_texture_picker(window.find_child("RoughnessVBox"), IMAGE_ROUGHNESS)
|
||||
|
||||
var pack_button_path: String = "Panel/MarginContainer/VBoxContainer/PackButton"
|
||||
(window.get_node(pack_button_path) as Button).pressed.connect(_on_pack_button_pressed)
|
||||
|
||||
(window.find_child("PackButton") as Button).pressed.connect(_on_pack_button_pressed)
|
||||
|
||||
|
||||
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.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:
|
||||
@@ -393,8 +394,8 @@ func _set_normal_vector(source: Image, quiet: bool = false) -> void:
|
||||
# Calculate texture normal sum direction
|
||||
var normal: Image = source
|
||||
var sum: Color = Color(0.0, 0.0, 0.0, 0.0)
|
||||
for x in normal.get_height():
|
||||
for y in normal.get_width():
|
||||
for x in normal.get_width():
|
||||
for y in normal.get_height():
|
||||
sum += normal.get_pixel(x, y)
|
||||
var div: float = normal.get_height() * normal.get_width()
|
||||
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
|
||||
var mat3: Basis = _alignment_basis(normal_vector)
|
||||
# re-align the normal map pixels
|
||||
for x in source.get_height():
|
||||
for y in source.get_width():
|
||||
for x in source.get_width():
|
||||
for y in source.get_height():
|
||||
var old_pixel: Color = source.get_pixel(x, y)
|
||||
var vector_pixel: Vector3 = Vector3(old_pixel.r, old_pixel.g, old_pixel.b)
|
||||
vector_pixel *= 2.0
|
||||
@@ -450,10 +451,9 @@ func _pack_textures(p_rgb_image: Image, p_a_image: Image, p_dst_path: String, p_
|
||||
if not output_image:
|
||||
_show_message(ERROR, "Failed to pack textures")
|
||||
return FAILED
|
||||
if output_image.detect_used_channels() != 5:
|
||||
_show_message(ERROR, "Packing Error, Alpha Channel empty")
|
||||
return FAILED
|
||||
|
||||
if output_image.detect_alpha() != Image.ALPHA_BLEND:
|
||||
_show_message(WARN, "Warning, Alpha channel empty")
|
||||
|
||||
output_image.save_png(p_dst_path)
|
||||
_create_import_file(p_dst_path)
|
||||
_show_message(INFO, "Packed to " + p_dst_path + ".")
|
||||
1
addons/terrain_3d/menu/channel_packer.gd.uid
Normal file
1
addons/terrain_3d/menu/channel_packer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bwldx4itd58o7
|
||||
530
addons/terrain_3d/menu/channel_packer.tscn
Normal file
530
addons/terrain_3d/menu/channel_packer.tscn
Normal 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"]
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Channel Packer Dragdropper for Terrain3D
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
1
addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid
Normal file
1
addons/terrain_3d/menu/channel_packer_dragdrop.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://br45krrqbw8bg
|
||||
@@ -1,13 +1,13 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Directory Setup for Terrain3D
|
||||
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 dialog: ConfirmationDialog
|
||||
var select_dir_btn: Button
|
||||
var selected_dir_le: LineEdit
|
||||
var select_upg_btn: Button
|
||||
var upgrade_file_le: LineEdit
|
||||
var editor_file_dialog: EditorFileDialog
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ func _init() -> void:
|
||||
editor_file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
|
||||
editor_file_dialog.ok_button_text = "Open"
|
||||
editor_file_dialog.title = "Open a folder or file"
|
||||
editor_file_dialog.file_selected.connect(_on_file_selected)
|
||||
editor_file_dialog.dir_selected.connect(_on_dir_selected)
|
||||
editor_file_dialog.size = Vector2i(850, 550)
|
||||
editor_file_dialog.transient = false
|
||||
@@ -34,22 +33,14 @@ func directory_setup_popup() -> void:
|
||||
# Nodes
|
||||
select_dir_btn = dialog.get_node("Margin/VBox/DirHBox/SelectDir")
|
||||
selected_dir_le = dialog.get_node("Margin/VBox/DirHBox/LineEdit")
|
||||
select_upg_btn = dialog.get_node("Margin/VBox/UpgradeHBox/SelectResFile")
|
||||
upgrade_file_le = dialog.get_node("Margin/VBox/UpgradeHBox/LineEdit")
|
||||
upgrade_file_le.text = ""
|
||||
|
||||
if plugin.terrain.data_directory:
|
||||
selected_dir_le.text = plugin.terrain.data_directory
|
||||
|
||||
if plugin.terrain.storage:
|
||||
upgrade_file_le.text = plugin.terrain.storage.get_path()
|
||||
|
||||
# Icons
|
||||
plugin.ui.set_button_editor_icon(select_upg_btn, "Folder")
|
||||
plugin.ui.set_button_editor_icon(select_dir_btn, "Folder")
|
||||
|
||||
#Signals
|
||||
select_upg_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_FILE))
|
||||
select_dir_btn.pressed.connect(_on_select_file_pressed.bind(EditorFileDialog.FILE_MODE_OPEN_DIR))
|
||||
dialog.confirmed.connect(_on_close_requested)
|
||||
dialog.canceled.connect(_on_close_requested)
|
||||
@@ -73,10 +64,6 @@ func _on_dir_selected(path: String) -> void:
|
||||
selected_dir_le.text = path
|
||||
|
||||
|
||||
func _on_file_selected(path: String) -> void:
|
||||
upgrade_file_le.text = path
|
||||
|
||||
|
||||
func _on_ok_pressed() -> void:
|
||||
if not plugin.terrain:
|
||||
push_error("Not connected terrain. Click the Terrain3D node first")
|
||||
@@ -97,20 +84,3 @@ func _on_ok_pressed() -> void:
|
||||
|
||||
print("Setting terrain directory: ", selected_dir_le.text)
|
||||
plugin.terrain.data_directory = selected_dir_le.text
|
||||
|
||||
if not upgrade_file_le.text.is_empty():
|
||||
if data_found:
|
||||
push_warning("Target directory already has terrain data. Specify an empty directory to upgrade")
|
||||
return
|
||||
if not FileAccess.file_exists(upgrade_file_le.text):
|
||||
push_error("File doesn't exist: ", upgrade_file_le.text)
|
||||
return
|
||||
|
||||
if not plugin.terrain.storage or \
|
||||
( plugin.terrain.storage and plugin.terrain.storage.get_path() != upgrade_file_le.text):
|
||||
print("Loading storage file: ", upgrade_file_le.text)
|
||||
plugin.terrain.set_storage(load(upgrade_file_le.text))
|
||||
|
||||
if plugin.terrain.storage:
|
||||
print("Begining upgrade of: ", upgrade_file_le.text)
|
||||
plugin.terrain.split_storage()
|
||||
1
addons/terrain_3d/menu/directory_setup.gd.uid
Normal file
1
addons/terrain_3d/menu/directory_setup.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://0034ukv2mngn
|
||||
@@ -3,14 +3,14 @@
|
||||
[node name="DirectorySetup" type="ConfirmationDialog"]
|
||||
title = "Terrain3D Data Directory Setup"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(750, 574)
|
||||
size = Vector2i(750, 330)
|
||||
visible = true
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 742.0
|
||||
offset_bottom = 525.0
|
||||
offset_bottom = 281.0
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 20
|
||||
theme_override_constants/margin_right = 20
|
||||
@@ -43,24 +43,6 @@ placeholder_text = "Data directory"
|
||||
[node name="SelectDir" type="Button" parent="Margin/VBox/DirHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="UpgradeLabel" type="Label" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(400, 0)
|
||||
layout_mode = 2
|
||||
text = "
|
||||
If you wish to upgrade a storage file from v0.8.4 - v0.9.2, specify it below. Data will be stored in the directory above upon save. The original file will not be touched."
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="UpgradeHBox" type="HBoxContainer" parent="Margin/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Margin/VBox/UpgradeHBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Storage .res to upgrade"
|
||||
|
||||
[node name="SelectResFile" type="Button" parent="Margin/VBox/UpgradeHBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Spacer" type="Control" parent="Margin/VBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
@@ -1,9 +1,11 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Menu for Terrain3D
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
const DirectoryWizard: Script = preload("res://addons/terrain_3d/src/directory_setup.gd")
|
||||
const Packer: Script = preload("res://addons/terrain_3d/src/channel_packer.gd")
|
||||
const Baker: Script = preload("res://addons/terrain_3d/src/baker.gd")
|
||||
const DirectoryWizard: Script = preload("res://addons/terrain_3d/menu/directory_setup.gd")
|
||||
const Packer: Script = preload("res://addons/terrain_3d/menu/channel_packer.gd")
|
||||
const Baker: Script = preload("res://addons/terrain_3d/menu/baker.gd")
|
||||
|
||||
var plugin: EditorPlugin
|
||||
var menu_button: MenuButton = MenuButton.new()
|
||||
@@ -31,7 +33,7 @@ func _enter_tree() -> void:
|
||||
add_child(directory_setup)
|
||||
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("Pack Textures...", MENU_PACK_TEXTURES)
|
||||
menu_button.get_popup().add_separator("", MENU_SEPARATOR)
|
||||
1
addons/terrain_3d/menu/terrain_menu.gd.uid
Normal file
1
addons/terrain_3d/menu/terrain_menu.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3gxvahogxa10
|
||||
@@ -3,5 +3,5 @@
|
||||
name="Terrain3D"
|
||||
description="A high performance, editable terrain system for Godot 4."
|
||||
author="Cory Petkovsek & Roope Palmroos"
|
||||
version="0.9.3a"
|
||||
script="editor.gd"
|
||||
version="1.0.0"
|
||||
script="src/editor_plugin.gd"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Asset Dock for Terrain3D
|
||||
@tool
|
||||
extends PanelContainer
|
||||
#class_name Terrain3DAssetDock
|
||||
|
||||
signal confirmation_closed
|
||||
signal confirmation_confirmed
|
||||
@@ -444,9 +445,6 @@ class ListContainer extends Container:
|
||||
func _ready() -> void:
|
||||
set_v_size_flags(SIZE_EXPAND_FILL)
|
||||
set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
focus_style = get_theme_stylebox("focus", "Button").duplicate()
|
||||
focus_style.set_border_width_all(2)
|
||||
focus_style.set_border_color(Color(1, 1, 1, .67))
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
@@ -588,17 +586,13 @@ class ListContainer extends Container:
|
||||
last_offset = 3
|
||||
set_selected_id(clamp(selected_id, 0, entries.size() - last_offset))
|
||||
|
||||
# Update editor with selected brush
|
||||
plugin.ui._on_setting_changed()
|
||||
|
||||
|
||||
func get_selected_id() -> int:
|
||||
return selected_id
|
||||
|
||||
|
||||
|
||||
func set_entry_width(value: float) -> void:
|
||||
width = clamp(value, 56, 230)
|
||||
width = clamp(value, 66, 230)
|
||||
redraw()
|
||||
|
||||
|
||||
@@ -651,45 +645,86 @@ class ListEntry extends VBoxContainer:
|
||||
var is_selected: bool = false
|
||||
var asset_list: Terrain3DAssets
|
||||
|
||||
var button_clear: TextureButton
|
||||
var button_edit: TextureButton
|
||||
var name_label: Label
|
||||
|
||||
@onready var add_icon: Texture2D = get_theme_icon("Add", "EditorIcons")
|
||||
@onready var button_row := HBoxContainer.new()
|
||||
@onready var button_clear := TextureButton.new()
|
||||
@onready var button_edit := TextureButton.new()
|
||||
@onready var spacer := Control.new()
|
||||
@onready var button_enabled := TextureButton.new()
|
||||
@onready var clear_icon: Texture2D = get_theme_icon("Close", "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")
|
||||
var focus_style: StyleBox
|
||||
@onready var focus_style: StyleBox = get_theme_stylebox("focus", "Button").duplicate()
|
||||
|
||||
|
||||
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 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_clear.set_texture_normal(clear_icon)
|
||||
button_clear.set_custom_minimum_size(icon_size)
|
||||
button_clear.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_clear.set_visible(resource != null)
|
||||
button_clear.pressed.connect(clear)
|
||||
add_child(button_clear)
|
||||
button_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
button_row.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
margin_container.add_child(button_row)
|
||||
|
||||
if type == Terrain3DAssets.TYPE_MESH:
|
||||
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_custom_minimum_size(icon_size)
|
||||
button_edit.set_h_size_flags(Control.SIZE_SHRINK_END)
|
||||
button_edit.set_visible(resource != null)
|
||||
button_edit.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
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()
|
||||
add_child(name_label, true)
|
||||
name_label.visible = false
|
||||
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||
name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
name_label.add_theme_color_override("font_color", Color.WHITE)
|
||||
name_label.add_theme_color_override("font_shadow_color", Color.BLACK)
|
||||
name_label.add_theme_constant_override("shadow_offset_x", 1)
|
||||
name_label.add_theme_constant_override("shadow_offset_y", 1)
|
||||
name_label.add_theme_constant_override("shadow_offset_x", 1.)
|
||||
name_label.add_theme_constant_override("shadow_offset_y", 1.)
|
||||
name_label.add_theme_font_size_override("font_size", 15)
|
||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
@@ -702,6 +737,9 @@ class ListEntry extends VBoxContainer:
|
||||
func _notification(p_what) -> void:
|
||||
match p_what:
|
||||
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())
|
||||
if !resource:
|
||||
draw_style_box(background, rect)
|
||||
@@ -723,6 +761,7 @@ class ListEntry extends VBoxContainer:
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
||||
else:
|
||||
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)
|
||||
if drop_data:
|
||||
draw_style_box(focus_style, rect)
|
||||
@@ -791,8 +830,8 @@ class ListEntry extends VBoxContainer:
|
||||
var ma := Terrain3DMeshAsset.new()
|
||||
if resource is Terrain3DMeshAsset:
|
||||
ma.id = resource.id
|
||||
ma.set_scene_file(res)
|
||||
set_edited_resource(ma, false)
|
||||
ma.set_scene_file(res)
|
||||
resource = ma
|
||||
elif res is Terrain3DMeshAsset and type == Terrain3DAssets.TYPE_MESH:
|
||||
if resource is Terrain3DMeshAsset:
|
||||
@@ -808,6 +847,8 @@ class ListEntry extends VBoxContainer:
|
||||
if resource:
|
||||
resource.setting_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:
|
||||
button_clear.set_visible(resource != null)
|
||||
@@ -818,6 +859,7 @@ class ListEntry extends VBoxContainer:
|
||||
|
||||
|
||||
func _on_resource_changed() -> void:
|
||||
queue_redraw()
|
||||
emit_signal("changed", resource)
|
||||
|
||||
|
||||
@@ -834,3 +876,8 @@ class ListEntry extends VBoxContainer:
|
||||
func edit() -> void:
|
||||
emit_signal("selected")
|
||||
emit_signal("inspected", resource)
|
||||
|
||||
|
||||
func enable() -> void:
|
||||
if resource is Terrain3DMeshAsset:
|
||||
resource.set_enabled(!resource.is_enabled())
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bcfr30tpffi5o
|
||||
uid://bgoifepft1hjw
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[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"]
|
||||
custom_minimum_size = Vector2(256, 95)
|
||||
@@ -40,10 +40,9 @@ custom_minimum_size = Vector2(80, 30)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
item_count = 9
|
||||
selected = 7
|
||||
item_count = 9
|
||||
popup/item_0/text = "Left_UL"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Left_BL"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Left_UR"
|
||||
@@ -65,7 +64,7 @@ popup/item_8/id = 8
|
||||
custom_minimum_size = Vector2(80, 10)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 56.0
|
||||
min_value = 66.0
|
||||
max_value = 230.0
|
||||
value = 83.0
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://dft2g3p2pjw12
|
||||
@@ -1 +0,0 @@
|
||||
uid://byi465tyumga0
|
||||
@@ -1 +0,0 @@
|
||||
uid://y7pm2ndr5k2m
|
||||
@@ -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"]
|
||||
@@ -1 +0,0 @@
|
||||
uid://r0eex2idvm56
|
||||
@@ -1 +0,0 @@
|
||||
uid://dt80i8xsj8tun
|
||||
@@ -1,3 +1,6 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# DoubleSlider for Terrain3D
|
||||
# Should work for other UIs
|
||||
@tool
|
||||
class_name DoubleSlider
|
||||
extends Control
|
||||
@@ -10,9 +13,23 @@ var min_value: float = 0.0
|
||||
var max_value: float = 100.0
|
||||
var step: float = 1.0
|
||||
var range := Vector2(0, 100)
|
||||
var display_scale: float = 1.
|
||||
var position_x: float = 0.
|
||||
var minimum_x: float = 60.
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -68,11 +85,21 @@ func get_value() -> Vector2:
|
||||
func update_label() -> void:
|
||||
if label:
|
||||
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:
|
||||
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():
|
||||
var mid_point = (range.x + range.y) / 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
|
||||
else:
|
||||
grabbed_handle = -1
|
||||
set_slider(p_event.get_position().x)
|
||||
match button:
|
||||
MOUSE_BUTTON_LEFT:
|
||||
set_slider(p_event.get_position().x)
|
||||
MOUSE_BUTTON_WHEEL_DOWN:
|
||||
set_slider(-1., true)
|
||||
MOUSE_BUTTON_WHEEL_UP:
|
||||
set_slider(1., true)
|
||||
else:
|
||||
grabbed_handle = 0
|
||||
|
||||
@@ -89,14 +122,20 @@ func _gui_input(p_event: InputEvent) -> void:
|
||||
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:
|
||||
return
|
||||
var xpos_step: float = clamp(snappedf((p_xpos / size.x) * max_value, step), min_value, max_value)
|
||||
if(grabbed_handle < 0):
|
||||
range.x = xpos_step
|
||||
if p_relative:
|
||||
range.x += p_xpos
|
||||
else:
|
||||
range.x = xpos_step
|
||||
else:
|
||||
range.y = xpos_step
|
||||
if p_relative:
|
||||
range.y += p_xpos
|
||||
else:
|
||||
range.y = xpos_step
|
||||
set_value(range)
|
||||
|
||||
|
||||
@@ -111,14 +150,15 @@ func _notification(p_what: int) -> void:
|
||||
# Draw foreground bar
|
||||
var handle: Texture2D = get_theme_icon("grabber", "HSlider")
|
||||
var area: StyleBox = get_theme_stylebox("grabber_area", "HSlider")
|
||||
var h: float = size.y / 2 - handle.get_size().y / 2
|
||||
var startx: float = (range.x / max_value) * size.x
|
||||
var endx: float = (range.y / max_value) * size.x #- startx
|
||||
var endx: float = (range.y / max_value) * size.x
|
||||
draw_style_box(area, Rect2(Vector2(startx, mid_y), Vector2(endx - startx, bg_height)))
|
||||
|
||||
# Draw handles, slightly in so they don't get on the outside edges
|
||||
var handle_pos: Vector2
|
||||
handle_pos.x = clamp(startx - handle.get_size().x/2, -5, size.x)
|
||||
handle_pos.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)
|
||||
draw_texture(handle, Vector2(handle_pos.x, -mid_y))
|
||||
draw_texture(handle, Vector2(handle_pos.y, -mid_y))
|
||||
draw_texture(handle, Vector2(handle_pos.x, -mid_y - 10 * (display_scale - 1.)))
|
||||
draw_texture(handle, Vector2(handle_pos.y, -mid_y - 10 * (display_scale - 1.)))
|
||||
|
||||
update_label()
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://cd6164a8pp48f
|
||||
uid://stro0p1oawfb
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Editor Plugin for Terrain3D
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
#class_name Terrain3DEditorPlugin Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# Includes
|
||||
@@ -13,6 +14,7 @@ var modifier_alt: bool
|
||||
var modifier_shift: bool
|
||||
var _last_modifiers: int = 0
|
||||
var _input_mode: int = 0 # -1: camera move, 0: none, 1: operating
|
||||
var _use_meta: bool = false
|
||||
|
||||
var terrain: Terrain3D
|
||||
var _last_terrain: Terrain3D
|
||||
@@ -29,6 +31,9 @@ var godot_editor_window: Window # The Godot Editor window
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
if OS.get_name() == "macOS":
|
||||
_use_meta = true
|
||||
|
||||
# Get the Godot Editor window. Structure is root:Window/EditorNode/Base Control
|
||||
godot_editor_window = EditorInterface.get_base_control().get_parent().get_parent()
|
||||
godot_editor_window.focus_entered.connect(_on_godot_focus_entered)
|
||||
@@ -113,10 +118,6 @@ func _edit(p_object: Object) -> void:
|
||||
ui.set_visible(true)
|
||||
terrain.set_meta("_edit_lock_", true)
|
||||
|
||||
# Deprecated 0.9.3 - Remove 1.0
|
||||
if terrain.storage:
|
||||
ui.terrain_menu.directory_setup.directory_setup_popup()
|
||||
|
||||
# Get alerted when a new asset list is loaded
|
||||
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
|
||||
terrain.assets_changed.connect(asset_dock.update_assets)
|
||||
@@ -153,40 +154,43 @@ func _clear() -> void:
|
||||
func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> int:
|
||||
if not is_terrain_valid():
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
|
||||
_read_input(p_event)
|
||||
ui.update_decal()
|
||||
|
||||
## 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
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
# Detect if viewport is set to half_resolution
|
||||
# Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D
|
||||
var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
|
||||
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
|
||||
|
||||
## Get mouse location on terrain
|
||||
# Project 2D mouse position to 3D position and direction
|
||||
var 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_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
|
||||
|
||||
# If region tool, grab mouse position without considering height
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
|
||||
mouse_global_position = (camera_pos + t * camera_dir)
|
||||
else:
|
||||
#Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir, true)
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
## Handle mouse movement
|
||||
if p_event is InputEventMouseMotion:
|
||||
## Setup active camera & viewport
|
||||
|
||||
# Snap terrain to current camera
|
||||
terrain.set_camera(p_viewport_camera)
|
||||
|
||||
# Detect if viewport is set to half_resolution
|
||||
# Structure is: Node3DEditorViewportContainer/Node3DEditorViewport(4)/SubViewportContainer/SubViewport/Camera3D
|
||||
var editor_vpc: SubViewportContainer = p_viewport_camera.get_parent().get_parent()
|
||||
var full_resolution: bool = false if editor_vpc.stretch_shrink == 2 else true
|
||||
|
||||
## Get mouse location on terrain
|
||||
|
||||
# Project 2D mouse position to 3D position and direction
|
||||
var mouse_pos: Vector2 = p_event.position if full_resolution else p_event.position/2
|
||||
var camera_pos: Vector3 = p_viewport_camera.project_ray_origin(mouse_pos)
|
||||
var camera_dir: Vector3 = p_viewport_camera.project_ray_normal(mouse_pos)
|
||||
|
||||
# If region tool, grab mouse position without considering height
|
||||
if editor.get_tool() == Terrain3DEditor.REGION:
|
||||
var t = -Vector3(0, 1, 0).dot(camera_pos) / Vector3(0, 1, 0).dot(camera_dir)
|
||||
mouse_global_position = (camera_pos + t * camera_dir)
|
||||
else:
|
||||
# Else look for intersection with terrain
|
||||
var intersection_point: Vector3 = terrain.get_intersection(camera_pos, camera_dir)
|
||||
if intersection_point.z > 3.4e38 or is_nan(intersection_point.y): # max double or nan
|
||||
return AFTER_GUI_INPUT_STOP
|
||||
mouse_global_position = intersection_point
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if _input_mode != -1: # Not cam rotation
|
||||
## Update region highlight
|
||||
@@ -205,8 +209,6 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
|
||||
|
||||
return AFTER_GUI_INPUT_PASS
|
||||
|
||||
ui.update_decal()
|
||||
|
||||
if p_event is InputEventMouseButton and _input_mode > 0:
|
||||
if p_event.is_pressed():
|
||||
# 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():
|
||||
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 editor.get_tool() == Terrain3DEditor.REGION:
|
||||
# Skip regions that already exist or don't
|
||||
@@ -273,7 +280,7 @@ func _read_input(p_event: InputEvent = null) -> void:
|
||||
|
||||
## Determine modifiers pressed
|
||||
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
|
||||
var alt_key: int
|
||||
match get_setting("terrain3d/config/alt_key_bind", 0):
|
||||
@@ -369,29 +376,6 @@ func setup_editor_settings() -> void:
|
||||
"hint_string": "Alt,Space,Meta,Capslock"
|
||||
}
|
||||
editor_settings.add_property_info(property_info)
|
||||
|
||||
_cleanup_old_settings()
|
||||
|
||||
|
||||
# Remove or rename old settings
|
||||
func _cleanup_old_settings() -> void:
|
||||
# Rename deprecated settings - Remove in 1.0
|
||||
var value: Variant
|
||||
var rename_arr := [ "terrain3d/config/dock_slot", "terrain3d/config/dock_tile_size",
|
||||
"terrain3d/config/dock_floating", "terrain3d/config/dock_always_on_top",
|
||||
"terrain3d/config/dock_window_size", "terrain3d/config/dock_window_position", ]
|
||||
for es: String in rename_arr:
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
editor_settings.erase(es)
|
||||
editor_settings.set_setting(es.replace("/config/dock_", "/dock/"), value)
|
||||
|
||||
# Special handling
|
||||
var es: String = "terrain3d/tool_settings/slope"
|
||||
if editor_settings.has_setting(es):
|
||||
value = editor_settings.get_setting(es)
|
||||
if typeof(value) == TYPE_FLOAT:
|
||||
editor_settings.erase(es)
|
||||
|
||||
|
||||
func set_setting(p_str: String, p_value: Variant) -> void:
|
||||
1
addons/terrain_3d/src/editor_plugin.gd.uid
Normal file
1
addons/terrain_3d/src/editor_plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bsgxo1qywjdf3
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -52,4 +54,3 @@ func apply_operation(p_editor: Terrain3DEditor, p_global_position: Vector3, p_ca
|
||||
p_editor.stop_operation()
|
||||
|
||||
_get_point_picker().clear()
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://b1qt62vc3vnx7
|
||||
uid://def7sych6dp8b
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Multipicker for Terrain3D
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://dqwxbfvjra3p3
|
||||
uid://dvdtoa32h6xdn
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Operation Builder for Terrain3D
|
||||
extends RefCounted
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://3rphgkvw2bi1
|
||||
uid://bu5cm0eh052rm
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Editor Region Gizmos for Terrain3D
|
||||
extends EditorNode3DGizmo
|
||||
|
||||
var material: StandardMaterial3D
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://ddnxpafte8kb7
|
||||
uid://bh6qwe1ok4cx3
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://cbtr86lrjls7a
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Tool settings bar for Terrain3D
|
||||
extends PanelContainer
|
||||
|
||||
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 brush_preview_material: ShaderMaterial
|
||||
var select_brush_button: Button
|
||||
var selected_brush_imgs: Array
|
||||
var main_list: HFlowContainer
|
||||
var advanced_list: VBoxContainer
|
||||
var height_list: VBoxContainer
|
||||
@@ -86,6 +89,9 @@ func _ready() -> void:
|
||||
add_setting({ "name":"enable_texture", "label":"Texture", "type":SettingType.CHECKBOX,
|
||||
"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,
|
||||
"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) })
|
||||
add_setting({ "name":"jitter", "type":SettingType.SLIDER, "list":advanced_list, "default":50,
|
||||
"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:
|
||||
@@ -267,29 +275,33 @@ func add_brushes(p_parent: Control) -> void:
|
||||
while file_name != "":
|
||||
if !dir.current_is_dir() and file_name.ends_with(".exr"):
|
||||
var img: Image = Image.load_from_file(BRUSH_PATH + "/" + file_name)
|
||||
img = Terrain3DUtil.black_to_alpha(img)
|
||||
if img.get_width() < 1024 and img.get_height() < 1024:
|
||||
img.resize(1024, 1024, Image.INTERPOLATE_CUBIC)
|
||||
var tex: ImageTexture = ImageTexture.create_from_image(img)
|
||||
var thumbimg: Image = img.duplicate()
|
||||
img.convert(Image.FORMAT_RF)
|
||||
|
||||
var btn: Button = Button.new()
|
||||
btn.set_custom_minimum_size(Vector2.ONE * 100)
|
||||
btn.set_button_icon(tex)
|
||||
btn.set_meta("image", img)
|
||||
btn.set_expand_icon(true)
|
||||
btn.set_material(_get_brush_preview_material())
|
||||
btn.set_toggle_mode(true)
|
||||
btn.set_button_group(brush_button_group)
|
||||
btn.mouse_entered.connect(_on_brush_hover.bind(true, btn))
|
||||
btn.mouse_exited.connect(_on_brush_hover.bind(false, btn))
|
||||
brush_list.add_child(btn, true)
|
||||
if thumbimg.get_width() != 100 and thumbimg.get_height() != 100:
|
||||
thumbimg.resize(100, 100, Image.INTERPOLATE_CUBIC)
|
||||
thumbimg = Terrain3DUtil.black_to_alpha(thumbimg)
|
||||
thumbimg.convert(Image.FORMAT_LA8)
|
||||
var thumbtex: ImageTexture = ImageTexture.create_from_image(thumbimg)
|
||||
|
||||
var brush_btn: Button = Button.new()
|
||||
brush_btn.set_custom_minimum_size(Vector2.ONE * 100)
|
||||
brush_btn.set_button_icon(thumbtex)
|
||||
brush_btn.set_meta("image", img)
|
||||
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:
|
||||
default_brush_btn = btn
|
||||
default_brush_btn = brush_btn
|
||||
|
||||
var lbl: Label = Label.new()
|
||||
btn.name = file_name.get_basename().to_pascal_case()
|
||||
btn.add_child(lbl, true)
|
||||
lbl.text = btn.name
|
||||
brush_btn.name = file_name.get_basename().to_pascal_case()
|
||||
brush_btn.add_child(lbl, true)
|
||||
lbl.text = brush_btn.name
|
||||
lbl.visible = false
|
||||
lbl.position.y = 70
|
||||
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:
|
||||
default_brush_btn = brush_button_group.get_buttons()[0]
|
||||
default_brush_btn.set_pressed(true)
|
||||
_generate_brush_texture(default_brush_btn)
|
||||
|
||||
settings["brush"] = brush_button_group
|
||||
|
||||
@@ -443,7 +456,7 @@ func add_setting(p_args: Dictionary) -> void:
|
||||
pending_children.push_back(option)
|
||||
control = option
|
||||
|
||||
SettingType.SLIDER, SettingType.DOUBLE_SLIDER:
|
||||
SettingType.SLIDER, SettingType.DOUBLE_SLIDER:
|
||||
var slider: Control
|
||||
if p_type == SettingType.SLIDER:
|
||||
# Create an editable value box
|
||||
@@ -473,7 +486,7 @@ func add_setting(p_args: Dictionary) -> void:
|
||||
else: # DOUBLE_SLIDER
|
||||
var label := Label.new()
|
||||
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.label = label
|
||||
slider.suffix = p_suffix
|
||||
@@ -560,9 +573,7 @@ func get_setting(p_setting: String) -> Variant:
|
||||
elif object is DoubleSlider:
|
||||
value = object.get_value()
|
||||
elif object is ButtonGroup: # "brush"
|
||||
var img: Image = object.get_pressed_button().get_meta("image")
|
||||
var tex: Texture2D = object.get_pressed_button().get_button_icon()
|
||||
value = [ img, tex ]
|
||||
value = selected_brush_imgs
|
||||
elif object is CheckBox:
|
||||
value = object.is_pressed()
|
||||
elif object is ColorPickerButton:
|
||||
@@ -610,19 +621,30 @@ func show_settings(p_settings: PackedStringArray) -> void:
|
||||
select_brush_button.show()
|
||||
|
||||
|
||||
func _on_setting_changed(p_data: Variant = null) -> void:
|
||||
# If a button was clicked on a submenu
|
||||
if p_data is Button and p_data.get_parent().get_parent() is PopupPanel:
|
||||
if p_data.get_parent().name == "BrushList":
|
||||
# Optionally Set selected brush texture in main brush button
|
||||
p_data.get_parent().get_parent().get_parent().set_button_icon(p_data.get_button_icon())
|
||||
# Hide popup
|
||||
p_data.get_parent().get_parent().set_visible(false)
|
||||
# Hide label
|
||||
if p_data.get_child_count() > 0:
|
||||
p_data.get_child(0).visible = false
|
||||
func _on_setting_changed(p_object: Variant = null) -> void:
|
||||
# If a brush was selected
|
||||
if p_object is Button and p_object.get_parent().name == "BrushList":
|
||||
_generate_brush_texture(p_object)
|
||||
# Optionally Set selected brush texture in main brush button
|
||||
if select_brush_button:
|
||||
select_brush_button.set_button_icon(p_object.get_button_icon())
|
||||
# Hide popup
|
||||
p_object.get_parent().get_parent().set_visible(false)
|
||||
# Hide label
|
||||
if p_object.get_child_count() > 0:
|
||||
p_object.get_child(0).visible = false
|
||||
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:
|
||||
if not p_button_pressed:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://b0hs7rbtc8jfy
|
||||
uid://ciskaaennrffu
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Toolbar for Terrain3D
|
||||
extends VFlowContainer
|
||||
|
||||
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))
|
||||
|
||||
func _ready() -> void:
|
||||
add_tool_group.connect("pressed", _on_tool_selected)
|
||||
sub_tool_group.connect("pressed", _on_tool_selected)
|
||||
add_tool_group.pressed.connect(_on_tool_selected)
|
||||
sub_tool_group.pressed.connect(_on_tool_selected)
|
||||
|
||||
add_tool_button({ "tool":Terrain3DEditor.REGION,
|
||||
"add_text":"Add Region", "add_op":Terrain3DEditor.ADD, "add_icon":ICON_REGION_ADD,
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bthdcp8t4awsk
|
||||
uid://b1j37u6utjbom
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# UI for Terrain3D
|
||||
extends Node
|
||||
#class_name Terrain3DUI Cannot be named until Godot #75388
|
||||
|
||||
|
||||
# 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 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 GradientOperationBuilder: Script = preload("res://addons/terrain_3d/src/gradient_operation_builder.gd")
|
||||
const COLOR_RAISE := Color.WHITE
|
||||
const COLOR_LOWER := Color(.02, .02, .02)
|
||||
const COLOR_SMOOTH := Color(0.5, 0, .1)
|
||||
const COLOR_LOWER := Color.BLACK
|
||||
const COLOR_SMOOTH := Color(0.5, 0, .2)
|
||||
const COLOR_LIFT := Color.ORANGE
|
||||
const COLOR_FLATTEN := Color.BLUE_VIOLET
|
||||
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_AUTOSHADER := Color.DODGER_BLUE
|
||||
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_PICK_COLOR := Color.WHITE
|
||||
const COLOR_PICK_HEIGHT := Color.DARK_RED
|
||||
@@ -31,8 +32,13 @@ const OP_POSITIVE_ONLY: int = 0x01
|
||||
const OP_NEGATIVE_ONLY: int = 0x02
|
||||
|
||||
const RING1: String = "res://addons/terrain_3d/brushes/ring1.exr"
|
||||
@onready var ring_texture := ImageTexture.create_from_image(Terrain3DUtil.black_to_alpha(Image.load_from_file(RING1)))
|
||||
|
||||
var 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 toolbar: Toolbar
|
||||
var tool_settings: ToolSettings
|
||||
@@ -41,31 +47,42 @@ var setting_has_changed: bool = false
|
||||
var visible: bool = false
|
||||
var picking: int = Terrain3DEditor.TOOL_MAX
|
||||
var picking_callback: Callable
|
||||
var decal: Decal
|
||||
var decal_timer: Timer
|
||||
var gradient_decals: Array[Decal]
|
||||
var brush_data: Dictionary
|
||||
var operation_builder: OperationBuilder
|
||||
var last_tool: Terrain3DEditor.Tool
|
||||
var last_operation: Terrain3DEditor.Operation
|
||||
var last_rmb_time: int = 0 # Set in editor.gd
|
||||
|
||||
# Compatibility decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
|
||||
var editor_decal_position: Array[Vector2]
|
||||
var editor_decal_rotation: Array[float]
|
||||
var editor_decal_size: Array[float]
|
||||
var editor_decal_color: Array[Color]
|
||||
var editor_decal_visible: Array[bool]
|
||||
# Editor decals, indices; 0 = main brush, 1 = slope point A, 2 = slope point B
|
||||
var mat_rid: RID
|
||||
var editor_decal_position: Array[Vector2] = [Vector2(), Vector2(), Vector2()]
|
||||
var editor_decal_rotation: Array[float] = [float(), float(), float()]
|
||||
var editor_decal_size: Array[float] = [float(), float(), float()]
|
||||
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:
|
||||
toolbar = Toolbar.new()
|
||||
toolbar.hide()
|
||||
toolbar.connect("tool_changed", _on_tool_changed)
|
||||
toolbar.tool_changed.connect(_on_tool_changed)
|
||||
|
||||
tool_settings = ToolSettings.new()
|
||||
tool_settings.connect("setting_changed", _on_setting_changed)
|
||||
tool_settings.connect("picking", _on_picking)
|
||||
tool_settings.setting_changed.connect(_on_setting_changed)
|
||||
tool_settings.picking.connect(_on_picking)
|
||||
tool_settings.plugin = plugin
|
||||
tool_settings.hide()
|
||||
|
||||
@@ -79,15 +96,19 @@ func _enter_tree() -> void:
|
||||
|
||||
_on_tool_changed(Terrain3DEditor.REGION, Terrain3DEditor.ADD)
|
||||
|
||||
decal = Decal.new()
|
||||
add_child(decal)
|
||||
decal_timer = Timer.new()
|
||||
decal_timer.wait_time = .5
|
||||
decal_timer.one_shot = true
|
||||
decal_timer.timeout.connect(Callable(func(node):
|
||||
if node:
|
||||
get_tree().create_tween().tween_property(node, "albedo_mix", 0.0, 0.15)).bind(decal))
|
||||
add_child(decal_timer)
|
||||
editor_decal_timer = Timer.new()
|
||||
editor_decal_timer.wait_time = .5
|
||||
editor_decal_timer.one_shot = true
|
||||
editor_decal_timer.timeout.connect(func():
|
||||
get_tree().create_tween().tween_property(self, "editor_decal_fade", 0.0, 0.15))
|
||||
add_child(editor_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:
|
||||
@@ -96,11 +117,7 @@ func _exit_tree() -> void:
|
||||
toolbar.queue_free()
|
||||
tool_settings.queue_free()
|
||||
terrain_menu.queue_free()
|
||||
decal.queue_free()
|
||||
decal_timer.queue_free()
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.queue_free()
|
||||
gradient_decals.clear()
|
||||
editor_decal_timer.queue_free()
|
||||
|
||||
|
||||
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(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)
|
||||
else:
|
||||
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_picker")
|
||||
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("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_picker")
|
||||
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("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("gamma")
|
||||
to_show.push_back("jitter")
|
||||
to_show.push_back("crosshair_threshold")
|
||||
tool_settings.show_settings(to_show)
|
||||
|
||||
operation_builder = null
|
||||
@@ -258,9 +276,9 @@ func _on_setting_changed() -> void:
|
||||
return
|
||||
brush_data = tool_settings.get_settings()
|
||||
brush_data["asset_id"] = plugin.asset_dock.get_current_list().get_selected_id()
|
||||
update_decal()
|
||||
plugin.editor.set_brush_data(brush_data)
|
||||
plugin.editor.set_operation(_modify_operation(plugin.editor.get_operation()))
|
||||
update_decal()
|
||||
|
||||
|
||||
func update_modifiers() -> void:
|
||||
@@ -299,195 +317,211 @@ func _invert_operation(p_operation: Terrain3DEditor.Operation, flags: int = OP_N
|
||||
|
||||
|
||||
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 visible or \
|
||||
not plugin.terrain or \
|
||||
plugin._input_mode < 0 or \
|
||||
# Wait for cursor to recenter after moving camera before revealing
|
||||
# See https://github.com/godotengine/godot/issues/70098
|
||||
Time.get_ticks_msec() - last_rmb_time <= 30 or \
|
||||
brush_data.is_empty() or \
|
||||
plugin.editor.get_tool() == Terrain3DEditor.REGION or \
|
||||
(plugin._input_mode > 0 and not brush_data["show_cursor_while_painting"]):
|
||||
decal.visible = false
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
hide_decal()
|
||||
return
|
||||
|
||||
decal.position = plugin.mouse_global_position
|
||||
decal.visible = true
|
||||
decal.size = Vector3.ONE * maxf(brush_data["size"], .5)
|
||||
if brush_data["align_to_view"]:
|
||||
var cam: Camera3D = plugin.terrain.get_camera();
|
||||
if (cam):
|
||||
decal.rotation.y = cam.rotation.y
|
||||
else:
|
||||
decal.rotation.y = 0
|
||||
|
||||
|
||||
reset_decal_arrays()
|
||||
editor_decal_position[0] = Vector2(plugin.mouse_global_position.x, plugin.mouse_global_position.z)
|
||||
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
|
||||
if picking != Terrain3DEditor.TOOL_MAX:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.size = Vector3.ONE * 10. * plugin.terrain.get_vertex_spacing()
|
||||
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:
|
||||
decal.modulate = COLOR_PICK_HEIGHT
|
||||
editor_decal_color[0] = COLOR_PICK_HEIGHT
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = COLOR_PICK_COLOR
|
||||
editor_decal_color[0] = COLOR_PICK_COLOR
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_PICK_ROUGH
|
||||
decal.modulate.a = 1.0
|
||||
editor_decal_color[0] = COLOR_PICK_ROUGH
|
||||
editor_decal_color[0].a = 1.0
|
||||
else:
|
||||
decal.texture_albedo = brush_data["brush"][1]
|
||||
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"]:
|
||||
var cam: Camera3D = plugin.terrain.get_camera();
|
||||
if (cam):
|
||||
editor_decal_rotation[0] = cam.rotation.y
|
||||
else:
|
||||
editor_decal_rotation[0] = 0.
|
||||
match plugin.editor.get_tool():
|
||||
Terrain3DEditor.SCULPT:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.ADD:
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_LIFT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_LIFT
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5)
|
||||
else:
|
||||
decal.modulate = COLOR_RAISE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_RAISE
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5)
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
if plugin.modifier_alt:
|
||||
decal.modulate = COLOR_FLATTEN
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_FLATTEN
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .25, .5) + .1
|
||||
else:
|
||||
decal.modulate = COLOR_LOWER
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .5
|
||||
editor_decal_color[0] = COLOR_LOWER
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
|
||||
Terrain3DEditor.AVERAGE:
|
||||
decal.modulate = COLOR_SMOOTH
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5) + .2
|
||||
editor_decal_color[0] = COLOR_SMOOTH
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
|
||||
Terrain3DEditor.GRADIENT:
|
||||
decal.modulate = COLOR_SLOPE
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_SLOPE
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .4)
|
||||
Terrain3DEditor.HEIGHT:
|
||||
decal.modulate = COLOR_HEIGHT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_HEIGHT
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .25
|
||||
Terrain3DEditor.TEXTURE:
|
||||
match plugin.editor.get_operation():
|
||||
Terrain3DEditor.REPLACE:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = .7
|
||||
editor_decal_color[0] = COLOR_PAINT
|
||||
editor_decal_color[0].a = .6
|
||||
Terrain3DEditor.SUBTRACT:
|
||||
decal.modulate = COLOR_PAINT
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_PAINT
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
|
||||
Terrain3DEditor.ADD:
|
||||
decal.modulate = COLOR_SPRAY
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_SPRAY
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .15, .4)
|
||||
Terrain3DEditor.COLOR:
|
||||
decal.modulate = brush_data["color"].srgb_to_linear()
|
||||
decal.modulate.a *= clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = brush_data["color"].srgb_to_linear()
|
||||
editor_decal_color[0].a *= clamp(brush_data["strength"], .2, .5)
|
||||
Terrain3DEditor.ROUGHNESS:
|
||||
decal.modulate = COLOR_ROUGHNESS
|
||||
decal.modulate.a = clamp(brush_data["strength"], .2, .5)
|
||||
editor_decal_color[0] = COLOR_ROUGHNESS
|
||||
editor_decal_color[0].a = clamp(brush_data["strength"], .2, .5) + .1
|
||||
Terrain3DEditor.AUTOSHADER:
|
||||
decal.modulate = COLOR_AUTOSHADER
|
||||
decal.modulate.a = .7
|
||||
editor_decal_color[0] = COLOR_AUTOSHADER
|
||||
editor_decal_color[0].a = .6
|
||||
Terrain3DEditor.HOLES:
|
||||
decal.modulate = COLOR_HOLES
|
||||
decal.modulate.a = .85
|
||||
editor_decal_color[0] = COLOR_HOLES
|
||||
editor_decal_color[0].a = .75
|
||||
Terrain3DEditor.NAVIGATION:
|
||||
decal.modulate = COLOR_NAVIGATION
|
||||
decal.modulate.a = .85
|
||||
editor_decal_color[0] = COLOR_NAVIGATION
|
||||
editor_decal_color[0].a = .80
|
||||
Terrain3DEditor.INSTANCER:
|
||||
decal.texture_albedo = ring_texture
|
||||
decal.modulate = COLOR_INSTANCER
|
||||
decal.modulate.a = 1.0
|
||||
decal.size.y = max(1000, decal.size.y)
|
||||
decal.albedo_mix = 1.0
|
||||
decal.cull_mask = 1 << ( plugin.terrain.get_mouse_layer() - 1 )
|
||||
decal_timer.start()
|
||||
|
||||
for gradient_decal in gradient_decals:
|
||||
gradient_decal.visible = false
|
||||
editor_brush_texture_rid = ring_texture.get_rid()
|
||||
editor_decal_color[0] = COLOR_INSTANCER
|
||||
editor_decal_color[0].a = .75
|
||||
|
||||
editor_decal_visible[1] = false
|
||||
editor_decal_visible[2] = false
|
||||
|
||||
if plugin.editor.get_operation() == Terrain3DEditor.GRADIENT:
|
||||
var index := 0
|
||||
for point in brush_data["gradient_points"]:
|
||||
if point != Vector3.ZERO:
|
||||
var point_decal: Decal = _get_gradient_decal(index)
|
||||
point_decal.visible = true
|
||||
point_decal.position = point
|
||||
index += 1
|
||||
|
||||
update_compatibility_decal()
|
||||
|
||||
|
||||
func _get_gradient_decal(index: int) -> Decal:
|
||||
if gradient_decals.size() > index:
|
||||
return gradient_decals[index]
|
||||
var point1: Vector3 = brush_data["gradient_points"][0]
|
||||
if point1 != Vector3.ZERO:
|
||||
editor_decal_color[1] = COLOR_SLOPE
|
||||
editor_decal_size[1] = 10. * plugin.terrain.get_vertex_spacing()
|
||||
editor_decal_visible[1] = true
|
||||
editor_decal_position[1] = Vector2(point1.x, point1.z)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
gradient_decals.push_back(gradient_decal)
|
||||
return gradient_decal
|
||||
editor_decal_fade = editor_decal_color[0].a
|
||||
# Update Shader params
|
||||
if is_shader_valid():
|
||||
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)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
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 update_compatibility_decal() -> void:
|
||||
if not plugin.terrain.is_compatibility_mode():
|
||||
return
|
||||
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
|
||||
|
||||
# Verify setup
|
||||
if editor_decal_position.size() != 3:
|
||||
editor_decal_position.resize(3)
|
||||
editor_decal_rotation.resize(3)
|
||||
editor_decal_size.resize(3)
|
||||
editor_decal_color.resize(3)
|
||||
editor_decal_visible.resize(3)
|
||||
decal_timer.timeout.connect(func():
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
editor_decal_visible[0] = false
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
)
|
||||
|
||||
# Update compatibility decal
|
||||
var mat_rid: RID = plugin.terrain.material.get_material_rid()
|
||||
if decal.visible:
|
||||
editor_decal_position[0] = Vector2(decal.global_position.x, decal.global_position.z)
|
||||
editor_decal_rotation[0] = decal.rotation.y
|
||||
editor_decal_size[0] = brush_data.get("size")
|
||||
editor_decal_color[0] = decal.modulate
|
||||
editor_decal_visible[0] = decal.visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_0", decal.texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 1:
|
||||
editor_decal_position[1] = Vector2(gradient_decals[0].global_position.x,
|
||||
gradient_decals[0].global_position.z)
|
||||
editor_decal_rotation[1] = gradient_decals[0].rotation.y
|
||||
editor_decal_size[1] = 10.0
|
||||
editor_decal_color[1] = gradient_decals[0].modulate
|
||||
editor_decal_visible[1] = gradient_decals[0].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_1", gradient_decals[0].texture_albedo.get_rid()
|
||||
)
|
||||
if gradient_decals.size() >= 2:
|
||||
editor_decal_position[2] = Vector2(gradient_decals[1].global_position.x,
|
||||
gradient_decals[1].global_position.z)
|
||||
editor_decal_rotation[2] = gradient_decals[1].rotation.y
|
||||
editor_decal_size[2] = 10.0
|
||||
editor_decal_color[2] = gradient_decals[1].modulate
|
||||
editor_decal_visible[2] = gradient_decals[1].visible
|
||||
RenderingServer.material_set_param(
|
||||
mat_rid, "_editor_decal_2", gradient_decals[1].texture_albedo.get_rid()
|
||||
)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_position", editor_decal_position)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_rotation", editor_decal_rotation)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_size", editor_decal_size)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_color", editor_decal_color)
|
||||
RenderingServer.material_set_param(mat_rid, "_editor_decal_visible", editor_decal_visible)
|
||||
func 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:
|
||||
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:
|
||||
@@ -515,7 +549,7 @@ func pick(p_global_position: Vector3) -> void:
|
||||
var color: Color
|
||||
match picking:
|
||||
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:
|
||||
color = plugin.terrain.data.get_pixel(Terrain3DRegion.TYPE_COLOR, p_global_position)
|
||||
Terrain3DEditor.COLOR:
|
||||
@@ -527,7 +561,9 @@ func pick(p_global_position: Vector3) -> void:
|
||||
_:
|
||||
push_error("Unsupported picking type: ", picking)
|
||||
return
|
||||
picking_callback.call(picking, color, p_global_position)
|
||||
if picking_callback.is_valid():
|
||||
picking_callback.call(picking, color, p_global_position)
|
||||
picking_callback = Callable()
|
||||
picking = Terrain3DEditor.TOOL_MAX
|
||||
|
||||
elif operation_builder and operation_builder.is_picking():
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bv4lj2cvubl7p
|
||||
uid://bpad72s36mwkx
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[configuration]
|
||||
|
||||
entry_symbol = "terrain_3d_init"
|
||||
compatibility_minimum = 4.2
|
||||
compatibility_minimum = 4.4
|
||||
|
||||
[icons]
|
||||
|
||||
@@ -26,4 +26,7 @@ android.debug.arm64 = "res://addons/terrain_3d/bin/libterrain.android.debug.arm6
|
||||
android.release.arm64 = "res://addons/terrain_3d/bin/libterrain.android.release.arm64.so"
|
||||
|
||||
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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://chcl8jili5ggt
|
||||
uid://bdn2ygldx56a8
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Importer for Terrain3D
|
||||
@tool
|
||||
extends Terrain3D
|
||||
|
||||
@@ -20,10 +22,14 @@ func reset_settings(p_value) -> void:
|
||||
r16_size = Vector2i(1024, 1024)
|
||||
material = null
|
||||
assets = null
|
||||
reset_terrain(true)
|
||||
|
||||
|
||||
func reset_terrain(p_value) -> void:
|
||||
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:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://co2gd0uhumg34
|
||||
uid://cib2rig7vup10
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[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"]
|
||||
offsets = PackedFloat32Array(0.2, 1)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Objects parent for Terrain3D
|
||||
# Children nodes get transform updates on sculpting
|
||||
@tool
|
||||
extends Node3D
|
||||
class_name Terrain3DObjects
|
||||
@@ -97,7 +100,8 @@ func _on_child_exiting_tree(p_node: Node) -> void:
|
||||
|
||||
var helper: TransformChangedNotifier = p_node.get_node_or_null(CHILD_HELPER_PATH)
|
||||
if helper:
|
||||
helper.transform_changed.disconnect(_on_child_transform_changed)
|
||||
if helper.transform_changed.is_connected(_on_child_transform_changed):
|
||||
helper.transform_changed.disconnect(_on_child_transform_changed)
|
||||
p_node.remove_child(helper)
|
||||
helper.queue_free()
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://cl5vs8p47r15v
|
||||
uid://dbndw8p05yam7
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Transform Changed Notifier for Terrain3D
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://drw381usdrd8b
|
||||
uid://claxtgppe8keq
|
||||
|
||||
Reference in New Issue
Block a user