Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions project/demo/assets/models/LODExample.tscn
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
[gd_scene load_steps=11 format=3 uid="uid://bn5nf4esciwex"]

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"]
albedo_color = Color(1, 0, 0, 1)

[sub_resource type="SphereMesh" id="SphereMesh_u4ac2"]
material = SubResource("StandardMaterial3D_788j8")

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3i52q"]
albedo_color = Color(1, 0.540167, 0.11, 1)

[sub_resource type="BoxMesh" id="BoxMesh_xyuxq"]
material = SubResource("StandardMaterial3D_3i52q")
size = Vector3(0.85, 0.85, 0.85)

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_788j8"]
albedo_color = Color(1, 0, 0, 1)

[sub_resource type="SphereMesh" id="SphereMesh_u4ac2"]
material = SubResource("StandardMaterial3D_788j8")

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p0ha4"]
albedo_color = Color(0.48, 1, 0.497333, 1)

Expand All @@ -38,14 +38,15 @@ cull_mode = 2
[node name="TestMultimesh" type="Node3D"]

[node name="Node3D" type="Node3D" parent="."]
transform = Transform3D(0.707107, -0.707107, 0, 0.5, 0.5, -0.707107, 0.5, 0.5, 0.707107, 0, 0.725, 0.125)

[node name="MeshInstance3D1" type="MeshInstance3D" parent="Node3D"]
mesh = SubResource("SphereMesh_u4ac2")
mesh = SubResource("BoxMesh_xyuxq")
skeleton = NodePath("../..")

[node name="MeshInstance3D2" type="MeshInstance3D" parent="Node3D"]
visible = false
mesh = SubResource("BoxMesh_xyuxq")
mesh = SubResource("SphereMesh_u4ac2")
skeleton = NodePath("../..")

[node name="MeshInstance3D3" type="MeshInstance3D" parent="Node3D"]
Expand Down
15 changes: 12 additions & 3 deletions project/demo/data/assets.tres
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[gd_resource type="Terrain3DAssets" load_steps=11 format=3 uid="uid://dal3jhw6241qg"]
[gd_resource type="Terrain3DAssets" load_steps=13 format=3 uid="uid://dal3jhw6241qg"]

[ext_resource type="PackedScene" uid="uid://bn5nf4esciwex" path="res://demo/assets/models/LODExample.tscn" id="1_4jrdu"]
[ext_resource type="PackedScene" uid="uid://b1ronv2ln7dhb" path="res://plane.tscn" id="2_adagb"]
[ext_resource type="Texture2D" uid="uid://c88j3oj0lf6om" path="res://demo/assets/textures/rock023_alb_ht.png" id="2_pog6b"]
[ext_resource type="Texture2D" uid="uid://ddprscrpsofah" path="res://demo/assets/textures/ground037_alb_ht.png" id="3_g8f2m"]
[ext_resource type="Texture2D" uid="uid://c307hdmos4gtm" path="res://demo/assets/textures/rock023_nrm_rgh.png" id="3_wncaf"]
Expand All @@ -21,6 +22,7 @@ name = "TextureCard"
generated_type = 1
height_offset = 0.5
material_override = SubResource("StandardMaterial3D_b2vqk")
generated_faces = 1
last_lod = 0
last_shadow_lod = 0
lod0_range = 128.0
Expand All @@ -29,11 +31,18 @@ lod0_range = 128.0
name = "LODExample"
id = 1
scene_file = ExtResource("1_4jrdu")
height_offset = 0.5
last_lod = 4
last_shadow_lod = 4
lod4_range = 256.0

[sub_resource type="Terrain3DMeshAsset" id="Terrain3DMeshAsset_e6edf"]
name = "plane"
id = 2
scene_file = ExtResource("2_adagb")
last_lod = 0
last_shadow_lod = 0
lod0_range = 128.0

