Add Ground Zero environment asset variation

This commit is contained in:
2026-05-16 02:28:31 -07:00
parent 25ffbfc564
commit f78c552d30
6 changed files with 209 additions and 9 deletions
+1 -1
View File
@@ -457,7 +457,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add biome-appropriate natural resources based on Ground Zero.
- [x] Add water source.
- [x] Replace grey-box environment presentation with an MVP natural environment pass: terrain material, grass, shrubs, bushes, trees, water-source visuals, and clearer Ground Zero biome dressing. Added repeatable Ground Zero environment material generation, applied terrain/foliage/resource/water materials in the map setup, regenerated the demo map, documented the pass, and added verification for material assets plus map assignments.
- [ ] Add first-pass environment asset variation so trees, bushes, grass, resource nodes, and water do not read as repeated placeholders.
- [x] Add first-pass environment asset variation so trees, bushes, grass, resource nodes, and water do not read as repeated placeholders. Added repeatable labeled variation actors for tree canopies/trunks, rounded bushes, grass mats, rock slabs, and a freshwater surface using multiple prototype mesh silhouettes, unique scales, rotations, and Ground Zero material families; extended the natural-environment verifier to require variation coverage.
- [ ] Replace `LevelPrototyping` cube/cylinder mesh dependencies in Agrarian setup scripts and prototype Blueprints with Agrarian-native placeholder environment meshes.
- [ ] Add weather exposure zones if needed.
- [ ] Add landmark or ruin placeholder.
Binary file not shown.
+4
View File
@@ -39,3 +39,7 @@ current prototype geometry now uses dedicated Ground Zero tree, shrub, and dry
grass materials. The next biome/resource passes should replace prototype meshes
with real coastal California scrub, grassland, woodland, and resource-specific
assets, then tie spawn density to land-cover and hydrography data.
The natural environment variation pass adds nearby labeled accent actors for
additional canopy, trunk, bush, grass mat, rock, and water silhouettes so the
demo no longer depends only on one repeated foliage mesh per family.
@@ -11,6 +11,9 @@ space.
tree, shrub, and dry grass materials.
- Wood, fiber, stone, and freshwater actors receive distinct first-pass
materials.
- First-pass asset variation actors add additional tree canopies/trunks, rounded
bushes, grass mats, rock slabs, and a visible freshwater surface with varied
meshes, scales, rotations, and material families.
- The setup remains repeatable through `Scripts/setup_ground_zero_demo_map.py`.
## Material Assets
@@ -29,11 +32,14 @@ space.
`Scripts/verify_ground_zero_natural_environment_pass.py` checks that the
materials exist, the landscape uses the terrain material, the foliage actor has
the expected instance counts and material assignments, and resource/water actors
are visually dressed.
are visually dressed. It also checks the first-pass asset variation layer:
eleven labeled variation actors, at least four mesh silhouettes, unique scale
profiles, and coverage across tree, bush, grass, rock, and water visual
families.
## Follow-Up
This pass deliberately keeps the current prototype geometry so it stays small
and stable. The next environment work should add first-pass asset variation so
trees, bushes, grass, resource nodes, and water no longer read as repeated
placeholder shapes.
and stable. Later environment work should replace these prototype silhouettes
with region-appropriate sourced or custom meshes, then drive density and shape
selection from land-cover, slope, hydrography, and biome data.
+133
View File
@@ -22,6 +22,13 @@ FOLIAGE_MESHES = {
"shrub": "/Game/LevelPrototyping/Meshes/SM_Cube",
"grass": "/Game/LevelPrototyping/Meshes/SM_Cylinder",
}
VARIATION_MESHES = {
"cube": "/Game/LevelPrototyping/Meshes/SM_Cube",
"chamfer_cube": "/Game/LevelPrototyping/Meshes/SM_ChamferCube",
"cylinder": "/Game/LevelPrototyping/Meshes/SM_Cylinder",
"quarter_cylinder": "/Game/LevelPrototyping/Meshes/SM_QuarterCylinder",
"plane": "/Game/LevelPrototyping/Meshes/SM_Plane",
}
MATERIAL_FOLDER = "/Game/Agrarian/Materials"
ENVIRONMENT_MATERIALS = {
"terrain": {
@@ -239,6 +246,108 @@ WATER_SOURCE_ACTORS = [
},
]
ENVIRONMENT_VARIATION_ACTORS = [
{
"label": "AGR_GZ_EnvVar_Tree_Canopy_01",
"mesh_key": "chamfer_cube",
"material_key": "tree",
"location_xy": unreal.Vector(-27500.0, 6900.0, 0.0),
"z_offset": 390.0,
"scale": unreal.Vector(2.5, 2.0, 1.6),
"rotation": unreal.Rotator(0.0, 32.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Trunk_01",
"mesh_key": "cylinder",
"material_key": "wood_resource",
"location_xy": unreal.Vector(-27500.0, 6900.0, 0.0),
"z_offset": 170.0,
"scale": unreal.Vector(0.35, 0.35, 3.6),
"rotation": unreal.Rotator(0.0, 32.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Canopy_02",
"mesh_key": "chamfer_cube",
"material_key": "tree",
"location_xy": unreal.Vector(17600.0, 31800.0, 0.0),
"z_offset": 430.0,
"scale": unreal.Vector(2.0, 2.8, 1.45),
"rotation": unreal.Rotator(0.0, -18.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Trunk_02",
"mesh_key": "cylinder",
"material_key": "wood_resource",
"location_xy": unreal.Vector(17600.0, 31800.0, 0.0),
"z_offset": 190.0,
"scale": unreal.Vector(0.3, 0.3, 4.1),
"rotation": unreal.Rotator(0.0, -18.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Bush_Rounded_01",
"mesh_key": "chamfer_cube",
"material_key": "shrub",
"location_xy": unreal.Vector(-33400.0, -15200.0, 0.0),
"z_offset": 70.0,
"scale": unreal.Vector(1.9, 1.4, 0.7),
"rotation": unreal.Rotator(0.0, 11.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Bush_Rounded_02",
"mesh_key": "chamfer_cube",
"material_key": "shrub",
"location_xy": unreal.Vector(30400.0, -3900.0, 0.0),
"z_offset": 75.0,
"scale": unreal.Vector(1.45, 2.1, 0.85),
"rotation": unreal.Rotator(0.0, 73.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Grass_Mat_01",
"mesh_key": "plane",
"material_key": "grass",
"location_xy": unreal.Vector(8200.0, -17100.0, 0.0),
"z_offset": 14.0,
"scale": unreal.Vector(3.6, 2.2, 1.0),
"rotation": unreal.Rotator(0.0, -34.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Grass_Mat_02",
"mesh_key": "plane",
"material_key": "grass",
"location_xy": unreal.Vector(-6200.0, 8300.0, 0.0),
"z_offset": 14.0,
"scale": unreal.Vector(2.7, 4.0, 1.0),
"rotation": unreal.Rotator(0.0, 41.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Rock_Slab_01",
"mesh_key": "chamfer_cube",
"material_key": "stone_resource",
"location_xy": unreal.Vector(22900.0, 18300.0, 0.0),
"z_offset": 45.0,
"scale": unreal.Vector(1.8, 1.05, 0.45),
"rotation": unreal.Rotator(0.0, 24.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Rock_Slab_02",
"mesh_key": "quarter_cylinder",
"material_key": "stone_resource",
"location_xy": unreal.Vector(38100.0, 29300.0, 0.0),
"z_offset": 55.0,
"scale": unreal.Vector(1.2, 1.8, 0.75),
"rotation": unreal.Rotator(0.0, -47.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Water_Surface_01",
"mesh_key": "plane",
"material_key": "fresh_water",
"location_xy": unreal.Vector(-7200.0, 10400.0, 0.0),
"z_offset": 18.0,
"scale": unreal.Vector(2.6, 1.6, 1.0),
"rotation": unreal.Rotator(0.0, -15.0, 0.0),
},
]
FOLIAGE_ZONES = {
"trees": {
@@ -535,6 +644,27 @@ def spawn_demo_actor(spec, height_values, materials):
return actor
def spawn_environment_variation_actor(spec, height_values, materials):
location_xy = spec["location_xy"]
z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0)
actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world(
unreal.StaticMeshActor,
unreal.Vector(location_xy.x, location_xy.y, z),
spec["rotation"],
spec["label"],
)
if not actor:
raise RuntimeError(f"Could not spawn {spec['label']}")
set_actor_label(actor, spec["label"])
actor.set_actor_scale3d(spec["scale"])
mesh_component = actor.static_mesh_component
mesh_component.set_editor_property("static_mesh", load_required_asset(VARIATION_MESHES[spec["mesh_key"]]))
mesh_component.set_material(0, materials[spec["material_key"]])
unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}")
return actor
def main():
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
raise RuntimeError(f"Could not load map: {MAP_PATH}")
@@ -543,6 +673,7 @@ def main():
labels.update(LEGACY_DEMO_LIGHTING_LABELS)
labels.update(spec["label"] for spec in BIOME_RESOURCE_ACTORS)
labels.update(spec["label"] for spec in WATER_SOURCE_ACTORS)
labels.update(spec["label"] for spec in ENVIRONMENT_VARIATION_ACTORS)
labels.add(FOLIAGE_LABEL)
remove_existing_demo_actors(labels)
@@ -554,6 +685,8 @@ def main():
spawn_demo_actor(spec, height_values, materials)
for spec in WATER_SOURCE_ACTORS:
spawn_demo_actor(spec, height_values, materials)
for spec in ENVIRONMENT_VARIATION_ACTORS:
spawn_environment_variation_actor(spec, height_values, materials)
for spec in DEMO_ACTORS:
spawn_demo_actor(spec, height_values, materials)
@@ -26,6 +26,16 @@ RESOURCE_MATERIALS = {
"AGR_DemoFiberResource": "fiber_resource",
"AGR_GZ_FreshWaterSource": "fresh_water",
}
VARIATION_PREFIX = "AGR_GZ_EnvVar_"
EXPECTED_VARIATION_COUNT = 11
EXPECTED_VARIATION_MATERIALS = {
"AGR_GZ_EnvVar_Tree_Canopy": "tree",
"AGR_GZ_EnvVar_Tree_Trunk": "wood_resource",
"AGR_GZ_EnvVar_Bush": "shrub",
"AGR_GZ_EnvVar_Grass": "grass",
"AGR_GZ_EnvVar_Rock": "stone_resource",
"AGR_GZ_EnvVar_Water": "fresh_water",
}
def get_actor_label(actor):
@@ -48,6 +58,13 @@ def material_key_for_label(label):
return None
def variation_material_key_for_label(label):
for prefix, material_key in EXPECTED_VARIATION_MATERIALS.items():
if label.startswith(prefix):
return material_key
return None
def assert_asset(path):
asset = unreal.EditorAssetLibrary.load_asset(path)
if not asset:
@@ -118,11 +135,50 @@ def main():
if checked_resource_actors < 10:
failures.append(f"expected at least 10 dressed resource/water actors, checked {checked_resource_actors}")
variation_actors = [actor for actor in actors if get_actor_label(actor).startswith(VARIATION_PREFIX)]
if len(variation_actors) != EXPECTED_VARIATION_COUNT:
failures.append(f"expected {EXPECTED_VARIATION_COUNT} environment variation actors, found {len(variation_actors)}")
variation_meshes = set()
variation_scales = set()
variation_material_keys = set()
for actor in variation_actors:
label = get_actor_label(actor)
expected_material_key = variation_material_key_for_label(label)
if not expected_material_key:
failures.append(f"{label} does not match a known variation material family")
continue
mesh_components = actor.get_components_by_class(unreal.StaticMeshComponent)
if not mesh_components:
failures.append(f"{label} has no static mesh component")
continue
component = mesh_components[0]
mesh = component.get_editor_property("static_mesh")
variation_meshes.add(mesh.get_path_name().split(".", 1)[0] if mesh else "")
scale = actor.get_actor_scale3d()
variation_scales.add((round(scale.x, 2), round(scale.y, 2), round(scale.z, 2)))
variation_material_keys.add(expected_material_key)
assigned = material_path(component.get_material(0))
expected = MATERIALS[expected_material_key]
if assigned != expected:
failures.append(f"{label} material expected {expected}, got {assigned}")
if len(variation_meshes) < 4:
failures.append(f"expected at least 4 variation mesh silhouettes, got {len(variation_meshes)}")
if len(variation_scales) < EXPECTED_VARIATION_COUNT:
failures.append("environment variation actors should use unique scale profiles")
if not {"tree", "wood_resource", "shrub", "grass", "stone_resource", "fresh_water"}.issubset(variation_material_keys):
failures.append("environment variation actors do not cover tree, shrub, grass, resource, and water families")
docs = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "Docs/Terrain/GroundZeroNaturalEnvironmentPass.md"
roadmap = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "AGRARIAN_DEVELOPMENT_ROADMAP.md"
for path, snippet in [
(docs, "MVP natural environment pass"),
(docs, "asset variation"),
(roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"),
(roadmap, "[x] Add first-pass environment asset variation"),
]:
with open(path, "r", encoding="utf-8") as handle:
text = handle.read()
@@ -134,7 +190,8 @@ def main():
unreal.log(
"Ground Zero natural environment verification complete: "
f"{len(materials)} materials, {len(landscapes)} landscape(s), {checked_resource_actors} dressed resource/water actor(s)."
f"{len(materials)} materials, {len(landscapes)} landscape(s), "
f"{checked_resource_actors} dressed resource/water actor(s), {len(variation_actors)} variation actor(s)."
)