diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 8031732..1d9edc7 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -946,7 +946,7 @@ looks intentional, grounded, and investor-readable. Required order: -- [ ] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground. +- [x] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground. Rebuilt `M_AGR_GZ_Terrain_CoastalScrub` as a procedural coastal scrub material that blends dry soil, scrub green, and sandy path color families with broad and fine noise, documented the visual baseline, and extended the natural-environment verifier so flat constant-color terrain fails. - [ ] Replace or upgrade grasses, shrubs, and trees with believable coastal-scrub vegetation assets, density, color variation, scale variation, and LOD/performance limits. - [ ] Replace or upgrade freshwater visuals with readable water surface, edge treatment, bank dressing, reflection/roughness tuning, and collectability cues. - [ ] Replace or upgrade character bodies and clothing so selected characters read as realistic near-future post-collapse frontier people rather than template mannequins or proxy stacks. diff --git a/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap b/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap index 2dac23d..c9a5f86 100644 --- a/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap +++ b/Content/Agrarian/Maps/L_GroundZeroTerrain_Test.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6996ce7793c8d18a0b1f876ea25a91a55d3e269818a22ac1f1a9b4aa4572bc16 -size 7603168 +oid sha256:e038563e58391a3914efffb19cb1f19f23a1a7a17c52ff9138780db08da786b9 +size 7603560 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource.uasset b/Content/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource.uasset index c5c4713..99f47ca 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_EdiblePlant_Resource.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cefaf55c19b2c0c6955d3d6ff9fd242bfcd768d1bc01d174d8217e4ad693f6a -size 5489 +oid sha256:8c4c52ab4ed8724ac61ced86167af7fb609b46a49767d3ee72c51aa6af6df17c +size 5453 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.uasset index dd4c903..482b070 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a357a87996af05d2ab5f3a8d2cdb97415485c471aa2dbb814c0e36adf2038301 -size 5453 +oid sha256:85b3740341fd56ec55d5c6bc680dcfac015e8edd47ccdaeaf0bf4b8327c05f8a +size 5417 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_FreshWater.uasset b/Content/Agrarian/Materials/M_AGR_GZ_FreshWater.uasset index c879f38..71da3b8 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_FreshWater.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_FreshWater.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5a12043bd12eb677401ce0d88a2bb61edbd3161264ad94dce0ec44fda220db2 -size 5429 +oid sha256:432bb4413bb043fa51cecf81c257a613710333765bf291651fde2ddba3e7cdbd +size 5393 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal.uasset index 5944c30..b829487 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20973d47e7b27d57eb46b42d73ef4fac650b17b98eb8a1dd0ef166ef4ea9c6d5 -size 5529 +oid sha256:68321c2f88568097df87b3f75ac6caac755169f4251c7ad3561afcb9cb3462fa +size 5493 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Shrub_CoyoteBrush.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Shrub_CoyoteBrush.uasset index 20a3c02..1d82d40 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Shrub_CoyoteBrush.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Shrub_CoyoteBrush.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5f0ee318060330cc143ef4d92e951af147eb0585eefd166a0a0d2a3e19c2c36 -size 5535 +oid sha256:d9c5a5da61a42ec63a308ca573bd297a09cddda92481331f340c6aa2e7b72e00 +size 5499 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone.uasset index f62b60d..dd0bd19 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca064f560aef1d806fd05988a49377fa0020be5c5d34b1ffdd73819922097c73 -size 5459 +oid sha256:273e76837d7f5f1d614abeccce29305828a19424dd74ded3a510853008603dcd +size 5423 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset index 1799005..addf839 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de713486a9ead46fb496ab9779be5e39835b6165beb929da2e5368a062fc59d5 -size 5489 +oid sha256:0cb1ca8d8b87db6a87ec88784cfb7222857c6291aa17a7dc7f00aa860cdb25c1 +size 8740 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Tree_CoastalOak.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Tree_CoastalOak.uasset index 191648a..99d3856 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Tree_CoastalOak.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Tree_CoastalOak.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc248f27ed4477cd977ace9e3dc40ca3beeb5c3d86d9fbd2999bac70253699d8 -size 5523 +oid sha256:1593f87873971cd89b24a48482d7dc1af36647ff593fbe17a8b0c858ec8c2d1b +size 5487 diff --git a/Content/Agrarian/Materials/M_AGR_GZ_Wood_Resource.uasset b/Content/Agrarian/Materials/M_AGR_GZ_Wood_Resource.uasset index f94ff50..08f5bf1 100644 --- a/Content/Agrarian/Materials/M_AGR_GZ_Wood_Resource.uasset +++ b/Content/Agrarian/Materials/M_AGR_GZ_Wood_Resource.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1bf69532edacd20ad1780284eb35ea32a14bf0c0fb413339957a76cc1ce8a51 -size 5447 +oid sha256:fd06837632542011d459df37e39c9e021ed5e0f496417bac03ef54298a103cea +size 5411 diff --git a/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md b/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md index a1994e5..24c75ae 100644 --- a/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md +++ b/Docs/Terrain/GroundZeroNaturalEnvironmentPass.md @@ -6,7 +6,9 @@ space. ## Scope -- Terrain receives a warm coastal scrub ground material. +- Terrain receives a procedural coastal scrub terrain material that blends + dry soil, scrub green, and sandy path color families with broad and fine + noise so Ground Zero does not read as flat tan placeholder ground. - Foliage patch instances keep the current prototype meshes but use distinct tree, shrub, and dry grass materials. - Wood, fiber, stone, and freshwater actors receive distinct first-pass @@ -48,7 +50,10 @@ 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 investor-facing instance counts and material assignments, and -resource/water actors are visually dressed. It also checks the asset variation +resource/water actors are visually dressed. It also checks that the terrain +material contains procedural color breakup rather than a flat constant color: +noise, blend, and coastal-scrub color-vector expression families must be present +in the saved material package. It also checks the asset variation layer: twenty-three labeled variation actors, at least four mesh silhouettes, unique scale profiles, and coverage across tree, bush, grass, rock, and water visual families. `Scripts/verify_native_placeholder_meshes.py` checks that playable diff --git a/Scripts/setup_ground_zero_demo_map.py b/Scripts/setup_ground_zero_demo_map.py index 7956d39..e162e23 100644 --- a/Scripts/setup_ground_zero_demo_map.py +++ b/Scripts/setup_ground_zero_demo_map.py @@ -48,6 +48,10 @@ ENVIRONMENT_MATERIALS = { "terrain": { "path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub", "color": unreal.LinearColor(0.16, 0.23, 0.12, 1.0), + "dry_soil_color": unreal.LinearColor(0.28, 0.24, 0.16, 1.0), + "scrub_green_color": unreal.LinearColor(0.12, 0.22, 0.10, 1.0), + "sandy_path_color": unreal.LinearColor(0.42, 0.36, 0.23, 1.0), + "noise_scale": 42.0, "roughness": 0.92, }, "tree": { @@ -837,11 +841,102 @@ def ensure_environment_materials(): material.set_editor_property("used_with_instanced_static_meshes", True) unreal.MaterialEditingLibrary.recompile_material(material) unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) + if key == "terrain": + rebuild_ground_zero_terrain_material(material, spec) created_or_loaded[key] = material return created_or_loaded +def set_expression_property(expression, property_name, value): + try: + expression.set_editor_property(property_name, value) + return True + except Exception: + return False + + +def connect_expression(source, source_output, target, target_input): + try: + unreal.MaterialEditingLibrary.connect_material_expressions(source, source_output, target, target_input) + return True + except Exception as exc: + unreal.log_warning(f"Could not connect material expression input {target_input}: {exc}") + return False + + +def create_constant_color(material, color, x, y): + expression = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionConstant3Vector, x, y + ) + expression.set_editor_property("constant", color) + return expression + + +def create_constant_scalar(material, value, x, y): + expression = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionConstant, x, y + ) + expression.set_editor_property("r", value) + return expression + + +def rebuild_ground_zero_terrain_material(material, spec): + """Build a simple procedural material so the landscape no longer reads as a flat color.""" + if hasattr(unreal.MaterialEditingLibrary, "delete_all_material_expressions"): + unreal.MaterialEditingLibrary.delete_all_material_expressions(material) + + dry_soil = create_constant_color(material, spec["dry_soil_color"], -900, -260) + scrub_green = create_constant_color(material, spec["scrub_green_color"], -900, -80) + sandy_path = create_constant_color(material, spec["sandy_path_color"], -900, 100) + + broad_noise = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionNoise, -560, -160 + ) + set_expression_property(broad_noise, "scale", spec.get("noise_scale", 42.0)) + set_expression_property(broad_noise, "quality", 3) + set_expression_property(broad_noise, "levels", 5) + + fine_noise = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionNoise, -560, 100 + ) + set_expression_property(fine_noise, "scale", 145.0) + set_expression_property(fine_noise, "quality", 2) + set_expression_property(fine_noise, "levels", 3) + + scrub_blend = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionLinearInterpolate, -260, -180 + ) + connect_expression(dry_soil, "", scrub_blend, "A") + connect_expression(scrub_green, "", scrub_blend, "B") + connect_expression(broad_noise, "", scrub_blend, "Alpha") + + final_blend = unreal.MaterialEditingLibrary.create_material_expression( + material, unreal.MaterialExpressionLinearInterpolate, 40, -70 + ) + connect_expression(scrub_blend, "", final_blend, "A") + connect_expression(sandy_path, "", final_blend, "B") + connect_expression(fine_noise, "", final_blend, "Alpha") + unreal.MaterialEditingLibrary.connect_material_property( + final_blend, "", unreal.MaterialProperty.MP_BASE_COLOR + ) + + roughness = create_constant_scalar(material, spec["roughness"], -260, 160) + unreal.MaterialEditingLibrary.connect_material_property( + roughness, "", unreal.MaterialProperty.MP_ROUGHNESS + ) + + specular = create_constant_scalar(material, 0.18, -260, 260) + unreal.MaterialEditingLibrary.connect_material_property( + specular, "", unreal.MaterialProperty.MP_SPECULAR + ) + + material.set_editor_property("use_material_attributes", False) + unreal.MaterialEditingLibrary.recompile_material(material) + unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False) + unreal.log("Rebuilt Ground Zero terrain material with coastal scrub color variation and procedural noise.") + + def apply_material_to_actor_meshes(actor, material): applied_count = 0 for component in actor.get_components_by_class(unreal.StaticMeshComponent): diff --git a/Scripts/verify_ground_zero_natural_environment_pass.py b/Scripts/verify_ground_zero_natural_environment_pass.py index dc7950c..4eb3373 100644 --- a/Scripts/verify_ground_zero_natural_environment_pass.py +++ b/Scripts/verify_ground_zero_natural_environment_pass.py @@ -82,6 +82,28 @@ def assert_asset(path): return asset +def verify_terrain_material_is_not_flat(material, failures): + project_root = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + material_package = project_root + "Content/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub.uasset" + try: + with open(material_package, "rb") as handle: + package_bytes = handle.read() + except Exception as exc: + failures.append(f"could not inspect terrain material package: {exc}") + return + + noise_count = package_bytes.count(b"MaterialExpressionNoise") + lerp_count = package_bytes.count(b"MaterialExpressionLinearInterpolate") + color_count = package_bytes.count(b"MaterialExpressionConstant3Vector") + + if noise_count < 1: + failures.append("terrain material should include a noise expression for color breakup") + if lerp_count < 1: + failures.append("terrain material should include a blend expression instead of a flat base color") + if color_count < 1: + failures.append("terrain material should include coastal scrub color vector expressions") + + def main(): if not unreal.EditorLevelLibrary.load_level(MAP_PATH): raise RuntimeError(f"Could not load map: {MAP_PATH}") @@ -98,6 +120,7 @@ def main(): expected = MATERIALS["terrain"] if assigned != expected: failures.append(f"landscape material expected {expected}, got {assigned}") + verify_terrain_material_is_not_flat(materials["terrain"], failures) foliage_actors = [actor for actor in actors if get_actor_label(actor) == FOLIAGE_LABEL] if len(foliage_actors) != 1: @@ -187,8 +210,10 @@ def main(): roadmap = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "AGRARIAN_DEVELOPMENT_ROADMAP.md" for path, snippet in [ (docs, "asset variation"), + (docs, "procedural coastal scrub terrain material"), (roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"), (roadmap, "[x] Add first-pass environment asset variation"), + (roadmap, "[x] Replace or upgrade the terrain material first so Ground Zero no longer reads as flat tan placeholder ground."), ]: with open(path, "r", encoding="utf-8") as handle: text = handle.read()