diff --git a/project/demo/assets/models/LODExample.tscn b/project/demo/assets/models/LODExample.tscn index 450f34bdc..b404cb675 100644 --- a/project/demo/assets/models/LODExample.tscn +++ b/project/demo/assets/models/LODExample.tscn @@ -1,11 +1,5 @@ [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) @@ -13,6 +7,12 @@ albedo_color = Color(1, 0.540167, 0.11, 1) 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) @@ -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"] diff --git a/project/demo/data/assets.tres b/project/demo/data/assets.tres index 1970b28e9..81f33e4ad 100644 --- a/project/demo/data/assets.tres +++ b/project/demo/data/assets.tres @@ -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"] @@ -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 @@ -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") @@ -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")]) diff --git a/project/plane.tscn b/project/plane.tscn new file mode 100644 index 000000000..6d67d37e0 --- /dev/null +++ b/project/plane.tscn @@ -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") diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp index adcb2fadc..2be2e5fdd 100644 --- a/src/terrain_3d_instancer.cpp +++ b/src/terrain_3d_instancer.cpp @@ -540,7 +540,8 @@ void Terrain3DInstancer::add_instances(const Vector3 &p_global_position, const D TypedArray 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()); @@ -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); } @@ -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 { @@ -776,7 +789,10 @@ void Terrain3DInstancer::add_transforms(const int p_mesh_id, const TypedArrayget_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]; @@ -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]); diff --git a/src/terrain_3d_mesh_asset.cpp b/src/terrain_3d_mesh_asset.cpp index ce8865259..f46dfd2d4 100644 --- a/src/terrain_3d_mesh_asset.cpp +++ b/src/terrain_3d_mesh_asset.cpp @@ -210,6 +210,26 @@ void Terrain3DMeshAsset::set_scene_file(const Ref &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(mesh_instances[i]); + // Store the transforms of LOD0 up to the scene root + if (i == 0) { + _mesh_transform = Transform3D(); // Reset to identity + Vector transforms; + Node *current = mi; + while (current) { + Node3D *n = cast_to(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(); diff --git a/src/terrain_3d_mesh_asset.h b/src/terrain_3d_mesh_asset.h index cdaf52535..05ecde591 100644 --- a/src/terrain_3d_mesh_asset.h +++ b/src/terrain_3d_mesh_asset.h @@ -55,6 +55,7 @@ class Terrain3DMeshAsset : public Terrain3DAssetResource { // Working data TypedArray _meshes; + Transform3D _mesh_transform = Transform3D(); Ref _thumbnail; void _clear_lod_ranges(); @@ -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 get_mesh(const int p_lod = 0) const; + Transform3D get_mesh_transform() const { return _mesh_transform; } Ref get_thumbnail() const { return _thumbnail; } void set_height_offset(const real_t p_offset); real_t get_height_offset() const { return _height_offset; }