feat(facades): 0.1.6 retail second pass — relief, material mapping, framing

What the first pass got wrong, and what changed:

- The mural plates were placed 0.20 m INSIDE the solid blockout volume, so they
  never rendered. Nothing can be recessed into a solid we do not cut: the inset is
  now faked by a proud frame with a near-flush plate, and the row reads.
- Detail sat 0.05 m off the wall, under a pixel of depth at 40 m. Trim planes now
  stand 0.10-0.20 m proud, and the parapet became two tiers with deeper reveals.
- 6806's parapet started at 0.62 H and left a 4.6 m blank band; the window storey
  and parapet moved up to follow the photograph.
- Security bars on the upper windows, twelve fluted fins on the corner tower.
- The trim's stucco/white/glass slots are mapped onto the demo's look materials, so
  the walls sit in the same noise/texture family as the rest of the block; the
  colour slots sampled from the photograph stay as authored.
- Review camera pulled back and widened to 66 deg so the east end is in frame.
- The scene audit caught duplicate RetailTrim_ actors: the demo builder's clear list
  did not include the new label, so every rebuild stacked another copy.

Still open and not fixable by geometry: wall lettering. A plate at 0.10 m does not
read at 40 m, so it moves to the 0.9.0 decal route (TODO.md, Screenshots/README.md).
This commit is contained in:
zwitschi committed 2026-11-09 09:54:33 +01:00
1 parent 4132f42ca1
commit 3942e9a215
36 files changed
+136 -75

