diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 21ed7b4..34b7b1e 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:5544cf164533ce61534d98075bbf41e6fcaa91f340492c82ce10154a75028013 -size 55058 +oid sha256:0dc9468e94f868cd76893dd1d8e6cd2ef0b365ce3fa86f3feed5f65a630af9cb +size 55066 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 14f0f6c..d83bc87 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:8f56c5b314d6f7844f2833beefe709676368ccbd2a45ae30f48ec0ae83da4af2 +oid sha256:29dcab210b352dbcec4665b5d18e03080327938ea2be01dfe0c36862c8161095 size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 2cc2114..909a78f 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:2471e65a5e1809aa78abdb6e52de01e72a426ed9c825c2d90e7a0f8bf1dda400 +oid sha256:14c5b272b2a67088a123541518fa5749e47e2dc994b51ae699fd8fc114aee3ad size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index 44e5528..8eb662b 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:832e10baec763e12727cd6be226ea1fb9ced63aadaf55f25cd5323a7a8fec8c4 +oid sha256:158ac3fd592d71816fe061cac6aa23a025bb3a8f83ca338bc510e144bc4a06dd size 8667 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index dd46ad6..93e7535 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be4d972ad3655d1c9ec383b69f66c7ead7c8279e9425dfc442dfd7c7393e78a8 +oid sha256:cb21c5ac661880794989401129b2ea4d424c6a19dc5072b1505f778709d562bd size 8662 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 0e63bee..0f7c2de 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf20102e53609e517647200ad44528284dbc5bd26e00d1b41467e3db2ca3c6f1 +oid sha256:643078c1b3aef3b980d208bc4bc9cdc727b337a6e00ac58460a4b601476e9019 size 12131 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index f1d76f5..00bdaa8 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8876871cf45828b2da703e3082bc3bb5a2fb63263958ca5314b4072fe7418ec1 +oid sha256:bb32d2e8dc8f85f2f1f6bde81faa7742a9c9b3f2f5b5c9adc66a96be43a1fc69 size 12161 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 3317c51..0f0755d 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:adb033875c629572815f0d27b94a16bdc3b869344419ad7343b59c8ccdc1e850 +oid sha256:b22a46ee7729364ba83d6cac7b38634fa32d93140c29b2003f73f7bf2a2c0aac size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 1becb71..ab8e270 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:715ec7945cf7eb69f07755252ccc4b39ea1cbfad1a55eddb99616517ef5def42 +oid sha256:a06a0150737402a11bf660ff6848564975da950654eacac7023b3932be2e962f size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 91ddd02..e333504 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:dcfda0b94f7141564391069629682cac71c9152100038a5e927a2355c89c869a +oid sha256:b25e55f9bfb3181e8f12535b43ce829d3923668bbab3635ec4c321534fd76305 size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 9f20fe3..00df7ae 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:940807fd433a5eab6726c830b4c54874a4acf3c352e415e6de71f0f6ed1b4ede +oid sha256:df485df5c559d21b96682f2a381a6a53c9456c5d14b34394e2b5f0e93cbf04b8 size 8637 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 1dc6b23..d60380d 100644 --- a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7b7c866693db2f80334baeb66af3ff2585368a503af806c3d78cae71f328b17 +oid sha256:69075d0ba49303ed0c25fbbc966abc7b0d2f3fe34d716bf648989e04394da70d size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset new file mode 100644 index 0000000..8862664 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa06702c3b89e8c16522f94292bc69e01ffe11e92d050bdee27341431774fa21 +size 70786 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset new file mode 100644 index 0000000..b5e0223 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90efc297644eae3bb067300cef611efe68e48e25960a259fa482e717d77a35ad +size 28917 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset new file mode 100644 index 0000000..88ae661 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8f007448f3038bbac6bb70a77d26a4bc8ddccd02a227cedba74d14d901c631a +size 58474 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset new file mode 100644 index 0000000..3ba2425 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcc36e012cb5428e1568976cdf440be179330e635e09c2ce5b78d3d71dea67a4 +size 17423 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset new file mode 100644 index 0000000..b3fae28 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1b2cac653493aec57f1878f81ffe823dea291c2a6ea27e1e78c3fa3aed2359 +size 40517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset new file mode 100644 index 0000000..491e0a6 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0613545632883b4256025b712c35a8c9345c364beeb0089592163a1a84973f69 +size 18677 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset new file mode 100644 index 0000000..198d125 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6edac9a08e27459933560127a922e504f18b88656e37932699fc18e8920979f6 +size 62653 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset new file mode 100644 index 0000000..8639638 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:089a9df31bce521e14f6519539063ac1c5eb73641a73c70189aad9725cd4eefd +size 17448 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 14c9200..bd50cc5 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:6bef39374cc710661ddf2f15194d28b298bfa0d0adcf25668740712381c2ba29 +oid sha256:06db40a8beab60daaf548554fc70b85eb4cd4ab1f8371b0e8ccd4b7e33c1648d size 28811 diff --git a/MEMORY.md b/MEMORY.md index 1006f57..5834e6d 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -550,3 +550,21 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel. `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %. + +## 2026-11-09 — facade texture maps (0.1.4) + +- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and + roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG + encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and + byte-identical on a re-run (checked by md5sum). +- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples + them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) + → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, + concrete 250. +- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the + output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are + R/G/B/A booleans. A textured material skips the graph coursing on purpose — + overlaying both would double the course pattern. +- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting + either fails with `NameError` and an exit code of 0 from the commandlet (always + read the result file under /tmp/gigline). diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 4476642..ce2ce2d 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:a21bd2e7d32099ee8490de63551e817e144ce74cade34dbf7854bfc125a899d0 -size 2756050 +oid sha256:7b1aeaed19783379067f81e67c4286bb28017c50c4fa713574ac18370c3846bb +size 2755870 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 83da020..1c4503e 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:f93538fc3aa3d3b0a83c5b0ba9637445ef66a2fba845e0dd3f39fc04ddac06ac -size 2265651 +oid sha256:0d83115402d0a44b8b025dc8ba35505bba8fc1410ead6f830df8c819d3020e79 +size 2267060 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 4a928e0..0eaeefd 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:d4a781b779e59facdcd0275cc147ea4f9a5cf9f36c9061c400eca79f69aceb2d -size 2843274 +oid sha256:701203ac6afe5d7897e32414bce1bc09dc85cfd747636104ab5b3cad48306d47 +size 2838039 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 2da3f96..fc88e69 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:744bb6b3ddcf3130e11d93c1ba6eb0b71e5968629d2517401cd36ecc74e020c8 -size 2704752 +oid sha256:2607aaa8e9333dd033745ebbac6b99b31641049e03982d66d5d1a0b1b086de9d +size 2703487 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index d1c2dc1..de5a517 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:e794bb187f5f1cd27cfcdf89a504ce22a33a529cc3244ba5381af930c33e778d -size 2539235 +oid sha256:2d38dc63e0c09483ec15aaa493b483baa8d6d7d7a2678415fef5519561226ebd +size 2540302 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 7c5785b..113c7d5 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:586c717e6066aed87c6f7caf1f566789c5ecf17cecf786cd7894a8e4d75afeb8 -size 1756397 +oid sha256:2797ca6c909c3956795361fcef86dfa8268007ab3df23adf07be807d0f0e073f +size 1750724 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index ccb004b..d290e35 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:e6ad9425e1cda90135358752c655a70a08e0d449629d1f5075b31fcc272eb601 -size 2297053 +oid sha256:f428e43ac9117e9e45a662a89d82f5b636bd8eebd07cad5de8c8c3894a146b09 +size 2298703 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index 410d1ff..d603bef 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:9731498fc8054b94de923cb311f38c31be95af642786bf64f6015db357823559 -size 1516067 +oid sha256:ae882d2fb120ce45c4bed16c4e5288400bbbe9e565a93f29768e353ebb280870 +size 1515587 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index 4f8adb4..b73d080 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:8d02821b21a4b61fed206bb5a8f38ff3b1ab27a0cc16e26cfd0639eb1f5a32ab -size 1327638 +oid sha256:795bffa54e8efc0bb3aafb47a3cfa6282ea41c3a55a0402fb349c2e7ddbedd53 +size 1321845 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 38979c2..f0c8c75 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:c8e3664310a7e70ff816b8e677f2d6c4ce2a31bb8e0f07e048f9df3467be4de0 -size 1737260 +oid sha256:12ba9d44bb231629abf2e0a607d22e1c7c940b953b56c089efb8810d8ffce88a +size 1736896 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 2b947d0..0a51b2a 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:c028e5e01bf9d46f33b7a903f50923ccaee537112d9bb519bb4376766ca6beff -size 2619516 +oid sha256:1d3ccc49c65e6347e273d07fe7cf72839ad3b4b74c37ab9e4e1d92f4020b4390 +size 2614061 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 8b45239..c80f9f1 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:a4a003f484e9e048c1c73f6d5bfac87c1db332b1aae02eddf7f7c913af098a9a -size 2027282 +oid sha256:e8ad82386c9504b3e2bdd461c22c57e2e9cb19762aaf794f47dd8472ff01f436 +size 2025973 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 0ce7ec0..71f4bef 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:f8026dd45a8fdb6580c3a0e82aad50518a1d0b27fc9a2a93e419924df651c72e -size 1938060 +oid sha256:3aa35281f616ad86f1d7b2de6a79b0bf3fd476b8c259dc3d3449c6ef7f3dc960 +size 1942628 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index 1b0cd7a..aa480e0 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:dcb80cdfaca40ab3ec68df835e1c31aa66ac8f24bea0eb44e928946b2a9b378d -size 1752868 +oid sha256:b7a03d6be117470d86bd351aa35584350140bcf6ff2aae3b35da79e062672a4c +size 1723008 diff --git a/Tools/README.md b/Tools/README.md index 988b14a..f54bb8d 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -16,6 +16,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | Tool | Purpose | | --- | --- | | `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob | +| `generate_facade_textures.py` | Deterministic stdlib PNG generator for the facade maps (`T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` base colour + `_R` roughness). Pure-zlib writer, hash-based value noise, identical bytes on every run; `--size` and `--out` (`/tmp/gigline/textures` by default). `ue_build_demo_map.py` imports them into `/Game/Hollywood/Textures` and samples them world-aligned. | | `ue_common.py` | Shared helpers for the Unreal commandlet scripts: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False silently), and `require_saved()` makes that failure explicit. | | `fbx_export_preset.py` | The **single** FBX export preset (`ue_metric_v1` / `ue_character_v1`): metres→centimetres via `FBX_SCALE_NONE`, axis mapping east +X / north −Y / up +Z, `mesh_smooth_type="FACE"` (smoothing groups survive the import). Every Blender exporter imports it instead of copying the argument block; `preset_kwargs()` is bpy-free and unit-tested. | | `sibling_asset_audit.py` | Reads the sibling project's prop library from GLB headers into `Art/props/reuse-manifest.json`; copies nothing | diff --git a/Tools/generate_facade_textures.py b/Tools/generate_facade_textures.py new file mode 100644 index 0000000..f42488a --- /dev/null +++ b/Tools/generate_facade_textures.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +"""Generate the facade texture maps the demo materials sample. + +The materials started as procedural graphs only (noise + coursing), which reads +as tinted stone but has no grain. These maps add the grain: an ashlar stone +course pattern, a rendered stucco tooth, asphalt aggregate and concrete pores, +each as base colour plus a roughness map so the specular breaks up. + +Standard library only — the PNG encoder is the same pure-zlib writer used for the +MPFB skin, so this runs on the host without numpy or Pillow. + + python3 Tools/generate_facade_textures.py # write to /tmp/gigline/textures + python3 Tools/generate_facade_textures.py --size 1024 + python3 Tools/generate_facade_textures.py --out Art/blender/textures + +Output names match the Unreal import destination `/Game/Hollywood/Textures`: + + T_StoneAshlar_BC.png T_StoneAshlar_R.png + T_Stucco_BC.png T_Stucco_R.png + T_Asphalt_BC.png T_Asphalt_R.png + T_Concrete_BC.png T_Concrete_R.png + +Determinism: every pattern is a deterministic hash of the pixel coordinate, so a +re-run reproduces the same files byte for byte (no RNG state, no seam drift). +""" + +from __future__ import annotations + +import argparse +import math +import os +import struct +import zlib + +# --- PNG ------------------------------------------------------------------- + + +def write_png(path, width, height, rgb_rows): + """Write 8-bit RGB rows (bytes) to a PNG. `rgb_rows` is one bytes per row.""" + raw = bytearray() + for y in range(height): + raw.append(0) # filter type 0 + raw += rgb_rows[y] + + def chunk(tag, data): + c = struct.pack(">I", len(data)) + tag + data + c += struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) + return c + + ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) # colour type 2 = RGB + png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + png += chunk(b"IDAT", zlib.compress(bytes(raw), 6)) + chunk(b"IEND", b"") + os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True) + with open(path, "wb") as handle: + handle.write(png) + return path + + +# --- deterministic value noise -------------------------------------------- + + +def _hash2(x, y, seed=0): + """Deterministic 0..1 hash of an integer cell. No RNG, fully reproducible.""" + n = (x * 374761393 + y * 668265263 + seed * 1442695040888963407) & 0xFFFFFFFF + n = (n ^ (n >> 13)) * 1274126177 & 0xFFFFFFFF + return ((n ^ (n >> 16)) & 0xFFFF) / 65535.0 + + +def value_noise(x, y, seed=0): + """Smooth bilinear value noise at continuous (x, y) in cell units.""" + xi, yi = math.floor(x), math.floor(y) + fx, fy = x - xi, y - yi + # smoothstep weights so cells blend without visible lattice lines + sx = fx * fx * (3.0 - 2.0 * fx) + sy = fy * fy * (3.0 - 2.0 * fy) + n00 = _hash2(xi, yi, seed) + n10 = _hash2(xi + 1, yi, seed) + n01 = _hash2(xi, yi + 1, seed) + n11 = _hash2(xi + 1, yi + 1, seed) + return (n00 * (1 - sx) + n10 * sx) * (1 - sy) + (n01 * (1 - sx) + n11 * sx) * sy + + +def fractal(x, y, octaves=4, seed=0): + """Fractal sum of value noise, normalised to 0..1.""" + total, amp, freq, norm = 0.0, 1.0, 1.0, 0.0 + for _ in range(octaves): + total += value_noise(x * freq, y * freq, seed) * amp + norm += amp + amp *= 0.5 + freq *= 2.0 + return total / norm + + +def clamp8(value): + return max(0, min(255, int(value * 255.0 + 0.5))) + + +# --- patterns -------------------------------------------------------------- +# Each returns (base_rgb_function, roughness_function) over u, v in 0..1. + + +def stone_ashlar(courses=8, per_course=4): + """Coursed ashlar: horizontal beds, staggered vertical joints, stone grain.""" + course_h = 1.0 / courses + + def block_uv(u, v): + row = int(v / course_h) + shift = 0.5 * (row % 2) / per_course # stagger alternate courses + col = int(((u + shift) % 1.0) * per_course) + return row, col, (v % course_h) / course_h, ((u + shift) % 1.0) * per_course - col + + def base(u, v, px, py): + row, col, fj, fi = block_uv(u, v) + tone = 0.62 + 0.10 * _hash2(row, col, 11) # each block a little different + grain = 0.06 * (fractal(px * 0.15, py * 0.15, 5, 3) - 0.5) * 2.0 + # joint: a dark band along the bed and the head of each block + joint = 0.0 + if fj < 0.055 or fi < 0.055: + joint = -0.22 + value = tone + grain + joint + return (value * 1.02, value, value * 0.94) # faintly warm stone + + def rough(u, v, px, py): + row, col, fj, fi = block_uv(u, v) + base_r = 0.66 + 0.10 * _hash2(row, col, 29) + if fj < 0.055 or fi < 0.055: + base_r = 0.86 # joints are matte + return base_r + 0.06 * fractal(px * 0.2, py * 0.2, 3, 7) + + return base, rough + + +def stucco(): + def base(u, v, px, py): + blot = fractal(px * 0.03, py * 0.03, 4, 21) + tooth = fractal(px * 0.9, py * 0.9, 3, 22) + value = 0.58 + 0.12 * (blot - 0.5) + 0.05 * (tooth - 0.5) + return (value * 1.03, value, value * 0.96) + + def rough(u, v, px, py): + return 0.74 + 0.18 * fractal(px * 0.06, py * 0.06, 3, 31) + + return base, rough + + +def asphalt(): + def base(u, v, px, py): + agg = fractal(px * 1.4, py * 1.4, 3, 41) # fine aggregate + patch = fractal(px * 0.02, py * 0.02, 3, 42) # repair patches + value = 0.055 + 0.05 * (patch - 0.5) + 0.06 * (agg - 0.5) + return (value, value, value * 1.05) # cool bitumen + + def rough(u, v, px, py): + return 0.90 + 0.08 * fractal(px * 0.5, py * 0.5, 2, 43) + + return base, rough + + +def concrete(): + def base(u, v, px, py): + pores = fractal(px * 0.8, py * 0.8, 3, 51) + tone = fractal(px * 0.04, py * 0.04, 4, 52) + value = 0.50 + 0.10 * (tone - 0.5) + 0.05 * (pores - 0.5) + return (value * 1.01, value, value * 0.97) + + def rough(u, v, px, py): + return 0.84 + 0.10 * fractal(px * 0.15, py * 0.15, 3, 53) + + return base, rough + + +TEXTURES = { + "T_StoneAshlar": stone_ashlar(), + "T_Stucco": stucco(), + "T_Asphalt": asphalt(), + "T_Concrete": concrete(), +} + + +def render(name, pattern, size): + """Render one texture; returns {suffix: path} for _BC and _R.""" + base_fn, rough_fn = pattern + paths = {} + for suffix, fn in (("BC", base_fn), ("R", rough_fn)): + # Wrap in a function so the inner loop stays tight. + rows = [] + for y in range(size): + v = y / size + row = bytearray() + for x in range(size): + u = x / size + value = fn(u, v, x / size * size / 64.0, y / size * size / 64.0) + if isinstance(value, tuple): + row += bytes((clamp8(value[0]), clamp8(value[1]), clamp8(value[2]))) + else: + grey = clamp8(value) + row += bytes((grey, grey, grey)) + rows.append(bytes(row)) + path = os.path.join(OUT_DIR, f"{name}_{suffix}.png") + write_png(path, size, size, rows) + paths[suffix] = path + return paths + + +def main(argv=None) -> int: + global OUT_DIR + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/textures", + help="output directory (default /tmp/gigline/textures)") + ap.add_argument("--size", type=int, default=512, help="edge length in pixels") + args = ap.parse_args(argv) + OUT_DIR = args.out + + print(f"writing {len(TEXTURES) * 2} maps at {args.size}² to {OUT_DIR}") + for name, pattern in TEXTURES.items(): + paths = render(name, pattern, args.size) + for suffix, path in paths.items(): + print(f" {os.path.basename(path):28} {os.path.getsize(path):>8} bytes") + return 0 + + +OUT_DIR = "/tmp/gigline/textures" + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 4dcd7bb..33e5f2b 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -16,10 +16,13 @@ # deterministically instead of stacking duplicates. import os +import sys import unreal MAP = "/Game/Hollywood/DemoMap" +# Repository root, so the texture generator can be invoked by path. +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) RESULT = "/tmp/gigline/demo-map.txt" lines = [] @@ -50,7 +53,46 @@ def spawn(cls, loc, label, rot=None): return actor -def ensure_material(mat_name, spec): +TEXTURE_DIR = "/Game/Hollywood/Textures" + + +def import_texture(png_path, asset_name): + """Import a generated PNG as a Texture2D (idempotent: replaces the asset).""" + if not os.path.exists(png_path): + log(f"texture missing: {png_path}") + return None + task = unreal.AssetImportTask() + task.filename = png_path + task.destination_path = TEXTURE_DIR + task.destination_name = asset_name + task.automated = True + task.save = True + task.replace_existing = True + ui = unreal.TextureImportUI() if hasattr(unreal, "TextureImportUI") else None + if ui: + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + return unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{asset_name}") + + +def ensure_textures(): + """Generate/point at the facade maps and import them. Returns {name: asset}.""" + out_dir = "/tmp/gigline/textures" + gen = os.path.join(ROOT, "Tools", "generate_facade_textures.py") + if os.path.exists(gen) and not os.path.exists(os.path.join(out_dir, "T_StoneAshlar_BC.png")): + import subprocess + subprocess.run([sys.executable, gen, "--out", out_dir, "--size", "512"], check=False) + imported = {} + for name in ("T_StoneAshlar_BC", "T_StoneAshlar_R", "T_Stucco_BC", "T_Stucco_R", + "T_Asphalt_BC", "T_Asphalt_R", "T_Concrete_BC", "T_Concrete_R"): + asset = import_texture(os.path.join(out_dir, name + ".png"), name) + if asset: + imported[name] = asset + log(f"textures imported: {len(imported)}/8") + return imported + + +def ensure_material(mat_name, spec, textures=None): """Create/reuse a look-developed material under /Game/Hollywood/Site. Blender's flat diffuse colours do not survive the FBX -> Unreal import (the @@ -165,6 +207,44 @@ def ensure_material(mat_name, spec): link(joint, tinted, "Alpha") base_color_expr = tinted + # If a texture set is supplied for this material, sample it world-aligned and + # use it instead of the graph-only coursing: the map carries the coursing and + # the grain, so overlaying both would double the pattern. + tex = (textures or {}).get(spec.get("texture", "")) + if tex: + bc = (textures or {}).get(spec.get("texture", "") + "_BC") + # world position (cm) -> UV: one texture tile every tile_cm centimetres + tile_cm = float(spec.get("tile_cm", 300.0)) + uv_scale = node(unreal.MaterialExpressionMultiply) + link(wp, uv_scale, "A") + link(scalar(1.0 / tile_cm), uv_scale, "B") + mask_xy = node(unreal.MaterialExpressionComponentMask) + for pin in ("R", "G"): + try: + mask_xy.set_editor_property(pin, True) + except Exception: # noqa: BLE001 + pass + try: + mask_xy.set_editor_property("B", False) + mask_xy.set_editor_property("A", False) + except Exception: # noqa: BLE001 + pass + link(uv_scale, mask_xy) + if bc: + sample = node(unreal.MaterialExpressionTextureSample) + sample.set_editor_property("texture", bc) + link(mask_xy, sample, "UVs") + lib.connect_material_property(sample, "RGB", + unreal.MaterialProperty.MP_BASE_COLOR) + base_color_expr = sample + r_map = (textures or {}).get(spec.get("texture", "") + "_R") + if r_map: + r_sample = node(unreal.MaterialExpressionTextureSample) + r_sample.set_editor_property("texture", r_map) + link(mask_xy, r_sample, "UVs") + lib.connect_material_property(r_sample, "R", + unreal.MaterialProperty.MP_ROUGHNESS) + lib.connect_material_property(base_color_expr, "", unreal.MaterialProperty.MP_BASE_COLOR) # Roughness: lerp(base, base + variation, noise) so the highlight breaks up. @@ -337,9 +417,11 @@ def build(): # 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), + roughness=0.92, specular=0.22, noise_scale=0.006, + texture="T_Asphalt", tile_cm=900.0), "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), + roughness=0.88, specular=0.28, noise_scale=0.004, + texture="T_Concrete", tile_cm=250.0), "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), @@ -352,12 +434,13 @@ def build(): "M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.76, 0.72, 0.64), roughness=0.70, specular=0.32, noise_scale=0.005, courses_m=0.55, joint_width=0.11, - joint_tint=(0.40, 0.37, 0.32), roughness_variation=0.12), + joint_tint=(0.40, 0.37, 0.32), roughness_variation=0.12, + texture="T_StoneAshlar", tile_cm=440.0), # Rendered/stucco frontage: no coursing, but a fine tooth and a wider # roughness range than before, because flat stucco is what read as plastic. "M_Stucco": dict(dark=(0.50, 0.46, 0.40), bright=(0.68, 0.64, 0.57), roughness=0.76, specular=0.25, noise_scale=0.0025, - roughness_variation=0.20), + roughness_variation=0.20, texture="T_Stucco", tile_cm=260.0), # Stone in shadow: the hero's recessed wall behind the colonnade. Same # coursing as the face, a stop darker, so depth reads without a light. "M_FacadeStoneShadow": dict(dark=(0.40, 0.37, 0.32), bright=(0.52, 0.49, 0.43), @@ -385,9 +468,10 @@ def build(): "Kerb_North": "M_KerbConcrete", "Crossing_Markings": "M_RoadPaint", } + textures = ensure_textures() built = {} for mat_name, spec in MATERIAL_SPECS.items(): - built[mat_name] = ensure_material(mat_name, spec) + built[mat_name] = ensure_material(mat_name, spec, textures) imported = 0 for name, mat_name in SURFACE_MATERIALS.items():