Upgrade Ground Zero vegetation assets
This commit is contained in:
@@ -18,6 +18,8 @@ LANDSCAPE_MIN_XY = -50000.0
|
||||
FOLIAGE_LABEL = "AGR_GroundZeroFoliage_FirstPass"
|
||||
FOLIAGE_RANDOM_SEED = 4160544
|
||||
PLACEHOLDER_MESH_FOLDER = "/Game/Agrarian/Environment/PlaceholderMeshes"
|
||||
VEGETATION_MESH_FOLDER = "/Game/Agrarian/Environment/Vegetation"
|
||||
VEGETATION_SOURCE_FOLDER = PROJECT_ROOT / "Saved" / "CodexGenerated" / "Vegetation"
|
||||
PLACEHOLDER_MESH_SOURCES = {
|
||||
"SM_AGR_Placeholder_Cube": "/Game/LevelPrototyping/Meshes/SM_Cube",
|
||||
"SM_AGR_Placeholder_ChamferCube": "/Game/LevelPrototyping/Meshes/SM_ChamferCube",
|
||||
@@ -30,9 +32,9 @@ PLACEHOLDER_MESHES = {
|
||||
for name in PLACEHOLDER_MESH_SOURCES
|
||||
}
|
||||
FOLIAGE_MESHES = {
|
||||
"tree": "/Engine/BasicShapes/Cone",
|
||||
"shrub": "/Engine/BasicShapes/Sphere",
|
||||
"grass": "/Engine/BasicShapes/Plane",
|
||||
"tree": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_CoastalOak_Proxy",
|
||||
"shrub": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_CoyoteBrush_Proxy",
|
||||
"grass": f"{VEGETATION_MESH_FOLDER}/SM_AGR_GZ_DryGrassClump_Proxy",
|
||||
}
|
||||
VARIATION_MESHES = {
|
||||
"cube": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cube"],
|
||||
@@ -40,8 +42,9 @@ VARIATION_MESHES = {
|
||||
"cylinder": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Cylinder"],
|
||||
"quarter_cylinder": PLACEHOLDER_MESHES["SM_AGR_Placeholder_QuarterCylinder"],
|
||||
"plane": PLACEHOLDER_MESHES["SM_AGR_Placeholder_Plane"],
|
||||
"sphere": "/Engine/BasicShapes/Sphere",
|
||||
"cone": "/Engine/BasicShapes/Cone",
|
||||
"coastal_oak": FOLIAGE_MESHES["tree"],
|
||||
"coyote_brush": FOLIAGE_MESHES["shrub"],
|
||||
"dry_grass_clump": FOLIAGE_MESHES["grass"],
|
||||
}
|
||||
MATERIAL_FOLDER = "/Game/Agrarian/Materials"
|
||||
ENVIRONMENT_MATERIALS = {
|
||||
@@ -57,20 +60,25 @@ ENVIRONMENT_MATERIALS = {
|
||||
"tree": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Tree_CoastalOak",
|
||||
"color": unreal.LinearColor(0.07, 0.18, 0.06, 1.0),
|
||||
"variation_color": unreal.LinearColor(0.14, 0.24, 0.09, 1.0),
|
||||
"roughness": 0.88,
|
||||
"used_with_instanced_static_meshes": True,
|
||||
},
|
||||
"shrub": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Shrub_CoyoteBrush",
|
||||
"color": unreal.LinearColor(0.15, 0.28, 0.10, 1.0),
|
||||
"variation_color": unreal.LinearColor(0.24, 0.34, 0.15, 1.0),
|
||||
"roughness": 0.9,
|
||||
"used_with_instanced_static_meshes": True,
|
||||
"two_sided": True,
|
||||
},
|
||||
"grass": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Grass_DryCoastal",
|
||||
"color": unreal.LinearColor(0.32, 0.34, 0.13, 1.0),
|
||||
"variation_color": unreal.LinearColor(0.52, 0.45, 0.22, 1.0),
|
||||
"roughness": 0.95,
|
||||
"used_with_instanced_static_meshes": True,
|
||||
"two_sided": True,
|
||||
},
|
||||
"wood_resource": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Wood_Resource",
|
||||
@@ -355,7 +363,7 @@ WEATHER_EXPOSURE_ZONES = [
|
||||
ENVIRONMENT_VARIATION_ACTORS = [
|
||||
{
|
||||
"label": "AGR_GZ_EnvVar_Tree_Canopy_01",
|
||||
"mesh_key": "sphere",
|
||||
"mesh_key": "coastal_oak",
|
||||
"material_key": "tree",
|
||||
"location_xy": unreal.Vector(-27500.0, 6900.0, 0.0),
|
||||
"z_offset": 390.0,
|
||||
@@ -373,7 +381,7 @@ ENVIRONMENT_VARIATION_ACTORS = [
|
||||
},
|
||||
{
|
||||
"label": "AGR_GZ_EnvVar_Tree_Canopy_02",
|
||||
"mesh_key": "sphere",
|
||||
"mesh_key": "coastal_oak",
|
||||
"material_key": "tree",
|
||||
"location_xy": unreal.Vector(17600.0, 31800.0, 0.0),
|
||||
"z_offset": 430.0,
|
||||
@@ -391,7 +399,7 @@ ENVIRONMENT_VARIATION_ACTORS = [
|
||||
},
|
||||
{
|
||||
"label": "AGR_GZ_EnvVar_Bush_Rounded_01",
|
||||
"mesh_key": "sphere",
|
||||
"mesh_key": "coyote_brush",
|
||||
"material_key": "shrub",
|
||||
"location_xy": unreal.Vector(-33400.0, -15200.0, 0.0),
|
||||
"z_offset": 70.0,
|
||||
@@ -400,7 +408,7 @@ ENVIRONMENT_VARIATION_ACTORS = [
|
||||
},
|
||||
{
|
||||
"label": "AGR_GZ_EnvVar_Bush_Rounded_02",
|
||||
"mesh_key": "sphere",
|
||||
"mesh_key": "coyote_brush",
|
||||
"material_key": "shrub",
|
||||
"location_xy": unreal.Vector(30400.0, -3900.0, 0.0),
|
||||
"z_offset": 75.0,
|
||||
@@ -783,6 +791,154 @@ def ensure_native_placeholder_meshes():
|
||||
unreal.log(f"Created native placeholder mesh: {destination_path}")
|
||||
|
||||
|
||||
def write_obj_mesh(path, vertices, faces):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as handle:
|
||||
handle.write("# Agrarian generated coastal scrub vegetation proxy\n")
|
||||
for vertex in vertices:
|
||||
handle.write(f"v {vertex[0]:.3f} {vertex[1]:.3f} {vertex[2]:.3f}\n")
|
||||
for face in faces:
|
||||
handle.write("f " + " ".join(str(index + 1) for index in face) + "\n")
|
||||
|
||||
|
||||
def add_box(vertices, faces, center, size):
|
||||
cx, cy, cz = center
|
||||
sx, sy, sz = size[0] * 0.5, size[1] * 0.5, size[2] * 0.5
|
||||
base = len(vertices)
|
||||
vertices.extend(
|
||||
[
|
||||
(cx - sx, cy - sy, cz - sz),
|
||||
(cx + sx, cy - sy, cz - sz),
|
||||
(cx + sx, cy + sy, cz - sz),
|
||||
(cx - sx, cy + sy, cz - sz),
|
||||
(cx - sx, cy - sy, cz + sz),
|
||||
(cx + sx, cy - sy, cz + sz),
|
||||
(cx + sx, cy + sy, cz + sz),
|
||||
(cx - sx, cy + sy, cz + sz),
|
||||
]
|
||||
)
|
||||
faces.extend(
|
||||
[
|
||||
(base + 0, base + 1, base + 2, base + 3),
|
||||
(base + 4, base + 7, base + 6, base + 5),
|
||||
(base + 0, base + 4, base + 5, base + 1),
|
||||
(base + 1, base + 5, base + 6, base + 2),
|
||||
(base + 2, base + 6, base + 7, base + 3),
|
||||
(base + 3, base + 7, base + 4, base + 0),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def add_leaf_card(vertices, faces, center, width, height, yaw_degrees, lean_degrees=0.0):
|
||||
yaw = math.radians(yaw_degrees)
|
||||
lean = math.radians(lean_degrees)
|
||||
right = (math.cos(yaw) * width * 0.5, math.sin(yaw) * width * 0.5, 0.0)
|
||||
up_offset = (math.sin(lean) * height * 0.35, 0.0, math.cos(lean) * height)
|
||||
cx, cy, cz = center
|
||||
base = len(vertices)
|
||||
vertices.extend(
|
||||
[
|
||||
(cx - right[0], cy - right[1], cz),
|
||||
(cx + right[0], cy + right[1], cz),
|
||||
(cx + right[0] + up_offset[0], cy + right[1] + up_offset[1], cz + up_offset[2]),
|
||||
(cx - right[0] + up_offset[0], cy - right[1] + up_offset[1], cz + up_offset[2]),
|
||||
]
|
||||
)
|
||||
faces.append((base + 0, base + 1, base + 2, base + 3))
|
||||
|
||||
|
||||
def add_low_poly_ellipsoid(vertices, faces, center, radius_x, radius_y, radius_z, segments=10):
|
||||
cx, cy, cz = center
|
||||
top_index = len(vertices)
|
||||
vertices.append((cx, cy, cz + radius_z))
|
||||
upper = []
|
||||
lower = []
|
||||
for ring_z, ring_radius_scale, target in ((0.18, 0.9, upper), (-0.25, 1.0, lower)):
|
||||
for index in range(segments):
|
||||
angle = (math.tau * index) / segments
|
||||
target.append(len(vertices))
|
||||
vertices.append(
|
||||
(
|
||||
cx + math.cos(angle) * radius_x * ring_radius_scale,
|
||||
cy + math.sin(angle) * radius_y * ring_radius_scale,
|
||||
cz + (radius_z * ring_z),
|
||||
)
|
||||
)
|
||||
bottom_index = len(vertices)
|
||||
vertices.append((cx, cy, cz - radius_z * 0.45))
|
||||
for index in range(segments):
|
||||
next_index = (index + 1) % segments
|
||||
faces.append((top_index, upper[index], upper[next_index]))
|
||||
faces.append((upper[index], lower[index], lower[next_index], upper[next_index]))
|
||||
faces.append((bottom_index, lower[next_index], lower[index]))
|
||||
|
||||
|
||||
def coastal_oak_mesh():
|
||||
vertices = []
|
||||
faces = []
|
||||
add_box(vertices, faces, (0.0, 0.0, 140.0), (36.0, 30.0, 280.0))
|
||||
add_box(vertices, faces, (-34.0, 14.0, 250.0), (18.0, 16.0, 125.0))
|
||||
add_box(vertices, faces, (42.0, -12.0, 275.0), (18.0, 16.0, 115.0))
|
||||
add_low_poly_ellipsoid(vertices, faces, (0.0, 0.0, 350.0), 150.0, 125.0, 105.0)
|
||||
add_low_poly_ellipsoid(vertices, faces, (-95.0, 30.0, 320.0), 105.0, 82.0, 78.0)
|
||||
add_low_poly_ellipsoid(vertices, faces, (95.0, -20.0, 330.0), 112.0, 88.0, 82.0)
|
||||
return vertices, faces
|
||||
|
||||
|
||||
def coyote_brush_mesh():
|
||||
vertices = []
|
||||
faces = []
|
||||
for yaw in (0.0, 35.0, 82.0, 128.0, 171.0):
|
||||
add_leaf_card(vertices, faces, (0.0, 0.0, 0.0), 190.0, 145.0, yaw, 8.0)
|
||||
add_low_poly_ellipsoid(vertices, faces, (-32.0, 18.0, 78.0), 92.0, 68.0, 56.0, 8)
|
||||
add_low_poly_ellipsoid(vertices, faces, (48.0, -16.0, 70.0), 86.0, 70.0, 50.0, 8)
|
||||
add_low_poly_ellipsoid(vertices, faces, (0.0, 42.0, 62.0), 78.0, 52.0, 45.0, 8)
|
||||
return vertices, faces
|
||||
|
||||
|
||||
def dry_grass_clump_mesh():
|
||||
vertices = []
|
||||
faces = []
|
||||
for index, yaw in enumerate((0.0, 22.0, 47.0, 76.0, 111.0, 146.0, 178.0)):
|
||||
width = 18.0 + (index % 3) * 4.0
|
||||
height = 95.0 + (index % 4) * 14.0
|
||||
add_leaf_card(vertices, faces, (0.0, 0.0, 0.0), width, height, yaw, -5.0 + (index % 3) * 5.0)
|
||||
return vertices, faces
|
||||
|
||||
|
||||
def import_static_mesh_obj(obj_path, destination_folder, asset_name):
|
||||
task = unreal.AssetImportTask()
|
||||
task.set_editor_property("filename", str(obj_path))
|
||||
task.set_editor_property("destination_path", destination_folder)
|
||||
task.set_editor_property("destination_name", asset_name)
|
||||
task.set_editor_property("automated", True)
|
||||
task.set_editor_property("replace_existing", True)
|
||||
task.set_editor_property("save", True)
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
asset_path = f"{destination_folder}/{asset_name}"
|
||||
asset = unreal.EditorAssetLibrary.load_asset(asset_path)
|
||||
if not asset:
|
||||
raise RuntimeError(f"Could not import vegetation mesh asset: {asset_path}")
|
||||
return asset
|
||||
|
||||
|
||||
def ensure_ground_zero_vegetation_meshes():
|
||||
if not unreal.EditorAssetLibrary.does_directory_exist(VEGETATION_MESH_FOLDER):
|
||||
unreal.EditorAssetLibrary.make_directory(VEGETATION_MESH_FOLDER)
|
||||
|
||||
mesh_builders = {
|
||||
"SM_AGR_GZ_CoastalOak_Proxy": coastal_oak_mesh,
|
||||
"SM_AGR_GZ_CoyoteBrush_Proxy": coyote_brush_mesh,
|
||||
"SM_AGR_GZ_DryGrassClump_Proxy": dry_grass_clump_mesh,
|
||||
}
|
||||
for asset_name, builder in mesh_builders.items():
|
||||
vertices, faces = builder()
|
||||
obj_path = VEGETATION_SOURCE_FOLDER / f"{asset_name}.obj"
|
||||
write_obj_mesh(obj_path, vertices, faces)
|
||||
import_static_mesh_obj(obj_path, VEGETATION_MESH_FOLDER, asset_name)
|
||||
unreal.log(f"Created or refreshed Ground Zero vegetation mesh: {VEGETATION_MESH_FOLDER}/{asset_name}")
|
||||
|
||||
|
||||
def ensure_environment_materials():
|
||||
if not unreal.EditorAssetLibrary.does_directory_exist(MATERIAL_FOLDER):
|
||||
unreal.EditorAssetLibrary.make_directory(MATERIAL_FOLDER)
|
||||
@@ -843,6 +999,8 @@ def ensure_environment_materials():
|
||||
unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False)
|
||||
if key == "terrain":
|
||||
rebuild_ground_zero_terrain_material(material, spec)
|
||||
if key in {"tree", "shrub", "grass"}:
|
||||
rebuild_ground_zero_foliage_material(material, spec)
|
||||
created_or_loaded[key] = material
|
||||
|
||||
return created_or_loaded
|
||||
@@ -937,6 +1095,43 @@ def rebuild_ground_zero_terrain_material(material, spec):
|
||||
unreal.log("Rebuilt Ground Zero terrain material with coastal scrub color variation and procedural noise.")
|
||||
|
||||
|
||||
def rebuild_ground_zero_foliage_material(material, spec):
|
||||
"""Use per-instance color variation so repeated foliage clumps do not tile visually."""
|
||||
if hasattr(unreal.MaterialEditingLibrary, "delete_all_material_expressions"):
|
||||
unreal.MaterialEditingLibrary.delete_all_material_expressions(material)
|
||||
|
||||
color_a = create_constant_color(material, spec["color"], -720, -160)
|
||||
color_b = create_constant_color(material, spec["variation_color"], -720, 20)
|
||||
blend = unreal.MaterialEditingLibrary.create_material_expression(
|
||||
material, unreal.MaterialExpressionLinearInterpolate, -360, -80
|
||||
)
|
||||
connect_expression(color_a, "", blend, "A")
|
||||
connect_expression(color_b, "", blend, "B")
|
||||
|
||||
random_expression_class = getattr(unreal, "MaterialExpressionPerInstanceRandom", None)
|
||||
if random_expression_class:
|
||||
random_expression = unreal.MaterialEditingLibrary.create_material_expression(
|
||||
material, random_expression_class, -600, 170
|
||||
)
|
||||
connect_expression(random_expression, "", blend, "Alpha")
|
||||
|
||||
unreal.MaterialEditingLibrary.connect_material_property(
|
||||
blend, "", unreal.MaterialProperty.MP_BASE_COLOR
|
||||
)
|
||||
|
||||
roughness = create_constant_scalar(material, spec["roughness"], -360, 140)
|
||||
unreal.MaterialEditingLibrary.connect_material_property(
|
||||
roughness, "", unreal.MaterialProperty.MP_ROUGHNESS
|
||||
)
|
||||
|
||||
material.set_editor_property("use_material_attributes", False)
|
||||
material.set_editor_property("used_with_instanced_static_meshes", True)
|
||||
if spec.get("two_sided"):
|
||||
material.set_editor_property("two_sided", True)
|
||||
unreal.MaterialEditingLibrary.recompile_material(material)
|
||||
unreal.EditorAssetLibrary.save_asset(spec["path"], only_if_is_dirty=False)
|
||||
|
||||
|
||||
def apply_material_to_actor_meshes(actor, material):
|
||||
applied_count = 0
|
||||
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
|
||||
@@ -1363,6 +1558,7 @@ def main():
|
||||
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
||||
|
||||
ensure_native_placeholder_meshes()
|
||||
ensure_ground_zero_vegetation_meshes()
|
||||
|
||||
labels = {spec["label"] for spec in DEMO_ACTORS}
|
||||
labels.update(LEGACY_DEMO_LIGHTING_LABELS)
|
||||
|
||||
@@ -7,9 +7,9 @@ import unreal
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
FOLIAGE_LABEL = "AGR_GroundZeroFoliage_FirstPass"
|
||||
EXPECTED_FOLIAGE_COUNTS = {
|
||||
"trees": 64,
|
||||
"shrubs": 148,
|
||||
"grass": 260,
|
||||
"trees": 96,
|
||||
"shrubs": 220,
|
||||
"grass": 420,
|
||||
}
|
||||
CRITICAL_CLEARANCE_CM = {
|
||||
"trees": 5200.0,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -6,6 +6,7 @@ import unreal
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
PROJECT_ROOT = Path(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()))
|
||||
PLACEHOLDER_MESH_FOLDER = "/Game/Agrarian/Environment/PlaceholderMeshes"
|
||||
VEGETATION_MESH_FOLDER = "/Game/Agrarian/Environment/Vegetation"
|
||||
PLACEHOLDER_MESHES = {
|
||||
"SM_AGR_Placeholder_Cube",
|
||||
"SM_AGR_Placeholder_ChamferCube",
|
||||
@@ -47,6 +48,13 @@ def assert_native_mesh(path, failures):
|
||||
failures.append(f"template mesh reference remains: {path}")
|
||||
|
||||
|
||||
def assert_agrarian_environment_mesh(path, failures):
|
||||
if not path.startswith((PLACEHOLDER_MESH_FOLDER, VEGETATION_MESH_FOLDER)):
|
||||
failures.append(f"expected native Agrarian environment mesh, got {path}")
|
||||
if "LevelPrototyping" in path or path.startswith("/Engine/BasicShapes/"):
|
||||
failures.append(f"template/basic mesh reference remains: {path}")
|
||||
|
||||
|
||||
def main():
|
||||
failures = []
|
||||
|
||||
@@ -79,7 +87,7 @@ def main():
|
||||
else:
|
||||
for property_name in ("tree_instances", "shrub_instances", "grass_instances"):
|
||||
component = foliage_actors[0].get_editor_property(property_name)
|
||||
assert_native_mesh(asset_path(component.get_editor_property("static_mesh")), failures)
|
||||
assert_agrarian_environment_mesh(asset_path(component.get_editor_property("static_mesh")), failures)
|
||||
|
||||
for actor in actors:
|
||||
label = get_actor_label(actor)
|
||||
@@ -90,7 +98,7 @@ def main():
|
||||
if not mesh_components:
|
||||
failures.append(f"{label} has no static mesh component")
|
||||
continue
|
||||
assert_native_mesh(asset_path(mesh_components[0].get_editor_property("static_mesh")), failures)
|
||||
assert_agrarian_environment_mesh(asset_path(mesh_components[0].get_editor_property("static_mesh")), failures)
|
||||
|
||||
for path, snippet in DOC_SNIPPETS:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user