gameplay: designated street crossing — zebra markings, corridor cut, up-facing street

- street_mesh.py: continental zebra markings (0.5 m bars, 0.5 m gaps, 3 m
  band) at all three signalised crossings, triangles not quads (FBX SDK
  refuses to triangulate the crown-twisted quads), stripes +6 cm above the
  DEM so they stay above the road surface; winding flipped so every street
  surface faces up (road/sidewalks/markings were 100% down-facing and
  invisible in engine once the terrain stopped covering them).
- terrain_from_dem.py: cut a 10.7 m corridor around the Hollywood Blvd
  centreline so road, kerbs and sidewalks are not buried under the terrain;
  sidewalks themselves cover the transition band, walk-check stays at 0 holes.
- Blender builders save with a writability guard + fatal failure: *.blend
  is LFS-lockable and checks out read-only; a failed save previously left
  downstream steps building from stale files.
- ue_import_site.py: Crossing_Markings imports with no collision (paint on
  the road); ue_build_demo_map.py stages it and chmods the lockable .umap
  before saving (save_map silently returned False on read-only maps).
- ue_build_review_map.py: demo-crossing camera moved to the real crossing
  (s=130.1 m -> UE -24291,-1340) and fixed the ensure_writable path (was
  missing the .umap suffix, so the chmod never fired).
This commit is contained in:
zwitschi committed 2026-11-07 15:46:45 +01:00
1 parent 985dfcbac7
commit bc2e421d33
20 files changed
+133 -36

No files matched your search

+9 -1
View File
@@ -15,6 +15,8 @@
# Every actor is (re)created by label, so rerunning the script rebuilds the map
# deterministically instead of stacking duplicates.
import os
import unreal
MAP = "/Game/Hollywood/DemoMap"
@@ -83,7 +85,7 @@ def build():
# --- site meshes, if they have been imported --------------------------
imported = 0
for name in ("Terrain_DEM", "Road_Hollywood", "Sidewalk_South", "Sidewalk_North",
"Kerb_South", "Kerb_North"):
"Kerb_South", "Kerb_North", "Crossing_Markings"):
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
if mesh is None:
continue
@@ -103,6 +105,12 @@ def build():
unreal.Rotator(pitch=0.0, yaw=0.0, roll=0.0))
log("staged: Demo_PlayerStart on the north sidewalk facing south")
# A lockable LFS .umap checks out read-only, which makes save_map silently
# return False — chmod u+w first or the staged actors never persist.
umap = os.path.join(unreal.Paths.project_dir(), MAP.replace("/Game/", "Content/") + ".umap")
if os.path.exists(umap) and not os.access(umap, os.W_OK):
os.chmod(umap, 0o644)
log(f"map writable: chmod u+w {umap}")
saved = unreal.EditorLoadingAndSavingUtils.save_map(
unreal.EditorLevelLibrary.get_editor_world(), MAP)
log(f"save_map {MAP}: {saved}")
+2 -2
View File
@@ -29,7 +29,7 @@ def ensure_writable(path):
# A packed .umap left read-only (git sets 644->444 on checkout, or the
# editor re-flags it) makes save_map silently return False with no error.
# chmod u+w it so the staged camera list and actors actually persist.
p = os.path.join(unreal.Paths.project_dir(), path.replace("/Game/", "Content/"))
p = os.path.join(unreal.Paths.project_dir(), path.replace("/Game/", "Content/") + ".umap")
try:
mode = os.stat(p).st_mode & 0o777
if mode & 0o200 == 0:
@@ -229,7 +229,7 @@ try:
# the colonnade's underside.
("demo-spawn-view", unreal.Vector(-10722.5, -3500, 165), unreal.Vector(-10722.5, -1450, 700)),
("demo-street-level", unreal.Vector(-11000, -400, 165), unreal.Vector(-10722.5, -1450, 500)),
("demo-crossing", unreal.Vector(-13600, -1650, 165), unreal.Vector(-12722.5, -1450, 500)),
("demo-crossing", unreal.Vector(-23750, -1800, 480), unreal.Vector(-24291, -1400, 300)),
# the two north-side background buildings (TCL, Tussaud's) seen from
# the south sidewalk, across Hollywood Blvd: their frontage and mass.
("demo-north-buildings", unreal.Vector(-22800, -1000, 400), unreal.Vector(-22800, 3000, 300)),
+18 -4
View File
@@ -27,7 +27,10 @@ def log(msg):
unreal.log(str(msg))
def import_fbx(path, name, collision):
def import_fbx(path, name, collision="complex"):
# 5.8 note: there is no FBX import 'no collision' switch exposed to Python;
# a default import simply generates no collision primitives, so collision="none"
# means: skip the trace-flag step below and leave the body setup empty.
task = unreal.AssetImportTask()
task.filename = path
task.destination_path = DEST
@@ -58,10 +61,16 @@ def import_fbx(path, name, collision):
# something to stand on. Terrain and street get complex-as-simple: a simple
# hull would ramp straight over the kerbs.
#
# UE 5.8 exposes this only through StaticMeshEditorSubsystem (the mesh itself
# UE 5.8 exposes this only through the body setup's trace flag (the mesh itself
# has no trace-flag property, and the subsystem has no setter in Python), so
# the import path sets the body setup's trace flag through the mesh's
# collision options at import, then verifies it by reading it back.
# the import path sets the body setup's trace flag, then verifies it by reading
# it back.
if collision == "none":
box = mesh.get_bounding_box()
mn, mx = box.min, box.max
log(f"{name:18} import ok size=({mx.x - mn.x:.0f}, {mx.y - mn.y:.0f}, {mx.z - mn.z:.0f}) cm "
f"min_z={mn.z:.1f} collision=none")
return
trace = (unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE
if collision == "complex" else unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
try:
@@ -83,6 +92,11 @@ def main():
return
for path in files:
stem = os.path.splitext(os.path.basename(path))[0]
# Crossing markings are paint on the road surface — no collision at all,
# so the player never stands on a 1 cm lip at the band edges.
if stem == "Crossing_Markings":
import_fbx(path, stem, "none")
continue
collision = "complex" if stem in ("Terrain_DEM", "Road_Hollywood",
"Sidewalk_South", "Sidewalk_North") else "simple"
import_fbx(path, stem, collision)