Add Ground Zero environment asset variation
This commit is contained in:
@@ -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.
@@ -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.
|
||||
|
||||
@@ -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)."
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user