Validate Ground Zero safe spawn area

This commit is contained in:
2026-05-16 10:44:08 -07:00
parent 56f8ef9bae
commit e50745dbdd
5 changed files with 251 additions and 5 deletions
+91 -2
View File
@@ -93,6 +93,25 @@ RESOURCE_MATERIAL_BY_LABEL_PREFIX = {
"AGR_GZ_FreshWaterSource": "fresh_water",
}
SAFE_SPAWN_CONFIG = {
"player_start_label": "AGR_DemoPlayerStart",
"safe_z_offset": 220.0,
"min_elevation_m": 2.0,
"max_slope_degrees": 8.0,
"slope_sample_distance_cm": 500.0,
"min_water_distance_cm": 10000.0,
"min_resource_distance_cm": 5000.0,
"fallback_location_xy": unreal.Vector(-22000.0, -3500.0, 0.0),
"candidate_locations_xy": [
unreal.Vector(-22000.0, -3500.0, 0.0),
unreal.Vector(-25000.0, -4000.0, 0.0),
unreal.Vector(-20500.0, -10500.0, 0.0),
unreal.Vector(-28000.0, -9000.0, 0.0),
unreal.Vector(-30000.0, -12000.0, 0.0),
unreal.Vector(-35000.0, 0.0, 0.0),
],
}
DEMO_ACTORS = [
{
@@ -597,6 +616,18 @@ def terrain_elevation_m(height_values, x_cm, y_cm):
return terrain_z_cm(height_values, x_cm, y_cm) / 100.0
def terrain_slope_degrees(height_values, x_cm, y_cm, sample_distance_cm):
dz_dx_m = (
terrain_elevation_m(height_values, x_cm + sample_distance_cm, y_cm)
- terrain_elevation_m(height_values, x_cm - sample_distance_cm, y_cm)
) / ((sample_distance_cm * 2.0) / 100.0)
dz_dy_m = (
terrain_elevation_m(height_values, x_cm, y_cm + sample_distance_cm)
- terrain_elevation_m(height_values, x_cm, y_cm - sample_distance_cm)
) / ((sample_distance_cm * 2.0) / 100.0)
return math.degrees(math.atan(math.sqrt((dz_dx_m * dz_dx_m) + (dz_dy_m * dz_dy_m))))
def remove_existing_demo_actors(labels):
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
if get_actor_label(actor) in labels:
@@ -607,6 +638,60 @@ def distance_2d(a, b):
return math.hypot(a.x - b.x, a.y - b.y)
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)
slope_degrees = terrain_slope_degrees(
height_values,
location_xy.x,
location_xy.y,
SAFE_SPAWN_CONFIG["slope_sample_distance_cm"],
)
min_resource_distance_cm = min(distance_2d(location_xy, point) for point in resource_points)
min_water_distance_cm = min(distance_2d(location_xy, point) for point in water_points)
if elevation_m < SAFE_SPAWN_CONFIG["min_elevation_m"]:
failures.append(f"elevation {elevation_m:.2f}m below minimum")
if slope_degrees > SAFE_SPAWN_CONFIG["max_slope_degrees"]:
failures.append(f"slope {slope_degrees:.2f}deg above maximum")
if min_resource_distance_cm < SAFE_SPAWN_CONFIG["min_resource_distance_cm"]:
failures.append(f"resource distance {min_resource_distance_cm:.0f}cm below minimum")
if min_water_distance_cm < SAFE_SPAWN_CONFIG["min_water_distance_cm"]:
failures.append(f"water distance {min_water_distance_cm:.0f}cm below minimum")
return failures
def select_safe_spawn_location(height_values):
resource_points = [spec["location_xy"] for spec in BIOME_RESOURCE_ACTORS]
resource_points.extend(
spec["location_xy"]
for spec in DEMO_ACTORS
if spec["label"] in {"AGR_DemoWoodResource_01", "AGR_DemoFiberResource_01"}
)
water_points = [spec["location_xy"] for spec in WATER_SOURCE_ACTORS]
candidates = list(SAFE_SPAWN_CONFIG["candidate_locations_xy"])
fallback = SAFE_SPAWN_CONFIG["fallback_location_xy"]
if all(distance_2d(fallback, candidate) > 1.0 for candidate in candidates):
candidates.append(fallback)
rejected = []
for candidate in candidates:
failures = validate_safe_spawn_location(height_values, candidate, resource_points, water_points)
if not failures:
unreal.log(
"Selected safe Ground Zero spawn at "
f"({candidate.x:.0f}, {candidate.y:.0f}) "
f"elevation {terrain_elevation_m(height_values, candidate.x, candidate.y):.2f}m "
f"slope {terrain_slope_degrees(height_values, candidate.x, candidate.y, SAFE_SPAWN_CONFIG['slope_sample_distance_cm']):.2f}deg"
)
return candidate
rejected.append(f"({candidate.x:.0f}, {candidate.y:.0f}): {', '.join(failures)}")
raise RuntimeError("No safe Ground Zero spawn candidate found. Rejected: " + "; ".join(rejected))
def make_foliage_transform(height_values, x, y, yaw, z_offset, scale_xy, scale_z):
return unreal.Transform(
location=unreal.Vector(x, y, terrain_z_cm(height_values, x, y) + z_offset),
@@ -720,8 +805,11 @@ def spawn_foliage_actor(height_values, materials):
return foliage_actor
def spawn_demo_actor(spec, height_values, materials):
def spawn_demo_actor(spec, height_values, materials, safe_spawn_location_xy=None):
location_xy = spec["location_xy"]
if spec["label"] == SAFE_SPAWN_CONFIG["player_start_label"] and safe_spawn_location_xy is not None:
location_xy = safe_spawn_location_xy
z = spec.get("fixed_z")
if z is None:
z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0)
@@ -811,6 +899,7 @@ def main():
materials = ensure_environment_materials()
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)
for spec in BIOME_RESOURCE_ACTORS:
spawn_demo_actor(spec, height_values, materials)
@@ -823,7 +912,7 @@ def main():
for spec in RUIN_PLACEHOLDER_ACTORS:
spawn_environment_variation_actor(spec, height_values, materials)
for spec in DEMO_ACTORS:
spawn_demo_actor(spec, height_values, materials)
spawn_demo_actor(spec, height_values, materials, safe_spawn_location_xy)
unreal.EditorLevelLibrary.save_current_level()
unreal.log("Ground Zero demo map setup complete.")