Upgrade Ground Zero vegetation assets
This commit is contained in:
@@ -19,6 +19,20 @@ EXPECTED_FOLIAGE_COUNTS = {
|
||||
"shrubs": 220,
|
||||
"grass": 420,
|
||||
}
|
||||
EXPECTED_FOLIAGE_MESHES = {
|
||||
"tree_instances": "/Game/Agrarian/Environment/Vegetation/SM_AGR_GZ_CoastalOak_Proxy",
|
||||
"shrub_instances": "/Game/Agrarian/Environment/Vegetation/SM_AGR_GZ_CoyoteBrush_Proxy",
|
||||
"grass_instances": "/Game/Agrarian/Environment/Vegetation/SM_AGR_GZ_DryGrassClump_Proxy",
|
||||
}
|
||||
EXPECTED_FOLIAGE_CULL_DISTANCES = {
|
||||
"tree_instances": (65000, 95000),
|
||||
"shrub_instances": (28000, 52000),
|
||||
"grass_instances": (9000, 22000),
|
||||
}
|
||||
FORBIDDEN_FOLIAGE_MESH_PREFIXES = (
|
||||
"/Engine/BasicShapes/",
|
||||
"/Game/LevelPrototyping/",
|
||||
)
|
||||
RESOURCE_MATERIALS = {
|
||||
"AGR_GZ_Wood": "wood_resource",
|
||||
"AGR_GZ_Fiber": "fiber_resource",
|
||||
@@ -61,6 +75,29 @@ def material_path(material):
|
||||
return material.get_path_name().split(".", 1)[0]
|
||||
|
||||
|
||||
def asset_path(asset):
|
||||
if not asset:
|
||||
return ""
|
||||
return asset.get_path_name().split(".", 1)[0]
|
||||
|
||||
|
||||
def get_component_property(component, property_name, default=None):
|
||||
try:
|
||||
return component.get_editor_property(property_name)
|
||||
except Exception:
|
||||
return getattr(component, property_name, default)
|
||||
|
||||
|
||||
def static_mesh_vertex_count(mesh):
|
||||
library = getattr(unreal, "EditorStaticMeshLibrary", None)
|
||||
if not mesh or not library:
|
||||
return -1
|
||||
try:
|
||||
return library.get_number_verts(mesh, 0)
|
||||
except Exception:
|
||||
return -1
|
||||
|
||||
|
||||
def material_key_for_label(label):
|
||||
for prefix, material_key in RESOURCE_MATERIALS.items():
|
||||
if label.startswith(prefix):
|
||||
@@ -104,6 +141,22 @@ def verify_terrain_material_is_not_flat(material, failures):
|
||||
failures.append("terrain material should include coastal scrub color vector expressions")
|
||||
|
||||
|
||||
def verify_foliage_material_has_variation(material_path_name, failures):
|
||||
project_root = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir())
|
||||
package_path = project_root + "Content" + material_path_name.replace("/Game", "") + ".uasset"
|
||||
try:
|
||||
with open(package_path, "rb") as handle:
|
||||
package_bytes = handle.read()
|
||||
except Exception as exc:
|
||||
failures.append(f"could not inspect foliage material package {material_path_name}: {exc}")
|
||||
return
|
||||
|
||||
if package_bytes.count(b"MaterialExpressionLinearInterpolate") < 1:
|
||||
failures.append(f"{material_path_name} should blend foliage color variation")
|
||||
if package_bytes.count(b"MaterialExpressionConstant3Vector") < 1:
|
||||
failures.append(f"{material_path_name} should include foliage color vectors")
|
||||
|
||||
|
||||
def main():
|
||||
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
|
||||
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
||||
@@ -143,10 +196,31 @@ def main():
|
||||
}
|
||||
for property_name, material_key in component_expectations.items():
|
||||
component = foliage.get_editor_property(property_name)
|
||||
static_mesh = component.get_editor_property("static_mesh")
|
||||
mesh_path = asset_path(static_mesh)
|
||||
expected_mesh = EXPECTED_FOLIAGE_MESHES[property_name]
|
||||
if mesh_path != expected_mesh:
|
||||
failures.append(f"{property_name} mesh expected {expected_mesh}, got {mesh_path}")
|
||||
if mesh_path.startswith(FORBIDDEN_FOLIAGE_MESH_PREFIXES):
|
||||
failures.append(f"{property_name} still uses placeholder/basic mesh {mesh_path}")
|
||||
vertex_count = static_mesh_vertex_count(static_mesh)
|
||||
if vertex_count <= 0:
|
||||
failures.append(f"{property_name} mesh {mesh_path} has no renderable vertices")
|
||||
|
||||
assigned = material_path(component.get_material(0))
|
||||
expected = MATERIALS[material_key]
|
||||
if assigned != expected:
|
||||
failures.append(f"{property_name} material expected {expected}, got {assigned}")
|
||||
verify_foliage_material_has_variation(expected, failures)
|
||||
|
||||
start_cull, end_cull = EXPECTED_FOLIAGE_CULL_DISTANCES[property_name]
|
||||
actual_start = int(get_component_property(component, "instance_start_cull_distance", -1))
|
||||
actual_end = int(get_component_property(component, "instance_end_cull_distance", -1))
|
||||
if actual_start != start_cull or actual_end != end_cull:
|
||||
failures.append(
|
||||
f"{property_name} cull distances expected {start_cull}/{end_cull}, "
|
||||
f"got {actual_start}/{actual_end}"
|
||||
)
|
||||
|
||||
checked_resource_actors = 0
|
||||
for actor in actors:
|
||||
@@ -211,9 +285,11 @@ def main():
|
||||
for path, snippet in [
|
||||
(docs, "asset variation"),
|
||||
(docs, "procedural coastal scrub terrain material"),
|
||||
(docs, "native low-poly coastal scrub vegetation meshes"),
|
||||
(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."),
|
||||
(roadmap, "[x] Replace or upgrade grasses, shrubs, and trees with believable coastal-scrub vegetation assets"),
|
||||
]:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
|
||||
Reference in New Issue
Block a user