No files matched your search

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.
+2 -2
View File
@@ -85,8 +85,8 @@ Read this one against the reference photograph it was modelled from: `Art/refere
Known gaps in this first pass, recorded rather than hidden:
- **The parapet reads as a blank white band.** It is modelled as one 2.18 m wall with four 0.10 m reveal strips, and at review distance the strips disappear into the wash.
- **The mural row and lettering band barely read.** They are shallow plates (0.06–0.08 m) on the same plane as the wall; they need either depth or a decal before they read as the photographs' black-framed panels.
- **The parapet still reads as a blank band.** It is now two tiers with three deeper reveals, and the step is visible, but there is no profile or shadow line to break the wall — a moulded coping and a modelled reveal need the `0.9.0` façade system.
- **The mural row now reads** (framed plates standing 0.12–0.20 m off the wall), but **the lettering band does not**: it is a plate at 0.10 m and it disappears at 40 m. Wall lettering is a decal/texture problem, scoped in `0.9.0`; no amount of extra plate depth fixed it in the second pass.
- **The corner tower is a plain cylinder with two cap rings** — the stepped Deco profile in the photograph is richer than that.
- **The camera crops the east end** of Lee Drug at 68° from the north sidewalk.
- **6822 Budget is untouched** and still a bare blockout volume: no photograph of it exists.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+78 -30
View File
@@ -58,8 +58,8 @@ LD_PARAPET_Z = 0.82 * LD_H # 9.92 m: bottom of the stepped parapet
B6_SHOP_TOP = 0.32 * B6_H # 3.87 m
B6_FASCIA_TOP = 0.38 * B6_H # 4.60 m
B6_WINDOW_Z = (0.45 * B6_H, 0.60 * B6_H)
B6_PARAPET_Z = 0.62 * B6_H # 7.50 m
B6_WINDOW_Z = (0.47 * B6_H, 0.64 * B6_H)
B6_PARAPET_Z = 0.74 * B6_H # 8.95 m (first pass used 0.62 and left a 4.6 m blank band)
BAY = 2.20 # mural / bay rhythm on Lee Drug, assumed
PROJECT = 0.55 # bronze cornice projection, assumed
@@ -129,6 +129,7 @@ def build_lee_drug(mats):
rounded corner tower."""
stucco, bronze, mural, glass, white = (mats["stucco"], mats["bronze"], mats["mural"],
mats["glass"], mats["white"])
lettering, reveal = mats["lettering"], mats["reveal"]
parts = []
cx = (LD_X0 + LD_X1) / 2.0
width = LD_X1 - LD_X0
@@ -151,31 +152,49 @@ def build_lee_drug(mats):
parts.append((mullion, "assumption: slim mullion at each bay boundary"))
# Celebrity-portrait panels, one per bay, in the band above the cornice.
#
# First pass modelled these as a shallow plate on the wall plane and they vanished
# at review distance: no shadow, no edge. Now each panel is a near-black plate set
# 0.14 m back inside a stucco frame that stands 0.10 m proud, so the sun casts a
# shadow line down one side and the row reads as a row of pictures.
z0, z1 = LD_MURAL_Z
for i, bx in enumerate(bay_centres(LD_X0, LD_X1, margin=1.2), start=1):
panel = cube(f"LD_Mural_{i}", bx, LD_FACE_Y - 0.04, (z0 + z1) / 2.0,
BAY - 0.75, 0.08, z1 - z0, mural)
frame = cube(f"LD_MuralFrame_{i}", bx, LD_FACE_Y + 0.20, (z0 + z1) / 2.0,
BAY - 0.55, 0.12, (z1 - z0) + 0.30, white)
parts.append((frame, "photographs: stucco frame around each portrait panel; "
"0.30 m border assumed"))
panel = cube(f"LD_Mural_{i}", bx, LD_FACE_Y + 0.12, (z0 + z1) / 2.0,
BAY - 0.85, 0.06, (z1 - z0), mural)
parts.append((panel, "photographs: row of black-framed portrait panels; count and "
"size assumed from the visible row"))
# Lettering band: a recessed plate, not text. Reproducing the lettering needs a
# decal, which is a materials-pass job.
# Lettering band: the photographs show dark letters painted straight on the stucco,
# so this is a slightly proud plate in a mid-dark tone rather than a black recess.
# The words themselves remain a decal job; the plate only reserves the band.
lz0, lz1 = LD_LETTER_Z
letter = cube("LD_LetterBand", cx, LD_FACE_Y - 0.03, (lz0 + lz1) / 2.0,
width - 3.0, 0.06, lz1 - lz0, mural)
letter = cube("LD_LetterBand", cx, LD_FACE_Y + 0.10, (lz0 + lz1) / 2.0,
width - 3.6, 0.10, lz1 - lz0, lettering)
parts.append((letter, "photographs: large dark lettering across the upper wall; "
"modelled as a recessed plate, the text itself is a decal job"))
"modelled as a painted band, the text itself is a decal job"))
# Stepped Deco parapet: wall band plus four horizontal reveals.
para = cube("LD_Parapet", cx, LD_FACE_Y + 0.10, (LD_PARAPET_Z + LD_H) / 2.0,
width + 0.2, 0.30, LD_H - LD_PARAPET_Z, stucco)
parts.append((para, "photographs: parapet steps above the frontage wall; "
"thickness assumed"))
for i in range(4):
z = LD_PARAPET_Z + (LD_H - LD_PARAPET_Z) * (i + 1) / 5.0
groove = cube(f"LD_ParapetReveal_{i + 1}", cx, LD_FACE_Y - 0.03, z,
width + 0.2, 0.07, 0.10, mural)
parts.append((groove, "photographs: four horizontal reveal lines in the parapet; "
# Stepped Deco parapet. A single 2.18 m wall with 0.10 m reveals read as one blank
# white band, so it is now two tiers with a 0.10 m step between them, and the
# reveals are 0.22 m tall and cut 0.16 m deep - deep enough to hold a shadow.
tier_h = (LD_H - LD_PARAPET_Z) / 2.0
lower = cube("LD_ParapetLower", cx, LD_FACE_Y + 0.14, LD_PARAPET_Z + tier_h / 2.0,
width + 0.2, 0.34, tier_h, stucco)
parts.append((lower, "photographs: parapet lower tier above the frontage wall; "
"thickness assumed"))
upper = cube("LD_ParapetUpper", cx, LD_FACE_Y + 0.24, LD_PARAPET_Z + tier_h * 1.5,
width - 3.2, 0.30, tier_h, stucco)
parts.append((upper, "photographs: parapet upper tier, stepped back from the lower "
"one; setback and length assumed"))
for i, (z, span) in enumerate(((LD_PARAPET_Z + tier_h * 0.45, width + 0.2),
(LD_PARAPET_Z + tier_h * 1.30, width + 0.2),
(LD_H - 0.14, width - 3.2)), start=1):
groove = cube(f"LD_ParapetReveal_{i}", cx, LD_FACE_Y - 0.02, z,
span, 0.16, 0.22, reveal)
parts.append((groove, "photographs: horizontal reveal lines in the parapet; "
"count read off the frame"))
# Rounded corner tower at the east end, stepped twice at the top. The centre
@@ -186,6 +205,16 @@ def build_lee_drug(mats):
(LD_H - 0.9) / 2.0, TOWER_R, LD_H - 0.9, stucco)
parts.append((tower, "photographs: rounded Deco corner tower rising above the "
f"frontage; {TOWER_R:.2f} m radius assumed"))
# Vertical fins around the curve: the photograph's tower is fluted, not a smooth
# drum. Twelve ribs at this radius are legible from the street.
for i in range(12):
ang = math.pi * (i + 0.5) / 12.0
fx = (LD_X1 - TOWER_R) + math.cos(ang) * (TOWER_R + 0.06)
fy = tower_y + math.sin(ang) * (TOWER_R + 0.06)
fin = cube(f"LD_TowerFin_{i + 1}", fx, fy, (LD_H - 0.9) / 2.0,
0.16, 0.16, LD_H - 0.9, white)
parts.append((fin, "photographs: fluted fins on the corner tower; count assumed"))
# Cap rings stack exactly to the measured 12.10 m top: 11.20 -> 11.75 -> 12.10.
cap_z = LD_H - 0.9
for i, (dh, radius) in enumerate(((0.55, TOWER_R - 0.35), (0.35, TOWER_R - 0.70)), start=1):
@@ -200,6 +229,7 @@ def build_6806(mats):
"""6806–6820: burgundy fascia, brown pilasters, white-framed windows, banded parapet."""
stucco, bronze, glass, white, burgundy = (mats["stucco"], mats["bronze"], mats["glass"],
mats["white"], mats["burgundy"])
reveal = mats["reveal"]
parts = []
cx = (B6_X0 + B6_X1) / 2.0
width = B6_X1 - B6_X0
@@ -224,22 +254,36 @@ def build_6806(mats):
wz0, wz1 = B6_WINDOW_Z
for i, bx in enumerate(centres, start=1):
frame = cube(f"B6_WindowFrame_{i}", bx, B6_FACE_Y - 0.06, (wz0 + wz1) / 2.0,
1.70, 0.14, wz1 - wz0, white)
frame = cube(f"B6_WindowFrame_{i}", bx, B6_FACE_Y - 0.08, (wz0 + wz1) / 2.0,
1.70, 0.18, wz1 - wz0, white)
parts.append((frame, "photographs: white-framed upper windows with security bars; "
"count and size assumed"))
pane = cube(f"B6_WindowGlass_{i}", bx, B6_FACE_Y + 0.02, (wz0 + wz1) / 2.0,
pane = cube(f"B6_WindowGlass_{i}", bx, B6_FACE_Y + 0.04, (wz0 + wz1) / 2.0,
1.46, 0.08, wz1 - wz0 - 0.24, glass)
parts.append((pane, "photographs: dark glazing inside each frame"))
# Security bars: three thin verticals per window, the detail that stops the
# upper storey reading as a row of black rectangles.
for b in (-1, 0, 1):
bar = cube(f"B6_WindowBar_{i}_{b + 2}", bx + b * 0.40, B6_FACE_Y - 0.15,
(wz0 + wz1) / 2.0, 0.06, 0.06, wz1 - wz0 - 0.30, white)
parts.append((bar, "photographs: vertical security bars on the upper windows"))
para = cube("B6_Parapet", cx, B6_FACE_Y + 0.08, (B6_PARAPET_Z + B6_H) / 2.0,
width + 0.2, 0.26, B6_H - B6_PARAPET_Z, stucco)
parts.append((para, "photographs: parapet band above the window storey; thickness assumed"))
for i in range(3):
z = B6_PARAPET_Z + (B6_H - B6_PARAPET_Z) * (i + 1) / 4.0
groove = cube(f"B6_ParapetReveal_{i + 1}", cx, B6_FACE_Y - 0.03, z,
width + 0.2, 0.07, 0.09, bronze)
parts.append((groove, "photographs: three reveal lines in the parapet; count assumed"))
# Two-tier parapet with deep reveals, as on Lee Drug: one flat band with hairlines
# disappeared at review distance.
tier_h = (B6_H - B6_PARAPET_Z) / 2.0
para = cube("B6_Parapet", cx, B6_FACE_Y + 0.12, B6_PARAPET_Z + tier_h / 2.0,
width + 0.2, 0.30, tier_h, stucco)
parts.append((para, "photographs: parapet lower tier above the window storey; "
"thickness assumed"))
upper = cube("B6_ParapetUpper", cx, B6_FACE_Y + 0.20, B6_PARAPET_Z + tier_h * 1.5,
width - 2.4, 0.26, tier_h, stucco)
parts.append((upper, "photographs: parapet upper tier stepped back; length assumed"))
for i, (z, span) in enumerate(((B6_PARAPET_Z + tier_h * 0.45, width + 0.2),
(B6_PARAPET_Z + tier_h * 1.30, width + 0.2),
(B6_H - 0.12, width - 2.4)), start=1):
groove = cube(f"B6_ParapetReveal_{i}", cx, B6_FACE_Y - 0.02, z,
span, 0.14, 0.18, reveal)
parts.append((groove, "photographs: reveal lines in the parapet; count assumed"))
return parts
@@ -289,6 +333,10 @@ def main(argv=None) -> int:
"glass": make_material("M_Retail_Glass", (0.035, 0.045, 0.055), rough=0.15),
"white": make_material("M_Retail_White", (0.90, 0.88, 0.84), rough=0.60),
"burgundy": make_material("M_Retail_Burgundy", (0.20, 0.09, 0.09), rough=0.55),
# Painted lettering and shadow reveals: same value family as the wall so they
# read as paint and shadow, not as black holes.
"lettering": make_material("M_Retail_Lettering", (0.30, 0.27, 0.24), rough=0.65),
"reveal": make_material("M_Retail_Reveal", (0.42, 0.38, 0.34), rough=0.70),
}
ld_parts = build_lee_drug(mats)
+15 -2
View File
@@ -305,7 +305,8 @@ def build():
log(f"could not create {MAP}")
return
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer"))
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "RetailTrim_",
"ShotSequencer"))
log(f"cleared {removed} previous demo actors")
# --- lighting: fixed daytime ------------------------------------------
@@ -614,7 +615,19 @@ def build():
comp = actor.static_mesh_component
comp.set_static_mesh(mesh)
comp.set_mobility(unreal.ComponentMobility.MOVABLE)
log(f"staged: {label}")
# Map the trim's own slots onto the look-dev materials where the surface
# type matches, so the walls sit in the same noise/texture family as the
# rest of the block. The trim's colour slots (bronze, burgundy, mural,
# lettering) stay as authored: those tones came from the photograph.
TRIM_LOOK = {"M_Retail_Stucco": "M_Stucco", "M_Retail_White": "M_Stucco",
"M_Retail_Glass": "M_FacadeGlass"}
mapped = 0
for index, slot in enumerate(mesh.get_editor_property("static_materials")):
target = TRIM_LOOK.get(str(slot.get_editor_property("material_slot_name")))
if target and target in built:
comp.set_material(index, built[target])
mapped += 1
log(f"staged: {label} ({mapped} slot(s) onto look materials)")
# --- player start: north sidewalk, facing the hero frontage -------------
# Signs were measured from the imported meshes rather than assumed: the FBX
+1 -1
View File
@@ -305,7 +305,7 @@ try:
# sidewalk at 2 m, look south with a level horizon so the parapets and
# the cornice line read as horizontals. 68 deg covers both frontages at
# this distance; the ends crop rather than the elevation leaning.
("demo-retail-frontage", unreal.Vector(-4700, -2000, 200), unreal.Vector(-4700, 1400, 560), 68.0),
("demo-retail-frontage", unreal.Vector(-4700, -2600, 210), unreal.Vector(-4700, 1400, 620), 66.0),
# the two north-side background buildings (TCL, Tussaud's) seen from
# the south across Hollywood Blvd. Probed world-space (2026-11-08):
# TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..