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)
|
||||
|
||||
Reference in New Issue
Block a user