Tune Ground Zero foliage sightlines

This commit is contained in:
2026-05-19 11:07:46 -07:00
parent d0c1e22d98
commit b2e315a510
5 changed files with 339 additions and 15 deletions
+103 -12
View File
@@ -629,6 +629,37 @@ FOLIAGE_ZONES = {
},
}
SIGHTLINE_TUNING_CONFIG = {
"critical_clearance_cm": {
"trees": 5200.0,
"shrubs": 3400.0,
"grass": 1250.0,
},
"corridor_clearance_cm": {
"trees": 3600.0,
"shrubs": 2100.0,
"grass": 850.0,
},
"corridor_sample_spacing_cm": 1600.0,
"critical_actor_labels": {
"AGR_DemoPlayerStart",
"AGR_DemoWoodResource_01",
"AGR_DemoFiberResource_01",
"AGR_DemoCampfire_01",
"AGR_DemoPrimitiveShelter_01",
"AGR_DemoRabbitWildlife_01",
"AGR_GZ_FreshWaterSource_01",
},
"sightline_pairs": [
("AGR_DemoPlayerStart", "AGR_DemoWoodResource_01"),
("AGR_DemoPlayerStart", "AGR_DemoFiberResource_01"),
("AGR_DemoPlayerStart", "AGR_DemoCampfire_01"),
("AGR_DemoPlayerStart", "AGR_DemoPrimitiveShelter_01"),
("AGR_DemoPlayerStart", "AGR_DemoRabbitWildlife_01"),
("AGR_DemoPlayerStart", "AGR_GZ_FreshWaterSource_01"),
],
}
def get_actor_label(actor):
try:
@@ -796,6 +827,42 @@ def distance_2d(a, b):
return math.hypot(a.x - b.x, a.y - b.y)
def reserved_point(point, source_label, clearances):
return {
"point": point,
"source_label": source_label,
"clearances": clearances,
}
def reserved_point_clearance(reservation, foliage_family):
return reservation["clearances"].get(foliage_family, 0.0)
def add_sightline_corridor_reservations(reservations, label_points):
spacing_cm = SIGHTLINE_TUNING_CONFIG["corridor_sample_spacing_cm"]
corridor_clearances = SIGHTLINE_TUNING_CONFIG["corridor_clearance_cm"]
for start_label, end_label in SIGHTLINE_TUNING_CONFIG["sightline_pairs"]:
start = label_points.get(start_label)
end = label_points.get(end_label)
if not start or not end:
continue
length_cm = distance_2d(start, end)
sample_count = max(1, int(length_cm / spacing_cm))
for index in range(1, sample_count):
alpha = index / sample_count
point = unreal.Vector(
start.x + ((end.x - start.x) * alpha),
start.y + ((end.y - start.y) * alpha),
0.0,
)
reservations.append(
reserved_point(point, f"{start_label}->{end_label}", corridor_clearances)
)
def validate_safe_spawn_location(height_values, location_xy, resource_points, water_points):
failures = []
elevation_m = terrain_elevation_m(height_values, location_xy.x, location_xy.y)
@@ -882,7 +949,7 @@ def apply_foliage_materials(foliage_actor, materials):
component.set_material(0, material)
def choose_foliage_points(height_values, zone, reserved_points, existing_points):
def choose_foliage_points(height_values, family_name, zone, reservations, existing_points):
rng = random.Random(FOLIAGE_RANDOM_SEED + len(existing_points) + int(zone["count"]))
chosen = []
attempts = 0
@@ -897,7 +964,10 @@ def choose_foliage_points(height_values, zone, reserved_points, existing_points)
if terrain_elevation_m(height_values, x, y) < zone["min_elevation_m"]:
continue
if any(distance_2d(point, reserved) < zone["avoid_radius_cm"] for reserved in reserved_points):
if any(
distance_2d(point, reservation["point"]) < reserved_point_clearance(reservation, family_name)
for reservation in reservations
):
continue
if any(distance_2d(point, existing) < zone["avoid_radius_cm"] * 0.55 for existing in existing_points):
@@ -912,12 +982,33 @@ def choose_foliage_points(height_values, zone, reserved_points, existing_points)
return chosen
def spawn_foliage_actor(height_values, materials):
reserved_points = [
spec["location_xy"]
for spec in DEMO_ACTORS
if spec["label"] not in {"AGR_DemoSkyLightingController", "AGR_DemoWeatherAudioController", "AGR_DemoNoticeActor"}
]
def build_foliage_reservations(safe_spawn_location_xy):
label_points = {}
specs = DEMO_ACTORS + BIOME_RESOURCE_ACTORS + WATER_SOURCE_ACTORS
for spec in specs:
label = spec["label"]
location_xy = spec["location_xy"]
if label == SAFE_SPAWN_CONFIG["player_start_label"] and safe_spawn_location_xy is not None:
location_xy = safe_spawn_location_xy
label_points[label] = location_xy
reservations = []
critical_clearances = SIGHTLINE_TUNING_CONFIG["critical_clearance_cm"]
for label in SIGHTLINE_TUNING_CONFIG["critical_actor_labels"]:
point = label_points.get(label)
if point:
reservations.append(reserved_point(point, label, critical_clearances))
for spec in BIOME_RESOURCE_ACTORS:
reservations.append(reserved_point(spec["location_xy"], spec["label"], critical_clearances))
add_sightline_corridor_reservations(reservations, label_points)
unreal.log(f"Prepared {len(reservations)} Ground Zero foliage sightline reservation point(s).")
return reservations
def spawn_foliage_actor(height_values, materials, safe_spawn_location_xy):
reservations = build_foliage_reservations(safe_spawn_location_xy)
foliage_actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world(
unreal.AgrarianFoliagePatch,
@@ -936,19 +1027,19 @@ def spawn_foliage_actor(height_values, materials):
existing_points = []
rng = random.Random(FOLIAGE_RANDOM_SEED)
for point in choose_foliage_points(height_values, FOLIAGE_ZONES["trees"], reserved_points, existing_points):
for point in choose_foliage_points(height_values, "trees", FOLIAGE_ZONES["trees"], reservations, existing_points):
scale = rng.uniform(*FOLIAGE_ZONES["trees"]["scale_range"])
foliage_actor.add_tree_instance(
make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 5.0, scale, scale * 5.5)
)
for point in choose_foliage_points(height_values, FOLIAGE_ZONES["shrubs"], reserved_points, existing_points):
for point in choose_foliage_points(height_values, "shrubs", FOLIAGE_ZONES["shrubs"], reservations, existing_points):
scale = rng.uniform(*FOLIAGE_ZONES["shrubs"]["scale_range"])
foliage_actor.add_shrub_instance(
make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 4.0, scale * 2.2, scale * 0.85)
)
for point in choose_foliage_points(height_values, FOLIAGE_ZONES["grass"], reserved_points, existing_points):
for point in choose_foliage_points(height_values, "grass", FOLIAGE_ZONES["grass"], reservations, existing_points):
scale = rng.uniform(*FOLIAGE_ZONES["grass"]["scale_range"])
foliage_actor.add_grass_instance(
make_foliage_transform(height_values, point.x, point.y, rng.uniform(0.0, 360.0), 2.0, scale * 0.28, scale * 1.6)
@@ -1091,7 +1182,7 @@ def main():
apply_landscape_material(materials["terrain"])
height_values = load_heightmap()
safe_spawn_location_xy = select_safe_spawn_location(height_values)
spawn_foliage_actor(height_values, materials)
spawn_foliage_actor(height_values, materials, safe_spawn_location_xy)
for spec in BIOME_RESOURCE_ACTORS:
spawn_demo_actor(spec, height_values, materials)
for spec in WATER_SOURCE_ACTORS: