This commit is contained in:
Vysyr committed 2026-09-05 03:19:55 +02:00
1 parent 2a4da34fe9
commit bf525034e0
10 files changed
+142 -494

No files matched your search

Binary file not shown.
+47 -24
View File
@@ -17,7 +17,7 @@ import os, json, math, random, unreal
CONFIG_PATH = os.environ.get(
"TERRAIN_CONFIG",
os.path.join(os.path.dirname(os.path.abspath(__file__)), "terrain_config.json"),
os.path.join(os.path.dirname(os.path.abspath(__file__)), "terrain_config_variants_demo.json"),
)
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
@@ -85,7 +85,7 @@ class FractalHeightField:
frequency *= self.lacunarity
return (total / max_amp) * self.height_scale
def normal(self, x, y, step=25.0):
def normal(self, x, y, step=200.0):
h_l = self.height(x - step, y)
h_r = self.height(x + step, y)
h_d = self.height(x, y - step)
@@ -145,17 +145,42 @@ def build_terrain_mesh(cfg, field):
return static_mesh, bounds
_mesh_base_z_cache = {}
_mesh_bounds_cache = {}
def mesh_base_z(mesh):
"""Local-space Z of the mesh's lowest point, so we can plant its base on the
ground instead of its (often bounding-box-centered) pivot."""
def mesh_local_bounds(mesh):
path = mesh.get_path_name()
if path not in _mesh_base_z_cache:
if path not in _mesh_bounds_cache:
box = mesh.get_bounding_box()
_mesh_base_z_cache[path] = box.min.z
return _mesh_base_z_cache[path]
_mesh_bounds_cache[path] = (box.min, box.max)
return _mesh_bounds_cache[path]
def ground_offset_for(mesh, quat, scale_vec):
"""World-space Z distance from the actor's pivot down to the lowest point of
its (scaled, rotated) mesh, so the base can be planted exactly on the ground
no matter where the source asset's pivot was authored, or how it's tilted."""
box_min, box_max = mesh_local_bounds(mesh)
min_z = None
for x in (box_min.x, box_max.x):
for y in (box_min.y, box_max.y):
for z in (box_min.z, box_max.z):
corner = unreal.Vector(x * scale_vec.x, y * scale_vec.y, z * scale_vec.z)
world_z = quat.rotate_vector(corner).z
if min_z is None or world_z < min_z:
min_z = world_z
return -min_z
def align_and_yaw_quat(normal, yaw_degrees, align_to_normal):
"""Random yaw about the object's own up axis, then (optionally) tilt that
up axis to match the terrain normal - in that order, since tilting first
would rotate the yaw axis out from under the random spin."""
yaw_quat = unreal.Rotator(roll=0.0, pitch=0.0, yaw=yaw_degrees).quaternion()
if not align_to_normal:
return yaw_quat
align_quat = unreal.Vector(0.0, 0.0, 1.0).find_quat_between_normals(normal)
return align_quat * yaw_quat
# ---------------------------------------------------------------------------
@@ -199,21 +224,17 @@ def scatter_layer(layer_cfg, field, bounds, origin, base_folder):
mesh = rng.choice(meshes)
scale = rng.uniform(layer_cfg["min_scale"], layer_cfg["max_scale"])
# Shift up by -min_z*scale so the mesh's lowest vertex sits on the ground,
# regardless of where the source asset's pivot was authored.
ground_offset = -mesh_base_z(mesh) * scale
location = unreal.Vector(origin.x + x, origin.y + y, origin.z + z + ground_offset)
scale_vec = unreal.Vector(scale, scale, scale)
yaw = rng.uniform(0.0, 360.0)
if layer_cfg.get("align_to_normal", False):
# Approximate tilt from the surface normal; good enough for gentle slopes.
pitch = -math.degrees(math.atan2(normal.x, normal.z))
roll = math.degrees(math.atan2(normal.y, normal.z))
rotation = unreal.Rotator(roll=roll, pitch=pitch, yaw=yaw)
else:
rotation = unreal.Rotator(roll=0.0, pitch=0.0, yaw=yaw)
quat = align_and_yaw_quat(normal, yaw, layer_cfg.get("align_to_normal", False))
rotation = quat.rotator()
ground_offset = ground_offset_for(mesh, quat, scale_vec)
location = unreal.Vector(origin.x + x, origin.y + y, origin.z + z + ground_offset)
actor = actor_subsystem.spawn_actor_from_object(mesh, location, rotation)
actor.set_actor_scale3d(unreal.Vector(scale, scale, scale))
actor.set_actor_scale3d(scale_vec)
actor.set_folder_path(f"{base_folder}/{layer_cfg['name']}")
actor.set_actor_label(f"{layer_cfg['name']}_{spawned:04d}")
spawned += 1
@@ -245,13 +266,15 @@ def place_explicit(entries, field, origin, base_folder):
else:
scale_vec = unreal.Vector(scale, scale, scale)
roll, pitch, yaw = entry.get("rotation", [0.0, 0.0, 0.0])
rotation = unreal.Rotator(roll=roll, pitch=pitch, yaw=yaw)
quat = rotation.quaternion()
z = z_offset
if entry.get("snap_to_terrain", True):
z += field.height(x, y) - mesh_base_z(mesh) * scale_vec.z
z += field.height(x, y) + ground_offset_for(mesh, quat, scale_vec)
roll, pitch, yaw = entry.get("rotation", [0.0, 0.0, 0.0])
location = unreal.Vector(origin.x + x, origin.y + y, origin.z + z)
rotation = unreal.Rotator(roll=roll, pitch=pitch, yaw=yaw)
actor = actor_subsystem.spawn_actor_from_object(mesh, location, rotation)
actor.set_actor_scale3d(scale_vec)
+2 -4
View File
@@ -37,9 +37,7 @@
{
"name": "Rocks",
"meshes": [
"/Game/ImportedAssets/Rocks/SM_rock1",
"/Game/ImportedAssets/Rocks/SM_rock2",
"/Game/ImportedAssets/Rocks/SM_rock3"
"/Game/ImportedAssets/Rocks/SM_rock1"
],
"count": 150,
"min_height": -100.0,
@@ -63,7 +61,7 @@
},
{
"name": "RockCairn_Waypoint",
"mesh": "/Game/ImportedAssets/Rocks/SM_rock2",
"mesh": "/Game/ImportedAssets/Rocks/SM_rock1",
"location": [-1500.0, -600.0, 0.0],
"snap_to_terrain": true,
"rotation": [0.0, 0.0, 0.0],