feat(hero): facade rework — inscription, medallions, motto band, banner lettering #39

Merged
zwitschi merged 93 commits from hero-facade-0.3.0 into main 2026-11-10 15:37:40 +01:00
84 changed files with 180 additions and 143 deletions
Showing only changes of commit d0d87e8fda - Show all commits

No files matched your search

+34 -4
View File
@@ -30,6 +30,19 @@ SEGMENTED = ("Sidewalk_North", "Sidewalk_South", "Kerb_North", "Kerb_South")
SEGMENT_M = 20.0
def _ucx_box(name, x0, x1, y0, y1, z0, z1):
"""A thin convex box for a ground slice, named for Unreal's UCX convention."""
thickness = max(0.10, min(0.30, (z1 - z0) * 0.25))
cx, cy = (x0 + x1) * 0.5, (y0 + y1) * 0.5
cz = z1 - thickness * 0.5
bpy.ops.mesh.primitive_cube_add(size=1.0, location=(cx, cy, cz))
hull = bpy.context.active_object
hull.name = f"UCX_{name}_01"
hull.scale = ((x1 - x0) * 0.99, (y1 - y0) * 0.99, thickness)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
return hull
def _segment(obj, name, out_dir, length_m):
"""Export `obj` as slices of `length_m` along X, each with its own honest bbox."""
xs = [(obj.matrix_world @ v.co).x for v in obj.data.vertices]
@@ -46,18 +59,35 @@ def _segment(obj, name, out_dir, length_m):
piece = bpy.context.active_object
bpy.context.view_layer.objects.active = piece
bpy.ops.object.mode_set(mode="EDIT")
# Two cuts, each with the normal pointing at the half to KEEP and clear_inner=True:
# normal +X at a -> keeps x > a
# normal -X at b -> keeps x < b
# The first version used clear_outer for the far cut and produced nested slices that
# each still spanned to the end of the street, which made every slice's bbox top out
# at the same far-away maximum.
bpy.ops.mesh.select_all(action="SELECT")
# Cut at both boundaries and keep only the middle: faces spanning a boundary are
# split cleanly rather than dropped, which a face-centre filter would do.
bpy.ops.mesh.bisect(plane_co=(a, 0.0, 0.0), plane_no=(1.0, 0.0, 0.0),
clear_inner=True)
bpy.ops.mesh.bisect(plane_co=(b, 0.0, 0.0), plane_no=(1.0, 0.0, 0.0),
clear_outer=True)
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.bisect(plane_co=(b, 0.0, 0.0), plane_no=(-1.0, 0.0, 0.0),
clear_inner=True)
bpy.ops.object.mode_set(mode="OBJECT")
piece.name = f"{name}_{index:02d}"
zs = [(piece.matrix_world @ v.co).z for v in piece.data.vertices]
# Explicit UCX box per slice. Neither the triangulated complex collision nor
# complex-as-simple makes these meshes block in Unreal, so the collision is authored
# here: one convex box per 20 m slice, thin, with its top at the slice's surface.
xs_w = [(piece.matrix_world @ v.co).x for v in piece.data.vertices]
ys_w = [(piece.matrix_world @ v.co).y for v in piece.data.vertices]
hull = _ucx_box(piece.name, min(xs_w), max(xs_w), min(ys_w), max(ys_w),
min(zs), max(zs))
path = os.path.join(out_dir, f"{piece.name}.fbx")
bpy.ops.object.select_all(action="DESELECT")
piece.select_set(True)
hull.select_set(True)
bpy.context.view_layer.objects.active = piece
fbx_preset.export_static(path, selection=True)
bpy.data.objects.remove(hull, do_unlink=True)
print(f"[site] {piece.name:22} x {a:+.1f}..{b:+.1f} m "
f"z {min(zs):+.2f}..{max(zs):+.2f} m -> {path}")
bpy.data.objects.remove(piece, do_unlink=True)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7
View File
@@ -533,3 +533,10 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
- **The sidewalks' normals are correct** (2790 faces, all pointing up) and they have no loose edges, so winding was not the reason they fail to block.
- **`set_complex_collision_as_simple` on sidewalks and kerbs did not make them block** either - the terrain needs it, they do not respond to it.
- **The heights at the player, corrected once more:** the road is **366** and the sidewalk **380** in the shipped map; the 158 cm figure came from the site blend's own Z datum, which is ~2.2 m below the map's because the terrain was added after that blend was written. The float is therefore about **14 cm** - the character stands on the road because the sidewalk does not collide - not the metre it looks like against the kerb.
## 2026-11-09 — The slices were nested, and the site surfaces disagree with each other
- **Bug in the slice export:** the far cut used `clear_outer=True`, which kept the wrong half, so every "slice" still spanned to the end of the street (-27950..-43) and its bbox topped out at the same far-away maximum. Fixed by making both cuts keep the middle (`clear_inner=True` with the normal pointing at the half to keep). The slices are now disjoint and their tops read 380, 340, 309, 284, 254, 231, 205, 169, 138, 104 cm along the street.
- **The surfaces themselves disagree:** at the player's X (-133 m) the sidewalk top is **169 cm** while `Road_Hollywood` is **366 cm** - the road sits ~2 m ABOVE the sidewalk. The player therefore lands on the road, and no sidewalk collision trick will change that, because the sidewalk is not where the road is.
- That is a **site-data problem**, not a placement or collision one: the road and the sidewalks were generated from different passes and their heights no longer agree along this stretch.
- **Next:** regenerate the road and sidewalks from one elevation pass in the site assembly so their surfaces meet at the kerb, then re-export and re-import (one export + one import + one 14 s probe).