[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_lha57"]
name = "Cliff"
albedo_texture = ExtResource("2_pog6b")
Expand All @@ -54,5 +63,5 @@ ao_strength = 2.0
detiling_rotation = 0.161

[resource]
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_2qf8x"), SubResource("Terrain3DMeshAsset_y3ibi")])
mesh_list = Array[Terrain3DMeshAsset]([SubResource("Terrain3DMeshAsset_2qf8x"), SubResource("Terrain3DMeshAsset_y3ibi"), SubResource("Terrain3DMeshAsset_e6edf")])
texture_list = Array[Terrain3DTextureAsset]([SubResource("Terrain3DTextureAsset_lha57"), SubResource("Terrain3DTextureAsset_od0q7")])
18 changes: 18 additions & 0 deletions project/plane.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[gd_scene load_steps=3 format=3 uid="uid://b1ronv2ln7dhb"]

[sub_resource type="PlaneMesh" id="PlaneMesh_r8t2q"]

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_s46f0"]
cull_mode = 2
albedo_color = Color(0.79, 0.972, 1, 1)

[node name="root" type="Node3D"]

[node name="Meshes" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0)

[node name="MeshInstance3D" type="MeshInstance3D" parent="Meshes"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1)
mesh = SubResource("PlaneMesh_r8t2q")
skeleton = NodePath("../..")
surface_material_override/0 = SubResource("StandardMaterial3D_s46f0")
72 changes: 46 additions & 26 deletions src/terrain_3d_instancer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,8 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D
TypedArray<Transform3D> xforms;
PackedColorArray colors;
for (int i = 0; i < count; i++) {
Transform3D t;
// Start with the mesh transform to correct the mesh to its visual state
Transform3D t = mesh_asset->get_mesh_transform();

// Get random XZ position and height in a circle
real_t r_radius = radius * sqrt(UtilityFunctions::randf());
Expand All @@ -556,45 +557,55 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D
position.y = height_data[0];
bool raycast_hit = height_data[1];

// Orientation
Vector3 normal = Vector3(0.f, 1.f, 0.f);
// Orientation: Align the corrected mesh's up axis
Vector3 mesh_up = t.basis.get_column(1).normalized(); // Mesh's up axis after correction (typically Y)
if (align_to_normal) {
// Use either collision normal or terrain normal
normal = (on_collision && raycast_hit) ? (Vector3)height_data[2] : data->get_normal(position);
if (!normal.is_finite()) {
normal = Vector3(0.f, 1.f, 0.f);
} else {
Vector3 normal = (on_collision && raycast_hit) ? (Vector3)height_data[2] : data->get_normal(position);
if (normal.is_finite()) {
normal = normal.normalized();
Vector3 z_axis = Vector3(0.f, 0.f, 1.f);
Vector3 x_axis = -z_axis.cross(normal);
if (x_axis.length_squared() > 0.001) {
t.basis = Basis(x_axis, normal, z_axis).orthonormalized();
if (mesh_up.dot(normal) < 0.999f) { // Avoid rotation if already aligned
Quaternion rotation = Quaternion(mesh_up, normal);
t.basis = Basis(rotation) * t.basis;
}
}
} else {
// Align mesh's up axis to global Y when not aligning to normal
if (mesh_up.dot(Vector3(0, 1, 0)) < 0.999f) {
Quaternion rotation = Quaternion(mesh_up, Vector3(0, 1, 0));
t.basis = Basis(rotation) * t.basis;
}
}

// Apply spin around the current up axis (global Y if align_to_normal is false)
real_t spin = (fixed_spin + random_spin * UtilityFunctions::randf()) * Math_PI / 180.f;
if (abs(spin) > 0.001f) {
t.basis = t.basis.rotated(normal, spin);
t.basis = t.basis.rotated(t.basis.get_column(1), spin);
}

// Apply tilt around the post-transform X-axis
real_t tilt = (fixed_tilt + random_tilt * (2.f * UtilityFunctions::randf() - 1.f)) * Math_PI / 180.f;
if (abs(tilt) > 0.001f) {
t.basis = t.basis.rotated(t.basis.get_column(0), tilt); // Rotate pitch, X-axis
t.basis = t.basis.rotated(t.basis.get_column(0), tilt);
}

// Scale
// Apply scale
real_t t_scale = CLAMP(fixed_scale + random_scale * (2.f * UtilityFunctions::randf() - 1.f), 0.01f, 10.f);
t = t.scaled(Vector3(t_scale, t_scale, t_scale));

// Position. mesh_asset height offset added in add_transforms
// Apply editor height offset along the post-transform up axis
real_t offset = height_offset + random_height * (2.f * UtilityFunctions::randf() - 1.f);
position += t.basis.get_column(1) * offset; // Offset along UP axis
t = t.translated(position);
position += t.basis.get_column(1) * offset;

// Set the final position
t.origin = position;

// Color
Color col = vertex_color;
col.set_v(CLAMP(col.get_v() - random_darken * UtilityFunctions::randf(), 0.f, 1.f));
col.set_h(fmod(col.get_h() + random_hue * (2.f * UtilityFunctions::randf() - 1.f), 1.f));

t *= mesh_asset->get_mesh_transform(); // Apply mesh asset transform

xforms.push_back(t);
colors.push_back(col);
}
Expand Down Expand Up @@ -713,9 +724,11 @@ void Terrain3DInstancer::remove_instances(const Vector3 &p_global_position, cons
// Use localised ring center
real_t radial_distance = localised_ring_center.distance_to(Vector2(t.origin.x, t.origin.z));
Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset;

Vector3 base_position = t.origin + global_local_offset - height_offset;
if (radial_distance < radius &&
UtilityFunctions::randf() < CLAMP(0.175f * strength, 0.005f, 10.f) &&
data->is_in_slope(t.origin + global_local_offset - height_offset, slope_range, invert)) {
data->is_in_slope(base_position, slope_range, invert)) {
_backup_region(region);
continue;
} else {
Expand Down Expand Up @@ -776,7 +789,10 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArray<Tr
for (int i = 0; i < p_xforms.size(); i++) {
// Get adjusted xform/color
Transform3D trns = p_xforms[i];
trns.origin += trns.basis.get_column(1) * mesh_asset->get_height_offset(); // Offset along UP axis
// Apply the mesh transform first
trns = mesh_asset->get_mesh_transform() * trns;
// Then apply the height offset along the transformed up axis
trns.origin += trns.basis.get_column(1) * mesh_asset->get_height_offset();
Color col = COLOR_WHITE;
if (p_colors.size() > i) {
col = p_colors[i];
Expand Down Expand Up @@ -970,15 +986,19 @@ void Terrain3DInstancer::update_transforms(const AABB &p_aabb) {
Transform3D t = xforms[i];
Vector3 global_origin(t.origin + global_local_offset);
if (rect.has_point(Vector2(global_origin.x, global_origin.z))) {
Vector3 height_offset = t.basis.get_column(1) * mesh_height_offset;
t.origin -= height_offset;
Array height_data = _get_usable_height(global_origin, Vector2(0.f, 90.f), false, on_collision);
// Get the instance's up axis from its basis (post-transform)
Vector3 up_axis = t.basis.get_column(1).normalized();
// Calculate the base position by moving down along the up axis by height_offset
Vector3 base_position = global_origin - up_axis * mesh_height_offset;
// Get the new terrain height at the base position
Array height_data = _get_usable_height(base_position, Vector2(0.f, 90.f), false, on_collision);
if (height_data.size() != 3) {
continue;
}
t.origin.y = height_data[0];

t.origin += height_offset;
real_t new_terrain_height = height_data[0];
// Adjust the instance’s position so its base matches the new terrain height
Vector3 adjustment = up_axis * (new_terrain_height - base_position.y);
t.origin += adjustment;
}
updated_xforms.push_back(t);
updated_colors.push_back(colors[i]);
Expand Down
20 changes: 20 additions & 0 deletions src/terrain_3d_mesh_asset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,26 @@ void Terrain3DMeshAsset::set_scene_file(const Ref<PackedScene> &p_scene_file) {
// Now process the meshes
for (int i = 0, count = MIN(mesh_instances.size(), MAX_LOD_COUNT); i < count; i++) {
MeshInstance3D *mi = cast_to<MeshInstance3D>(mesh_instances[i]);
// Store the transforms of LOD0 up to the scene root
if (i == 0) {
_mesh_transform = Transform3D(); // Reset to identity
Vector<Transform3D> transforms;
Node *current = mi;
while (current) {
Node3D *n = cast_to<Node3D>(current);
if (n) {
transforms.push_back(n->get_transform());
}
if (current == node) {
break; // Stop at the scene root
}
current = current->get_parent();
}
// Apply transforms from root to mesh (left to right)
for (int i = transforms.size() - 1; i >= 0; i--) {
_mesh_transform = _mesh_transform * transforms[i];
}
}
LOG(DEBUG, "Found mesh: ", mi->get_name());
if (_name == "New Mesh") {
_name = _packed_scene->get_path().get_file().get_basename();
Expand Down
2 changes: 2 additions & 0 deletions src/terrain_3d_mesh_asset.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Terrain3DMeshAsset : public Terrain3DAssetResource {

// Working data
TypedArray<Mesh> _meshes;
Transform3D _mesh_transform = Transform3D();
Ref<Texture2D> _thumbnail;

void _clear_lod_ranges();
Expand All @@ -79,6 +80,7 @@ class Terrain3DMeshAsset : public Terrain3DAssetResource {
void set_generated_type(const GenType p_type);
GenType get_generated_type() const { return _generated_type; }
Ref<Mesh> get_mesh(const int p_lod = 0) const;
Transform3D get_mesh_transform() const { return _mesh_transform; }
Ref<Texture2D> get_thumbnail() const { return _thumbnail; }
void set_height_offset(const real_t p_offset);
real_t get_height_offset() const { return _height_offset; }
Expand Down
Loading