diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 460d86f..f5cf00b 100644 --- a/Game/GigLine/Content/Hollywood/DemoMap.umap +++ b/Game/GigLine/Content/Hollywood/DemoMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a3c68632e916cab692d991b550855054822cb0196c3f69be337c42ccbc5d60c -size 44952 +oid sha256:02c0472158a947e60eda4375729a5dcb7b5e90441dbe6659cc3816be00fe2726 +size 45507 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index bb6d30d..71d83e2 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc0d523f0fb587306a9e946e27b10113eb89df1218e6fdb8553b29f083384e49 -size 4605 +oid sha256:d3ce9d98468f5bb8dcfc530ad2d1567790c0e446fdf46c272a9fd6da09e9e808 +size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index fa49e20..a0603b3 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c765035f6c076c4016831884b167fa93e162bab7d0038f55cd09d240105dfa4c -size 4610 +oid sha256:f5f7213ec007a363d456ffb1376305a510e1113d95427f77633098700ca64a7b +size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset new file mode 100644 index 0000000..dd97e69 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4255a288970bcec6521a2ca3a2c718e240295336fda55f4fb6144ffc517fa27c +size 7896 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 5948d47..9a86660 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21454ed393d70dc103e0ad7ca00b06aed6fded381062ddcde87a424c05ff580d -size 4600 +oid sha256:6111173ab3a2a530cd42edc931d0e4d6f539527403b9cca016c064629a47184b +size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset new file mode 100644 index 0000000..874df3b --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc5c41a2c161635471d1dc74de3cedbe6fd11b4eaffbe6e397734e640831013a +size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 7aa4d2f..8ded297 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:891ee60d2feac3e741509e9e03756e9941b4561994e1a77732f565b7aae5bb52 -size 4615 +oid sha256:0209f2cef5ce37668e642f9d937242878381326b6d1d437167dea3f601aa3a02 +size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset new file mode 100644 index 0000000..4b309bf --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bf3cbcc33ccc1d5fc1e72c1c31d2bc32077bf6f00002963ad99b12486e98ebf +size 7871 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index f416a72..6e70625 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b60d901d3cdc13183eac08749faf028dc532c3f1da1a03d7aba8f2111d63c11 +oid sha256:4c46349a4d52e479d9580b1d1a45ff8bec3afc8ada802908a0f2387cbf887c84 size 28715 diff --git a/MEMORY.md b/MEMORY.md index ba707ff..4536527 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -185,3 +185,9 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **Camera placement in the demo map needs the mesh bounds, not intuition.** The hero façade body spans UE Y −1457..−200, so a "street level" camera at Y −400 is *inside* the building and renders its unlit back face. Read `get_bounding_box()` on the staged meshes before choosing a camera. - **"LIGHTING NEEDS TO BE REBUILT" (Build Lighting Only / `BuildLighting`) appears whenever a map has Stationary/Static lights and no baked lightmap.** For a fully dynamic scene the clean fix is `r.AllowStaticLighting=False` in `Config/DefaultEngine.ini` plus `set_mobility(ComponentMobility.MOVABLE)` on the directional light, sky light and height fog (and MOVABLE on the staged mesh components). Nothing to build, warning gone. Note the cvar is read at startup, so each new commandlet/game run picks it up. - The Android File Server `SecurityToken` kept reappearing in `Config/DefaultEngine.ini`; the plugin is disabled and excluded from shipping, so the value is cleared and must stay empty — it is a credential and must not be committed. + +## 2026-11-07 — Worktrees and the material look pass + +- **A fresh git worktree has no compiled game binaries.** `Binaries/` is gitignored, so `UnrealEditor-Cmd` on a new worktree exits "abnormally (error code: 1)" before running the Python script, with no Python error in the log. Run `Engine/Build/BatchFiles/Linux/Build.sh GigLineEditor Linux Development -project=/Game/GigLine/GigLine.uproject` first. +- `/tmp/gigline/*.txt` result files are **shared across worktrees**. Reading `/tmp/gigline/demo-map.txt` after a run can show another worktree's stale output — check the file mtime, or use a per-worktree result path. +- **Flat Constant3Vector materials read as plastic.** Giving each surface a roughness plus a world-space-noise blend between two shades (WorldPosition → Multiply → Noise → Lerp between two Constant3Vectors → BaseColor) is enough to stop the blockout looking like a toy; no textures required. diff --git a/Screenshots/README.md b/Screenshots/README.md index 411d572..6006d1e 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -23,7 +23,8 @@ and exits — about a minute. The camera list lives in ## Shot list -All ☑ approved 2026-11-07. +All shots ☑ approved 2026-11-07, including the material look pass that changed +every demo surface. | # | File | Preview | Map | View | What it shows | | --- | --- | --- | --- | --- | --- | @@ -50,10 +51,14 @@ All ☑ approved 2026-11-07. Z 2 m; both are exactly 1 m — the scale reference. - **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun at pitch −50 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed - exposure. Surface colours are assigned explicitly in - `Tools/ue_build_demo_map.py` (`M_Asphalt`, `M_Concrete`, `M_Ground`, - `M_RoadPaint`) because Blender's flat diffuse colours do not survive the - FBX → Unreal import. + exposure. +- **Materials**: every exported surface carries a look-developed material built + in `Tools/ue_build_demo_map.py` (`ensure_material`) because Blender's flat + diffuse colours do not survive the FBX → Unreal import. Each has an explicit + roughness and a subtle world-space noise blend between two shades: + `M_Asphalt` (0.92), `M_Concrete` (0.88), `M_KerbConcrete` (0.85), + `M_Ground` (0.98), `M_RoadPaint` (0.62), `M_FacadeStone` (0.72) and + `M_Stucco` (0.86) for the north-side buildings. - **Lighting is dynamic-only** (`r.AllowStaticLighting=False`, all lights Movable), so no lighting build is ever required. - **Attribution overlay**: every shot carries the required ODbL credit in the diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 3a11a09..434d0fb 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c653c408d41483f37ad1801da5b25896a7b55c4b685abf42cd5d396c32472767 -size 1588910 +oid sha256:676b50cd950b4ea3417073d1b604f64c9317c453f83db9eabfb5662d6821f867 +size 2534457 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index ebf9fa2..cd094da 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2155329bab55bcbbb507c78a6fe6246a8d5968769fc12d70d5d28f7ca7b5bafe -size 1551804 +oid sha256:bbe1fe758d2e10266204507da00f27c366deda7dfd1ee1f8888cacb097940465 +size 2766034 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 9805d4e..4784f39 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0f73699a0557705cab7cc08cb8563dfbc815b2412437f842a989eade31afb95 -size 1608333 +oid sha256:513a6f2ca281001b80295d0c82c533c48fdd8496938d7f835f9856db721383c6 +size 1588985 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 3291657..f702ef0 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06ebb28aa6276bf73488bd29fa2b3c6b9768fd5fdeccf449b9053a7482574b6a -size 1782195 +oid sha256:581898cdd897101711c72520f786021b24800c8f3c0731300692d4a898919734 +size 2241154 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index dde1aeb..39e2532 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3263c3e03689deff30963c8ba87b12db86e9e9144c980ea161d56719c189fbe -size 2281114 +oid sha256:737d7cacd9573cf9d41b9add8c6182b94d804c0f2a6f5e29ac317c9f55da135e +size 2496192 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 27d23dd..ae72e1f 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8d2ce6def7b696a6bf0a17ad9b7b83df8830b47272d940b3aafe2e5d0866cc5 -size 1941667 +oid sha256:5009a7639444b00f471fff091b0ba9cb354469b0a07134bf9a78a5b768ed54f8 +size 1742167 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index 286ab15..2824ce4 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e692691c25c96a303058f1c602f40c20399d8e722cfe7c2c603de37ef82d9a8 -size 2081796 +oid sha256:8f958d6988ca97321cfaf03415a67cbec4a34b75c20a524d3e3b9f59d2bd01cc +size 2110749 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index 6286615..441206e 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43dc46936a78cfaddc9de96e54176ee9bd5c4f437d577adfa1c8fb4fe7fe9131 -size 1966305 +oid sha256:a710d8ff8e5ff2cfc744d3f06c1aecec7568e1e813b4a7e83f72c04d8e323913 +size 1965431 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index 533a332..b13c180 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abf01a465d1a7bf67548c3218a0621259d2ed667c4c23dd9bab54b5990377c74 -size 1901906 +oid sha256:db1188759bda61be3362aa513ba06887aff39ae5b4bba590c8ba17fd0aa48455 +size 1778180 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index e366649..193bb32 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bda1773d8ec4b16f7cb9ca546c7806913186445cccdc79d9fde5536a8484c52 -size 1908365 +oid sha256:786e237feeda93652b73524d1cbf22869e71fa320ca664354efbf8142b34602e +size 1739520 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 6e5b3e0..3102ec8 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d7c9d86a752e4b9b61427d1f95aed0e11e02ce5031de0e0a8428e6620693831 -size 2621180 +oid sha256:90630d97e51c5c97b1e09738a95074e9f85294d9465a819f0833c785c33c24f6 +size 2604622 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 6a37d2d..1700256 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3a43c0786c7f458850041ae519633210fe22e9603e8ab34301a3dd0103f955d -size 2049465 +oid sha256:4b1925e2e85c39d66d7a245b63f7dd2e12e08a8e8058b23cf54fd87141549bbb +size 1986975 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 8412d14..2248d25 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8db5324138e9e9ad0ca9c0fd0c03d73cb9274f40f889e9fdf27f364912b1ec54 -size 2100021 +oid sha256:6cd0241d169bf92c043b08d30385792e7c2ac2c6d297f5697323deb499d1bf7f +size 1903735 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index 26ed002..c7a8dea 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81fbf6b9a504e125f7e990cd0be9c54ec31b117ddf8cc14a0461765e5d03f72c -size 1931209 +oid sha256:e71421e8c29fa36c4506bfcb9d99848329840d2eb0ac02946071672b04464257 +size 1711217 diff --git a/TODO.md b/TODO.md index cd9d0cd..2c7a4be 100644 --- a/TODO.md +++ b/TODO.md @@ -78,7 +78,7 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep - [x] Keep the character as a reusable NPC prefab: a visible body, a collision capsule, and a head-height check that the camera does not clip at 1.60 m — for when NPCs/pedestrians are wired in (`0.2.0+`), not the `0.1.0` player. Done 2026-11-07: `AGigLineNPCPerson` (C++), ACharacter with capsule half-height 83 cm / total 1.66 m (head at ~1.66 m, so a 1.60 m camera does not clip), the MPFB human on the built-in mesh as the visible body, `AutoPossessAI` for pedestrians; confirmed spawned feet-on-Z-0 and rendered in `Screenshots/review-npc-prefab` (☑ approved 2026-11-07). - [x] Screenshots use stable camera names (no index prefix), so inserting a shot no longer renumbers/ orphans files; `AGigLineShotActor` + `Screenshots/README.md` updated. -### Asset production +### Asset production - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `materials-lookdev-0.1.0`) - [x] Establish a consistent scale test, named export presets, and repeatable import settings. Scale test = the three-cube calibration scene (`Art/blender/scaletest/`, regenerable via `Tools/scale_test_export.py`); named export preset `ue_metric_v1` (FBX settings in that script); named import preset `ue_metric_v1` (`FbxImportUI` settings in `Tools/ue_scale_test_import.py`). Both scripts are the templates for production asset export/import. - [A] Collect reference photography for manual façade work. Local only, never committed: `Art/reference/` is gitignored. **72 images in eleven folders**: hero façades to 4k, the El Capitan Theatre exterior series, premiere-night staging, Ovation Hollywood, the north side, and the Lee Drug neighbour. Gaps largely closed by the Flickr/web-search pass: the hero entrance is now documented (three portals with individual short flights — the inherited "eight steps, 12 m wide" is overturned), and the 6806–6822 retail run appears in an oblique 2013 frame. Still missing: a clean isolated frame of the door recess and a night frame of 6840 itself. @@ -87,7 +87,7 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep - [ ] Model surrounding block frontage. - [x] Model simplified background buildings directly across Hollywood Boulevard. Done 2026-11-07, the two in-scope north-side buildings — TCL Chinese Theatre (SM_TCL_Chinese_Theatre) and Madame Tussaud's (SM_Madame_Tussauds) — taken from the verified OSM blockout volumes and exported with `ue_metric_v1`, with a Y-mirror so they land north of Hollywood Blvd across the street from the hero (TCL spans Unreal Y 1802..9431, Tussaud's 1888..7586; road centreline ≈ 0, hero façade at Y −1450). Staged in the DemoMap as `North_TCL_Chinese` / `North_Madame_Tussauds`; demo shot `review-03-demo-north-buildings` captured. Simple flat facade materials (surfaces are the `0.1.4` patch). - [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`). -- [ ] Materials and look development for all exported assets. +- [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch. ### Gameplay - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `gameplay-attribution-0.1.0`) diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 2883931..2b08281 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -50,26 +50,78 @@ def spawn(cls, loc, label, rot=None): return actor -def ensure_material(asset_name, rgb): - """Create/reuse a flat-colour material under /Game/Hollywood/Site. +def ensure_material(mat_name, spec): + """Create/reuse a look-developed material under /Game/Hollywood/Site. - Used where the FBX material round-trip does not survive import: the terrain - arrives with WorldGridMaterial (UE's bright default grid), which reads as a - blown-out white field under the demo sun. + Blender's flat diffuse colours do not survive the FBX -> Unreal import (the + terrain even lands with WorldGridMaterial), so the demo has always assigned + its own material per surface. Those were flat colour only, which read as + plastic. Each material here gets: + + * base colour with a subtle world-space noise blend between two shades, + so asphalt and concrete have large-scale variation instead of a fill, + * an explicit roughness (and specular) so the surface type is readable, + wet-looking stone vs matte concrete vs chalky road paint. + + spec keys: dark, bright (RGB tuples), roughness, specular, noise_scale + (world units per noise cycle; smaller = larger features). """ - path = f"/Game/Hollywood/Site/{asset_name}" + path = f"/Game/Hollywood/Site/{mat_name}" if unreal.EditorAssetLibrary.does_asset_exist(path): - return unreal.EditorAssetLibrary.load_asset(path) + # Rebuilt every run so a look change cannot be masked by a stale asset. + unreal.EditorAssetLibrary.delete_asset(path) + mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset( - asset_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew()) - node = unreal.MaterialEditingLibrary.create_material_expression( - mat, unreal.MaterialExpressionConstant3Vector) - node.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0)) - unreal.MaterialEditingLibrary.connect_material_property( - node, "", unreal.MaterialProperty.MP_BASE_COLOR) - unreal.MaterialEditingLibrary.recompile_material(mat) + mat_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew()) + lib = unreal.MaterialEditingLibrary + + def node(cls): + return lib.create_material_expression(mat, cls) + + def c3(rgb): + n = node(unreal.MaterialExpressionConstant3Vector) + n.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0)) + return n + + def scalar(value): + n = node(unreal.MaterialExpressionConstant) + n.set_editor_property("r", float(value)) + return n + + # world position -> noise -> lerp(dark, bright) -> base colour + wp = node(unreal.MaterialExpressionWorldPosition) + scale = scalar(spec.get("noise_scale", 0.004)) + mul = node(unreal.MaterialExpressionMultiply) + lib.connect_material_expressions(wp, "", mul, "A") + lib.connect_material_expressions(scale, "", mul, "B") + + noise = node(unreal.MaterialExpressionNoise) + for prop, value in (("scale", 1.0), ("quality", 1), ("levels", 2), + ("output_min", 0.0), ("output_max", 1.0)): + try: + noise.set_editor_property(prop, value) + except Exception: # noqa: BLE001 + pass + lib.connect_material_expressions(mul, "", noise, "Position") + + lerp = node(unreal.MaterialExpressionLinearInterpolate) + lib.connect_material_expressions(c3(spec["dark"]), "", lerp, "A") + lib.connect_material_expressions(c3(spec["bright"]), "", lerp, "B") + lib.connect_material_expressions(noise, "", lerp, "Alpha") + lib.connect_material_property(lerp, "", unreal.MaterialProperty.MP_BASE_COLOR) + + lib.connect_material_property( + scalar(spec.get("roughness", 0.85)), "", unreal.MaterialProperty.MP_ROUGHNESS) + try: + lib.connect_material_property( + scalar(spec.get("specular", 0.3)), "", unreal.MaterialProperty.MP_SPECULAR) + except Exception: # noqa: BLE001 - specular input is optional in UE5 + pass + + lib.recompile_material(mat) unreal.EditorAssetLibrary.save_loaded_asset(mat) - log(f"created material {asset_name} rgb={rgb}") + log(f"material {mat_name}: rough={spec.get('roughness')} " + f"dark={spec['dark']} bright={spec['bright']}") return mat @@ -209,17 +261,38 @@ def build(): # builds a material with a default white base colour, and the terrain even # lands with WorldGridMaterial. Every site surface therefore rendered white, # which is what washed out the demo. Assign explicit colours per mesh here. - SURFACE_MATERIALS = { - "Terrain_DEM": ("M_Ground", (0.30, 0.28, 0.25)), - "Road_Hollywood": ("M_Asphalt", (0.055, 0.055, 0.06)), - "Sidewalk_South": ("M_Concrete", (0.52, 0.50, 0.47)), - "Sidewalk_North": ("M_Concrete", (0.52, 0.50, 0.47)), - "Kerb_South": ("M_Concrete", (0.52, 0.50, 0.47)), - "Kerb_North": ("M_Concrete", (0.52, 0.50, 0.47)), - "Crossing_Markings": ("M_RoadPaint", (0.80, 0.80, 0.78)), + # Look-developed surface materials (see ensure_material for the graph). + MATERIAL_SPECS = { + "M_Asphalt": dict(dark=(0.040, 0.040, 0.044), bright=(0.072, 0.072, 0.078), + roughness=0.92, specular=0.22, noise_scale=0.006), + "M_Concrete": dict(dark=(0.44, 0.42, 0.39), bright=(0.56, 0.54, 0.50), + roughness=0.88, specular=0.28, noise_scale=0.004), + "M_KerbConcrete": dict(dark=(0.47, 0.45, 0.42), bright=(0.60, 0.58, 0.54), + roughness=0.85, specular=0.30, noise_scale=0.008), + "M_Ground": dict(dark=(0.22, 0.20, 0.17), bright=(0.33, 0.30, 0.26), + roughness=0.98, specular=0.15, noise_scale=0.003), + "M_RoadPaint": dict(dark=(0.74, 0.74, 0.72), bright=(0.86, 0.86, 0.84), + roughness=0.62, specular=0.35, noise_scale=0.02), + "M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.74, 0.70, 0.62), + roughness=0.72, specular=0.32, noise_scale=0.005), + "M_Stucco": dict(dark=(0.52, 0.48, 0.42), bright=(0.66, 0.62, 0.55), + roughness=0.86, specular=0.25, noise_scale=0.004), } + SURFACE_MATERIALS = { + "Terrain_DEM": "M_Ground", + "Road_Hollywood": "M_Asphalt", + "Sidewalk_South": "M_Concrete", + "Sidewalk_North": "M_Concrete", + "Kerb_South": "M_KerbConcrete", + "Kerb_North": "M_KerbConcrete", + "Crossing_Markings": "M_RoadPaint", + } + built = {} + for mat_name, spec in MATERIAL_SPECS.items(): + built[mat_name] = ensure_material(mat_name, spec) + imported = 0 - for name, (mat_name, rgb) in SURFACE_MATERIALS.items(): + for name, mat_name in SURFACE_MATERIALS.items(): mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}") if mesh is None: continue @@ -227,12 +300,29 @@ def build(): if actor: actor.static_mesh_component.set_static_mesh(mesh) actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) - mat = ensure_material(mat_name, rgb) + mat = built.get(mat_name) if mat: actor.static_mesh_component.set_material(0, mat) imported += 1 log(f"staged {imported} site meshes with explicit surface materials") + # The hero facade and the two north-side buildings are staged by other + # scripts (or reused between rebuilds), so assign their look materials by + # actor label after everything is in the level. + LABEL_MATERIALS = {"Hero_Facade": "M_FacadeStone", + "North_TCL_Chinese": "M_Stucco", + "North_Madame_Tussauds": "M_Stucco"} + assigned = 0 + for actor in unreal.EditorLevelLibrary.get_all_level_actors(): + mat_name = LABEL_MATERIALS.get(actor.get_actor_label()) + if not mat_name or mat_name not in built: + continue + comp = actor.get_component_by_class(unreal.StaticMeshComponent) + if comp and comp.static_mesh: + comp.set_material(0, built[mat_name]) + assigned += 1 + log(f"assigned look materials to {assigned} facade/background actors") + # --- player start: north sidewalk, facing the hero frontage ------------- # Signs were measured from the imported meshes rather than assumed: the FBX # preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH