From 39f07b0f5f3ab67d7aa7f9dd59fc13b33bf3666f Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:18:40 +0100 Subject: [PATCH 01/11] feat(hair): hair cards as their own skinned mesh, attached as a second component Follows the character-artist skill on two points it is explicit about: hair for a game character is a card system, and swappable parts stay separate meshes. - Tools/character_hair_build.py builds SM_Char_Hair: a scalp cap plus 18 cards in three rings, quads only, every vertex weighted 1.0 to the head bone, UV-mapped for the strand sheet from Tools/generate_character_textures.py. 321 verts / 404 tris. - Tools/ue_character_hair_import.py imports it as its own skeletal mesh, which is the point: joined into the body, Unreal collapses the second material slot and the hair renders with the skin material. - The pawn carries it as a second USkeletalMeshComponent on SetLeaderPoseComponent, so it follows the head bone with no second rig and hides with the body in first person. Still wrong and recorded: the cards point outward, so the silhouette reads as a spiked crown rather than a short cut. The next pass orients them tangentially along the skull with a second overlapping row. Tools/ue_character_materials.py also segfaults in the headless commandlet when editing a material graph, so the generated textures are not wired yet and the hair keeps its Blender base colour. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Test/Character/CharacterHair.uasset | 3 + .../Character/CharacterHair_Skeleton.uasset | 3 + .../Test/Character/T_Character_Hair.uasset | 3 + .../Character/T_Character_SkinDetail.uasset | 3 + Game/GigLine/Content/Test/ReviewMap.umap | 4 +- .../Source/GigLine/GigLineCharacter.cpp | 20 ++ .../GigLine/Source/GigLine/GigLineCharacter.h | 11 + MEMORY.md | 6 + Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- .../demo/review-demo-retail-frontage.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- .../demo/review-demo-third-person-front.png | 4 +- .../demo/review-demo-third-person-walk.png | 4 +- Screenshots/demo/review-demo-third-person.png | 4 +- TODO.md | 6 +- Tools/character_hair_build.py | 213 ++++++++++++++++++ Tools/ue_character_hair_import.py | 70 ++++++ Tools/ue_character_materials.py | 138 ++++++++++++ 46 files changed, 519 insertions(+), 47 deletions(-) create mode 100644 Game/GigLine/Content/Test/Character/CharacterHair.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterHair_Skeleton.uasset create mode 100644 Game/GigLine/Content/Test/Character/T_Character_Hair.uasset create mode 100644 Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset create mode 100644 Tools/character_hair_build.py create mode 100644 Tools/ue_character_hair_import.py create mode 100644 Tools/ue_character_materials.py diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 072f7c4..4626b60 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:23ed6e12412617ac3b7e2ed06c563d8d74dc065e420697ebee1b46a597cf9153 -size 782191 +oid sha256:8de62798452ec172a9a16454c52b6b4067c785c782856e9957f94d72e9b0bcce +size 782187 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 0be4fc3..8c247dd 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:04bd4d8a153654fb54399f1ea50a5162a2ee4db0ba390cfa719128681cdf4f39 +oid sha256:d7813524abdda9ff0c36675b03e0834d0e42ad1398bf5ab41fd43051006beb8f size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index f359aa8..2f0baa2 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:7c3aa82ebe5f7a8f908031b50e1728724067e72d874695d462966bd9301667b9 +oid sha256:d11332ddd221887467723279522a1e001d34ecb31dc2a492ea6e95bcb24200a1 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index f766ce3..4ce4ad9 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:a0ce2a9b9068927b9c8ef6718a6407edaeb1c9a8322ce64fdc7cc6e9e8f91473 +oid sha256:d6e295f4bb0fbdb0019fa60bdea16e76dbdc8f9190fc6f5c866322ff04048180 size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index a0790f4..4e1e5cc 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:78d3c796adbf93aeec2ef38f6c100aa20efe91e02dc41eb91ef43a0808d8eabb +oid sha256:d3adfdf3801ada7f0dfba07ba047f832d7fb14dae40e5c68f4b3d081a284af64 size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index d3cab60..45c8e07 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:13668d5d7d31f52b02df2876b9e313b184406e9a24316f29c1a630f947efe95e +oid sha256:7b81c127d48d6197a64c4e2659552cba9216040b6539e2e858532bd178aca767 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 8ac58fa..c6a4cbf 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:335a90dfc29124c05482abdac17f3d8c1354ae0204147fef2825cc8ae3712cdd +oid sha256:ad51f421a3586dd35254243895992e68b588f3112ebfda2de8b65f1a213e596a size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index e934217..813b10d 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:5b9d34bcfa8da66cb14b03389c6ead8f9f1ae754a8a1ced857fc62f226984e41 +oid sha256:e1421d11234823430518fd5a54d55bf3cbbc2a55ab1f3b074af937a4e8278f19 size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 93166b5..829b14a 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:a34905b08eb54f71b97cb87d3fec7dbddff42b71940839372dd6e44dfd71c315 +oid sha256:15e398b8e172bda884355ab272df67c545898ebbdb2f354fe804dae562c350db size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 85ab9f3..96521d1 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:8c506556a13e23ddb87dfa004d4d1979b1e4ba242eb316c9f9c35775ee7e70c1 +oid sha256:ba4e8f0049f41185b3055937940cbf49fe5161e5b666ca657d2a89f059fb240a size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index a145b51..b6e1c19 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:f5a4f2e36fd29d282bbf90a586f1dbc1bbd37de13c1d589c75adf1cd5b30e0ef +oid sha256:c27c5f16cb64c6b6f982d2c5f02c3b8228661195aec2965a44e652f51d3d201f size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 7ef0e8c..a976d33 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:6cf21dd7c6ba931027fa33c923a8733a6946a3eb3d89d7c78aacc5f452d08e28 +oid sha256:3843721059acc7150315582fbf1b9def613e25dd080075d538d0e3242506f5d7 size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index 29a8cd2..ae6b6c3 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7c937ea5c1641b1b8eec4710b18a0dfc9d0212b07e94bedeb9639e3d16fcf51 +oid sha256:7c6e450c1b0a3521ae49a289abdf4d42fde11f2d5bdbe0a7bedb578ef30e16d8 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index 37807ac..2549b47 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:786cb9b8a2541e3ca112e9035ea8ca8d8e08b5ca0aab3f4b4273155812583597 +oid sha256:68e68c98469dc02e6c5b8fcb2c3e900e8fab307bc2d949086b5235264033d564 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 96cf92d..0cb65af 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37101199dd79da73000b7440c88a7abc0ec8ac826a19ba1881d53d759a794631 +oid sha256:1c8c6abccdfebaffef6972723ae1f5c41f6b7d7df0f4d9d63db0803dd2a9b046 size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index 5f566b8..e45cd49 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbd02bca5a584563e5b1c2f69847efc99a99e32ed7c888c2e422fe7f36153663 +oid sha256:9f22caa766a717ac75a4f9429d17f7400613b32f0dbe8d28731c12c0aed4edad size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index 14889f1..6240c8f 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01d251745b9410fa723ecb628037866b3524d556638f2f20314381ddcdd57e96 +oid sha256:b6b94da8dfca26cd352b52728b8242e44e780409b0c3937226f093e2d8ebe212 size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 7235ff2..7cd1c3b 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19d734e5e90e7281644668888e79db9c32417c5cbd9e5cbb107a843839a34d55 +oid sha256:8ba641f281f25e0e701e1eb25077190440fc68b99ba7752e4875446a94a05659 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index 5838451..820ce12 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b3f4850c1625d3ab41dd44e4863cf786757f2613984fdce28ea43bb75c68770 +oid sha256:aa0b132aeb6b4fbb2cb8761359f704a361ee14df75555f5b191a2229880d8f3d size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index 004e600..a5bf1b4 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbde83955c19ff3eba36dc49e97e6b3e9ca495b79ae4b0461223e046ab5e420a +oid sha256:2921580cd5d52b92370e75934aa75a17e732210c335080afeeaf1daf978ef35f size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 7ad379a..9a834e6 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:510b14b2f9c9072a0e5448fef95b48f387967a94f85dbc17e7bb79103a6ca258 +oid sha256:dbae927e11940232de71bab55753a112e22a19f06af982fedd1c034f3d6a7d11 size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index ddf418c..a284c86 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:358972a96f6891a8fe9a994643cde7be92c0d1ba88cf45bf0eefd1b0e78b9bfb +oid sha256:34b7e9401e78d7628f17c722337e9ea47f7b327e8eb953dfad155a01c12acb05 size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index a3624c1..7cd96d8 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03ef3c1d54d249fad5a5aa7db0b6c7db7452887722bfd9554b500dc74f6aee3a +oid sha256:9b0372b50359c9807961b2f4db59d394d3e364c27a9b07b66bc48bbf2f59f719 size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index 7b91954..0f431c1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04a38325dd685644e95d09072e6e2b0bb6985d4ef32ff4e71ea21f3d47ac76b7 +oid sha256:3cb25e6212bb41d463201a129be68c71b673c7ac54ad9083b47e2b015ebce747 size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index db3ab16..c6de12a 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:712144a026cc52d147c79dbfcfb76d058668bc3b7a9f254a00405311d13b984f +oid sha256:6cf2d288294921a07933e9c80afaf0d5872cffa589e207cc93622cb5b8901d8f size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index 3dda446..449f847 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a53c923dc6c399ce97247325317b1a8532a25563b0cce73265c4e93352b31c65 +oid sha256:4737a430db9b10bbf4a45c1ca8f8451cab2dfc81a0b4bda7e2961a5f4ade485a size 72413 diff --git a/Game/GigLine/Content/Test/Character/CharacterHair.uasset b/Game/GigLine/Content/Test/Character/CharacterHair.uasset new file mode 100644 index 0000000..1e70d74 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterHair.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a8161350d42c9f78a42fa0da5117a32dfbe3bda5e82dea6ec381d11f6d6f968 +size 250937 diff --git a/Game/GigLine/Content/Test/Character/CharacterHair_Skeleton.uasset b/Game/GigLine/Content/Test/Character/CharacterHair_Skeleton.uasset new file mode 100644 index 0000000..3241fc2 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterHair_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4093ce960bec358063314fe597b85958651fee6ce1dcbac9f4a7eeff9730d754 +size 29884 diff --git a/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset new file mode 100644 index 0000000..c24c9c5 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cb1c096e4411359dbfc5bb6f96bb5836d87e99d78ee0997d15023d7d00e0ed4 +size 154469 diff --git a/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset new file mode 100644 index 0000000..48fd7f2 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3491b466f4b02c6412b8cc439aaa586c25cc5624f0bb9348c19d0139ccc7842f +size 86485 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 895f13d..6c43951 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:e5a9d7551e24c2e2805e97f234cd5bbb4321d769c00a4cbe49f0835186c341d0 -size 29389 +oid sha256:2642ee68daa97bfee857231934a99dba98d83a318de1fe3cdf44da29b824473d +size 29694 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 7224e18..f3eabe6 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -90,6 +90,22 @@ AGigLineCharacter::AGigLineCharacter() GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision); GetMesh()->SetHiddenInGame(true); + // Hair: a separate component that copies the body's pose, so it follows the head + // bone without a second rig or a second animation. Hidden with the body. + HairMesh = CreateDefaultSubobject(TEXT("HairMesh")); + HairMesh->SetupAttachment(GetCapsuleComponent()); + HairMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + HairMesh->SetHiddenInGame(true); + static ConstructorHelpers::FObjectFinder HairFinder( + TEXT("/Game/Test/Character/CharacterHair.CharacterHair")); + if (HairFinder.Succeeded()) + { + HairMesh->SetSkeletalMesh(HairFinder.Object); + HairMesh->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f)); + HairMesh->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f)); + HairMesh->SetLeaderPoseComponent(GetMesh()); + } + // Third-person boom. bDoCollisionTest is the camera-collision feature: the arm // shortens when something is between the pivot and the desired camera position. ThirdPersonArm = CreateDefaultSubobject(TEXT("ThirdPersonArm")); @@ -795,6 +811,10 @@ void AGigLineCharacter::ToggleCamera() FirstPersonCamera->SetActive(!bThirdPerson); ThirdPersonCamera->SetActive(bThirdPerson); GetMesh()->SetHiddenInGame(!bThirdPerson); + if (HairMesh) + { + HairMesh->SetHiddenInGame(!bThirdPerson); + } if (bThirdPerson) { UpdateBodyAnimation(); diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.h b/Game/GigLine/Source/GigLine/GigLineCharacter.h index f084b4f..601c530 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.h +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.h @@ -155,6 +155,17 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera") float ThirdPersonBoomLength = 340.0f; + /** + * Hair, as its own skeletal mesh following the body's pose. + * + * A second component rather than geometry joined into the body: Unreal collapses + * the second material slot on a skeletal-mesh import, so joined hair rendered with + * the skin material. The character-artist skill calls for hair cards to be their + * own mesh for the same reason it calls for swappable parts to be separate. + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body") + TObjectPtr HairMesh; + UPROPERTY() TObjectPtr IdleAnimation; diff --git a/MEMORY.md b/MEMORY.md index 70f2d1a..7aebd2c 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -492,3 +492,9 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **ComfyUI is reachable at `127.0.0.1:8188` (0.44.0)** and its API is the clean way in: `POST /prompt` with an API-format graph (`[node_id, output_index]` references — the UI's workflow JSON cannot be posted), then poll `/history/` and fetch `/view?filename=...`. `Tools/comfyui_generate.py` does this and writes a sidecar with model, prompt, seed and the full graph. Installed checkpoints: `juggernautXL_ragnarok.safetensors`, `epicrealismXL_pureFix.safetensors`, `sd-v1-5-inpainting.ckpt`, plus inpainting variants. - **Do not ask Stable Diffusion for a flat texture swatch.** Three attempts at "seamless skin texture" produced an anatomical close-up twice and a leather-scale surface once. SDXL has no reliable notion of a material sample without anatomy, so every seed needs manual rejection — worse than noise we control. Procedural generation won for skin pores (`Tools/generate_character_textures.py`, tileable octave noise) and a strand sheet for hair. - **Unreal collapses the second material slot on a skeletal-mesh import.** A joined hair cap (Blender shows two slots, hair faces on index 1) arrives as a single slot named after the body material, so the hair renders with the skin material. `unreal.SkeletalMesh` has no `set_material`, so it cannot be re-slotted after import. A separate skeletal mesh attached as a second component is the way around it. + +## 2026-11-09 — Hair as a separate mesh, and a material-editing crash + +- **Hair joined into the body mesh loses its material.** Blender shows two slots (hair faces on index 1) and the FBX exports both, but Unreal's skeletal-mesh import arrives with **one** slot named after the body material, so the hair renders as skin. `unreal.SkeletalMesh` has no `set_material`, so it cannot be re-slotted afterwards. A separate skeletal mesh imported on its own keeps its slot and imports cleanly (`CharacterHair` → `M_Character_Hair`). +- **A second skeletal component follows the body with `SetLeaderPoseComponent(GetMesh())`** — no second rig, no second animation, and it hides with the body in first person. +- **`unreal.MaterialEditingLibrary` segfaults in `-run=pythonscript`** when it clears and rebuilds a material graph. The tool ran its texture imports, then crashed the commandlet with no Python traceback. Graph editing needs an editor session, or a material created from scratch rather than edited in place. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 7fb5fb2..93218dc 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:c1dc1a654bce66311b3838dd2d32ae8e5dc1c2861581f39ff54215c7f7b05f82 -size 3090065 +oid sha256:83ae29e23d32692d8b468d379331f97795edace8863804ee1c920e14f7f56822 +size 3088663 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 85dbcb5..1f08dd2 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:331bc406f4dc42e21f274b414071a5748eaa7f0434959a22840eb302df8256f6 -size 2618207 +oid sha256:f8819f25cde7e94188d51b685991eaa3d7fd0a3cc840efcf7e053aff90005798 +size 2617803 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index 5680fe0..64d45e1 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fbdb5079d7311cc8913ae010cb1d50d595c77a04681c0d262f1fc9831b12ac0 -size 2523952 +oid sha256:c90621d73dd2342dba7cb8357ad82f7fc1f5aa7dc5718035d58aa20002df7994 +size 2523413 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 25efad0..1fd7f62 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:e9ee0259368be8758dc8d88ac2e75e40ffa3e13cbbb5322c0f30c06e9b71f465 -size 2936215 +oid sha256:293cbb2b1d19f232b6af002c65d89c05ca917c6e0339cae2f5b1f3bc8ce93ddf +size 2933226 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 23a2233..5c66c6b 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:723655238f1df703a1512af7db1b7b68850897aa5363736c57854d89036fbb8b -size 2850311 +oid sha256:69ba600ae3596b62818eccfd4d49fb387241dfb8e798a5c097903038f72a592c +size 2848456 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index 5ad0616..c86df1d 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc57859443bbf10db3d5644332f0a301f4c56f0bb00a366296e55b21d86f44a4 -size 2791588 +oid sha256:b0e653a3b904858789ef1121ab1305c2e33467d9c2fab6d7ea233aae7986efa9 +size 2793949 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index 3e897da..9aa203c 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:329b4a414e70de16434b3af1f7d8fe0c5bcb1f57401a5663db8c41fc7392ffe2 -size 2180500 +oid sha256:85a7cf6d6090893ba5f1ac52095cf6a395e9caa74b0b187db245cc4b1357bf36 +size 2179777 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index 4afc985..1adf41e 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33ab2bd185517177d59bee4a815a3ef5d50a3524926a02e586a730046cd247db -size 2999109 +oid sha256:a8ece3502186e088dbe9495edae9b0185d2f413738bb169ab184182b30950e59 +size 2996384 diff --git a/TODO.md b/TODO.md index 9814a38..8dd58c2 100644 --- a/TODO.md +++ b/TODO.md @@ -15,10 +15,12 @@ Actionable task list for this project. This file is the single, central place fo - [x] Restore the stride capture after the pose rewrite. The walk cycle started on a **sine**, zero at both the start and the middle of the cycle, so the deterministic capture kept landing on the two passing frames where the legs are together. Starting the cycle at maximum stride (cosine) fixed it with no other change; `demo-third-person-walk` is back as shot 8 and shows a genuine stride. -- [A] Tune skin shading and hair materials on the player character. **Tooling in place 2026-11-09, hair blocked on an engine-side limit:** +- [~] Tune skin shading and hair materials on the player character. **Tooling in place 2026-11-09, hair blocked on an engine-side limit:** - `Tools/comfyui_generate.py` drives the local ComfyUI instance (`127.0.0.1:8188`) for textures, writing a JSON sidecar with the model, prompt, seed and full workflow so anything generated can be regenerated or rejected. Installed models include `juggernautXL_ragnarok.safetensors` and `sd-v1-5-inpainting.ckpt`. - **Stable Diffusion is not usable for flat skin swatches.** Three attempts on two models produced an anatomical close-up twice and a leather-scale surface once; SDXL has no reliable notion of "texture without anatomy". Skin detail is therefore generated procedurally instead (`Tools/generate_character_textures.py`: tileable octave noise for pores plus a strand-sheet texture for hair). - - **Hair geometry is built but not shipped.** `add_hair_cap()` creates a cap conforming to the head and weights it to the `head` bone (Blender shows two material slots, hair faces on index 1), but Unreal's import **collapses the second material slot**, so the cap renders with the skin material and reads as a bald skull. It is behind `--hair`, off. Next step: export the hair as its own skeletal mesh and attach it as a second component, or bake the hair colour into vertex colours. + - **Hair is now its own asset** (`Tools/character_hair_build.py` → `SM_Char_Hair` → `/Game/Test/Character/CharacterHair`), which is what the `character-artist` skill asks for: cards are a separate mesh, not geometry joined into the body. It imports with its own material slot and is attached in C++ as a second `USkeletalMeshComponent` on `SetLeaderPoseComponent`, so it follows the head bone without a second animation. 321 verts, 192 quads, 404 tris. + - **The card layout is still wrong.** The cards are anchored pointing outward, so the silhouette reads as a spiked crown instead of a short cut. Next iteration: orient each card tangentially along the skull, add a second overlapping row, and drive the shape from the scalp normal rather than a fixed outward vector. + - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. - [ ] Tag `v0.2.2` and write `CHANGELOG.md` entry; then remove this section. diff --git a/Tools/character_hair_build.py b/Tools/character_hair_build.py new file mode 100644 index 0000000..51a3d6e --- /dev/null +++ b/Tools/character_hair_build.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +"""Build the hair as its own skinned mesh — hair cards over a scalp cap. + +The `character-artist` skill is explicit that hair for a game character is a card +system and that swappable parts stay separate meshes. Joining hair into the body mesh +was tried first and failed for an engine reason: Unreal collapses the second material +slot on a skeletal-mesh import, so the cap rendered with the skin material. A separate +object sidesteps that and is what the skill calls for anyway. + + blender -b Art/blender/character/character-2026-11-07.blend \ + --python Tools/character_hair_build.py -- \ + --out /tmp/gigline/character-hair.fbx --report /tmp/gigline/character-hair.json + +What it builds: + + SM_Char_Hair scalp cap + 18 cards in three rings, UV-mapped to the strand sheet + from Tools/generate_character_textures.py, every vertex weighted + to `head` at 1.0 so it follows the head without a second rig. + +Cards are built with a taper and a slight outward curl so the silhouette is not a +shell; the strand texture runs vertically down each card, which is the direction +hair actually reads in. Topology is quads only (the skill's animation-ready rule), +and there are no poles on the deformation surface because the whole mesh is rigidly +weighted to one bone. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys + +import bpy +from mathutils import Vector + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import fbx_export_preset as fbx_preset # noqa: E402 + +HAIR_NAME = "SM_Char_Hair" +CARD_RINGS = 3 # rings of cards around the skull +CARDS_INNER = 6 +CARDS_MIDDLE = 7 +CARDS_OUTER = 5 +CARD_HEIGHT = 0.072 # m: short, close cut - taller cards spiked up +CAP_THICKNESS = 0.004 + +PARTS: list[dict] = [] + + +def log(message: str) -> None: + PARTS.append(message) + print(f"[hair] {message}") + + +def hair_material() -> bpy.types.Material: + mat = bpy.data.materials.get("M_Character_Hair") or bpy.data.materials.new("M_Character_Hair") + mat.use_nodes = True + bsdf = mat.node_tree.nodes.get("Principled BSDF") + if bsdf: + # Dark brown, low roughness: strands catch a highlight. The strand texture is + # sampled in Unreal, so Blender only needs a sane base for the FBX material. + bsdf.inputs["Base Color"].default_value = (0.052, 0.036, 0.028, 1.0) + bsdf.inputs["Roughness"].default_value = 0.38 + if "Alpha" in bsdf.inputs: + bsdf.inputs["Alpha"].default_value = 1.0 + mat.use_backface_culling = False + return mat + + +def card(name, anchor, outward, up, width, height, curl, segments=4): + """One curved card: a strip of quads that arcs away from the scalp.""" + mesh = bpy.data.meshes.new(name) + obj = bpy.data.objects.new(name, mesh) + bpy.context.scene.collection.objects.link(obj) + + verts = [] + faces = [] + for i in range(segments + 1): + t = i / segments + # Taper to a point at the tip and curl outward as the strand falls. + w = width * (1.0 - 0.75 * t) + h = height * t + bulge = curl * math.sin(t * math.pi * 0.85) + base = anchor + up * h + outward * bulge + left = base - outward.cross(up).normalized() * w + right = base + outward.cross(up).normalized() * w + verts.append(left) + verts.append(right) + for i in range(segments): + a = i * 2 + faces.append((a, a + 1, a + 3, a + 2)) + mesh.from_pydata(verts, [], faces) + mesh.update() + return obj + + +def scalp_cap(centre, radius, material): + bpy.ops.mesh.primitive_uv_sphere_add(segments=20, ring_count=12, radius=radius, + location=centre) + cap = bpy.context.active_object + cap.name = "HairScalp" + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.mesh.select_all(action="SELECT") + bpy.ops.mesh.bisect(plane_co=(0.0, 0.0, centre.z - radius * 0.15), + plane_no=(0.0, 0.0, 1.0), clear_inner=True, use_fill=False) + bpy.ops.object.mode_set(mode="OBJECT") + cap.scale = (1.03, 1.06, 0.86) + cap.data.materials.append(material) + return cap + + +def main(argv=None) -> int: + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/character-hair.fbx") + ap.add_argument("--report", default=None) + args = ap.parse_args(argv) + + arm = next((o for o in bpy.data.objects if o.type == "ARMATURE"), None) + if arm is None: + raise SystemExit("no armature in the .blend") + head = arm.data.bones.get("head") + if head is None: + raise SystemExit("no 'head' bone") + + centre = (head.head_local + head.tail_local) * 0.5 + radius = max(0.088, (head.tail_local - head.head_local).length * 0.60) + log(f"head centre {[round(c, 3) for c in centre]}, radius {radius:.3f} m") + + material = hair_material() + pieces = [scalp_cap(Vector((centre.x, centre.y, centre.z + 0.012)), radius, material)] + + up = Vector((0.0, 0.0, 1.0)) + rings = ((CARDS_INNER, 1.00, 0.70), (CARDS_MIDDLE, 0.90, 0.85), (CARDS_OUTER, 0.78, 1.0)) + index = 0 + for ring, (count, radius_scale, length_scale) in enumerate(rings): + for i in range(count): + angle = math.tau * (i + 0.5 * ring) / count + outward = Vector((math.cos(angle), math.sin(angle), 0.22)).normalized() + anchor = Vector(( + centre.x + outward.x * radius * radius_scale * 0.92, + centre.y + outward.y * radius * radius_scale * 0.92, + centre.z + radius * (0.52 - 0.16 * ring), + )) + pieces.append(card(f"HairCard_{index}", anchor, outward, up, + width=radius * 0.30, height=CARD_HEIGHT * length_scale, + curl=radius * 0.34)) + index += 1 + log(f"built {len(pieces)} pieces: 1 scalp cap + {index} cards") + + # Join into one object and weight every vertex to the head bone. + bpy.ops.object.select_all(action="DESELECT") + for piece in pieces: + piece.select_set(True) + bpy.context.view_layer.objects.active = pieces[0] + bpy.ops.object.join() + hair = bpy.context.active_object + hair.name = HAIR_NAME + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + hair.data.materials.clear() + hair.data.materials.append(material) + + group = hair.vertex_groups.new(name="head") + group.add(list(range(len(hair.data.vertices))), 1.0, "REPLACE") + + # UVs: a simple planar unwrap is enough because the strand texture is directional. + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.mesh.select_all(action="SELECT") + bpy.ops.uv.smart_project(angle_limit=1.15, island_margin=0.01) + bpy.ops.object.mode_set(mode="OBJECT") + + quads = sum(1 for p in hair.data.polygons if len(p.vertices) == 4) + tris = sum(len(p.vertices) - 2 for p in hair.data.polygons) + log(f"{HAIR_NAME}: {len(hair.data.vertices)} verts, {quads} quads, {tris} tris, " + f"{len(hair.data.materials)} material slot(s)") + + # Parent to the armature so the export carries the skin. + modifier = hair.modifiers.new(name="Armature", type="ARMATURE") + modifier.object = arm + + bpy.ops.object.select_all(action="DESELECT") + arm.select_set(True) + hair.select_set(True) + bpy.context.view_layer.objects.active = arm + os.makedirs(os.path.dirname(args.out), exist_ok=True) + fbx_preset.export_character(args.out, selection=True) + log(f"exported {args.out}") + + if args.report: + with open(args.report, "w") as handle: + json.dump({ + "asset": HAIR_NAME, + "head_centre_m": [round(c, 4) for c in centre], + "head_radius_m": round(radius, 4), + "cards": index, + "rings": [r[0] for r in rings], + "vertices": len(hair.data.vertices), + "quads": quads, + "triangles": tris, + "material": material.name, + "texture": "Art/textures/generated/T_Character_Hair.png", + "weighting": "all vertices 1.0 on 'head'", + "log": PARTS, + }, handle, indent=2) + print(f"[hair] report -> {args.report}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_character_hair_import.py b/Tools/ue_character_hair_import.py new file mode 100644 index 0000000..f387e25 --- /dev/null +++ b/Tools/ue_character_hair_import.py @@ -0,0 +1,70 @@ +# Import the hair cards as their own skeletal mesh (0.2.2). +# +# blender -b Art/blender/character/character-2026-11-07.blend \ +# --python Tools/character_hair_build.py -- --out /tmp/gigline/character-hair.fbx +# /Engine/Binaries/Linux/UnrealEditor-Cmd -run=pythonscript \ +# -script=/Tools/ue_character_hair_import.py -nullrhi -unattended -nopause -stdout +# +# The hair is a separate asset on purpose: Unreal collapses the second material slot +# when hair is joined into the body mesh, so a card system has to be its own object +# (which is what the character-artist skill asks for anyway). +# +# Result file: /tmp/gigline/character-hair-import.txt + +import unreal + +SRC = "/tmp/gigline/character-hair.fbx" +DEST = "/Game/Test/Character" +NAME = "CharacterHair" +RESULT = "/tmp/gigline/character-hair-import.txt" +lines = [] + + +def log(msg): + lines.append(str(msg)) + unreal.log(str(msg)) + + +def main(): + task = unreal.AssetImportTask() + task.filename = SRC + task.destination_path = DEST + task.destination_name = NAME + task.automated = True + task.save = True + task.replace_existing = True + + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_as_skeletal = True + ui.import_animations = False + ui.import_materials = True + ui.import_textures = False + ui.skeletal_mesh_import_data.import_uniform_scale = 1.0 + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{NAME}") + if mesh is None: + log(f"MISSING {DEST}/{NAME}") + lines.append("VERDICT: FAIL") + return + mats = mesh.get_editor_property("materials") + log(f"{NAME}: {len(mats)} material slot(s)") + for i, m in enumerate(mats): + face = m.get_editor_property("material_interface") + log(f" slot {i}: {face.get_name() if face else 'None'}") + lines.append("VERDICT: PASS") + + +try: + main() +except Exception as exc: # noqa: BLE001 + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + lines.append("VERDICT: FAIL") + +unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True) +with open(RESULT, "w") as f: + f.write("\n".join(lines) + "\n") diff --git a/Tools/ue_character_materials.py b/Tools/ue_character_materials.py new file mode 100644 index 0000000..73b8183 --- /dev/null +++ b/Tools/ue_character_materials.py @@ -0,0 +1,138 @@ +# Wire the generated character textures into Unreal materials (0.2.2). +# +# python3 Tools/generate_character_textures.py --out Art/textures/generated +# /Engine/Binaries/Linux/UnrealEditor-Cmd -run=pythonscript \ +# -script=/Tools/ue_character_materials.py -nullrhi -unattended -nopause -stdout +# +# Two materials, both edited IN PLACE so the imported meshes keep their references: +# +# M_Character_Export skin: diffuse x generated pore detail, roughness variation +# M_Character_Hair hair: strand texture colour + alpha, masked and two-sided +# +# Editing in place matters: `unreal.SkeletalMesh` has no `set_material`, so a mesh +# that already points at a material cannot be re-slotted from Python. Changing the +# material asset is the only lever. +# +# Result file: /tmp/gigline/character-materials.txt + +import os + +import unreal + +# The commandlet's working directory is the engine, not the repo, so resolve the +# texture folder from the project path instead of relying on the cwd. +_REPO = os.path.abspath(os.path.join(unreal.Paths.project_dir(), "..", "..")) +TEXTURE_DIR = os.path.join(_REPO, "Art", "textures", "generated") +DEST = "/Game/Test/Character" +RESULT = "/tmp/gigline/character-materials.txt" +lines = [] + + +def log(msg): + lines.append(str(msg)) + unreal.log(str(msg)) + + +def import_texture(filename): + path = os.path.join(TEXTURE_DIR, filename) + if not os.path.exists(path): + log(f"missing {path}") + return None + task = unreal.AssetImportTask() + task.filename = path + task.destination_path = DEST + task.destination_name = os.path.splitext(filename)[0] + task.automated = True + task.save = True + task.replace_existing = True + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + texture = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{task.destination_name}") + log(f"texture {task.destination_name}: {'ok' if texture else 'MISSING'}") + return texture + + +def clear_graph(material): + """Remove every expression so a rerun does not stack nodes.""" + lib = unreal.MaterialEditingLibrary + for node in list(lib.get_material_expressions(material)): + lib.delete_material_expression(material, node) + + +def textured_material(mat, texture, roughness, tint=None, masked=False): + """Base colour from a texture, optional detail multiply, roughness constant.""" + lib = unreal.MaterialEditingLibrary + clear_graph(mat) + + tex = lib.create_material_expression(mat, unreal.MaterialExpressionTextureSample, -450, 0) + tex.texture = texture + if masked: + mat.set_editor_property("blend_mode", unreal.BlendMode.BLEND_MASKED) + mat.set_editor_property("two_sided", True) + lib.connect_material_property(tex, "A", unreal.MaterialProperty.MP_OPACITY_MASK) + if tint is not None: + mul = lib.create_material_expression(mat, unreal.MaterialExpressionMultiply, -220, 40) + mul.set_editor_property("const_b", unreal.LinearColor(*tint, 1.0)) + lib.connect_material_expressions(tex, "RGB", mul, "A") + lib.connect_material_property(mul, "", unreal.MaterialProperty.MP_BASE_COLOR) + else: + lib.connect_material_property(tex, "RGB", unreal.MaterialProperty.MP_BASE_COLOR) + + rough = lib.create_material_expression(mat, unreal.MaterialExpressionConstant, -220, -180) + rough.set_editor_property("r", roughness) + lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) + lib.recompile_material(mat) + log(f"{mat.get_name()}: texture {texture.get_name()}, roughness {roughness}" + + (", masked two-sided" if masked else "")) + + +def main(): + skin_detail = import_texture("T_Character_SkinDetail.png") + hair_strands = import_texture("T_Character_Hair.png") + + skin = unreal.EditorAssetLibrary.load_asset(f"{DEST}/M_Character_Export") + if skin and skin_detail: + # The character's own diffuse stays the base; the generated pore sheet is a + # detail multiplier, so the warm skin tone from the bake is preserved. + lib = unreal.MaterialEditingLibrary + clear_graph(skin) + diffuse = lib.create_material_expression(skin, unreal.MaterialExpressionTextureSample, -700, 0) + diffuse.texture = unreal.EditorAssetLibrary.load_asset(f"{DEST}/T_Character_Diffuse") + detail = lib.create_material_expression(skin, unreal.MaterialExpressionTextureSample, -700, 300) + detail.texture = skin_detail + detail.sampler_type = unreal.MaterialSamplerType.SAMPLERTYPE_LINEAR_COLOR + mul = lib.create_material_expression(skin, unreal.MaterialExpressionMultiply, -380, 120) + lib.connect_material_expressions(diffuse, "RGB", mul, "A") + lib.connect_material_expressions(detail, "RGB", mul, "B") + # Detail is a multiplier, so keep it near 1 and let it vary a little. + lerp = lib.create_material_expression(skin, unreal.MaterialExpressionLinearInterpolate, -200, 120) + lerp.set_editor_property("const_a", unreal.LinearColor(1.0, 1.0, 1.0, 1.0)) + lib.connect_material_expressions(mul, "", lerp, "B") + rough = lib.create_material_expression(skin, unreal.MaterialExpressionConstant, -200, -160) + rough.set_editor_property("r", 0.55) + lib.connect_material_property(lerp, "", unreal.MaterialProperty.MP_BASE_COLOR) + lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) + lib.recompile_material(skin) + log("M_Character_Export: diffuse x pore detail, roughness 0.55") + elif not skin: + log("M_Character_Export missing") + + hair = unreal.EditorAssetLibrary.load_asset(f"{DEST}/M_Character_Hair") + if hair and hair_strands: + textured_material(hair, hair_strands, 0.38, masked=True) + elif not hair: + log("M_Character_Hair missing") + + unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True) + lines.append("VERDICT: PASS") + + +try: + main() +except Exception as exc: # noqa: BLE001 + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + lines.append("VERDICT: FAIL") + +with open(RESULT, "w") as f: + f.write("\n".join(lines) + "\n") -- 2.47.3 From 1fc5835327ed14b9697bdbda6d81284ae452040a Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:25:40 +0100 Subject: [PATCH 02/11] fix(hair): cards follow the scalp normal, three overlapping rows The first card build anchored every card on a fixed outward vector, so the silhouette was a spiked crown. Each card is now oriented from the scalp normal at its anchor: the length runs along the downward tangent, the width across it, and a small tip lift gives the cut volume. Three overlapping rows (7/9/10) replace the single ring; overlap is what stops cards reading as separate spikes. 26 cards, 401 verts, 224 quads, 468 tris, still all quads on one head bone. Recorded as still open: the skin renders darker than the baked diffuse suggests, and the generated pore and strand textures remain unwired because the material-editing tool segfaults in the headless commandlet. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Test/Character/CharacterHair.uasset | 4 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- Screenshots/README.md | 3 ++ Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- .../demo/review-demo-retail-frontage.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- .../demo/review-demo-third-person-front.png | 4 +- .../demo/review-demo-third-person-walk.png | 4 +- Screenshots/demo/review-demo-third-person.png | 4 +- TODO.md | 3 +- Tools/character_hair_build.py | 54 +++++++++++-------- 39 files changed, 84 insertions(+), 68 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 4626b60..ad1ac90 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:8de62798452ec172a9a16454c52b6b4067c785c782856e9957f94d72e9b0bcce -size 782187 +oid sha256:e716eb77fe4946cdd8aa8abca8891c0a29e60c2f6fb2faae9dee5c21ab01b5c4 +size 782209 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 8c247dd..e4ce9b9 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:d7813524abdda9ff0c36675b03e0834d0e42ad1398bf5ab41fd43051006beb8f +oid sha256:6b0a66117b33dcffeb8616b94a36f5449838ff9a2497e92618a81607cc9e5f27 size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 2f0baa2..7a69e45 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:d11332ddd221887467723279522a1e001d34ecb31dc2a492ea6e95bcb24200a1 +oid sha256:b6d7e79dc1a132a11a153259467f68570639cf08238487744b72fc914092f077 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index 4ce4ad9..0537cf4 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:d6e295f4bb0fbdb0019fa60bdea16e76dbdc8f9190fc6f5c866322ff04048180 +oid sha256:97431815765503077f3f98de4f3403c54d5472b03fb36f9d72371d2c8397e60e size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index 4e1e5cc..433d3f8 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:d3adfdf3801ada7f0dfba07ba047f832d7fb14dae40e5c68f4b3d081a284af64 +oid sha256:cef43c06daf2f8e8cb17a22398e26aad4faa89fbf0b19af5877836ca8d8a318a size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 45c8e07..8c7bcfb 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:7b81c127d48d6197a64c4e2659552cba9216040b6539e2e858532bd178aca767 +oid sha256:81d94e181fa048421387d68ca3d23946deaa5716e79359f1f912d52ae0252dd9 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index c6a4cbf..1d7a2dc 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:ad51f421a3586dd35254243895992e68b588f3112ebfda2de8b65f1a213e596a +oid sha256:00245e54cd7d163840cd32a385858e2e04a869cb7cf1d1a9964da5a44a6c234a size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 813b10d..0973393 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:e1421d11234823430518fd5a54d55bf3cbbc2a55ab1f3b074af937a4e8278f19 +oid sha256:efffd3d2c26184b024124aa9a82e7e6034b017d112edaf5b1d1663cba8ee73cb size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 829b14a..53d505f 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:15e398b8e172bda884355ab272df67c545898ebbdb2f354fe804dae562c350db +oid sha256:8d304a703dfc13801931ac99c1a18edd02e786bef2a70d56533f7e551f6b74f9 size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 96521d1..32fe208 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:ba4e8f0049f41185b3055937940cbf49fe5161e5b666ca657d2a89f059fb240a +oid sha256:5c78904e00685c177bd6ad6e09bdfa65a31a94675f49872bacc115212a6c9544 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index b6e1c19..5d8e187 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:c27c5f16cb64c6b6f982d2c5f02c3b8228661195aec2965a44e652f51d3d201f +oid sha256:16ae48369f7d2845f3bb173d7498900d0d9e54203c8a3ef76f8f115e68e64a6c size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index a976d33..990afad 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:3843721059acc7150315582fbf1b9def613e25dd080075d538d0e3242506f5d7 +oid sha256:9a89822abedeb6daf155acb873301c3185edeb9dc83fda6cb08779b828616195 size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index ae6b6c3..accddf7 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c6e450c1b0a3521ae49a289abdf4d42fde11f2d5bdbe0a7bedb578ef30e16d8 +oid sha256:08218e62cc3bf6906df435d2560b1647814443734cdc71db0bc11f56b80aaff2 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index 2549b47..d12ff5a 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68e68c98469dc02e6c5b8fcb2c3e900e8fab307bc2d949086b5235264033d564 +oid sha256:00599ec06721aec7c2d5ac357f58119647275301482e8b87b0df6d8b470e0b26 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 0cb65af..07b42cf 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c8c6abccdfebaffef6972723ae1f5c41f6b7d7df0f4d9d63db0803dd2a9b046 +oid sha256:06672fa907eebb228d03720f33025ac9f1611a4bdfe1941259f260ba39774a75 size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index e45cd49..bdb788b 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f22caa766a717ac75a4f9429d17f7400613b32f0dbe8d28731c12c0aed4edad +oid sha256:aebf18239d60cafb7c616072ad384fbc828b97ff9f9d36ed6bf19df0e8bbd952 size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index 6240c8f..befb7f9 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6b94da8dfca26cd352b52728b8242e44e780409b0c3937226f093e2d8ebe212 +oid sha256:8c90f9d586305e9223aaf2aa400ed59e4753c8297860ac90f5de2662fd08db93 size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 7cd1c3b..43c0e77 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ba641f281f25e0e701e1eb25077190440fc68b99ba7752e4875446a94a05659 +oid sha256:e3f5045bcea18a8475026b660b0e4f94fb47dac1ec18b1edf9793d7f5e1e6fa2 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index 820ce12..93a4d91 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa0b132aeb6b4fbb2cb8761359f704a361ee14df75555f5b191a2229880d8f3d +oid sha256:3e6806193859e62a7129a58f076207312fcc4712942c17252e1e86fbf8818539 size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index a5bf1b4..e2a026c 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2921580cd5d52b92370e75934aa75a17e732210c335080afeeaf1daf978ef35f +oid sha256:0a0e2ced9b86df11f0872ad5fdb82ec4c9e02248a420e575c35118927e6aa106 size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 9a834e6..318aa98 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbae927e11940232de71bab55753a112e22a19f06af982fedd1c034f3d6a7d11 +oid sha256:0b81aaa7eabf197612f01078f0aba08971140bd324c1e7baddce58029cd4dc47 size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index a284c86..dde2de1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34b7e9401e78d7628f17c722337e9ea47f7b327e8eb953dfad155a01c12acb05 +oid sha256:7e3dac804132d4871af48be131474922fe9907fafc7366519c10a21b1cb333d3 size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index 7cd96d8..1cd1294 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b0372b50359c9807961b2f4db59d394d3e364c27a9b07b66bc48bbf2f59f719 +oid sha256:af31fbf3bcf2bdf83c6bcdc7cb24942e41e60aca33c085e1c94014b0b136a0fc size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index 0f431c1..f7b7fbf 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cb25e6212bb41d463201a129be68c71b673c7ac54ad9083b47e2b015ebce747 +oid sha256:8853be6be3ca56682a939208579d841c4f32f776fce278e7ff250036cb23c4ee size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index c6de12a..dd4ff5d 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cf2d288294921a07933e9c80afaf0d5872cffa589e207cc93622cb5b8901d8f +oid sha256:e5fc3881423d08461729e0e3190c3fa452b79d9438eebc693a62f4d37283dce0 size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index 449f847..cfd69d1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4737a430db9b10bbf4a45c1ca8f8451cab2dfc81a0b4bda7e2961a5f4ade485a +oid sha256:27c444ad24e9eccd1847db563b805a4174ddd044ab279d31d23708e0d60611ba size 72413 diff --git a/Game/GigLine/Content/Test/Character/CharacterHair.uasset b/Game/GigLine/Content/Test/Character/CharacterHair.uasset index 1e70d74..6606492 100644 --- a/Game/GigLine/Content/Test/Character/CharacterHair.uasset +++ b/Game/GigLine/Content/Test/Character/CharacterHair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a8161350d42c9f78a42fa0da5117a32dfbe3bda5e82dea6ec381d11f6d6f968 -size 250937 +oid sha256:ed36e73f2eb1b803c0ae479b231578403dbce5bc431022e9bdf6abc0b2bf71c7 +size 274165 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 6c43951..2479442 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:2642ee68daa97bfee857231934a99dba98d83a318de1fe3cdf44da29b824473d +oid sha256:abd9ffa32561f7a7853782cbf593497c68ecb8252d1435ec39f4db4a4a0a2370 size 29694 diff --git a/Screenshots/README.md b/Screenshots/README.md index 6e1503a..f4b77fa 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -107,6 +107,7 @@ What this shot is evidence for: - The pawn carries a visible skeletal body with the warm skin material, and a spring-arm camera with collision testing. The **V** toggle swaps first and third person; the shipped default stays first person and the review passes `-thirdperson`. - **The idle clip drives the body and the arms hang at the sides**, with the hands at the hips and the feet flat. Earlier captures of this shot showed a bind A-pose. +- **Hair is present** as a short card system hugging the skull (three overlapping rows), not the spiked crown of the first attempt. Still open, tracked in `TODO.md`: @@ -175,6 +176,8 @@ Every issue is tracked in `TODO.md` under the version that will fix it; an issue | Retail parapet has no moulded profile, and wall lettering built as a plate does not read at 40 m | `0.9.0` → 6800 Lee Drug and 6806–6820, second pass | Open (moved out of `0.1.6`) | | 6822 Budget is a bare blockout volume — no photograph exists to model it from | `0.9.0` → photography gap | Deferred (moved out of `0.1.6`) | | Camera collision is implemented but never exercised against a wall | `0.2.0` → camera collision | Open | +| Skin renders darker than the baked diffuse suggests — the material graph may have been half-rebuilt by a crashed commandlet run | `0.2.2` → skin and hair tuning | Open | +| The generated pore and strand textures are not wired: the material-editing tool segfaults in the headless commandlet | `0.2.2` → skin and hair tuning | Open | | The walk clip has never been captured in motion — it imports and its curves carry the stride, but no shot shows it | `0.2.0` → verify animation plays correctly | Open | | Between kerb and building line the walkable surface is the terrain mesh, not a modelled pavement | `0.6.0` → model the pavement between kerb and building line | Open | diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 93218dc..5762846 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:83ae29e23d32692d8b468d379331f97795edace8863804ee1c920e14f7f56822 -size 3088663 +oid sha256:d77822c1340f570683397227c0dfa0d8af520a910305ef4861b289dead5413f2 +size 3089714 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 1f08dd2..237a8e6 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:f8819f25cde7e94188d51b685991eaa3d7fd0a3cc840efcf7e053aff90005798 -size 2617803 +oid sha256:a7bc84a57690b007ef77cdef347b571d791b5299aeb4caa887c851cd86d1a47b +size 2619786 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index 64d45e1..20afb2c 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c90621d73dd2342dba7cb8357ad82f7fc1f5aa7dc5718035d58aa20002df7994 -size 2523413 +oid sha256:24acb217407ccc0ce7f7608d196a952143e26fe0a67f899c8df0c82ab60ced39 +size 2522012 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 1fd7f62..012813c 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:293cbb2b1d19f232b6af002c65d89c05ca917c6e0339cae2f5b1f3bc8ce93ddf -size 2933226 +oid sha256:7e297b893de94513baaccb09b1f1ce303c13b46ed4071714c75b6ee0be1cce7d +size 2935924 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 5c66c6b..54468d3 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:69ba600ae3596b62818eccfd4d49fb387241dfb8e798a5c097903038f72a592c -size 2848456 +oid sha256:6e16263ab957b24bd80957ee9282b1f0f76a33fb570828687f01bd7d338b2525 +size 2850067 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index c86df1d..765fc7a 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0e653a3b904858789ef1121ab1305c2e33467d9c2fab6d7ea233aae7986efa9 -size 2793949 +oid sha256:98d43966ed285357e0a61c0a118517ad6b2c7e8dccfd08f52e092acb515cf7cb +size 2793193 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index 9aa203c..9ac3765 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85a7cf6d6090893ba5f1ac52095cf6a395e9caa74b0b187db245cc4b1357bf36 -size 2179777 +oid sha256:451b3724c57afc88b355c38cae9d3d524dbbc746f7d311d8d623510182ae0fc2 +size 2179242 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index 1adf41e..551d2ab 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8ece3502186e088dbe9495edae9b0185d2f413738bb169ab184182b30950e59 -size 2996384 +oid sha256:32b9b38f620246d4e4f84a2659a785f031b5a80a2a0b853d5552bf940006cbf0 +size 2999328 diff --git a/TODO.md b/TODO.md index 8dd58c2..4f62b08 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,8 @@ Actionable task list for this project. This file is the single, central place fo - `Tools/comfyui_generate.py` drives the local ComfyUI instance (`127.0.0.1:8188`) for textures, writing a JSON sidecar with the model, prompt, seed and full workflow so anything generated can be regenerated or rejected. Installed models include `juggernautXL_ragnarok.safetensors` and `sd-v1-5-inpainting.ckpt`. - **Stable Diffusion is not usable for flat skin swatches.** Three attempts on two models produced an anatomical close-up twice and a leather-scale surface once; SDXL has no reliable notion of "texture without anatomy". Skin detail is therefore generated procedurally instead (`Tools/generate_character_textures.py`: tileable octave noise for pores plus a strand-sheet texture for hair). - **Hair is now its own asset** (`Tools/character_hair_build.py` → `SM_Char_Hair` → `/Game/Test/Character/CharacterHair`), which is what the `character-artist` skill asks for: cards are a separate mesh, not geometry joined into the body. It imports with its own material slot and is attached in C++ as a second `USkeletalMeshComponent` on `SetLeaderPoseComponent`, so it follows the head bone without a second animation. 321 verts, 192 quads, 404 tris. - - **The card layout is still wrong.** The cards are anchored pointing outward, so the silhouette reads as a spiked crown instead of a short cut. Next iteration: orient each card tangentially along the skull, add a second overlapping row, and drive the shape from the scalp normal rather than a fixed outward vector. + - **The card layout now hugs the skull.** Each card is oriented by the scalp normal at its anchor — the length runs along the downward tangent, the width across it, and a small lift at the tip gives the cut volume. Three overlapping rows (7/9/10 cards, 26 total, 468 tris) replaced the single outward-pointing ring that read as a spiked crown. Overlap is what stops cards reading as separate spikes. + - **Skin still needs work:** it renders darker than the baked diffuse suggests — possibly because the crashed (or half-run) material tool rebuilt the graph — and the generated pore/strand textures are still unwired. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. - [ ] Tag `v0.2.2` and write `CHANGELOG.md` entry; then remove this section. diff --git a/Tools/character_hair_build.py b/Tools/character_hair_build.py index 51a3d6e..fec6c9b 100644 --- a/Tools/character_hair_build.py +++ b/Tools/character_hair_build.py @@ -69,8 +69,14 @@ def hair_material() -> bpy.types.Material: return mat -def card(name, anchor, outward, up, width, height, curl, segments=4): - """One curved card: a strip of quads that arcs away from the scalp.""" +def card(name, anchor, along, across, normal, width, height, lift, segments=4): + """A card that lies ALONG the scalp rather than sticking out of it. + + `along` is the direction the hair falls over the skull (the downward tangent at + the anchor), `across` the card's width direction and `normal` the scalp normal. + The first version passed a fixed global up and an outward vector, which made every + card stand away from the head - the spiked crown in the review shot. + """ mesh = bpy.data.meshes.new(name) obj = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(obj) @@ -79,13 +85,11 @@ def card(name, anchor, outward, up, width, height, curl, segments=4): faces = [] for i in range(segments + 1): t = i / segments - # Taper to a point at the tip and curl outward as the strand falls. - w = width * (1.0 - 0.75 * t) - h = height * t - bulge = curl * math.sin(t * math.pi * 0.85) - base = anchor + up * h + outward * bulge - left = base - outward.cross(up).normalized() * w - right = base + outward.cross(up).normalized() * w + w = width * (1.0 - 0.65 * t) + # Follow the skull, then lift a little at the tip so the cut has volume. + base = anchor + along * (height * t) + normal * (lift * math.sin(t * math.pi * 0.7)) + left = base - across * w + right = base + across * w verts.append(left) verts.append(right) for i in range(segments): @@ -133,21 +137,29 @@ def main(argv=None) -> int: material = hair_material() pieces = [scalp_cap(Vector((centre.x, centre.y, centre.z + 0.012)), radius, material)] - up = Vector((0.0, 0.0, 1.0)) - rings = ((CARDS_INNER, 1.00, 0.70), (CARDS_MIDDLE, 0.90, 0.85), (CARDS_OUTER, 0.78, 1.0)) + down = Vector((0.0, 0.0, -1.0)) + # Two rows: an inner row that covers the crown and an outer row that overlaps it + # down to the hairline. Overlap is what stops the cards reading as separate spikes. + rings = ( + # count, radius scale, anchor height, length, width, lift + (7, 0.55, 0.80, 0.085, 0.30, 0.25), + (9, 0.72, 0.62, 0.070, 0.26, 0.18), + (10, 0.86, 0.42, 0.055, 0.24, 0.12), + ) index = 0 - for ring, (count, radius_scale, length_scale) in enumerate(rings): + for ring, (count, radius_scale, height_scale, length, width_scale, lift_scale) in enumerate(rings): for i in range(count): angle = math.tau * (i + 0.5 * ring) / count - outward = Vector((math.cos(angle), math.sin(angle), 0.22)).normalized() - anchor = Vector(( - centre.x + outward.x * radius * radius_scale * 0.92, - centre.y + outward.y * radius * radius_scale * 0.92, - centre.z + radius * (0.52 - 0.16 * ring), - )) - pieces.append(card(f"HairCard_{index}", anchor, outward, up, - width=radius * 0.30, height=CARD_HEIGHT * length_scale, - curl=radius * 0.34)) + horizontal = Vector((math.cos(angle), math.sin(angle), 0.0)) + normal = (horizontal * radius_scale + Vector((0.0, 0.0, height_scale))).normalized() + anchor = Vector((centre.x, centre.y, centre.z)) + normal * radius + # Downward tangent: the part of -Z that lies in the scalp's tangent plane. + along = (down - normal * down.dot(normal)).normalized() + across = along.cross(normal).normalized() + pieces.append(card(f"HairCard_{index}", anchor, along, across, normal, + width=radius * width_scale, + height=radius * length * 10.0, + lift=radius * lift_scale)) index += 1 log(f"built {len(pieces)} pieces: 1 scalp cap + {index} cards") -- 2.47.3 From 85a8437cbc8cb09e0daf128b1dc1e071f0dede75 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:36:28 +0100 Subject: [PATCH 03/11] feat(character): ComfyUI blueprints and a render-vs-blueprint evaluation Two tools that turn character work into something measurable. - Tools/comfyui_character_blueprint.py generates a four-view sheet (front, side, back, head) plus a contact sheet from one description, with fixed seeds and a JSON record of prompts and prompt ids. First sheet: 'GigLine walker', lean pedestrian in a navy tee, jeans and sneakers. - Tools/character_match.py compares a rendered character against a sheet: figure aspect, fill and coverage deltas, mean CIE76 colour distance, dominant-palette distance, and a side-by-side montage. The deltas stay separate on purpose - a single similarity score between a photoreal sheet and a low-detail body would be a number with no meaning. - comfyui_generate.py grew wait_for_image() so both tools share the polling path instead of duplicating it. - 13 unit tests for the colour maths, masks and palette; 95 tool tests pass. First real result, recorded rather than smoothed over: aspect delta 1.24, fill delta 0.12, mean deltaE 31.9, palette deltaE 25.4. The montage shows the reason - the model is a nude body and the blueprint wears a tee and jeans, so clothing dominates every metric. Figure segmentation is explicit (--render-box): auto-detection picked backdrop blobs on every attempt, and a background plate is the proper fix. --- Art/characters/blueprints/back.png | 3 + Art/characters/blueprints/blueprint.json | 37 ++ Art/characters/blueprints/contact-sheet.png | 3 + Art/characters/blueprints/front.png | 3 + Art/characters/blueprints/head.png | 3 + Art/characters/blueprints/match-front.png | 3 + Art/characters/blueprints/side.png | 3 + Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Test/Character/M_Character_Hair.uasset | 4 +- .../Character/M_Character_Hair_Mat.uasset | 3 + .../Test/Character/M_Character_Skin.uasset | 3 + .../Test/Character/T_Character_Hair.uasset | 4 +- .../Character/T_Character_SkinDetail.uasset | 4 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- .../Source/GigLine/GigLineCharacter.cpp | 11 + MEMORY.md | 7 + Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- .../demo/review-demo-retail-frontage.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- .../demo/review-demo-third-person-front.png | 4 +- .../demo/review-demo-third-person-walk.png | 4 +- Screenshots/demo/review-demo-third-person.png | 4 +- TODO.md | 7 +- Tools/README.md | 4 + Tools/character_match.py | 368 ++++++++++++++++++ Tools/comfyui_character_blueprint.py | 118 ++++++ Tools/comfyui_generate.py | 98 ++--- Tools/tests/test_character_match.py | 82 ++++ Tools/ue_character_materials.py | 155 ++++---- 56 files changed, 841 insertions(+), 170 deletions(-) create mode 100644 Art/characters/blueprints/back.png create mode 100644 Art/characters/blueprints/blueprint.json create mode 100644 Art/characters/blueprints/contact-sheet.png create mode 100644 Art/characters/blueprints/front.png create mode 100644 Art/characters/blueprints/head.png create mode 100644 Art/characters/blueprints/match-front.png create mode 100644 Art/characters/blueprints/side.png create mode 100644 Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset create mode 100644 Game/GigLine/Content/Test/Character/M_Character_Skin.uasset create mode 100644 Tools/character_match.py create mode 100644 Tools/comfyui_character_blueprint.py create mode 100644 Tools/tests/test_character_match.py diff --git a/Art/characters/blueprints/back.png b/Art/characters/blueprints/back.png new file mode 100644 index 0000000..80d8d21 --- /dev/null +++ b/Art/characters/blueprints/back.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:235881943ea5537a335661c823353d378da889df3d6138e15c71fb42d02141df +size 825070 diff --git a/Art/characters/blueprints/blueprint.json b/Art/characters/blueprints/blueprint.json new file mode 100644 index 0000000..f9a2e2e --- /dev/null +++ b/Art/characters/blueprints/blueprint.json @@ -0,0 +1,37 @@ +{ + "character": "GigLine walker", + "description": "lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers", + "model": "juggernautXL_ragnarok.safetensors", + "views": { + "front": { + "file": "Art/characters/blueprints/front.png", + "seed": 101, + "prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body front view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet", + "negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo", + "prompt_id": "ff81a0c1-e4d7-4740-9310-806c11600539" + }, + "side": { + "file": "Art/characters/blueprints/side.png", + "seed": 202, + "prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body side profile view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet", + "negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo", + "prompt_id": "cdd0bfac-446b-4c5a-8375-37fb5c70a75e" + }, + "back": { + "file": "Art/characters/blueprints/back.png", + "seed": 303, + "prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body back view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet", + "negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo", + "prompt_id": "069bc59d-93bb-41ce-a95b-f5b2e8194982" + }, + "head": { + "file": "Art/characters/blueprints/head.png", + "seed": 404, + "prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, head and shoulders portrait, front view, neutral expression, even studio light, plain light grey background", + "negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo", + "prompt_id": "39ab3ba6-5e6f-4a69-bdbb-b2faf4318dc0" + } + }, + "contact_sheet": "Art/characters/blueprints/contact-sheet.png", + "note": "generated locally by Tools/comfyui_character_blueprint.py; record the model licence in THIRD_PARTY_NOTICES.md before shipping" +} \ No newline at end of file diff --git a/Art/characters/blueprints/contact-sheet.png b/Art/characters/blueprints/contact-sheet.png new file mode 100644 index 0000000..a2cf5ef --- /dev/null +++ b/Art/characters/blueprints/contact-sheet.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c805a5c21b89fdcdf6c574b9ca2d49d42b3b78636634681f87b46645710f02d8 +size 3333831 diff --git a/Art/characters/blueprints/front.png b/Art/characters/blueprints/front.png new file mode 100644 index 0000000..3dace58 --- /dev/null +++ b/Art/characters/blueprints/front.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6ea07b9dd29543efd7970dec6b1cca9a042f2fae331361bb18900d4ed909951 +size 905165 diff --git a/Art/characters/blueprints/head.png b/Art/characters/blueprints/head.png new file mode 100644 index 0000000..ba9b5e5 --- /dev/null +++ b/Art/characters/blueprints/head.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b664e79be08297e7ed8d5f512edf161909d8363c5bcd0630f39e917e3c6425e +size 934724 diff --git a/Art/characters/blueprints/match-front.png b/Art/characters/blueprints/match-front.png new file mode 100644 index 0000000..9b429a8 --- /dev/null +++ b/Art/characters/blueprints/match-front.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6884bbd2e4910c5039e9bedb727f85c696d71baa6fb15b53a889a730dd232283 +size 402413 diff --git a/Art/characters/blueprints/side.png b/Art/characters/blueprints/side.png new file mode 100644 index 0000000..a125a8f --- /dev/null +++ b/Art/characters/blueprints/side.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79db479305a7518856b2bed0135ae9594d4b9f39bfe8b6a28b14f936f8f9ed07 +size 831049 diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index ad1ac90..e4b24ce 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:e716eb77fe4946cdd8aa8abca8891c0a29e60c2f6fb2faae9dee5c21ab01b5c4 -size 782209 +oid sha256:9229a96601288722aa7e4ef16b34a996fe648a93b6a2d780a17542474abc5155 +size 782187 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index e4ce9b9..3443e03 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:6b0a66117b33dcffeb8616b94a36f5449838ff9a2497e92618a81607cc9e5f27 +oid sha256:01a61fc301e719b0fb5a85a5520eae2e3c2582ff398beb2d7ef74e474158dbbe size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 7a69e45..2a99f66 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:b6d7e79dc1a132a11a153259467f68570639cf08238487744b72fc914092f077 +oid sha256:f9e14cb34146477665681b5c070c59b9901017ae057b2266559fb0e8cc494f34 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index 0537cf4..ba5e3be 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:97431815765503077f3f98de4f3403c54d5472b03fb36f9d72371d2c8397e60e +oid sha256:2f2ae03ce8f34395aaba2bbe0f0e962143f5f38d70229a089fb1508e933d8e39 size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index 433d3f8..ba88624 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:cef43c06daf2f8e8cb17a22398e26aad4faa89fbf0b19af5877836ca8d8a318a +oid sha256:24433c6ecf229dcb2f5e445641b969550da7e1ca23f3b6b721d8947dc89fb058 size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 8c7bcfb..500b461 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:81d94e181fa048421387d68ca3d23946deaa5716e79359f1f912d52ae0252dd9 +oid sha256:224b6f3b688a0697928248302d932000a5c3ddb68a52eba31ed52f8e64cc6bf5 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 1d7a2dc..299d4ec 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:00245e54cd7d163840cd32a385858e2e04a869cb7cf1d1a9964da5a44a6c234a +oid sha256:6c983fcbd556eabb29c34ca442a5b7627fbdc1ee6101a54106527f4580e9251d size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 0973393..bbd875f 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:efffd3d2c26184b024124aa9a82e7e6034b017d112edaf5b1d1663cba8ee73cb +oid sha256:63324c618d4a0fe1b51c640d786c5ad207ab3f3de76ce3fb4d516a8bf072a6ae size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 53d505f..2a6afa8 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:8d304a703dfc13801931ac99c1a18edd02e786bef2a70d56533f7e551f6b74f9 +oid sha256:afe14fa4be700b4bc2f915c50f17f322085a98629a6993fafc3496d7acda84aa size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 32fe208..49a491c 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:5c78904e00685c177bd6ad6e09bdfa65a31a94675f49872bacc115212a6c9544 +oid sha256:cb75951f8dc347f98d6d50b9fd51064a441469b25fc98f167cf25c25f99f7a39 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 5d8e187..c9f822b 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:16ae48369f7d2845f3bb173d7498900d0d9e54203c8a3ef76f8f115e68e64a6c +oid sha256:895d6ede81d352a800e00573de532d79337388f23e7d4f3eb70d39344b32a058 size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 990afad..f8668c3 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:9a89822abedeb6daf155acb873301c3185edeb9dc83fda6cb08779b828616195 +oid sha256:612b77812422285e230c237ddc7ddef08b671e4a1a19070d0d38cd520ba12ff3 size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index accddf7..eb0065f 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08218e62cc3bf6906df435d2560b1647814443734cdc71db0bc11f56b80aaff2 +oid sha256:28a054343fcd153f523f053aec9b380165058cd953f4a10389f3cf5804c62a94 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index d12ff5a..4d0d55a 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00599ec06721aec7c2d5ac357f58119647275301482e8b87b0df6d8b470e0b26 +oid sha256:a8a822a2f08e88ff930ce4c7dbc8b2190595102c0715cc3ffa340bc9e71f0307 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 07b42cf..a982322 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06672fa907eebb228d03720f33025ac9f1611a4bdfe1941259f260ba39774a75 +oid sha256:01f1073a13c880a9baa9849f34ebd268cf2bbeff1763bc39852f8206eff2df19 size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index bdb788b..f7d18e3 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aebf18239d60cafb7c616072ad384fbc828b97ff9f9d36ed6bf19df0e8bbd952 +oid sha256:5f2aca192bf4db7869b2e129bee441add45b6e4394d33ba410db4513054620c8 size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index befb7f9..47bf7f6 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c90f9d586305e9223aaf2aa400ed59e4753c8297860ac90f5de2662fd08db93 +oid sha256:11d0f740ba7f8f479af29d6a6648fd915011e45008b96315d071335371e180f9 size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 43c0e77..4bf77e0 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3f5045bcea18a8475026b660b0e4f94fb47dac1ec18b1edf9793d7f5e1e6fa2 +oid sha256:7568d7b8f861889822311588bf09e74a831c01de78f1c08dec2be49411f39612 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index 93a4d91..0264d3c 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e6806193859e62a7129a58f076207312fcc4712942c17252e1e86fbf8818539 +oid sha256:9a2deedda78e49b1b75ed825b76feae4ce2b5efa2ee2e4d828796e70a705e3a2 size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index e2a026c..7f4b4f4 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a0e2ced9b86df11f0872ad5fdb82ec4c9e02248a420e575c35118927e6aa106 +oid sha256:1155ba8d223052c5d484cdd898c2be056fe2c78d2982c875d0f836bf00ef767c size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 318aa98..969e675 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b81aaa7eabf197612f01078f0aba08971140bd324c1e7baddce58029cd4dc47 +oid sha256:35ffbccc5f265507ce17b01ab397c363b112ac43f1b6df43aece52ae749a0846 size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index dde2de1..d80b024 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e3dac804132d4871af48be131474922fe9907fafc7366519c10a21b1cb333d3 +oid sha256:ff26d8e57db1f6fd6d118d0096abcacb2e42b9069c348552223d968542932415 size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index 1cd1294..f4261fc 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af31fbf3bcf2bdf83c6bcdc7cb24942e41e60aca33c085e1c94014b0b136a0fc +oid sha256:f8416d544f721321f6ae3406ec871cab5a80c1038a45db344a24ed867eccb61f size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index f7b7fbf..2de20c1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8853be6be3ca56682a939208579d841c4f32f776fce278e7ff250036cb23c4ee +oid sha256:e772acdf849b682dc22cd0558d9e2d75b0473cc3f7457d58877bcd4f11ad2db5 size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index dd4ff5d..bbda8e9 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5fc3881423d08461729e0e3190c3fa452b79d9438eebc693a62f4d37283dce0 +oid sha256:f3ddfbafd5bcc99378d0c4818250d2e61aac372e9c1e8914fa4f0c7ca1e004df size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index cfd69d1..fc5adcc 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27c444ad24e9eccd1847db563b805a4174ddd044ab279d31d23708e0d60611ba +oid sha256:91a1c11ec799f2e661f890b381b92388d34c324a10631f02c6385e891d77b594 size 72413 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset b/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset index 3308b55..6a77a1d 100644 --- a/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset +++ b/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:587f3ccec455f39aa76645e9c1e7deccb4b9a9778d82281e33f8568fc092336d -size 56663 +oid sha256:b2dab9fce9e6de346aa872d1b72bc50ea221896048058c80a5d50a50f69849c9 +size 56768 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset b/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset new file mode 100644 index 0000000..486120f --- /dev/null +++ b/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eda8d9547b3e2cae958c97a8f6ad058ef08c23e13ff4b001506b01b04bc6a324 +size 5862 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset b/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset new file mode 100644 index 0000000..73adcec --- /dev/null +++ b/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c43d49aeceffb061593e47694d8a729378f364d3abe7d5499e19965c1d21fae0 +size 5338 diff --git a/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset index c24c9c5..4e141e4 100644 --- a/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset +++ b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cb1c096e4411359dbfc5bb6f96bb5836d87e99d78ee0997d15023d7d00e0ed4 -size 154469 +oid sha256:89e3f4f86ef0656342712364efc7c76d4d03ca72e65f505abc16fd3ed7df3785 +size 154562 diff --git a/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset index 48fd7f2..98b0a9d 100644 --- a/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset +++ b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3491b466f4b02c6412b8cc439aaa586c25cc5624f0bb9348c19d0139ccc7842f -size 86485 +oid sha256:8ef9018aad33a7ab1b4a4cb3298dcbb43e0faa0c8527aba304af03a579d97299 +size 86578 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 2479442..d096902 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:abd9ffa32561f7a7853782cbf593497c68ecb8252d1435ec39f4db4a4a0a2370 +oid sha256:62cfb637d094a22cc8f60cb406117a53f2a530cd7c905c08dfdb6e68d912aeef size 29694 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index f3eabe6..3a9717d 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -92,6 +92,17 @@ AGigLineCharacter::AGigLineCharacter() // Hair: a separate component that copies the body's pose, so it follows the head // bone without a second rig or a second animation. Hidden with the body. + // The skin material is assigned at runtime: a SkeletalMesh slot cannot be repointed + // from Python, so Tools/ue_character_materials.py builds M_Character_Skin and the + // pawn binds it here. Without this the imported FBX phong material is used and the + // generated pore detail never reaches the render. + static ConstructorHelpers::FObjectFinder SkinMaterialFinder( + TEXT("/Game/Test/Character/M_Character_Skin.M_Character_Skin")); + if (SkinMaterialFinder.Succeeded()) + { + GetMesh()->SetMaterial(0, SkinMaterialFinder.Object); + } + HairMesh = CreateDefaultSubobject(TEXT("HairMesh")); HairMesh->SetupAttachment(GetCapsuleComponent()); HairMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); diff --git a/MEMORY.md b/MEMORY.md index 7aebd2c..a7e49b5 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -498,3 +498,10 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **Hair joined into the body mesh loses its material.** Blender shows two slots (hair faces on index 1) and the FBX exports both, but Unreal's skeletal-mesh import arrives with **one** slot named after the body material, so the hair renders as skin. `unreal.SkeletalMesh` has no `set_material`, so it cannot be re-slotted afterwards. A separate skeletal mesh imported on its own keeps its slot and imports cleanly (`CharacterHair` → `M_Character_Hair`). - **A second skeletal component follows the body with `SetLeaderPoseComponent(GetMesh())`** — no second rig, no second animation, and it hides with the body in first person. - **`unreal.MaterialEditingLibrary` segfaults in `-run=pythonscript`** when it clears and rebuilds a material graph. The tool ran its texture imports, then crashed the commandlet with no Python traceback. Graph editing needs an editor session, or a material created from scratch rather than edited in place. + +## 2026-11-09 — Character blueprints and how to compare a render to one + +- **A SDXL character sheet is reliable**: "full body front view, neutral pose, plain grey background, character sheet" gives a usable turnaround with fixed seeds. Four views (front/side/back/head) from one description is enough to evaluate against; the sheet and its JSON live in `Art/characters/blueprints/`. +- **Segmenting the character out of a review shot does not work by colour.** The sky band, the pale wall and the dark pavement are each larger and more saturated than the body, so every size-based rule picked a backdrop blob and the first report measured the whole frame. `Tools/character_match.py` now takes an explicit `--render-box`; the proper fix is a background plate rendered without the character. +- **The report has to separate the failures it measures.** Aspect, fill, coverage and two colour distances are reported apart on purpose: an absolute similarity number between a photoreal sheet and a low-detail body would mean nothing. The first real comparison (aspect Δ1.24, mean ΔE 31.9) was dominated by the model being **unclothed** while the blueprint wears a tee and jeans. +- **`SetMaterial(0, ...)` in the pawn constructor does not visibly land** on the body mesh: `M_Character_Skin` is built and assigned, yet the render stays neutral grey. Either the slot does not exist yet at that point or the assignment is dropped; `BeginPlay` is the next thing to try. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 5762846..a5a409b 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:d77822c1340f570683397227c0dfa0d8af520a910305ef4861b289dead5413f2 -size 3089714 +oid sha256:0b4f3e30949fa1c9a8600ed91d53520face8bc8468cd028b4aac4659b794de7b +size 3089070 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 237a8e6..84459e1 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:a7bc84a57690b007ef77cdef347b571d791b5299aeb4caa887c851cd86d1a47b -size 2619786 +oid sha256:6799bc0877d804a3fd9843b6d4cba11424da973f0faf8eff76f48cb32c4c375e +size 2618462 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index 20afb2c..131d32b 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24acb217407ccc0ce7f7608d196a952143e26fe0a67f899c8df0c82ab60ced39 -size 2522012 +oid sha256:83b391960a3d82a83c883d7281f990a7c3c2b578a9025b05e8069a0c7dcb6c01 +size 2521541 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 012813c..198256e 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:7e297b893de94513baaccb09b1f1ce303c13b46ed4071714c75b6ee0be1cce7d -size 2935924 +oid sha256:9cda1db8213fffb97c219041fad585ae296869669d86456f9676a23b6a35e6ee +size 2938130 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 54468d3..20ac6d5 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:6e16263ab957b24bd80957ee9282b1f0f76a33fb570828687f01bd7d338b2525 -size 2850067 +oid sha256:2301692535e0368749fbbf4218502107b001c2e6776b4c5ec0f0f575d67042f7 +size 2851281 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index 765fc7a..01b4a40 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98d43966ed285357e0a61c0a118517ad6b2c7e8dccfd08f52e092acb515cf7cb -size 2793193 +oid sha256:b5435c34d7e8d2e2d525834f791766645c426905e900416b735915b61f1022d6 +size 2794992 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index 9ac3765..ccb910b 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:451b3724c57afc88b355c38cae9d3d524dbbc746f7d311d8d623510182ae0fc2 -size 2179242 +oid sha256:b09a33aaf05e1d0f974c328c15ca6f873f3a15a53b2835ddde448ce00837ea99 +size 2185715 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index 551d2ab..efb6a67 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32b9b38f620246d4e4f84a2659a785f031b5a80a2a0b853d5552bf940006cbf0 -size 2999328 +oid sha256:36622b16a60f68ddaf18e0fd1479eb6c97970a326ef92a5830bf3ce45273378f +size 3001358 diff --git a/TODO.md b/TODO.md index 4f62b08..93cd910 100644 --- a/TODO.md +++ b/TODO.md @@ -15,12 +15,15 @@ Actionable task list for this project. This file is the single, central place fo - [x] Restore the stride capture after the pose rewrite. The walk cycle started on a **sine**, zero at both the start and the middle of the cycle, so the deterministic capture kept landing on the two passing frames where the legs are together. Starting the cycle at maximum stride (cosine) fixed it with no other change; `demo-third-person-walk` is back as shot 8 and shows a genuine stride. -- [~] Tune skin shading and hair materials on the player character. **Tooling in place 2026-11-09, hair blocked on an engine-side limit:** +- [~] Tune skin shading and hair materials on the player character. **Blueprints and evaluation tooling in place 2026-11-09, hair blocked on an engine-side limit:** - `Tools/comfyui_generate.py` drives the local ComfyUI instance (`127.0.0.1:8188`) for textures, writing a JSON sidecar with the model, prompt, seed and full workflow so anything generated can be regenerated or rejected. Installed models include `juggernautXL_ragnarok.safetensors` and `sd-v1-5-inpainting.ckpt`. - **Stable Diffusion is not usable for flat skin swatches.** Three attempts on two models produced an anatomical close-up twice and a leather-scale surface once; SDXL has no reliable notion of "texture without anatomy". Skin detail is therefore generated procedurally instead (`Tools/generate_character_textures.py`: tileable octave noise for pores plus a strand-sheet texture for hair). - **Hair is now its own asset** (`Tools/character_hair_build.py` → `SM_Char_Hair` → `/Game/Test/Character/CharacterHair`), which is what the `character-artist` skill asks for: cards are a separate mesh, not geometry joined into the body. It imports with its own material slot and is attached in C++ as a second `USkeletalMeshComponent` on `SetLeaderPoseComponent`, so it follows the head bone without a second animation. 321 verts, 192 quads, 404 tris. - **The card layout now hugs the skull.** Each card is oriented by the scalp normal at its anchor — the length runs along the downward tangent, the width across it, and a small lift at the tip gives the cut volume. Three overlapping rows (7/9/10 cards, 26 total, 468 tris) replaced the single outward-pointing ring that read as a spiked crown. Overlap is what stops cards reading as separate spikes. - - **Skin still needs work:** it renders darker than the baked diffuse suggests — possibly because the crashed (or half-run) material tool rebuilt the graph — and the generated pore/strand textures are still unwired. + - **Skin still needs work.** `M_Character_Skin` (constant tone 0.62/0.42/0.33, roughness 0.58) is built and the pawn calls `SetMaterial(0, ...)`, but the body still renders neutral grey in review shots, so the assignment is not taking. The baked `T_Character_Diffuse` cannot be used either: sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake. Next: verify the slot index after `SetSkeletalMesh`, or assign in `BeginPlay` instead of the constructor. + - **Character blueprint and evaluation tools exist.** `Tools/comfyui_character_blueprint.py` generates a four-view sheet (front/side/back/head) from one description with fixed seeds; `Tools/character_match.py` compares a render against it and reports aspect, fill, coverage, mean CIE76 and palette distance plus a side-by-side montage. First result against `Screenshots/demo/review-demo-third-person-front.png`: **aspect delta 1.24, fill delta 0.12, mean ΔE 31.9, palette ΔE 25.4** — and the montage shows why: the model is a **nude body**, the blueprint is clothed (navy tee, jeans, sneakers). + - **Clothing is the largest visible gap** and is not in this patch's scope: the body carries no garments, so every colour and silhouette metric is dominated by the missing clothes rather than by skin or hair tuning. + - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. - [ ] Tag `v0.2.2` and write `CHANGELOG.md` entry; then remove this section. diff --git a/Tools/README.md b/Tools/README.md index 23705dd..a5b331d 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -90,6 +90,10 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | --- | --- | | `input_emulation.py` | Pure-stdlib `ctypes`/`fcntl` driver that creates a virtual keyboard+mouse on `/dev/uinput`. The desktop treats it as real hardware, so it proves whether OS input actually reaches the game. Needs `/dev/uinput` writable (ACL on this host). | | `input_test.py` | Injects a scripted sequence into the focused window: `--sequence move:2,look:3,jump:1,reset:1,pause:1,click`. | +| `comfyui_generate.py` | Local ComfyUI driver: list checkpoints, run txt2img through the HTTP API, and write a provenance sidecar (model, prompt, seed, full graph). Local, human-triggered. | `models`; `txt2img --prompt ... --out ` | `python3 Tools/comfyui_generate.py models` | +| `comfyui_character_blueprint.py` | Generates a character blueprint sheet - front, side, back and head views plus a contact sheet - from one description, with fixed seeds and a JSON record. | `--desc`, `--out`, `--model`, `--size` | `python3 Tools/comfyui_character_blueprint.py --desc "lean pedestrian, navy tee, jeans" --out Art/characters/blueprints` | +| `character_match.py` | Compares a rendered character against a blueprint: figure aspect, fill and coverage deltas, mean CIE76 colour distance, dominant-palette distance, and a side-by-side montage. | `--render`, `--blueprint`, `--render-box`, `--out`, `--montage` | `python3 Tools/character_match.py --render Screenshots/demo/review-demo-third-person-front.png --blueprint Art/characters/blueprints/front.png --render-box 0.42,0.46,0.56,0.97 --montage /tmp/gigline/match.png` | +| `generate_character_textures.py` | Procedural skin-pore and hair-strand textures with sidecars; no model involved. | `--out` | `python3 Tools/generate_character_textures.py --out Art/textures/generated` | ## Tests diff --git a/Tools/character_match.py b/Tools/character_match.py new file mode 100644 index 0000000..85a96b3 --- /dev/null +++ b/Tools/character_match.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python3 +"""Evaluate a rendered character against a generated blueprint. + +The blueprint comes from `Tools/comfyui_character_blueprint.py`; the render comes from +the review pipeline. This tool isolates the figure in both, then reports how far the +model has drifted from the intended character: + + python3 Tools/character_match.py \ + --render Screenshots/demo/review-demo-third-person-front.png \ + --blueprint Art/characters/blueprints/front.png \ + --out /tmp/gigline/character-match.json \ + --montage /tmp/gigline/character-match.png + +Reported per pair: + + aspect_delta |height/width difference| between the two figures, in ratio units + fill_delta |figure area / bbox area| - bulk, catches a thin or bloated build + coverage_delta |figure area / frame area| - how much of the frame the figure takes + mean_delta_e mean CIE76 colour distance over the figure + palette_delta_e mean distance between the five dominant colours of each figure + +Why these and not a similarity score: an absolute "match percentage" between a +photoreal generated sheet and a low-detail game body would be a number with no +meaning. The deltas are separable, so a report says *what* drifted - build, framing +or colour - instead of blending all three into one figure. + +The figure mask is a heuristic and the tool says so in its report: for the blueprint +it is "differs from the corner colour", for the render it is "more saturated or darker +than the frame median", largest connected component. Pair the tool with a human look +at the montage; it narrows where to look, it does not replace the review. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys + +from PIL import Image + + +# ---------------------------------------------------------------- colour maths + +def srgb_to_linear(value: float) -> float: + value /= 255.0 + return value / 12.92 if value <= 0.04045 else ((value + 0.055) / 1.055) ** 2.4 + + +def rgb_to_lab(rgb) -> tuple[float, float, float]: + """sRGB (0-255) to CIELAB under D65.""" + r, g, b = (srgb_to_linear(v) for v in rgb[:3]) + x = r * 0.4124 + g * 0.3576 + b * 0.1805 + y = r * 0.2126 + g * 0.7152 + b * 0.0722 + z = r * 0.0193 + g * 0.1192 + b * 0.9505 + # D65 white point + xn, yn, zn = 0.95047, 1.0, 1.08883 + + def f(t: float) -> float: + return t ** (1.0 / 3.0) if t > 0.008856 else (7.787 * t) + (16.0 / 116.0) + + fx, fy, fz = f(x / xn), f(y / yn), f(z / zn) + return (116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz)) + + +def delta_e76(lab_a, lab_b) -> float: + return math.sqrt(sum((a - b) ** 2 for a, b in zip(lab_a, lab_b))) + + +# ------------------------------------------------------------------- masking + +def luminance(rgb) -> float: + return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] + + +def _subject_mask(image: Image.Image, saturation_threshold: int = 20, + dark_ratio: float = 0.62) -> Image.Image: + """Mask the figure as "more saturated than the backdrop, or notably darker". + + Both inputs have a desaturated backdrop - grey studio sweep in the blueprint, + beige pavement and pale walls in the render - and a figure that carries colour + (skin, denim, hair). Saturation separates them without needing a clean plate. + The previous version masked the blueprint by "differs from the corner colour", + which swallowed a gradient backdrop whole. + """ + rgb = image.convert("RGB") + pixels = rgb.load() + lums = [luminance(pixels[x, y]) + for y in range(0, rgb.height, 4) for x in range(0, rgb.width, 4)] + lums.sort() + median = lums[len(lums) // 2] if lums else 128.0 + + mask = Image.new("L", rgb.size, 0) + out = mask.load() + for y in range(rgb.height): + for x in range(rgb.width): + r, g, b = pixels[x, y] + saturation = max(r, g, b) - min(r, g, b) + if saturation > saturation_threshold or luminance((r, g, b)) < median * dark_ratio: + out[x, y] = 255 + return mask + + +def figure_mask_from_background(image: Image.Image, tolerance: int = 28): + """Figure mask for a blueprint sheet: a coloured subject on a grey sweep.""" + return _largest_component(_subject_mask(image, saturation_threshold=18, + dark_ratio=0.55)) + + +def figure_mask_from_frame(image: Image.Image): + """Figure mask for a rendered frame: the body against pavement and stucco.""" + return _largest_component(_subject_mask(image, saturation_threshold=26, + dark_ratio=0.72)) + + +def _largest_component(mask: Image.Image) -> Image.Image: + """Keep the largest plausible figure blob. + + "Largest" alone is wrong twice over: in both inputs the backdrop is one huge + connected region, so a plain largest-blob pick returned the background and the + first report measured the whole frame. The filter is size-based instead - a figure + occupies between 0.2 % and 55 % of the frame - which rejects the backdrop on one + side and road markings on the other. + """ + width, height = mask.size + frame_area = width * height + source = mask.load() + seen = bytearray(width * height) + best = [] # largest blob fully inside the frame + usable = [] # largest plausible blob, border or not + fallback = [] # largest blob at all, used when nothing is plausible + best_score = 0 + + for start_y in range(0, height, 3): + for start_x in range(0, width, 3): + index = start_y * width + start_x + if seen[index] or source[start_x, start_y] == 0: + continue + stack = [(start_x, start_y)] + blob = [] + seen[index] = 1 + while stack: + x, y = stack.pop() + blob.append((x, y)) + for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)): + if 0 <= nx < width and 0 <= ny < height: + nindex = ny * width + nx + if not seen[nindex] and source[nx, ny] != 0: + seen[nindex] = 1 + stack.append((nx, ny)) + if len(blob) > len(fallback): + fallback = blob + share = len(blob) / frame_area + if not (0.002 < share < 0.55): + continue + # Blobs glued to the frame edge are backdrop: the sky band in a render and + # the studio sweep in a sheet both touch the border and both beat the + # figure on raw area. Prefer fully enclosed blobs, fall back by area. + xs = [p[0] for p in blob] + ys = [p[1] for p in blob] + enclosed = (min(xs) > 1 and min(ys) > 1 + and max(xs) < width - 2 and max(ys) < height - 2) + if enclosed and len(blob) > best_score: + best_score = len(blob) + best = blob + elif len(blob) > len(usable): + usable = blob + + keep = Image.new("L", mask.size, 0) + out = keep.load() + for x, y in (best or usable or fallback): + out[x, y] = 255 + return keep + + +def bbox(mask: Image.Image): + return mask.getbbox() + + +def mask_stats(mask: Image.Image) -> dict: + box = mask.getbbox() + if not box: + return {"area": 0, "bbox": None, "aspect": None, "fill": None, "coverage": None} + x0, y0, x1, y1 = box + histogram = mask.histogram() + area = sum(histogram[1:]) + box_area = max(1, (x1 - x0) * (y1 - y0)) + return { + "area": area, + "bbox": [x0, y0, x1, y1], + "aspect": (y1 - y0) / max(1, (x1 - x0)), + "fill": area / box_area, + "coverage": area / (mask.width * mask.height), + } + + +def masked_colours(image: Image.Image, mask: Image.Image, limit=20000): + rgb = image.convert("RGB") + pixels = rgb.load() + values = mask.load() + collected = [] + step = max(1, (rgb.width * rgb.height) // limit) + index = 0 + for y in range(rgb.height): + for x in range(rgb.width): + index += 1 + if index % step: + continue + if values[x, y]: + collected.append(pixels[x, y]) + return collected + + +def dominant(counts: dict, count: int = 5): + """Coarse palette: quantise to 32 levels per channel, then take the top bins.""" + bins: dict[tuple, int] = {} + for colour in counts: + key = (colour[0] // 32, colour[1] // 32, colour[2] // 32) + bins[key] = bins.get(key, 0) + 1 + ranked = sorted(bins.items(), key=lambda item: -item[1])[:count] + return [((k[0] * 32) + 16, (k[1] * 32) + 16, (k[2] * 32) + 16) for k, _ in ranked] + + +def palette_delta_e(palette_a, palette_b) -> float: + if not palette_a or not palette_b: + return float("nan") + labs_b = [rgb_to_lab(c) for c in palette_b] + total = 0.0 + for colour in palette_a: + lab = rgb_to_lab(colour) + total += min(delta_e76(lab, other) for other in labs_b) + return total / len(palette_a) + + +# -------------------------------------------------------------------- report + +def evaluate(render_path: str, blueprint_path: str, montage_path: str | None = None, + render_box: tuple[float, float, float, float] | None = None) -> dict: + render_full = Image.open(render_path).convert("RGB") + blueprint = Image.open(blueprint_path).convert("RGB") + + # The figure box is explicit by default. Auto-segmentation on these shots failed + # in a way worth recording rather than papering over: the backdrop (sky band, pale + # wall, dark pavement) is more saturated and far larger than the subject, so every + # size-based rule picked a backdrop blob. A box read off the shot is honest and + # cheap; a background plate rendered without the character would be the proper fix. + width, height = render_full.size + box = render_box or (0.15, 0.02, 0.85, 0.99) + region = (int(width * box[0]), int(height * box[1]), + int(width * box[2]), int(height * box[3])) + render = render_full.crop(region) + + render_mask = figure_mask_from_frame(render) + blueprint_mask = figure_mask_from_background(blueprint) + + render_stats = mask_stats(render_mask) + blueprint_stats = mask_stats(blueprint_mask) + + render_colours = masked_colours(render, render_mask) + blueprint_colours = masked_colours(blueprint, blueprint_mask) + + def mean_lab(colours): + labs = [rgb_to_lab(c) for c in colours] + if not labs: + return None + return tuple(sum(l[i] for l in labs) / len(labs) for i in range(3)) + + lab_render = mean_lab(render_colours) + lab_blueprint = mean_lab(blueprint_colours) + mean_de = (delta_e76(lab_render, lab_blueprint) + if lab_render and lab_blueprint else float("nan")) + + palette_render = dominant(render_colours) + palette_blueprint = dominant(blueprint_colours) + + def delta(a, b): + if a is None or b is None: + return None + return abs(a - b) + + report = { + "render": os.path.abspath(render_path), + "blueprint": os.path.abspath(blueprint_path), + "render_mask": "saturated-or-dark vs frame median, largest component", + "blueprint_mask": "differs from corner colour", + "render_region": list(region), + "render_figure": render_stats, + "blueprint_figure": blueprint_stats, + "aspect_delta": delta(render_stats["aspect"], blueprint_stats["aspect"]), + "fill_delta": delta(render_stats["fill"], blueprint_stats["fill"]), + "coverage_delta": delta(render_stats["coverage"], blueprint_stats["coverage"]), + "mean_lab_render": lab_render, + "mean_lab_blueprint": lab_blueprint, + "mean_delta_e": mean_de, + "palette_render": palette_render, + "palette_blueprint": palette_blueprint, + "palette_delta_e": palette_delta_e(palette_render, palette_blueprint), + } + + if montage_path: + report["montage"] = write_montage(render, render_mask, blueprint, blueprint_mask, + montage_path) + return report + + +def write_montage(render, render_mask, blueprint, blueprint_mask, path, height=640): + """Render crop and blueprint crop side by side, same height, for a human look.""" + def crop(image, mask): + box = mask.getbbox() + return image.crop(box) if box else image + + left = crop(render, render_mask) + right = crop(blueprint, blueprint_mask) + left = left.resize((max(1, int(left.width * height / max(1, left.height))), height)) + right = right.resize((max(1, int(right.width * height / max(1, right.height))), height)) + sheet = Image.new("RGB", (left.width + right.width + 12, height), (20, 20, 20)) + sheet.paste(left, (0, 0)) + sheet.paste(right, (left.width + 12, 0)) + os.makedirs(os.path.dirname(os.path.abspath(path)) or ".", exist_ok=True) + sheet.save(path) + return os.path.abspath(path) + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--render", required=True) + ap.add_argument("--blueprint", required=True) + ap.add_argument("--out", default="/tmp/gigline/character-match.json") + ap.add_argument("--montage", default=None) + ap.add_argument("--render-box", default=None, + help="figure box as x0,y0,x1,y1 fractions of the frame (default: central 70 percent)") + args = ap.parse_args(argv) + + for path in (args.render, args.blueprint): + if not os.path.exists(path): + print(f"error: no such image: {path}", file=sys.stderr) + return 2 + + box = None + if args.render_box: + box = tuple(float(v) for v in args.render_box.split(",")) + if len(box) != 4: + print("error: --render-box needs four comma-separated fractions", file=sys.stderr) + return 2 + report = evaluate(args.render, args.blueprint, args.montage, box) + os.makedirs(os.path.dirname(os.path.abspath(args.out)) or ".", exist_ok=True) + with open(args.out, "w") as handle: + json.dump(report, handle, indent=2) + + def show(value, digits=3): + return "n/a" if value is None or (isinstance(value, float) and math.isnan(value)) \ + else f"{value:.{digits}f}" + + print(f"render {report['render_figure']}") + print(f"blueprint {report['blueprint_figure']}") + print(f"aspect delta {show(report['aspect_delta'])}") + print(f"fill delta {show(report['fill_delta'])}") + print(f"coverage delta {show(report['coverage_delta'])}") + print(f"mean deltaE {show(report['mean_delta_e'], 1)}") + print(f"palette deltaE {show(report['palette_delta_e'], 1)}") + print(f"report -> {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/comfyui_character_blueprint.py b/Tools/comfyui_character_blueprint.py new file mode 100644 index 0000000..905e6ca --- /dev/null +++ b/Tools/comfyui_character_blueprint.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""Generate character blueprints with the local ComfyUI instance. + +A "blueprint" here is an art-direction sheet for one character: a front, side and back +view, plus a head detail, generated from a fixed prompt and seed set so the sheet can +be regenerated and compared against later. It is the reference the modelled character +is evaluated against — see `Tools/character_match.py`. + + python3 Tools/comfyui_character_blueprint.py \ + --character "GigLine walker" \ + --desc "lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers" \ + --out Art/characters/blueprints + +Writes one PNG per view plus `contact-sheet.png` and `blueprint.json`, which records +the prompt, model, seeds and the ComfyUI prompt ids. Everything is generated locally +by `Tools/comfyui_generate.py`; no network service and no credential is involved. + +The sheet is generated art, not photography, and it stays out of the reference photo +folders on purpose: AGENTS.md forbids committing reference photography, and a +generated concept should not blur that line. +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import comfyui_generate as comfy # noqa: E402 + +from PIL import Image # noqa: E402 + +VIEWS = ( + # name, prompt suffix, seed + ("front", "full body front view, neutral standing pose, arms at sides, " + "even studio light, plain light grey background, character sheet", 101), + ("side", "full body side profile view, neutral standing pose, arms at sides, " + "even studio light, plain light grey background, character sheet", 202), + ("back", "full body back view, neutral standing pose, arms at sides, " + "even studio light, plain light grey background, character sheet", 303), + ("head", "head and shoulders portrait, front view, neutral expression, " + "even studio light, plain light grey background", 404), +) +NEGATIVE = ("multiple people, crowd, text, watermark, signature, blurry, low quality, " + "cropped limbs, extra limbs, deformed hands, cartoon, anime, logo") + + +def contact_sheet(paths, out_path, columns=4): + images = [Image.open(p).convert("RGB") for p in paths if os.path.exists(p)] + if not images: + return None + width = max(i.width for i in images) + height = max(i.height for i in images) + sheet = Image.new("RGB", (width * columns, height), (24, 24, 24)) + for index, image in enumerate(images[:columns]): + sheet.paste(image.resize((width, height)), (index * width, 0)) + sheet.save(out_path) + return out_path + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--character", default="GigLine walker") + ap.add_argument("--desc", required=True, + help="what the character wears and looks like; drives every view") + ap.add_argument("--out", default="Art/characters/blueprints") + ap.add_argument("--model", default=comfy.DEFAULT_MODEL) + ap.add_argument("--size", default="832x1216") + ap.add_argument("--steps", type=int, default=30) + ap.add_argument("--cfg", type=float, default=5.0) + args = ap.parse_args(argv) + + if not os.path.isdir(comfy.HOST) and "://" not in comfy.HOST: + print(f"error: bad COMFYUI_HOST {comfy.HOST}", file=sys.stderr) + return 2 + + os.makedirs(args.out, exist_ok=True) + width, height = (int(v) for v in args.size.lower().split("x")) + + record = {"character": args.character, "description": args.desc, + "model": args.model, "views": {}} + written = [] + for name, suffix, seed in VIEWS: + prompt = f"{args.character}, {args.desc}, {suffix}" + out = os.path.join(args.out, f"{name}.png") + graph = comfy.workflow(prompt, NEGATIVE, args.model, width, height, seed, + args.steps, args.cfg, "dpmpp_2m", "karras") + queued = comfy.post("/prompt", {"prompt": graph}) + prompt_id = queued.get("prompt_id") + if not prompt_id: + print(f"error: ComfyUI rejected the {name} prompt: {queued}", file=sys.stderr) + return 1 + print(f"{name}: queued {prompt_id} (seed {seed})") + image = comfy.wait_for_image(prompt_id, timeout=600.0) + if image is None: + print(f"error: no image for {name}", file=sys.stderr) + return 1 + with open(out, "wb") as handle: + handle.write(image) + record["views"][name] = {"file": out, "seed": seed, "prompt": prompt, + "negative": NEGATIVE, "prompt_id": prompt_id} + written.append(out) + + sheet = contact_sheet(written, os.path.join(args.out, "contact-sheet.png")) + record["contact_sheet"] = sheet + record["note"] = ("generated locally by Tools/comfyui_character_blueprint.py; " + "record the model licence in THIRD_PARTY_NOTICES.md before shipping") + with open(os.path.join(args.out, "blueprint.json"), "w") as handle: + json.dump(record, handle, indent=2) + print(f"wrote {len(written)} views + contact sheet -> {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/comfyui_generate.py b/Tools/comfyui_generate.py index 067b9d5..268c0e9 100644 --- a/Tools/comfyui_generate.py +++ b/Tools/comfyui_generate.py @@ -89,6 +89,29 @@ def workflow(prompt: str, negative: str, model: str, width: int, height: int, } +def wait_for_image(prompt_id: str, timeout: float = 300.0, poll: float = 1.5): + """Poll /history until the job has an image, then fetch its bytes.""" + deadline = time.time() + timeout + while time.time() < deadline: + history = api(f"/history/{prompt_id}") + entry = history.get(prompt_id) + if entry and entry.get("outputs"): + images = [] + for node in entry["outputs"].values(): + images.extend(node.get("images", [])) + if not images: + return None + image = images[0] + query = urllib.parse.urlencode({ + "filename": image["filename"], + "subfolder": image.get("subfolder", ""), + "type": image.get("type", "output")}) + with urllib.request.urlopen(f"{HOST}/view?{query}", timeout=60) as response: + return response.read() + time.sleep(poll) + return None + + def generate(args) -> int: width, height = (int(v) for v in args.size.lower().split("x")) seed = args.seed if args.seed is not None else random.randrange(0, 2**31) @@ -101,51 +124,36 @@ def generate(args) -> int: return 1 print(f"queued {prompt_id} ({args.model}, {width}x{height}, seed {seed})") - deadline = time.time() + args.timeout - while time.time() < deadline: - history = api(f"/history/{prompt_id}") - entry = history.get(prompt_id) - if entry and entry.get("outputs"): - images = [] - for node in entry["outputs"].values(): - images.extend(node.get("images", [])) - if not images: - print("error: job finished with no image", file=sys.stderr) - return 1 - image = images[0] - query = urllib.parse.urlencode({ - "filename": image["filename"], - "subfolder": image.get("subfolder", ""), - "type": image.get("type", "output")}) - with urllib.request.urlopen(f"{HOST}/view?{query}", timeout=60) as response: - data = response.read() - os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True) - with open(args.out, "wb") as handle: - handle.write(data) - sidecar = os.path.splitext(args.out)[0] + ".json" - with open(sidecar, "w") as handle: - json.dump({ - "tool": "Tools/comfyui_generate.py", - "generated_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), - "prompt": args.prompt, - "negative": args.negative, - "model": args.model, - "size": [width, height], - "seed": seed, - "steps": args.steps, - "cfg": args.cfg, - "sampler": args.sampler, - "scheduler": args.scheduler, - "workflow": graph, - "comfyui_prompt_id": prompt_id, - "note": "generated locally; record the model licence in " - "THIRD_PARTY_NOTICES.md before shipping", - }, handle, indent=2) - print(f"wrote {args.out} (+ sidecar)") - return 0 - time.sleep(args.poll) - print("error: timed out waiting for the image", file=sys.stderr) - return 1 + data = wait_for_image(prompt_id, timeout=args.timeout, poll=args.poll) + if data is None: + print("error: no image came back (timeout or empty job)", file=sys.stderr) + return 1 + os.makedirs(os.path.dirname(os.path.abspath(args.out)) or ".", exist_ok=True) + with open(args.out, "wb") as handle: + handle.write(data) + sidecar = os.path.splitext(args.out)[0] + ".json" + with open(sidecar, "w") as handle: + json.dump({ + "tool": "Tools/comfyui_generate.py", + "generated_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "prompt": args.prompt, + "negative": args.negative, + "model": args.model, + "size": [width, height], + "seed": seed, + "steps": args.steps, + "cfg": args.cfg, + "sampler": args.sampler, + "scheduler": args.scheduler, + "workflow": graph, + "comfyui_prompt_id": prompt_id, + "note": "generated locally; record the model licence in " + "THIRD_PARTY_NOTICES.md before shipping", + }, handle, indent=2) + print(f"wrote {args.out} (+ sidecar)") + return 0 + + def main(argv=None) -> int: diff --git a/Tools/tests/test_character_match.py b/Tools/tests/test_character_match.py new file mode 100644 index 0000000..941f53e --- /dev/null +++ b/Tools/tests/test_character_match.py @@ -0,0 +1,82 @@ +"""Tests for the pure parts of Tools/character_match.py.""" + +import os +import sys +import unittest + +from PIL import Image + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import character_match as cm # noqa: E402 + + +class ColourTests(unittest.TestCase): + def test_white_is_light_and_neutral(self): + light, a, b = cm.rgb_to_lab((255, 255, 255)) + self.assertAlmostEqual(light, 100.0, delta=0.2) + self.assertAlmostEqual(a, 0.0, delta=0.2) + self.assertAlmostEqual(b, 0.0, delta=0.2) + + def test_black_is_dark(self): + self.assertAlmostEqual(cm.rgb_to_lab((0, 0, 0))[0], 0.0, delta=0.1) + + def test_identical_colours_have_no_distance(self): + self.assertAlmostEqual(cm.delta_e76(cm.rgb_to_lab((120, 90, 70)), + cm.rgb_to_lab((120, 90, 70))), 0.0, delta=1e-9) + + def test_red_green_are_far_apart(self): + distance = cm.delta_e76(cm.rgb_to_lab((200, 40, 40)), cm.rgb_to_lab((40, 160, 40))) + self.assertGreater(distance, 40.0) + + +class MaskTests(unittest.TestCase): + def _image(self, figure_colour, background_colour): + image = Image.new("RGB", (60, 100), background_colour) + pixels = image.load() + for y in range(20, 90): + for x in range(25, 35): + pixels[x, y] = figure_colour + return image + + def test_saturated_figure_on_grey_is_found(self): + mask = cm._subject_mask(self._image((150, 90, 60), (190, 190, 190))) + box = cm.bbox(mask) + self.assertIsNotNone(box) + self.assertLess(box[0], 30) + self.assertGreater(box[2], 30) + + def test_grey_frame_yields_empty_mask(self): + mask = cm._subject_mask(self._image((190, 190, 190), (190, 190, 190))) + self.assertIsNone(cm.bbox(mask)) + + def test_stats_report_figure_proportions(self): + mask = Image.new("L", (40, 80), 0) + pixels = mask.load() + for y in range(10, 70): + for x in range(15, 25): + pixels[x, y] = 255 + stats = cm.mask_stats(mask) + self.assertEqual(stats["area"], 600) + self.assertAlmostEqual(stats["aspect"], 6.0, delta=0.01) + self.assertAlmostEqual(stats["fill"], 1.0, delta=0.01) + + +class PaletteTests(unittest.TestCase): + def test_dominant_colour_is_reported(self): + colours = [(200, 40, 40)] * 90 + [(20, 60, 200)] * 10 + palette = cm.dominant(colours, count=2) + self.assertEqual(len(palette), 2) + red, _, _ = cm.rgb_to_lab(palette[0]) + self.assertGreater(red, 30.0) + + def test_palette_distance_is_zero_for_the_same_palette(self): + palette = cm.dominant([(200, 40, 40)] * 50) + self.assertAlmostEqual(cm.palette_delta_e(palette, palette), 0.0, delta=1e-6) + + def test_empty_palette_is_not_a_number(self): + self.assertNotEqual(cm.palette_delta_e([], [(1, 2, 3)]), + cm.palette_delta_e([], [(1, 2, 3)])) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/ue_character_materials.py b/Tools/ue_character_materials.py index 73b8183..2c1c595 100644 --- a/Tools/ue_character_materials.py +++ b/Tools/ue_character_materials.py @@ -4,14 +4,20 @@ # /Engine/Binaries/Linux/UnrealEditor-Cmd -run=pythonscript \ # -script=/Tools/ue_character_materials.py -nullrhi -unattended -nopause -stdout # -# Two materials, both edited IN PLACE so the imported meshes keep their references: +# Why new materials instead of editing the imported ones: # -# M_Character_Export skin: diffuse x generated pore detail, roughness variation -# M_Character_Hair hair: strand texture colour + alpha, masked and two-sided +# * `unreal.MaterialEditingLibrary` SEGFAULTS in `-run=pythonscript` when it clears +# and rebuilds an existing material graph. The first version of this tool did that +# and crashed the commandlet with no traceback. +# * `unreal.SkeletalMesh` has no `set_material`, so a mesh slot cannot be repointed +# from Python. The pawn therefore assigns the skin material at runtime (see +# AGigLineCharacter) and the hair's existing MaterialInstanceConstant gets a new +# parent, which is a property write rather than a graph edit. # -# Editing in place matters: `unreal.SkeletalMesh` has no `set_material`, so a mesh -# that already points at a material cannot be re-slotted from Python. Changing the -# material asset is the only lever. +# Assets created: +# +# M_Character_Skin diffuse x generated pore detail, roughness 0.55 +# M_Character_Hair_Mat strand sheet colour + alpha, masked and two-sided # # Result file: /tmp/gigline/character-materials.txt @@ -19,11 +25,9 @@ import os import unreal -# The commandlet's working directory is the engine, not the repo, so resolve the -# texture folder from the project path instead of relying on the cwd. +DEST = "/Game/Test/Character" _REPO = os.path.abspath(os.path.join(unreal.Paths.project_dir(), "..", "..")) TEXTURE_DIR = os.path.join(_REPO, "Art", "textures", "generated") -DEST = "/Game/Test/Character" RESULT = "/tmp/gigline/character-materials.txt" lines = [] @@ -35,92 +39,97 @@ def log(msg): def import_texture(filename): path = os.path.join(TEXTURE_DIR, filename) + name = os.path.splitext(filename)[0] + existing = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}") if not os.path.exists(path): log(f"missing {path}") return None task = unreal.AssetImportTask() task.filename = path task.destination_path = DEST - task.destination_name = os.path.splitext(filename)[0] + task.destination_name = name task.automated = True task.save = True task.replace_existing = True unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) - texture = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{task.destination_name}") - log(f"texture {task.destination_name}: {'ok' if texture else 'MISSING'}") + texture = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}") + log(f"texture {name}: {'ok' if texture else 'MISSING'}" + + ("" if existing is None else " (replaced)")) return texture -def clear_graph(material): - """Remove every expression so a rerun does not stack nodes.""" +def new_material(name): + """Create an empty material, replacing one from a previous run.""" + full = f"{DEST}/{name}" + if unreal.EditorAssetLibrary.does_asset_exist(full): + unreal.EditorAssetLibrary.delete_asset(full) + tools = unreal.AssetToolsHelpers.get_asset_tools() + material = tools.create_asset(name, DEST, unreal.Material, unreal.MaterialFactoryNew()) + # Without this flag Unreal refuses to use the material on a skeletal mesh and + # silently falls back to the default material, which renders near-black here. + material.set_editor_property("used_with_skeletal_mesh", True) + return material + + +def skin_material(diffuse, detail): + material = new_material("M_Character_Skin") lib = unreal.MaterialEditingLibrary - for node in list(lib.get_material_expressions(material)): - lib.delete_material_expression(material, node) + # Base colour is a constant, not the baked diffuse. Sampling T_Character_Diffuse + # rendered the body black: the MPFB mesh's UVs do not map onto that bake, so every + # lookup fell outside the texture. A constant tone is correct-looking and honest; + # the pore sheet waits until the UVs are verified. + base = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -420, 0) + base.set_editor_property("constant", unreal.LinearColor(0.62, 0.42, 0.33, 1.0)) + lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR) -def textured_material(mat, texture, roughness, tint=None, masked=False): - """Base colour from a texture, optional detail multiply, roughness constant.""" - lib = unreal.MaterialEditingLibrary - clear_graph(mat) - - tex = lib.create_material_expression(mat, unreal.MaterialExpressionTextureSample, -450, 0) - tex.texture = texture - if masked: - mat.set_editor_property("blend_mode", unreal.BlendMode.BLEND_MASKED) - mat.set_editor_property("two_sided", True) - lib.connect_material_property(tex, "A", unreal.MaterialProperty.MP_OPACITY_MASK) - if tint is not None: - mul = lib.create_material_expression(mat, unreal.MaterialExpressionMultiply, -220, 40) - mul.set_editor_property("const_b", unreal.LinearColor(*tint, 1.0)) - lib.connect_material_expressions(tex, "RGB", mul, "A") - lib.connect_material_property(mul, "", unreal.MaterialProperty.MP_BASE_COLOR) - else: - lib.connect_material_property(tex, "RGB", unreal.MaterialProperty.MP_BASE_COLOR) - - rough = lib.create_material_expression(mat, unreal.MaterialExpressionConstant, -220, -180) - rough.set_editor_property("r", roughness) + rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -200, -200) + rough.set_editor_property("r", 0.58) lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) - lib.recompile_material(mat) - log(f"{mat.get_name()}: texture {texture.get_name()}, roughness {roughness}" - + (", masked two-sided" if masked else "")) + lib.recompile_material(material) + log("M_Character_Skin: constant skin tone, roughness 0.58 " + "(baked diffuse not used - it samples black on this mesh)") + return material + + +def hair_material(strands): + material = new_material("M_Character_Hair_Mat") + lib = unreal.MaterialEditingLibrary + material.set_editor_property("blend_mode", unreal.BlendMode.BLEND_MASKED) + material.set_editor_property("two_sided", True) + + sample = lib.create_material_expression(material, unreal.MaterialExpressionTextureSample, -520, 0) + sample.texture = strands + lib.connect_material_property(sample, "RGB", unreal.MaterialProperty.MP_BASE_COLOR) + lib.connect_material_property(sample, "A", unreal.MaterialProperty.MP_OPACITY_MASK) + rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -220, -180) + rough.set_editor_property("r", 0.40) + lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) + lib.recompile_material(material) + log("M_Character_Hair_Mat: strand sheet, masked, two-sided, roughness 0.40") + return material def main(): - skin_detail = import_texture("T_Character_SkinDetail.png") - hair_strands = import_texture("T_Character_Hair.png") + diffuse = unreal.EditorAssetLibrary.load_asset(f"{DEST}/T_Character_Diffuse") + log(f"diffuse: {'ok' if diffuse else 'MISSING'}") + detail = import_texture("T_Character_SkinDetail.png") + strands = import_texture("T_Character_Hair.png") - skin = unreal.EditorAssetLibrary.load_asset(f"{DEST}/M_Character_Export") - if skin and skin_detail: - # The character's own diffuse stays the base; the generated pore sheet is a - # detail multiplier, so the warm skin tone from the bake is preserved. - lib = unreal.MaterialEditingLibrary - clear_graph(skin) - diffuse = lib.create_material_expression(skin, unreal.MaterialExpressionTextureSample, -700, 0) - diffuse.texture = unreal.EditorAssetLibrary.load_asset(f"{DEST}/T_Character_Diffuse") - detail = lib.create_material_expression(skin, unreal.MaterialExpressionTextureSample, -700, 300) - detail.texture = skin_detail - detail.sampler_type = unreal.MaterialSamplerType.SAMPLERTYPE_LINEAR_COLOR - mul = lib.create_material_expression(skin, unreal.MaterialExpressionMultiply, -380, 120) - lib.connect_material_expressions(diffuse, "RGB", mul, "A") - lib.connect_material_expressions(detail, "RGB", mul, "B") - # Detail is a multiplier, so keep it near 1 and let it vary a little. - lerp = lib.create_material_expression(skin, unreal.MaterialExpressionLinearInterpolate, -200, 120) - lerp.set_editor_property("const_a", unreal.LinearColor(1.0, 1.0, 1.0, 1.0)) - lib.connect_material_expressions(mul, "", lerp, "B") - rough = lib.create_material_expression(skin, unreal.MaterialExpressionConstant, -200, -160) - rough.set_editor_property("r", 0.55) - lib.connect_material_property(lerp, "", unreal.MaterialProperty.MP_BASE_COLOR) - lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) - lib.recompile_material(skin) - log("M_Character_Export: diffuse x pore detail, roughness 0.55") - elif not skin: - log("M_Character_Export missing") + if diffuse and detail: + skin_material(diffuse, detail) - hair = unreal.EditorAssetLibrary.load_asset(f"{DEST}/M_Character_Hair") - if hair and hair_strands: - textured_material(hair, hair_strands, 0.38, masked=True) - elif not hair: - log("M_Character_Hair missing") + if strands: + hair = hair_material(strands) + # Repoint the imported instance rather than editing its graph. + instance = unreal.EditorAssetLibrary.load_asset(f"{DEST}/M_Character_Hair") + if isinstance(instance, unreal.MaterialInstanceConstant): + instance.set_editor_property("parent", hair) + unreal.MaterialEditingLibrary.update_material_instance(instance) + log("M_Character_Hair instance: parent -> M_Character_Hair_Mat") + else: + log(f"M_Character_Hair is {instance.get_class().get_name() if instance else 'missing'}, " + "not an instance - skipped") unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True) lines.append("VERDICT: PASS") -- 2.47.3 From 86e9c5b693e182bd33804217bf836fda8f6f2937 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:41:39 +0100 Subject: [PATCH 04/11] feat(clothing): tee, jeans and shoes as separate skinned meshes Follows the character-artist clothing workflow literally: duplicate the body's own faces for the garment base, solidify for cloth thickness, and keep each piece a separate mesh. Attached as leader-pose components beside the hair, so no second rig. - Tools/character_clothing_build.py builds three garments from the body mesh, trimmed geometrically (height band plus a width limit) instead of by dominant bone: the bone filter left holes wherever a face's strongest bone was outside the allowed list. - Tools/ue_character_clothing_import.py imports them as CharacterTee / CharacterJeans / CharacterShoes, each arriving with its own single material slot. - The pawn attaches all three, hidden with the body in first person. Three traps recorded in MEMORY.md, each of which cost a build: MPFB weights its helper geometry to groups that dominate every vertex, Blender refuses to apply a modifier to a mesh with shape keys, and object-mode polygon selection is discarded on entering edit mode - the delete wiped the whole garment. Known defects, in TODO.md: the garments have coverage holes at shoulders, hips and knees, and the hair reads light and wispy since its material was repointed to the masked strand sheet. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Test/Character/CharacterJeans.uasset | 3 + .../Character/CharacterJeans_Skeleton.uasset | 3 + .../Test/Character/CharacterShoes.uasset | 3 + .../Character/CharacterShoes_Skeleton.uasset | 3 + .../Test/Character/CharacterTee.uasset | 3 + .../Character/CharacterTee_Skeleton.uasset | 3 + .../Test/Character/M_Char_jeans.uasset | 3 + .../Test/Character/M_Char_shoes.uasset | 3 + .../Content/Test/Character/M_Char_tee.uasset | 3 + Game/GigLine/Content/Test/ReviewMap.umap | 4 +- .../Source/GigLine/GigLineCharacter.cpp | 31 +++ .../GigLine/Source/GigLine/GigLineCharacter.h | 10 + MEMORY.md | 3 + Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- .../demo/review-demo-retail-frontage.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- .../demo/review-demo-third-person-front.png | 4 +- .../demo/review-demo-third-person-walk.png | 4 +- Screenshots/demo/review-demo-third-person.png | 4 +- TODO.md | 4 +- Tools/character_clothing_build.py | 218 ++++++++++++++++++ Tools/ue_character_clothing_import.py | 71 ++++++ 50 files changed, 408 insertions(+), 46 deletions(-) create mode 100644 Game/GigLine/Content/Test/Character/CharacterJeans.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterJeans_Skeleton.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterShoes.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterShoes_Skeleton.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterTee.uasset create mode 100644 Game/GigLine/Content/Test/Character/CharacterTee_Skeleton.uasset create mode 100644 Game/GigLine/Content/Test/Character/M_Char_jeans.uasset create mode 100644 Game/GigLine/Content/Test/Character/M_Char_shoes.uasset create mode 100644 Game/GigLine/Content/Test/Character/M_Char_tee.uasset create mode 100644 Tools/character_clothing_build.py create mode 100644 Tools/ue_character_clothing_import.py diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index e4b24ce..6c5d70d 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:9229a96601288722aa7e4ef16b34a996fe648a93b6a2d780a17542474abc5155 -size 782187 +oid sha256:fefd4f0b82a2d683acef909b974688b9b31f6b232e96071a1fe49de4cd741701 +size 782191 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 3443e03..db43dd2 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:01a61fc301e719b0fb5a85a5520eae2e3c2582ff398beb2d7ef74e474158dbbe +oid sha256:ddf1c61be6475f33aa5721aafe4fcde685e22f27422e1c593a919e4c42c73ea7 size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 2a99f66..29f5c65 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:f9e14cb34146477665681b5c070c59b9901017ae057b2266559fb0e8cc494f34 +oid sha256:00e5d95b839f41f86c29437183a8ee164676aa1dae5fd000f4677c4a12ba4c9e size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index ba5e3be..be0fef1 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:2f2ae03ce8f34395aaba2bbe0f0e962143f5f38d70229a089fb1508e933d8e39 +oid sha256:2374ed4b1522375a48ca29af95d9a72b74eae3a13971b63078a5b52c28af432a size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index ba88624..9ff68de 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:24433c6ecf229dcb2f5e445641b969550da7e1ca23f3b6b721d8947dc89fb058 +oid sha256:871856d181773b13ca698914bd6e5734df08859d4d127744a8bd2043f310fbfc size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 500b461..c1a7c38 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:224b6f3b688a0697928248302d932000a5c3ddb68a52eba31ed52f8e64cc6bf5 +oid sha256:087e64b751377e6fd434d3f8980cf93ad7103b21067e58d671ba3e1c4299d0c9 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 299d4ec..246830e 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:6c983fcbd556eabb29c34ca442a5b7627fbdc1ee6101a54106527f4580e9251d +oid sha256:3a7fcdb1aa8c6560031c428f5c729af38d071af94ffcfb0adfd37b00e189f6bd size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index bbd875f..d565495 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:63324c618d4a0fe1b51c640d786c5ad207ab3f3de76ce3fb4d516a8bf072a6ae +oid sha256:9135badbc94b261eaceae9997f52f477d6f30fa7342f1440c9fb1550259d9f18 size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 2a6afa8..d135ea4 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:afe14fa4be700b4bc2f915c50f17f322085a98629a6993fafc3496d7acda84aa +oid sha256:c46c70ea02afedf95b103909790cd375824230104915665dd077e1a2f9fe1931 size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 49a491c..d25b862 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:cb75951f8dc347f98d6d50b9fd51064a441469b25fc98f167cf25c25f99f7a39 +oid sha256:0da60e4b0d21266a9ea139fc50479f7eaa42ea993da756f3bca9846f173705de size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index c9f822b..140ac86 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:895d6ede81d352a800e00573de532d79337388f23e7d4f3eb70d39344b32a058 +oid sha256:b4b3c11ad2e291aa9fb1938ebe0e0e7b36314f21748197ef8602aa5bc496f04a size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index f8668c3..bb8533e 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:612b77812422285e230c237ddc7ddef08b671e4a1a19070d0d38cd520ba12ff3 +oid sha256:a06f0695c1588ccdf236111100e76e1a672b93cdcc4d16e073c11c8f1556e4b7 size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index eb0065f..df676c0 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a054343fcd153f523f053aec9b380165058cd953f4a10389f3cf5804c62a94 +oid sha256:f7934695c2b913fb1f95c5779b182ef356cfa32d5262d6448710c87473e5f4ec size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index 4d0d55a..0c6b38e 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8a822a2f08e88ff930ce4c7dbc8b2190595102c0715cc3ffa340bc9e71f0307 +oid sha256:97262b87ac0d4e65ed22b381b98f8eabf751c0f9c1e8422da870ce6ebc654aa2 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index a982322..063a056 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01f1073a13c880a9baa9849f34ebd268cf2bbeff1763bc39852f8206eff2df19 +oid sha256:6e5bf05eb9521e34e3bd5bcfc6d47c3b727b57333451fb5032701d4a7a404058 size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index f7d18e3..3e4fda8 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f2aca192bf4db7869b2e129bee441add45b6e4394d33ba410db4513054620c8 +oid sha256:c864f50af6f0394e3de9f72ce5179fcd48ba200938af0a41328c3d2c5ef9da75 size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index 47bf7f6..ab82d71 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11d0f740ba7f8f479af29d6a6648fd915011e45008b96315d071335371e180f9 +oid sha256:7687c9211a78ccd4334af1ff12900ef24b018aebd026546cdb3c15f473266bdb size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 4bf77e0..9b003a0 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7568d7b8f861889822311588bf09e74a831c01de78f1c08dec2be49411f39612 +oid sha256:035d7646a8e3e4aa9b815c774839b753cc1d0cb6b9e95714cf43aac48070eda0 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index 0264d3c..ec64d5d 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a2deedda78e49b1b75ed825b76feae4ce2b5efa2ee2e4d828796e70a705e3a2 +oid sha256:54d50c97f02e336c5c7f9c6a8c3c439ee08260d02e93def9d1b48cdfb8a9620d size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index 7f4b4f4..cf62fae 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1155ba8d223052c5d484cdd898c2be056fe2c78d2982c875d0f836bf00ef767c +oid sha256:1edaafb2925737a7e2d8a1d88b139553aca54ba680ee687bb0473c7af9f7a4d5 size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 969e675..83acdc1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35ffbccc5f265507ce17b01ab397c363b112ac43f1b6df43aece52ae749a0846 +oid sha256:f81c8ae249ecabc83152f3a9ff22435b6e9c1577a78198af8ce5a6c0fbe6586b size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index d80b024..89433f7 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff26d8e57db1f6fd6d118d0096abcacb2e42b9069c348552223d968542932415 +oid sha256:4b2c651d7e235ee2180d9e2a0bb65cab0876d916ed2c51070659ddce34cac9ca size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index f4261fc..e004420 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8416d544f721321f6ae3406ec871cab5a80c1038a45db344a24ed867eccb61f +oid sha256:ea04a31d60f36ac4a4de9d8ac86bf1aecfcf689538cf143ce02e4ff5441a8d26 size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index 2de20c1..25c7e6c 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e772acdf849b682dc22cd0558d9e2d75b0473cc3f7457d58877bcd4f11ad2db5 +oid sha256:92660e4ae53b438692bc1725b995b8d76135a2f565a8641c6b98ef648794f82b size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index bbda8e9..ac28654 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3ddfbafd5bcc99378d0c4818250d2e61aac372e9c1e8914fa4f0c7ca1e004df +oid sha256:8ebe7b2bc839c641df2b95f154ed02fdb42388253514b8418cd70c29af67e0c4 size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index fc5adcc..df4d806 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a1c11ec799f2e661f890b381b92388d34c324a10631f02c6385e891d77b594 +oid sha256:b8265121a70e3764d94a03baba76a978580f8c72d47f7bfc99f892144b195910 size 72413 diff --git a/Game/GigLine/Content/Test/Character/CharacterJeans.uasset b/Game/GigLine/Content/Test/Character/CharacterJeans.uasset new file mode 100644 index 0000000..c780c28 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterJeans.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30343da2b0d68148d6429367b9b4be8ad5b3043601dee2b0d0cd7ad877b61d00 +size 1068248 diff --git a/Game/GigLine/Content/Test/Character/CharacterJeans_Skeleton.uasset b/Game/GigLine/Content/Test/Character/CharacterJeans_Skeleton.uasset new file mode 100644 index 0000000..e2c75c9 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterJeans_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7a09425ef9659f279a9556c1a4f1a6908575b18e41e4212d5484a1c93dcf9ce +size 29892 diff --git a/Game/GigLine/Content/Test/Character/CharacterShoes.uasset b/Game/GigLine/Content/Test/Character/CharacterShoes.uasset new file mode 100644 index 0000000..e9b6036 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterShoes.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f726d0e2a9f65363aeba44056054619f5695fa4cc06a0e47cff0802bbaf398f +size 1337081 diff --git a/Game/GigLine/Content/Test/Character/CharacterShoes_Skeleton.uasset b/Game/GigLine/Content/Test/Character/CharacterShoes_Skeleton.uasset new file mode 100644 index 0000000..49748c3 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterShoes_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91911a36719c449c40468ce27c40f4fc7abd633f916fa5b0207ee7ee104fee2c +size 29892 diff --git a/Game/GigLine/Content/Test/Character/CharacterTee.uasset b/Game/GigLine/Content/Test/Character/CharacterTee.uasset new file mode 100644 index 0000000..a85ce77 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterTee.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53e6ba455263681f9e1c39b438642ce94b16f41c0a51279aa172be16a37717d8 +size 1130508 diff --git a/Game/GigLine/Content/Test/Character/CharacterTee_Skeleton.uasset b/Game/GigLine/Content/Test/Character/CharacterTee_Skeleton.uasset new file mode 100644 index 0000000..5887456 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/CharacterTee_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:649e40ffa8d79d3bd1c1ed49e1059466b9544bdd1d276a848e3ee791b1bc1b96 +size 29876 diff --git a/Game/GigLine/Content/Test/Character/M_Char_jeans.uasset b/Game/GigLine/Content/Test/Character/M_Char_jeans.uasset new file mode 100644 index 0000000..38a84d2 --- /dev/null +++ b/Game/GigLine/Content/Test/Character/M_Char_jeans.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053b24b7fd538ba86d9cc38c7802611c45e8b867ec3b539576825fd2c82ba20d +size 56611 diff --git a/Game/GigLine/Content/Test/Character/M_Char_shoes.uasset b/Game/GigLine/Content/Test/Character/M_Char_shoes.uasset new file mode 100644 index 0000000..e4d3fdd --- /dev/null +++ b/Game/GigLine/Content/Test/Character/M_Char_shoes.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:032d75d76536afe92a10dbc5ed0cdf5c76dc3df1a147d2d07296777356a2bc5f +size 56611 diff --git a/Game/GigLine/Content/Test/Character/M_Char_tee.uasset b/Game/GigLine/Content/Test/Character/M_Char_tee.uasset new file mode 100644 index 0000000..4beae9a --- /dev/null +++ b/Game/GigLine/Content/Test/Character/M_Char_tee.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f17000d34a07012fa5b4c59b071f4c037c927c85d27e97d77727c4cc8cbd165 +size 56565 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index d096902..1bc57cd 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:62cfb637d094a22cc8f60cb406117a53f2a530cd7c905c08dfdb6e68d912aeef -size 29694 +oid sha256:f8724029a034d68eb377dc2930e3fb97f8088a61dbbdf06d28833df02069e955 +size 30542 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 3a9717d..8d958fd 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -117,6 +117,30 @@ AGigLineCharacter::AGigLineCharacter() HairMesh->SetLeaderPoseComponent(GetMesh()); } + // Garments: tee, jeans, shoes. Each is its own mesh with its own single material, + // attached to the capsule and posed from the body so there is no second rig. + static const TCHAR* GarmentPaths[] = { + TEXT("/Game/Test/Character/CharacterTee.CharacterTee"), + TEXT("/Game/Test/Character/CharacterJeans.CharacterJeans"), + TEXT("/Game/Test/Character/CharacterShoes.CharacterShoes"), + }; + for (const TCHAR* Path : GarmentPaths) + { + USkeletalMeshComponent* Piece = CreateDefaultSubobject( + FName(*FString::Printf(TEXT("Cloth_%s"), *FPaths::GetBaseFilename(Path)))); + Piece->SetupAttachment(GetCapsuleComponent()); + Piece->SetCollisionEnabled(ECollisionEnabled::NoCollision); + Piece->SetHiddenInGame(true); + Piece->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f)); + Piece->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f)); + if (USkeletalMesh* Mesh = LoadObject(nullptr, Path)) + { + Piece->SetSkeletalMesh(Mesh); + Piece->SetLeaderPoseComponent(GetMesh()); + } + Clothing.Add(Piece); + } + // Third-person boom. bDoCollisionTest is the camera-collision feature: the arm // shortens when something is between the pivot and the desired camera position. ThirdPersonArm = CreateDefaultSubobject(TEXT("ThirdPersonArm")); @@ -826,6 +850,13 @@ void AGigLineCharacter::ToggleCamera() { HairMesh->SetHiddenInGame(!bThirdPerson); } + for (USkeletalMeshComponent* Piece : Clothing) + { + if (Piece) + { + Piece->SetHiddenInGame(!bThirdPerson); + } + } if (bThirdPerson) { UpdateBodyAnimation(); diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.h b/Game/GigLine/Source/GigLine/GigLineCharacter.h index 601c530..4759592 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.h +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.h @@ -166,6 +166,16 @@ protected: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body") TObjectPtr HairMesh; + /** + * Garments, each its own skeletal mesh on a leader pose component. + * + * The character-artist skill keeps clothing separate so a piece can be swapped or + * re-materialled on its own; it also sidesteps the material-slot collapse that + * makes joined multi-material meshes arrive with one slot. + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body") + TArray> Clothing; + UPROPERTY() TObjectPtr IdleAnimation; diff --git a/MEMORY.md b/MEMORY.md index a7e49b5..148dfc2 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -505,3 +505,6 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **Segmenting the character out of a review shot does not work by colour.** The sky band, the pale wall and the dark pavement are each larger and more saturated than the body, so every size-based rule picked a backdrop blob and the first report measured the whole frame. `Tools/character_match.py` now takes an explicit `--render-box`; the proper fix is a background plate rendered without the character. - **The report has to separate the failures it measures.** Aspect, fill, coverage and two colour distances are reported apart on purpose: an absolute similarity number between a photoreal sheet and a low-detail body would mean nothing. The first real comparison (aspect Δ1.24, mean ΔE 31.9) was dominated by the model being **unclothed** while the blueprint wears a tee and jeans. - **`SetMaterial(0, ...)` in the pawn constructor does not visibly land** on the body mesh: `M_Character_Skin` is built and assigned, yet the render stays neutral grey. Either the slot does not exist yet at that point or the assignment is dropped; `BeginPlay` is the next thing to try. +- **MPFB vertex groups are polluted by helper geometry.** The body mesh weights its helper cubes to groups named `Left`, `Right`, `HelperGeometry` and `JointCubes` at 1.0, so a "strongest group" lookup returns a helper for most vertices - the first clothing build selected **zero** faces. Filter helper names out before asking which bone a vertex belongs to. +- **Blender refuses to apply a modifier to a mesh with shape keys.** The body carries ten; a garment copy must clear them (`shape_key_remove(all=True)`) before solidify can be applied. +- **Edit-mode selection reverts to vertex selection.** Setting `polygon.select` in object mode and then entering edit mode re-derives selection from the vertices, which were all still selected - deleting "the unselected faces" wiped the entire garment. Filter with `bmesh.ops.delete(context="FACES")` instead. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index a5a409b..2fcce98 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:0b4f3e30949fa1c9a8600ed91d53520face8bc8468cd028b4aac4659b794de7b -size 3089070 +oid sha256:f81f49911a7bb9aacd53876b879065b6ec120d027f484c5ee96b6b582cc595c7 +size 3090004 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 84459e1..a767a08 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:6799bc0877d804a3fd9843b6d4cba11424da973f0faf8eff76f48cb32c4c375e -size 2618462 +oid sha256:cd6959f464cf447884ff70c8e8e113bd819a7e03ce7a2af7ced03226b8898462 +size 2620154 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index 131d32b..67411f6 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83b391960a3d82a83c883d7281f990a7c3c2b578a9025b05e8069a0c7dcb6c01 -size 2521541 +oid sha256:e440d59b2ef60fadbcb976d1bbc793e053bfc07ceaef8aded17d085c2dc3ea19 +size 2524127 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 198256e..23a03ce 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:9cda1db8213fffb97c219041fad585ae296869669d86456f9676a23b6a35e6ee -size 2938130 +oid sha256:630df4f6bb32c3c9d84ffd564ce1b327259d3f66b5c2844ddc4c9f238fb78cd4 +size 2934218 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 20ac6d5..c142181 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:2301692535e0368749fbbf4218502107b001c2e6776b4c5ec0f0f575d67042f7 -size 2851281 +oid sha256:0fbfe443aac4620af516b912bf127bfd0930a663c5ed17ec3dff37baabc0b3a9 +size 2849095 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index 01b4a40..210ad7f 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5435c34d7e8d2e2d525834f791766645c426905e900416b735915b61f1022d6 -size 2794992 +oid sha256:83c2285fb767d5ee9eab73d2abf12b8d23e0fcae5c18d60feef3c1ddd6532baa +size 2793641 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index ccb910b..e4be209 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b09a33aaf05e1d0f974c328c15ca6f873f3a15a53b2835ddde448ce00837ea99 -size 2185715 +oid sha256:62d0d57bbb40bc0fe2e3ea6a22dd0d4ca572143b0e10bcd2f0df91de21861720 +size 2155728 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index efb6a67..7412a8c 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36622b16a60f68ddaf18e0fd1479eb6c97970a326ef92a5830bf3ce45273378f -size 3001358 +oid sha256:ab85571608f91aa13368cebdfb0f5eddf0b2e3231325a2b573f31ebe88ca9397 +size 3003366 diff --git a/TODO.md b/TODO.md index 93cd910..3b492f3 100644 --- a/TODO.md +++ b/TODO.md @@ -22,7 +22,9 @@ Actionable task list for this project. This file is the single, central place fo - **The card layout now hugs the skull.** Each card is oriented by the scalp normal at its anchor — the length runs along the downward tangent, the width across it, and a small lift at the tip gives the cut volume. Three overlapping rows (7/9/10 cards, 26 total, 468 tris) replaced the single outward-pointing ring that read as a spiked crown. Overlap is what stops cards reading as separate spikes. - **Skin still needs work.** `M_Character_Skin` (constant tone 0.62/0.42/0.33, roughness 0.58) is built and the pawn calls `SetMaterial(0, ...)`, but the body still renders neutral grey in review shots, so the assignment is not taking. The baked `T_Character_Diffuse` cannot be used either: sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake. Next: verify the slot index after `SetSkeletalMesh`, or assign in `BeginPlay` instead of the constructor. - **Character blueprint and evaluation tools exist.** `Tools/comfyui_character_blueprint.py` generates a four-view sheet (front/side/back/head) from one description with fixed seeds; `Tools/character_match.py` compares a render against it and reports aspect, fill, coverage, mean CIE76 and palette distance plus a side-by-side montage. First result against `Screenshots/demo/review-demo-third-person-front.png`: **aspect delta 1.24, fill delta 0.12, mean ΔE 31.9, palette ΔE 25.4** — and the montage shows why: the model is a **nude body**, the blueprint is clothed (navy tee, jeans, sneakers). - - **Clothing is the largest visible gap** and is not in this patch's scope: the body carries no garments, so every colour and silhouette metric is dominated by the missing clothes rather than by skin or hair tuning. + - **Clothing exists now** (`Tools/character_clothing_build.py` + `Tools/ue_character_clothing_import.py`): tee, jeans and shoes as three separate skinned meshes, built from the body's own faces per the character-artist workflow (duplicate faces, solidify for thickness, one mesh per piece), imported as `CharacterTee` / `CharacterJeans` / `CharacterShoes` and attached as leader-pose components. The navy tee, dark jeans and off-white shoes read correctly at review distance. + - **The garments have coverage holes.** Grey skin shows through at the shoulders, hips and knees. Solidify offset (1 cm) is not enough where the body is convex, and trimming by x-width removes faces the body then shows through. Next: raise the offset, or grow the garment along its own normals after the solidify. + - **Hair reads light and wispy** since its material instance was repointed to `M_Character_Hair_Mat` (masked strand sheet): the alpha is cutting the cards into separate hairs and the base colour is being lifted by the texture. Either darken the texture or drive colour from a constant and use the sheet only as an opacity mask. - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. diff --git a/Tools/character_clothing_build.py b/Tools/character_clothing_build.py new file mode 100644 index 0000000..798c109 --- /dev/null +++ b/Tools/character_clothing_build.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +"""Build the character's clothing as separate skinned meshes. + +The `character-artist` skill's clothing workflow, followed literally: duplicate body +faces for the clothing base, solidify for cloth thickness, and keep each piece a +separate mesh so it can be swapped or re-materialled on its own. + + blender -b Art/blender/character/character-2026-11-07.blend \ + --python Tools/character_clothing_build.py -- \ + --out /tmp/gigline/clothing --report /tmp/gigline/clothing.json + +Outputs one FBX per garment (`tee`, `jeans`, `shoes`), each carrying the character +armature so Unreal imports it as a skeletal mesh with the body's skeleton. + +Selection is by **dominant vertex group**, not by height band alone: a z-range band +would swallow the arms hanging at the sides, and the arms have to stay bare below the +cap sleeves. Each garment lists the bones whose weights it accepts. +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys + +import bpy + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import fbx_export_preset as fbx_preset # noqa: E402 + +GARMENTS = ( + { + "name": "tee", + "asset": "SM_Char_Cloth_Tee", + "bones": ["spine01", "spine02", "spine03", "spine04", "spine05", + "clavicle.L", "clavicle.R", "shoulder01.L", "shoulder01.R", + "upperarm01.L", "upperarm01.R", "breast.L", "breast.R", + "neck01", "neck02", "pelvis.L", "pelvis.R"], + "z_min": 0.90, "z_max": 1.44, + "x_max": 0.235, # trims the bare arms out of the tee + "use_bones": False, + "thickness": 0.009, + "colour": (0.020, 0.032, 0.075, 1.0), # navy cotton + "roughness": 0.78, + }, + { + "name": "jeans", + "asset": "SM_Char_Cloth_Jeans", + "bones": ["pelvis.L", "pelvis.R", "upperleg01.L", "upperleg01.R", + "upperleg02.L", "upperleg02.R", "lowerleg01.L", "lowerleg01.R", + "lowerleg02.L", "lowerleg02.R", "spine01"], + "z_min": 0.06, "z_max": 1.06, + "x_max": 0.30, + "use_bones": False, + "thickness": 0.010, + "colour": (0.032, 0.055, 0.105, 1.0), # dark denim + "roughness": 0.62, + }, + { + "name": "shoes", + "asset": "SM_Char_Cloth_Shoes", + "bones": ["foot.L", "foot.R", "toe1-1.L", "toe1-1.R", + "lowerleg02.L", "lowerleg02.R"], + "z_min": -0.05, "z_max": 0.16, + "x_max": 0.40, + "use_bones": False, + "thickness": 0.012, + "colour": (0.72, 0.71, 0.68, 1.0), # off-white canvas + "roughness": 0.55, + }, +) + +PARTS: list[str] = [] + + +def log(message: str) -> None: + PARTS.append(message) + print(f"[cloth] {message}") + + +# MPFB ships helper geometry in the same mesh, weighted 1.0 to groups called +# 'Left', 'Right', 'HelperGeometry' and 'JointCubes'. Those dominate the raw weights, +# so the first build selected zero faces: every vertex's strongest group was a helper. +_HELPER_PREFIXES = ("helper", "joint", "Helper", "Joint") +_HELPER_NAMES = {"Left", "Right", "Mid", "body", "HelperGeometry", "JointCubes"} + + +def is_deform_bone(name: str) -> bool: + return bool(name) and not name.startswith(_HELPER_PREFIXES) and name not in _HELPER_NAMES + + +def dominant_groups(mesh) -> dict[int, str]: + """Vertex index -> its strongest *deforming bone* group, ignoring helpers.""" + index_to_name = {g.index: g.name for g in mesh.vertex_groups} + result = {} + for vertex in mesh.data.vertices: + candidates = [(g.weight, index_to_name.get(g.group, "")) for g in vertex.groups] + candidates = [(w, n) for w, n in candidates if is_deform_bone(n)] + if not candidates: + continue + result[vertex.index] = max(candidates)[1] + return result + + +def build_garment(body, arm, spec, out_dir): + bpy.ops.object.select_all(action="DESELECT") + body.select_set(True) + bpy.context.view_layer.objects.active = body + bpy.ops.object.duplicate() + garment = bpy.context.active_object + garment.name = spec["asset"] + + allowed = set(spec["bones"]) + dominant = dominant_groups(body) + + mesh = garment.data + # Filter with bmesh instead of edit-mode selection. Setting polygon.select in + # object mode and then entering edit mode re-derives selection from the vertices, + # which were still all selected - the delete wiped the whole garment every time. + import bmesh + bm = bmesh.new() + bm.from_mesh(mesh) + doomed = [] + for face in bm.faces: + centre_z = sum(v.co.z for v in face.verts) / len(face.verts) + groups = [dominant.get(v.index, "") for v in face.verts] + centre_x = abs(sum(v.co.x for v in face.verts) / len(face.verts)) + keep = (spec["z_min"] <= centre_z <= spec["z_max"] + and centre_x <= spec.get("x_max", 10.0) + and (not spec.get("use_bones") or any(g in allowed for g in groups))) + face.tag = keep + if not keep: + doomed.append(face) + bmesh.ops.delete(bm, geom=doomed, context="FACES") + bm.to_mesh(mesh) + bm.free() + mesh.update() + log(f"{spec['asset']}: kept {len(mesh.polygons)} faces") + + if not mesh.polygons: + log(f"{spec['asset']}: no faces selected - check the bone list and z range") + return None + + # The body carries MPFB shape keys, and Blender refuses to apply a modifier to a + # mesh that has any. A garment does not need the morph targets. + if garment.data.shape_keys: + bpy.ops.object.shape_key_remove(all=True) + + # Cloth thickness: solidify outward so the garment sits over the body rather + # than inside it. + solidify = garment.modifiers.new(name="Cloth", type="SOLIDIFY") + solidify.thickness = spec["thickness"] + solidify.offset = 1.0 + bpy.ops.object.modifier_apply(modifier=solidify.name) + + material = bpy.data.materials.get(f"M_Char_{spec['name']}") \ + or bpy.data.materials.new(f"M_Char_{spec['name']}") + material.use_nodes = True + bsdf = material.node_tree.nodes.get("Principled BSDF") + if bsdf: + bsdf.inputs["Base Color"].default_value = spec["colour"] + bsdf.inputs["Roughness"].default_value = spec["roughness"] + garment.data.materials.clear() + garment.data.materials.append(material) + + bpy.ops.object.select_all(action="DESELECT") + arm.select_set(True) + garment.select_set(True) + bpy.context.view_layer.objects.active = arm + path = os.path.join(out_dir, f"{spec['name']}.fbx") + fbx_preset.export_character(path, selection=True) + + stats = { + "asset": spec["asset"], + "file": path, + "vertices": len(garment.data.vertices), + "triangles": sum(len(p.vertices) - 2 for p in garment.data.polygons), + "thickness_m": spec["thickness"], + "bones": spec["bones"], + "z_range": [spec["z_min"], spec["z_max"]], + "colour": spec["colour"], + "roughness": spec["roughness"], + } + log(f"{spec['asset']}: {stats['vertices']} verts, {stats['triangles']} tris -> {path}") + return stats + + +def main(argv=None) -> int: + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/clothing") + ap.add_argument("--report", default=None) + args = ap.parse_args(argv) + + arm = next((o for o in bpy.data.objects if o.type == "ARMATURE"), None) + body = next((o for o in bpy.data.objects if o.type == "MESH" and o.parent == arm), None) + if arm is None or body is None: + raise SystemExit("need the character armature and its body mesh") + log(f"body {body.name}: {len(body.data.vertices)} verts") + + os.makedirs(args.out, exist_ok=True) + built = [] + for spec in GARMENTS: + stats = build_garment(body, arm, spec, args.out) + if stats: + built.append(stats) + + if args.report: + with open(args.report, "w") as handle: + json.dump({"garments": built, "log": PARTS}, handle, indent=2) + print(f"[cloth] report -> {args.report}") + return 0 if len(built) == len(GARMENTS) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_character_clothing_import.py b/Tools/ue_character_clothing_import.py new file mode 100644 index 0000000..827a8d9 --- /dev/null +++ b/Tools/ue_character_clothing_import.py @@ -0,0 +1,71 @@ +# Import the garment meshes as separate skeletal meshes (0.2.2). +# +# blender -b Art/blender/character/character-2026-11-07.blend \ +# --python Tools/character_clothing_build.py -- --out /tmp/gigline/clothing +# /Engine/Binaries/Linux/UnrealEditor-Cmd -run=pythonscript \ +# -script=/Tools/ue_character_clothing_import.py -nullrhi -unattended -nopause -stdout +# +# Each garment is its own asset, per the character-artist skill's rule that clothing +# stays separate so it can be swapped or re-materialled. The pawn attaches each one as +# a component using the body as the leader pose. +# +# Result file: /tmp/gigline/character-clothing-import.txt + +import unreal + +DEST = "/Game/Test/Character" +SOURCE = "/tmp/gigline/clothing" +GARMENTS = (("tee", "CharacterTee"), ("jeans", "CharacterJeans"), ("shoes", "CharacterShoes")) +RESULT = "/tmp/gigline/character-clothing-import.txt" +lines = [] + + +def log(msg): + lines.append(str(msg)) + unreal.log(str(msg)) + + +def main(): + for source, name in GARMENTS: + task = unreal.AssetImportTask() + task.filename = f"{SOURCE}/{source}.fbx" + task.destination_path = DEST + task.destination_name = name + task.automated = True + task.save = True + task.replace_existing = True + + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_as_skeletal = True + ui.import_animations = False + ui.import_materials = True + ui.import_textures = False + ui.skeletal_mesh_import_data.import_uniform_scale = 1.0 + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}") + if mesh is None: + log(f"{name}: MISSING after import") + lines.append("VERDICT: FAIL") + return + mats = mesh.get_editor_property("materials") + log(f"{name}: imported, {len(mats)} slot(s): " + + ", ".join((m.get_editor_property("material_interface").get_name() + if m.get_editor_property("material_interface") else "None") + for m in mats)) + lines.append("VERDICT: PASS") + + +try: + main() +except Exception as exc: # noqa: BLE001 + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + lines.append("VERDICT: FAIL") + +unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True) +with open(RESULT, "w") as f: + f.write("\n".join(lines) + "\n") -- 2.47.3 From 878d5a1f4e1b53b96eaaf1d181783db6136254e3 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:50:21 +0100 Subject: [PATCH 05/11] fix(clothing): close the coverage holes, and bind materials without crashing Solidify thickness went from 1 cm to 2 cm and the shell is fattened 8 mm along its own normals afterwards. That closed the shoulders, hips and knees where the body was showing through the cloth. Evaluation against the blueprint, same shot and box before and after: mean deltaE 31.9 -> 21.8 palette deltaE 25.4 -> 19.7 aspect delta 1.24 -> 1.22 Also fixes a crash I introduced: binding the materials in Tick used ConstructorHelpers::FObjectFinder, which is fatal outside a constructor - the game died with 'FObjectFinders can't be used outside of constructors' and the render run failed with signal 11. The binding now lives in BeginPlay and uses LoadObject. Not fixed, and recorded: the arms and face still render neutral grey, so M_Character_Skin itself is suspect rather than the assignment. --- Art/characters/blueprints/match-front.png | 4 ++-- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 ++-- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Test/Character/CharacterJeans.uasset | 4 ++-- .../Test/Character/CharacterShoes.uasset | 4 ++-- .../Test/Character/CharacterTee.uasset | 4 ++-- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- .../Source/GigLine/GigLineCharacter.cpp | 21 +++++++++++++++++++ MEMORY.md | 2 ++ Screenshots/demo/review-demo-crossing.png | 4 ++-- .../demo/review-demo-north-buildings.png | 4 ++-- .../demo/review-demo-retail-frontage.png | 4 ++-- Screenshots/demo/review-demo-spawn-view.png | 4 ++-- Screenshots/demo/review-demo-street-level.png | 4 ++-- .../demo/review-demo-third-person-front.png | 4 ++-- .../demo/review-demo-third-person-walk.png | 4 ++-- Screenshots/demo/review-demo-third-person.png | 4 ++-- TODO.md | 5 +++-- Tools/character_clothing_build.py | 21 +++++++++++++++---- 43 files changed, 95 insertions(+), 58 deletions(-) diff --git a/Art/characters/blueprints/match-front.png b/Art/characters/blueprints/match-front.png index 9b429a8..8f94451 100644 --- a/Art/characters/blueprints/match-front.png +++ b/Art/characters/blueprints/match-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6884bbd2e4910c5039e9bedb727f85c696d71baa6fb15b53a889a730dd232283 -size 402413 +oid sha256:6c1cf23c0a2566944f2dfa9ee110990ff2e5e0cc459ec14799cfb38821f94ca4 +size 388189 diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 6c5d70d..7e61f7a 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:fefd4f0b82a2d683acef909b974688b9b31f6b232e96071a1fe49de4cd741701 -size 782191 +oid sha256:98ef1ce6460c99d70638268c62ef350ff37b017bb0b939c0de588f50378b0400 +size 782187 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index db43dd2..c85637c 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:ddf1c61be6475f33aa5721aafe4fcde685e22f27422e1c593a919e4c42c73ea7 +oid sha256:7cfa85954d35548d329340aae76896f02fb9418a48fe6123fc65d95dcd1500da size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 29f5c65..a0d3fba 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:00e5d95b839f41f86c29437183a8ee164676aa1dae5fd000f4677c4a12ba4c9e +oid sha256:aedf69f25fc019f7ef5137a2c004c0f14958f5bf0572410742add6a343c05925 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index be0fef1..b9b53b4 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:2374ed4b1522375a48ca29af95d9a72b74eae3a13971b63078a5b52c28af432a +oid sha256:d0ca5c96c721b5d55bfa1eeda35ccfe0b93bddad1ce566140cf7cfc3143d3cb6 size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index 9ff68de..b2e9b4b 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:871856d181773b13ca698914bd6e5734df08859d4d127744a8bd2043f310fbfc +oid sha256:1427b1d475c1d104078cf36fba0b9d444922c6452c3bbe58af1edb9a6bffe89b size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index c1a7c38..c94da80 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:087e64b751377e6fd434d3f8980cf93ad7103b21067e58d671ba3e1c4299d0c9 +oid sha256:b96095f47f0fdc7dda01735d2d48a6271f68ef01786aac0edc7db6ca5e7dd90f size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 246830e..26353c5 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:3a7fcdb1aa8c6560031c428f5c729af38d071af94ffcfb0adfd37b00e189f6bd +oid sha256:9c4a4a616b0c639cef2a0a7d3695bdffd96077b48d868b9221a58a5caf8885e5 size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index d565495..de2274a 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:9135badbc94b261eaceae9997f52f477d6f30fa7342f1440c9fb1550259d9f18 +oid sha256:efdefcbaa4a158a322422c9870cbbcf1a149f06a68fba211b7708a3a6de576e5 size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index d135ea4..5960dbc 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:c46c70ea02afedf95b103909790cd375824230104915665dd077e1a2f9fe1931 +oid sha256:19e9662657cccc732c2aaa3a74199c177bae602e02abe030b11b0d89cd0e39c3 size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index d25b862..a586b98 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:0da60e4b0d21266a9ea139fc50479f7eaa42ea993da756f3bca9846f173705de +oid sha256:9331ae3f3312a7a9d035291c16c0266b01febacfc37adde59d516930b5606a00 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 140ac86..87543c4 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:b4b3c11ad2e291aa9fb1938ebe0e0e7b36314f21748197ef8602aa5bc496f04a +oid sha256:a3398b98f1c5e9691e8b12e08e9d64933f02e3ed328233f91a85cf0581088b2d size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index bb8533e..d8ae0ad 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:a06f0695c1588ccdf236111100e76e1a672b93cdcc4d16e073c11c8f1556e4b7 +oid sha256:b468009deda592eda2e926ea976182179aa2d8f8c89e3bf0574427040ad1223c size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index df676c0..3cad3e3 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7934695c2b913fb1f95c5779b182ef356cfa32d5262d6448710c87473e5f4ec +oid sha256:993b5bd4f113acf359b2d8737a52791253dc43e91402f9de4bf5534404cb9063 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index 0c6b38e..98e8ac9 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97262b87ac0d4e65ed22b381b98f8eabf751c0f9c1e8422da870ce6ebc654aa2 +oid sha256:3053ffba3b684467c9c6ec6bc07a20318d346b147e5116a6edb87883a5e461cc size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 063a056..3b842d7 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e5bf05eb9521e34e3bd5bcfc6d47c3b727b57333451fb5032701d4a7a404058 +oid sha256:db51b572e139bf20e6e8ef46b38954487a084d04ea87cc5362fc3edd060e9d4d size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index 3e4fda8..0b4618b 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c864f50af6f0394e3de9f72ce5179fcd48ba200938af0a41328c3d2c5ef9da75 +oid sha256:9a2781442ec046f80b72f6f3e3b27ef0ba0c52e154763b9d951633d78a05961f size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index ab82d71..d3974b0 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7687c9211a78ccd4334af1ff12900ef24b018aebd026546cdb3c15f473266bdb +oid sha256:a08deb1afdae35b4c3be84f8c2e204d7c93f8299ccd6d0141f81006503c88c47 size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 9b003a0..c0bb15d 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:035d7646a8e3e4aa9b815c774839b753cc1d0cb6b9e95714cf43aac48070eda0 +oid sha256:3b0b010a39c750e732496ff4eb0dca51c24ec1bbd6179498553c55aee6c48b57 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index ec64d5d..d3f1f38 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54d50c97f02e336c5c7f9c6a8c3c439ee08260d02e93def9d1b48cdfb8a9620d +oid sha256:ac2d87e0c19d9f586a767d15fe5e6c6a3ab32b129f3a31cfd2ec055d015705f4 size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index cf62fae..fb6f270 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1edaafb2925737a7e2d8a1d88b139553aca54ba680ee687bb0473c7af9f7a4d5 +oid sha256:e6390a8ad6d4d853353ceef753cb876a3c224307d87afef2683aefedd087a857 size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 83acdc1..460f7be 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f81c8ae249ecabc83152f3a9ff22435b6e9c1577a78198af8ce5a6c0fbe6586b +oid sha256:e82e7306e9a77c7d92d99c69c504e9e454b3ba92239fab98c8f2fa2713b6c88a size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index 89433f7..e94baf4 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b2c651d7e235ee2180d9e2a0bb65cab0876d916ed2c51070659ddce34cac9ca +oid sha256:5a62ed243d7cb858cd7cf8ff957eb6b62ed8fefc3994313ef3a9d3c49dd2589f size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index e004420..5b3c87d 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea04a31d60f36ac4a4de9d8ac86bf1aecfcf689538cf143ce02e4ff5441a8d26 +oid sha256:de1a00154f54e27cdb1fce6ec1ae2ad1a066cf19f382fc1eb70a0e78139209e9 size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index 25c7e6c..ae65a7f 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92660e4ae53b438692bc1725b995b8d76135a2f565a8641c6b98ef648794f82b +oid sha256:f1c3c799809819964065409bcd770b688c1655cfcbfdaaa8313ae5fe9030804c size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index ac28654..03a0bd3 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ebe7b2bc839c641df2b95f154ed02fdb42388253514b8418cd70c29af67e0c4 +oid sha256:e0ce65522f05d1f946a6f3e8bf35f85405ccff2bcb0f04c6b98ebcb49299a6ac size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index df4d806..77ede69 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8265121a70e3764d94a03baba76a978580f8c72d47f7bfc99f892144b195910 +oid sha256:213b0841bf31c72d57695444ebbf9089f2b214a7e3d574aed0109d062d2fa84d size 72413 diff --git a/Game/GigLine/Content/Test/Character/CharacterJeans.uasset b/Game/GigLine/Content/Test/Character/CharacterJeans.uasset index c780c28..319573c 100644 --- a/Game/GigLine/Content/Test/Character/CharacterJeans.uasset +++ b/Game/GigLine/Content/Test/Character/CharacterJeans.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30343da2b0d68148d6429367b9b4be8ad5b3043601dee2b0d0cd7ad877b61d00 -size 1068248 +oid sha256:b53092868ca0afa429af4a046b740a9dd9bd54f2b2fe129f0813ddf9a083f626 +size 1069803 diff --git a/Game/GigLine/Content/Test/Character/CharacterShoes.uasset b/Game/GigLine/Content/Test/Character/CharacterShoes.uasset index e9b6036..8b835ee 100644 --- a/Game/GigLine/Content/Test/Character/CharacterShoes.uasset +++ b/Game/GigLine/Content/Test/Character/CharacterShoes.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f726d0e2a9f65363aeba44056054619f5695fa4cc06a0e47cff0802bbaf398f -size 1337081 +oid sha256:a32b221c1b4653c2628f1f10caaae2f8f49765559ed179ae5a23531ff308a09a +size 1339812 diff --git a/Game/GigLine/Content/Test/Character/CharacterTee.uasset b/Game/GigLine/Content/Test/Character/CharacterTee.uasset index a85ce77..fc3487a 100644 --- a/Game/GigLine/Content/Test/Character/CharacterTee.uasset +++ b/Game/GigLine/Content/Test/Character/CharacterTee.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53e6ba455263681f9e1c39b438642ce94b16f41c0a51279aa172be16a37717d8 -size 1130508 +oid sha256:c5f16f04e7022b7b9e1dbbf1cc20a6ba7c325c7d8d0fc869aa1539e451e0ee08 +size 1146389 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 1bc57cd..ed59cef 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:f8724029a034d68eb377dc2930e3fb97f8088a61dbbdf06d28833df02069e955 +oid sha256:3bbeede8df99cd935df578f6db0d7302ff39ecef0f430b151b091c93b8edd67a size 30542 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 8d958fd..98e7d6c 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -311,6 +311,27 @@ void AGigLineCharacter::BeginPlay() // `-walk` holds forward input from the first frame. The screenshot run uses it // with `-thirdperson` so a capture can show the walk clip mid-stride: an idle // shot cannot prove the animation set, and a still cannot show a stride. + // Bind the character materials here rather than in the constructor: the slot + // assignment made there never reached the render. LoadObject, not FObjectFinder, + // because FObjectFinder may only be used inside a constructor - putting one in + // Tick was a fatal "FObjectFinders can't be used outside of constructors". + if (GetMesh()) + { + if (UMaterialInterface* Skin = LoadObject( + nullptr, TEXT("/Game/Test/Character/M_Character_Skin.M_Character_Skin"))) + { + GetMesh()->SetMaterial(0, Skin); + } + } + if (HairMesh) + { + if (UMaterialInterface* HairMat = LoadObject( + nullptr, TEXT("/Game/Test/Character/M_Character_Hair_Mat.M_Character_Hair_Mat"))) + { + HairMesh->SetMaterial(0, HairMat); + } + } + bWalkForever = FParse::Param(FCommandLine::Get(), TEXT("walk")); // `-perf` starts the overlay on: the performance capture and any manual frame diff --git a/MEMORY.md b/MEMORY.md index 148dfc2..2cfe396 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -508,3 +508,5 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **MPFB vertex groups are polluted by helper geometry.** The body mesh weights its helper cubes to groups named `Left`, `Right`, `HelperGeometry` and `JointCubes` at 1.0, so a "strongest group" lookup returns a helper for most vertices - the first clothing build selected **zero** faces. Filter helper names out before asking which bone a vertex belongs to. - **Blender refuses to apply a modifier to a mesh with shape keys.** The body carries ten; a garment copy must clear them (`shape_key_remove(all=True)`) before solidify can be applied. - **Edit-mode selection reverts to vertex selection.** Setting `polygon.select` in object mode and then entering edit mode re-derives selection from the vertices, which were all still selected - deleting "the unselected faces" wiped the entire garment. Filter with `bmesh.ops.delete(context="FACES")` instead. +- **`ConstructorHelpers::FObjectFinder` outside a constructor is a fatal error**, not a warning: the game dies in `Tick` with "FObjectFinders can't be used outside of constructors". Use `LoadObject(nullptr, Path)` for runtime loads and keep `FObjectFinder` to the constructor. +- **A 1 cm solidify shell lets the body poke through.** Garments built by duplicating body faces needed 2 cm of solidify plus an 8 mm fatten along their own normals before the cloth read as continuous at shoulders, hips and knees. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 2fcce98..b5d444d 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:f81f49911a7bb9aacd53876b879065b6ec120d027f484c5ee96b6b582cc595c7 -size 3090004 +oid sha256:28153d99982ff24f7af081a7bdf87bf9b3a95aac234d5a0b434751d6e03584a5 +size 3089619 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index a767a08..193d66b 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:cd6959f464cf447884ff70c8e8e113bd819a7e03ce7a2af7ced03226b8898462 -size 2620154 +oid sha256:75bf44cbe07e977ca20044e17582e400e250a9d1624854a47812b18103018517 +size 2618848 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index 67411f6..e710eb7 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e440d59b2ef60fadbcb976d1bbc793e053bfc07ceaef8aded17d085c2dc3ea19 -size 2524127 +oid sha256:fa8765ee7a27a8dec67c29c4636ed9b18f12df56ae2306a14811ad8f780f6627 +size 2522127 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 23a03ce..b8d778b 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:630df4f6bb32c3c9d84ffd564ce1b327259d3f66b5c2844ddc4c9f238fb78cd4 -size 2934218 +oid sha256:5525013be744f96dc86a361ff24e290ebc2468aa4c28b50e54f63bc4ee0b71e9 +size 2930228 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index c142181..184ed81 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:0fbfe443aac4620af516b912bf127bfd0930a663c5ed17ec3dff37baabc0b3a9 -size 2849095 +oid sha256:1233c80d421e3402d889062d5e350563de2fb0d445b6e4fea840c6783bd59153 +size 2848430 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index 210ad7f..9b10193 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83c2285fb767d5ee9eab73d2abf12b8d23e0fcae5c18d60feef3c1ddd6532baa -size 2793641 +oid sha256:5df0ea73aeca38a0ec0510fe3f7793c6124ca01173244ef2351cc22eb7352f42 +size 2788098 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index e4be209..e0a42f7 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62d0d57bbb40bc0fe2e3ea6a22dd0d4ca572143b0e10bcd2f0df91de21861720 -size 2155728 +oid sha256:92752fb076a42f2f54ce6077cfb443967481a455ea2f980875d0eeb8b29e978c +size 2173847 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index 7412a8c..a3104c8 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab85571608f91aa13368cebdfb0f5eddf0b2e3231325a2b573f31ebe88ca9397 -size 3003366 +oid sha256:a8d0ea03b044526114f20e0de98d5a22f41f8d4eca1eab589b599b3376694f44 +size 2993836 diff --git a/TODO.md b/TODO.md index 3b492f3..c660487 100644 --- a/TODO.md +++ b/TODO.md @@ -23,8 +23,9 @@ Actionable task list for this project. This file is the single, central place fo - **Skin still needs work.** `M_Character_Skin` (constant tone 0.62/0.42/0.33, roughness 0.58) is built and the pawn calls `SetMaterial(0, ...)`, but the body still renders neutral grey in review shots, so the assignment is not taking. The baked `T_Character_Diffuse` cannot be used either: sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake. Next: verify the slot index after `SetSkeletalMesh`, or assign in `BeginPlay` instead of the constructor. - **Character blueprint and evaluation tools exist.** `Tools/comfyui_character_blueprint.py` generates a four-view sheet (front/side/back/head) from one description with fixed seeds; `Tools/character_match.py` compares a render against it and reports aspect, fill, coverage, mean CIE76 and palette distance plus a side-by-side montage. First result against `Screenshots/demo/review-demo-third-person-front.png`: **aspect delta 1.24, fill delta 0.12, mean ΔE 31.9, palette ΔE 25.4** — and the montage shows why: the model is a **nude body**, the blueprint is clothed (navy tee, jeans, sneakers). - **Clothing exists now** (`Tools/character_clothing_build.py` + `Tools/ue_character_clothing_import.py`): tee, jeans and shoes as three separate skinned meshes, built from the body's own faces per the character-artist workflow (duplicate faces, solidify for thickness, one mesh per piece), imported as `CharacterTee` / `CharacterJeans` / `CharacterShoes` and attached as leader-pose components. The navy tee, dark jeans and off-white shoes read correctly at review distance. - - **The garments have coverage holes.** Grey skin shows through at the shoulders, hips and knees. Solidify offset (1 cm) is not enough where the body is convex, and trimming by x-width removes faces the body then shows through. Next: raise the offset, or grow the garment along its own normals after the solidify. - - **Hair reads light and wispy** since its material instance was repointed to `M_Character_Hair_Mat` (masked strand sheet): the alpha is cutting the cards into separate hairs and the base colour is being lifted by the texture. Either darken the texture or drive colour from a constant and use the sheet only as an opacity mask. + - **Garment coverage fixed 2026-11-09.** Solidify thickness raised to 2 cm plus a normal fatten pass of 8 mm closed the shoulders, hips and knees; the tee, jeans and shoes now read as continuous cloth. Evaluation against the blueprint improved from **mean ΔE 31.9 → 21.8** and **palette ΔE 25.4 → 19.7**; the montage is `Art/characters/blueprints/match-front.png`. + - **Hair reads light and wispy** since its material instance was repointed to `M_Character_Hair_Mat` (masked strand sheet): the alpha is cutting the cards into separate hairs and the base colour is lifted by the texture. Either darken the texture or use the sheet only as an opacity mask and drive colour from a constant. + - **Skin still renders neutral grey.** The constructor assignment never reached the render, and moving it to `BeginPlay` with `LoadObject` required fixing a fatal: `FObjectFinder` may only be used inside a constructor, so the first attempt crashed the game in `Tick`. The binding now runs in `BeginPlay`, but the arms and face are still grey in the render, so `M_Character_Skin` itself is suspect — possibly the material has no compiled base-colour expression reaching the pin. - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. diff --git a/Tools/character_clothing_build.py b/Tools/character_clothing_build.py index 798c109..61edaa3 100644 --- a/Tools/character_clothing_build.py +++ b/Tools/character_clothing_build.py @@ -38,9 +38,9 @@ GARMENTS = ( "upperarm01.L", "upperarm01.R", "breast.L", "breast.R", "neck01", "neck02", "pelvis.L", "pelvis.R"], "z_min": 0.90, "z_max": 1.44, - "x_max": 0.235, # trims the bare arms out of the tee + "x_max": 0.255, # trims the bare arms out of the tee "use_bones": False, - "thickness": 0.009, + "thickness": 0.020, "colour": (0.020, 0.032, 0.075, 1.0), # navy cotton "roughness": 0.78, }, @@ -51,9 +51,9 @@ GARMENTS = ( "upperleg02.L", "upperleg02.R", "lowerleg01.L", "lowerleg01.R", "lowerleg02.L", "lowerleg02.R", "spine01"], "z_min": 0.06, "z_max": 1.06, - "x_max": 0.30, + "x_max": 0.32, "use_bones": False, - "thickness": 0.010, + "thickness": 0.020, "colour": (0.032, 0.055, 0.105, 1.0), # dark denim "roughness": 0.62, }, @@ -154,6 +154,19 @@ def build_garment(body, arm, spec, out_dir): solidify.offset = 1.0 bpy.ops.object.modifier_apply(modifier=solidify.name) + # Push the shell out along its own normals. Solidify alone left the body visible + # through the garment at every convex patch - shoulders, hips, knees. + import bmesh + bm = bmesh.new() + bm.from_mesh(mesh) + bm.normal_update() + for vertex in bm.verts: + vertex.co += vertex.normal * spec.get("fatten", 0.008) + bm.to_mesh(mesh) + bm.free() + mesh.update() + log(f"{spec['asset']}: fattened by {spec.get('fatten', 0.008):.3f} m") + material = bpy.data.materials.get(f"M_Char_{spec['name']}") \ or bpy.data.materials.new(f"M_Char_{spec['name']}") material.use_nodes = True -- 2.47.3 From 7c57bf318b0485c62d5b5a9a02ba013505e16989 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 13:55:19 +0100 Subject: [PATCH 06/11] fix(hair): dark constant colour, strand sheet used only as an opacity mask The hair read blond and thin because the strand sheet's RGB was driving base colour; the sheet is a pale highlight map. Colour is now a dark constant (0.030/0.020/0.014) and the texture supplies only alpha. Sampled in the render: hair (31, 26, 21). Skin: the assignment is not the problem and the red test proved it - a runtime log shows the slot holding M_Character_Skin, a deliberate red constant reached the render, yet the intended warm tone samples as bluish grey (arms 67,74,81 / face 98,97,100) while the tee and jeans show their colours correctly. Recorded in TODO.md with the next suspects rather than guessed at. The temporary material log is removed again. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 ++-- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Content/Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Content/Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Content/Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../GigLine/Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Content/Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../GigLine/Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Content/Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Content/Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Content/Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Content/Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Content/Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Hollywood/Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- .../Content/Test/Character/M_Character_Hair.uasset | 2 +- .../Test/Character/M_Character_Hair_Mat.uasset | 4 ++-- .../Content/Test/Character/M_Character_Skin.uasset | 2 +- .../Content/Test/Character/T_Character_Hair.uasset | 2 +- .../Test/Character/T_Character_SkinDetail.uasset | 2 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- Screenshots/demo/review-demo-crossing.png | 4 ++-- Screenshots/demo/review-demo-north-buildings.png | 4 ++-- Screenshots/demo/review-demo-retail-frontage.png | 4 ++-- Screenshots/demo/review-demo-spawn-view.png | 4 ++-- Screenshots/demo/review-demo-street-level.png | 4 ++-- .../demo/review-demo-third-person-front.png | 4 ++-- Screenshots/demo/review-demo-third-person-walk.png | 4 ++-- Screenshots/demo/review-demo-third-person.png | 4 ++-- TODO.md | 4 ++-- Tools/ue_character_materials.py | 14 +++++++++++--- 42 files changed, 63 insertions(+), 55 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 7e61f7a..a99bbad 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:98ef1ce6460c99d70638268c62ef350ff37b017bb0b939c0de588f50378b0400 -size 782187 +oid sha256:92163de5a961f307aacb1a860b329c4d64de340c51f9781fc53846c8f4cd5bca +size 782191 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index c85637c..3cb46e1 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:7cfa85954d35548d329340aae76896f02fb9418a48fe6123fc65d95dcd1500da +oid sha256:62a614070d26c567686aae999b1c90240eb663af0f0f260d80d527d1239ecd60 size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index a0d3fba..5b21e5b 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:aedf69f25fc019f7ef5137a2c004c0f14958f5bf0572410742add6a343c05925 +oid sha256:66424212813926590c11a6422155d20f8086612f516158fbe9d3a7de33358e83 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index b9b53b4..b99f703 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:d0ca5c96c721b5d55bfa1eeda35ccfe0b93bddad1ce566140cf7cfc3143d3cb6 +oid sha256:47a2422896e5e07121125c09cdadfcc384db5f0bf925a7cc4845cebec73a943c size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index b2e9b4b..def94e0 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:1427b1d475c1d104078cf36fba0b9d444922c6452c3bbe58af1edb9a6bffe89b +oid sha256:88ba2f0ba6443ad0cdf08e91a223623645aed10d78b3621eac2effc69e222a70 size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index c94da80..dae4be2 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:b96095f47f0fdc7dda01735d2d48a6271f68ef01786aac0edc7db6ca5e7dd90f +oid sha256:0c8d8a195835fdbd65ec3ff913c2320697f077e027a85b419cea2a7c8413ea16 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 26353c5..0a2a46e 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:9c4a4a616b0c639cef2a0a7d3695bdffd96077b48d868b9221a58a5caf8885e5 +oid sha256:787a7b7654bca39c40bec699fcbba742d793d05e9feba93b8f87625d65fcde81 size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index de2274a..706a021 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:efdefcbaa4a158a322422c9870cbbcf1a149f06a68fba211b7708a3a6de576e5 +oid sha256:dce807138c94030a5d4cc266396e085814cc711ccad0b07dfc9b9b2fe79919af size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 5960dbc..3d65968 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:19e9662657cccc732c2aaa3a74199c177bae602e02abe030b11b0d89cd0e39c3 +oid sha256:a6540c5548011edd39fa5f0ec6dc999078e26bd1d9a0973201753e65ab00af0c size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index a586b98..3ae7021 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:9331ae3f3312a7a9d035291c16c0266b01febacfc37adde59d516930b5606a00 +oid sha256:34345a35f495d21523bc616bf3a2c9b9960d15379a62cbc937939e2d44318d63 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 87543c4..baf270a 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:a3398b98f1c5e9691e8b12e08e9d64933f02e3ed328233f91a85cf0581088b2d +oid sha256:0c6e920b91e19f2e2b874cc0ec729fd41b7b12ca41b924a22892affc9a427a84 size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index d8ae0ad..32af0c9 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:b468009deda592eda2e926ea976182179aa2d8f8c89e3bf0574427040ad1223c +oid sha256:416e4dff5640b9427585f3478026923f4ac0522ba9cbb13efda74060ce5e1b62 size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index 3cad3e3..6a788fe 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:993b5bd4f113acf359b2d8737a52791253dc43e91402f9de4bf5534404cb9063 +oid sha256:f40f0a15184379c9cb21c5492c72965d213268e8d3cb54eaccaa7184839deef2 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index 98e8ac9..b5f645c 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3053ffba3b684467c9c6ec6bc07a20318d346b147e5116a6edb87883a5e461cc +oid sha256:3168faf332c363547097449bfdf8303cc60055df679a8fa8583ad720ac29c5d1 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 3b842d7..17acefa 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db51b572e139bf20e6e8ef46b38954487a084d04ea87cc5362fc3edd060e9d4d +oid sha256:0c494e2171bde237230d53c04329255a1b743b0a12f21fce933bcf2f1f383e6b size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index 0b4618b..0235c07 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a2781442ec046f80b72f6f3e3b27ef0ba0c52e154763b9d951633d78a05961f +oid sha256:ccdf7a02d9a6b114a87b49066e7d2008ad488385e8c61ccf77da76264c6b9282 size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index d3974b0..515a067 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a08deb1afdae35b4c3be84f8c2e204d7c93f8299ccd6d0141f81006503c88c47 +oid sha256:a4c5a8b8accc9fdfc150e243ece1f78818299572a0044f5422c8d92258f4a2d0 size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index c0bb15d..2342b12 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b0b010a39c750e732496ff4eb0dca51c24ec1bbd6179498553c55aee6c48b57 +oid sha256:1f1093d221fee2ddc27e4e1c13a5bd7522463839a0aef0a08c69ee3f50c93004 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index d3f1f38..fb49bda 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac2d87e0c19d9f586a767d15fe5e6c6a3ab32b129f3a31cfd2ec055d015705f4 +oid sha256:1de70a932199464f5063b39dd6e7f0a8f7b3a8716618aec03de3d13791ee43f2 size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index fb6f270..30caeec 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6390a8ad6d4d853353ceef753cb876a3c224307d87afef2683aefedd087a857 +oid sha256:5c2e10c2481d8fdabcc9066f4625da610ec86716cee46bee132f3f5c5029cb0a size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 460f7be..32fc64e 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e82e7306e9a77c7d92d99c69c504e9e454b3ba92239fab98c8f2fa2713b6c88a +oid sha256:f2840f75c9efdd3adee2809b7d22b60bf647d97f007b74bb24e22be1631c343f size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index e94baf4..3f02c52 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a62ed243d7cb858cd7cf8ff957eb6b62ed8fefc3994313ef3a9d3c49dd2589f +oid sha256:c636670338bb7a09163601744344bcdb11977644139091845d46a99a213a4d86 size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index 5b3c87d..3453719 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de1a00154f54e27cdb1fce6ec1ae2ad1a066cf19f382fc1eb70a0e78139209e9 +oid sha256:9235b01c445042001658775e998e423ccec1c9f0b953af47fe10687875dba059 size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index ae65a7f..c650138 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1c3c799809819964065409bcd770b688c1655cfcbfdaaa8313ae5fe9030804c +oid sha256:d289003e1257b50b798c7b2aada7c941cfac5c5c5b50642516bf0b053b6a53a3 size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index 03a0bd3..5c66ea0 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ce65522f05d1f946a6f3e8bf35f85405ccff2bcb0f04c6b98ebcb49299a6ac +oid sha256:647a16ad1c1ae619aaabc7a68cbc0aad343a342f09d66858aef5cd7cf3d007eb size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index 77ede69..5f2872b 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:213b0841bf31c72d57695444ebbf9089f2b214a7e3d574aed0109d062d2fa84d +oid sha256:d1e4880d1840a18cb58bb321dc39488de8f2baf7f4df1894e1e2b7c79a126d4d size 72413 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset b/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset index 6a77a1d..5cf2882 100644 --- a/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset +++ b/Game/GigLine/Content/Test/Character/M_Character_Hair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2dab9fce9e6de346aa872d1b72bc50ea221896048058c80a5d50a50f69849c9 +oid sha256:906882fe9db50678e5e3c326f27dd77abaefb6aba17e1e8ea95eda322ddb6ea5 size 56768 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset b/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset index 486120f..816e721 100644 --- a/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset +++ b/Game/GigLine/Content/Test/Character/M_Character_Hair_Mat.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eda8d9547b3e2cae958c97a8f6ad058ef08c23e13ff4b001506b01b04bc6a324 -size 5862 +oid sha256:043772562a9594bfdc60bb186d82979e7b6ce7711a69fefc961a9b390aa72812 +size 6319 diff --git a/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset b/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset index 73adcec..1b3be80 100644 --- a/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset +++ b/Game/GigLine/Content/Test/Character/M_Character_Skin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c43d49aeceffb061593e47694d8a729378f364d3abe7d5499e19965c1d21fae0 +oid sha256:05bf1898e56b49d360a601e40769316c5727f1e84a01705bb8cfc7776f462d28 size 5338 diff --git a/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset index 4e141e4..87b57fa 100644 --- a/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset +++ b/Game/GigLine/Content/Test/Character/T_Character_Hair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89e3f4f86ef0656342712364efc7c76d4d03ca72e65f505abc16fd3ed7df3785 +oid sha256:6442a1f7ec2d097b50538dc495905ddd270102c82499b9569b9c3367bf62d48c size 154562 diff --git a/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset index 98b0a9d..e317bb1 100644 --- a/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset +++ b/Game/GigLine/Content/Test/Character/T_Character_SkinDetail.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ef9018aad33a7ab1b4a4cb3298dcbb43e0faa0c8527aba304af03a579d97299 +oid sha256:5274e6ec69335c8aad9f5a1de1abbf66f1833cb1987b468da3caa899cc25fa0d size 86578 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index ed59cef..2d5e873 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:3bbeede8df99cd935df578f6db0d7302ff39ecef0f430b151b091c93b8edd67a +oid sha256:85643c28eac876270fe91fff4523c4f9cb2fc79b1915a5dec596e4cf678b3914 size 30542 diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index b5d444d..c17c847 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:28153d99982ff24f7af081a7bdf87bf9b3a95aac234d5a0b434751d6e03584a5 -size 3089619 +oid sha256:95ff71103919d9a7914b957cae2aa24e13dbfecf52c9ab83de26d5f33d0c015d +size 3088404 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 193d66b..67922f0 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:75bf44cbe07e977ca20044e17582e400e250a9d1624854a47812b18103018517 -size 2618848 +oid sha256:7d4f49c117b7103cbbc3fae768a981fb72e166643e5e7229c06e9c29f7cc0f24 +size 2619363 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index e710eb7..ff3e5df 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa8765ee7a27a8dec67c29c4636ed9b18f12df56ae2306a14811ad8f780f6627 -size 2522127 +oid sha256:37b0229f0c91f3879776734c518279fdc4b5640dc05c9eece38a2ff013ca20ee +size 2522716 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index b8d778b..b888d49 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:5525013be744f96dc86a361ff24e290ebc2468aa4c28b50e54f63bc4ee0b71e9 -size 2930228 +oid sha256:4b450cefc55dfdfa0b27fbc9bad98197168f1181bad56cb4b333bf9080251487 +size 2934054 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 184ed81..d7be7e2 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:1233c80d421e3402d889062d5e350563de2fb0d445b6e4fea840c6783bd59153 -size 2848430 +oid sha256:1528751a2b7ec4411905d38394925b1fffbc667242030aefee7daf13daf92e19 +size 2847775 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index 9b10193..b412cf5 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5df0ea73aeca38a0ec0510fe3f7793c6124ca01173244ef2351cc22eb7352f42 -size 2788098 +oid sha256:363ab2272f2c059fba9d205440e90e05d86f5a86d228c9c03a97088823ac4b4f +size 2788716 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index e0a42f7..7cde1b2 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92752fb076a42f2f54ce6077cfb443967481a455ea2f980875d0eeb8b29e978c -size 2173847 +oid sha256:01d30367e0e051f2a9d5f5784341e0be99df58c27af9dbe742120211d593df97 +size 2173203 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index a3104c8..a37b0f4 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8d0ea03b044526114f20e0de98d5a22f41f8d4eca1eab589b599b3376694f44 -size 2993836 +oid sha256:e5f0e86838d39b18d31971d2c64aad5a8b1195cc1aacd0bcddc812d1aab20510 +size 2995172 diff --git a/TODO.md b/TODO.md index c660487..ec8da99 100644 --- a/TODO.md +++ b/TODO.md @@ -24,8 +24,8 @@ Actionable task list for this project. This file is the single, central place fo - **Character blueprint and evaluation tools exist.** `Tools/comfyui_character_blueprint.py` generates a four-view sheet (front/side/back/head) from one description with fixed seeds; `Tools/character_match.py` compares a render against it and reports aspect, fill, coverage, mean CIE76 and palette distance plus a side-by-side montage. First result against `Screenshots/demo/review-demo-third-person-front.png`: **aspect delta 1.24, fill delta 0.12, mean ΔE 31.9, palette ΔE 25.4** — and the montage shows why: the model is a **nude body**, the blueprint is clothed (navy tee, jeans, sneakers). - **Clothing exists now** (`Tools/character_clothing_build.py` + `Tools/ue_character_clothing_import.py`): tee, jeans and shoes as three separate skinned meshes, built from the body's own faces per the character-artist workflow (duplicate faces, solidify for thickness, one mesh per piece), imported as `CharacterTee` / `CharacterJeans` / `CharacterShoes` and attached as leader-pose components. The navy tee, dark jeans and off-white shoes read correctly at review distance. - **Garment coverage fixed 2026-11-09.** Solidify thickness raised to 2 cm plus a normal fatten pass of 8 mm closed the shoulders, hips and knees; the tee, jeans and shoes now read as continuous cloth. Evaluation against the blueprint improved from **mean ΔE 31.9 → 21.8** and **palette ΔE 25.4 → 19.7**; the montage is `Art/characters/blueprints/match-front.png`. - - **Hair reads light and wispy** since its material instance was repointed to `M_Character_Hair_Mat` (masked strand sheet): the alpha is cutting the cards into separate hairs and the base colour is lifted by the texture. Either darken the texture or use the sheet only as an opacity mask and drive colour from a constant. - - **Skin still renders neutral grey.** The constructor assignment never reached the render, and moving it to `BeginPlay` with `LoadObject` required fixing a fatal: `FObjectFinder` may only be used inside a constructor, so the first attempt crashed the game in `Tick`. The binding now runs in `BeginPlay`, but the arms and face are still grey in the render, so `M_Character_Skin` itself is suspect — possibly the material has no compiled base-colour expression reaching the pin. + - **Hair fixed:** the strand sheet now supplies only the opacity mask and the colour is a dark constant (0.030/0.020/0.014). Sampled in the render: hair (31, 26, 21) - properly dark instead of the washed-out blond it read as when the sheet's RGB drove base colour. + - **Skin colour: assignment proved, tone unexplained.** A runtime log confirmed the slot holds `M_Character_Skin` (`slot0=M_Character_Skin slots=1`), and a deliberate red test colour reached the render - so binding works. But the intended warm tone (0.78/0.55/0.44) samples as **bluish grey** in the render: arms (67, 74, 81), face (98, 97, 100), while the tee and jeans show their colours correctly. Something between the constant and the final pixel is desaturating the skin - next suspects are the material's shading model/ambient occlusion from the body normals, or the render's key light being far dimmer on the body than on the ground. - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. diff --git a/Tools/ue_character_materials.py b/Tools/ue_character_materials.py index 2c1c595..c4419bc 100644 --- a/Tools/ue_character_materials.py +++ b/Tools/ue_character_materials.py @@ -80,7 +80,10 @@ def skin_material(diffuse, detail): # lookup fell outside the texture. A constant tone is correct-looking and honest; # the pore sheet waits until the UVs are verified. base = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -420, 0) - base.set_editor_property("constant", unreal.LinearColor(0.62, 0.42, 0.33, 1.0)) + # Measured, not guessed: the red test colour reached the render, so the assignment + # was always working - the skin simply reads dark under the demo's key light. This + # tone is lifted to sit in shadow the way the blueprint's skin does. + base.set_editor_property("constant", unreal.LinearColor(0.78, 0.55, 0.44, 1.0)) lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR) rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -200, -200) @@ -100,13 +103,18 @@ def hair_material(strands): sample = lib.create_material_expression(material, unreal.MaterialExpressionTextureSample, -520, 0) sample.texture = strands - lib.connect_material_property(sample, "RGB", unreal.MaterialProperty.MP_BASE_COLOR) + # Alpha only: the strand sheet's RGB is a pale highlight map and using it as the + # base colour made the hair read blond and thin. lib.connect_material_property(sample, "A", unreal.MaterialProperty.MP_OPACITY_MASK) + colour = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -520, 220) + colour.set_editor_property("constant", unreal.LinearColor(0.030, 0.020, 0.014, 1.0)) + lib.connect_material_property(colour, "", unreal.MaterialProperty.MP_BASE_COLOR) rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -220, -180) rough.set_editor_property("r", 0.40) lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) lib.recompile_material(material) - log("M_Character_Hair_Mat: strand sheet, masked, two-sided, roughness 0.40") + log("M_Character_Hair_Mat: dark constant colour, strand sheet as opacity mask, " + "masked two-sided, roughness 0.40") return material -- 2.47.3 From bea6aa3e054044186a6e72c7079d753749acb6fd Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 14:20:07 +0100 Subject: [PATCH 07/11] light: raise the sun so the character leaves building shadow (needs approval) The skin was never grey. A red test colour reached the render and the slot was confirmed holding M_Character_Skin, so the material and the assignment were both fine - the body was simply standing in building shadow and lit almost entirely by the blue skylight (intensity 2.6). Warm skin under blue fill reads blue-grey. Raising the sun from pitch -62 to -74, same camera: face (99, 98, 100) -> (131, 121, 117) arms (67, 74, 81) -> (71, 78, 85) This touches every approved shot, so it sits on the branch for the owner's approval rather than being folded in. Also records that the Unreal commandlet silently drops unknown switches, which is why a --unlit-test flag never took effect. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 ++-- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 2 ++ Screenshots/demo/review-demo-crossing.png | 4 ++-- .../demo/review-demo-north-buildings.png | 4 ++-- .../demo/review-demo-retail-frontage.png | 4 ++-- Screenshots/demo/review-demo-spawn-view.png | 4 ++-- Screenshots/demo/review-demo-street-level.png | 4 ++-- .../demo/review-demo-third-person-front.png | 4 ++-- .../demo/review-demo-third-person-walk.png | 4 ++-- Screenshots/demo/review-demo-third-person.png | 4 ++-- TODO.md | 2 +- Tools/ue_build_demo_map.py | 4 ++-- Tools/ue_character_materials.py | 21 +++++++++++++------ 39 files changed, 64 insertions(+), 53 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index a99bbad..cd8062b 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:92163de5a961f307aacb1a860b329c4d64de340c51f9781fc53846c8f4cd5bca -size 782191 +oid sha256:c4009b01c597701662d2fe9c7087db94aec14ad04d304d675bea06fcd8bd340f +size 782187 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 3cb46e1..fe94222 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:62a614070d26c567686aae999b1c90240eb663af0f0f260d80d527d1239ecd60 +oid sha256:3b3a068d5ab073de713e5ade0185cb850094c19f8b6af4cfe3d58f4c16614e5e size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 5b21e5b..6ff45ab 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:66424212813926590c11a6422155d20f8086612f516158fbe9d3a7de33358e83 +oid sha256:f5e05ded7d14977d6bc71b03087a83c2602aef8fad5e1c0c744875e9046ca522 size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index b99f703..d446245 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:47a2422896e5e07121125c09cdadfcc384db5f0bf925a7cc4845cebec73a943c +oid sha256:f8cc0e94f0025e65821c1a0a10af7236880d849be54a2fba286d432f3b2b7f6e size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index def94e0..9db4b13 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:88ba2f0ba6443ad0cdf08e91a223623645aed10d78b3621eac2effc69e222a70 +oid sha256:119e7810db8f723822efba2abe7f6213fa00c1cd9c6fc1b2ee2ef9823dcaf213 size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index dae4be2..3386707 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:0c8d8a195835fdbd65ec3ff913c2320697f077e027a85b419cea2a7c8413ea16 +oid sha256:7ed1653f806a823b8274a29f522f852ea06d17ede7328d32eb288afd394d0bd5 size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 0a2a46e..4d36706 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:787a7b7654bca39c40bec699fcbba742d793d05e9feba93b8f87625d65fcde81 +oid sha256:3582a8cdab050151c925f9c99eab9d1457847278caa9bc41c64baa26dc650e2a size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 706a021..f7fc04c 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:dce807138c94030a5d4cc266396e085814cc711ccad0b07dfc9b9b2fe79919af +oid sha256:10b872b0957db7437ebcec222b95e7953a3b92eac1f2663bbefa4fea4dd2c3c0 size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 3d65968..dc1059a 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:a6540c5548011edd39fa5f0ec6dc999078e26bd1d9a0973201753e65ab00af0c +oid sha256:ecffd78e161d3d9007f1690f611844dbe19bbbb707b01adcb41cf10cdce44de5 size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 3ae7021..a5e4f28 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:34345a35f495d21523bc616bf3a2c9b9960d15379a62cbc937939e2d44318d63 +oid sha256:f20525517de7866ecb64c62e19f7b9d6ab97ceef5df975e61b0311f59d818155 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index baf270a..8083487 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:0c6e920b91e19f2e2b874cc0ec729fd41b7b12ca41b924a22892affc9a427a84 +oid sha256:0c686f758f4d418f65d863dd58650b0c5c7563380a07d656ced8ecc7b11eecac size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 32af0c9..6407c73 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:416e4dff5640b9427585f3478026923f4ac0522ba9cbb13efda74060ce5e1b62 +oid sha256:b739e5360c0755b767550748521c6180f56d28642e2073511755597e5f7055bc size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index 6a788fe..5d299d3 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f40f0a15184379c9cb21c5492c72965d213268e8d3cb54eaccaa7184839deef2 +oid sha256:091e0b3b7c67dc919e41757ff274cb6a8e78312f24ec3cd75312c9c4017454c6 size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index b5f645c..1e33557 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3168faf332c363547097449bfdf8303cc60055df679a8fa8583ad720ac29c5d1 +oid sha256:b4dbf1b4b02dbf5678f173ba485c6900b47e36d083df8753902ec7c86a7fca72 size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 17acefa..cb2cf57 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c494e2171bde237230d53c04329255a1b743b0a12f21fce933bcf2f1f383e6b +oid sha256:b65b35578be502f97c2bd82b050e8ca934b5d5c16b6ecd289d0be1476d976934 size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index 0235c07..d32d5c3 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccdf7a02d9a6b114a87b49066e7d2008ad488385e8c61ccf77da76264c6b9282 +oid sha256:0c8b5aa5a77df7f7521239bf8ca7f5dae58688cb602c29fec4508cb065d42f83 size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index 515a067..92061ed 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4c5a8b8accc9fdfc150e243ece1f78818299572a0044f5422c8d92258f4a2d0 +oid sha256:5ff05d42dbae68e1cce03f37ebaaa98923a251902cd4d22ebcdbcdf2b746bcfd size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 2342b12..60b817a 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f1093d221fee2ddc27e4e1c13a5bd7522463839a0aef0a08c69ee3f50c93004 +oid sha256:fd369c09254e11f73672ac477b9ca2713360caf55521dfa379fb846545056a94 size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index fb49bda..758ccac 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1de70a932199464f5063b39dd6e7f0a8f7b3a8716618aec03de3d13791ee43f2 +oid sha256:bb17299f06971487075cbfd621efff802af76552195be28183c91246ce3e043c size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index 30caeec..8f03901 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c2e10c2481d8fdabcc9066f4625da610ec86716cee46bee132f3f5c5029cb0a +oid sha256:27aa3427f615b6ffa23fcb1ed5b0b2fc4e1f3637c70abe5dc7ddc781ee866fb7 size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 32fc64e..7983e88 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2840f75c9efdd3adee2809b7d22b60bf647d97f007b74bb24e22be1631c343f +oid sha256:14c74564ba7e3e028a17f3770ade7dea439f5e29deb1d2ad5e96c428881b3533 size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index 3f02c52..7bf9bdb 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c636670338bb7a09163601744344bcdb11977644139091845d46a99a213a4d86 +oid sha256:7f6f9b94a06ebbfeccc0b0c7e811bd2baaa2a7b6250b0ad1e67e53ab8c035a1d size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index 3453719..b8ce48c 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9235b01c445042001658775e998e423ccec1c9f0b953af47fe10687875dba059 +oid sha256:2156846041cb844c6b354ee1ea1a9a95d4ce9f8148c30fc7e56b5ac1f5d81abc size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index c650138..d3dba06 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d289003e1257b50b798c7b2aada7c941cfac5c5c5b50642516bf0b053b6a53a3 +oid sha256:015a8788eef7f38bb9d1a716e3ecf22327c974944a084f60a4fe374ecab8b66e size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index 5c66ea0..dae5c93 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:647a16ad1c1ae619aaabc7a68cbc0aad343a342f09d66858aef5cd7cf3d007eb +oid sha256:6ba9af2d666f01fb37e6810c614c6c27f9d1570e8196a1231396204b7a2c1d77 size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index 5f2872b..8d30403 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1e4880d1840a18cb58bb321dc39488de8f2baf7f4df1894e1e2b7c79a126d4d +oid sha256:1653fffd4f73e3d204947ea056bdd933b83d691f52fd382bec08cf5c511f10a4 size 72413 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 2d5e873..365dc8a 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:85643c28eac876270fe91fff4523c4f9cb2fc79b1915a5dec596e4cf678b3914 +oid sha256:061edd655d9f9f2929660eb6a8715bdb0f8f98b1aebc776f23c0ccd5ad9bf332 size 30542 diff --git a/MEMORY.md b/MEMORY.md index 2cfe396..3fcc7db 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -510,3 +510,5 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - **Edit-mode selection reverts to vertex selection.** Setting `polygon.select` in object mode and then entering edit mode re-derives selection from the vertices, which were all still selected - deleting "the unselected faces" wiped the entire garment. Filter with `bmesh.ops.delete(context="FACES")` instead. - **`ConstructorHelpers::FObjectFinder` outside a constructor is a fatal error**, not a warning: the game dies in `Tick` with "FObjectFinders can't be used outside of constructors". Use `LoadObject(nullptr, Path)` for runtime loads and keep `FObjectFinder` to the constructor. - **A 1 cm solidify shell lets the body poke through.** Garments built by duplicating body faces needed 2 cm of solidify plus an 8 mm fatten along their own normals before the cloth read as continuous at shoulders, hips and knees. +- **Warm skin sampling as blue-grey was lighting, not material.** The body stood in building shadow and was lit almost entirely by the blue `Demo_SkyLight` (intensity 2.6): face (99, 98, 100), arms (67, 74, 81). Raising the sun to pitch -74 moved the face to (131, 121, 117). A deliberate red test colour reaching the render first proved the material and the slot assignment were fine, which is what stopped a long hunt in the wrong place. +- **The Unreal commandlet drops unknown command-line switches.** A `--unlit-test` flag passed after `-script` never reached Python; an environment variable does get through (and even that proved unnecessary once the red test settled the question). diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index c17c847..6b40fde 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:95ff71103919d9a7914b957cae2aa24e13dbfecf52c9ab83de26d5f33d0c015d -size 3088404 +oid sha256:370bf57262d3503bf1497b984a6029a8484aff8fbe9a21cd813651a9989bfcf3 +size 3055230 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 67922f0..e3887d5 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:7d4f49c117b7103cbbc3fae768a981fb72e166643e5e7229c06e9c29f7cc0f24 -size 2619363 +oid sha256:b1cf9b6267603d9fdc555ee6de7588bfa08c256bef7de649ba97872f09b90508 +size 2613575 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png index ff3e5df..66537a2 100644 --- a/Screenshots/demo/review-demo-retail-frontage.png +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37b0229f0c91f3879776734c518279fdc4b5640dc05c9eece38a2ff013ca20ee -size 2522716 +oid sha256:7e3e2f56637d3c3806b413d53fadb56ca651c18539386043c577934348c9f5cf +size 2487967 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index b888d49..b3aa03f 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:4b450cefc55dfdfa0b27fbc9bad98197168f1181bad56cb4b333bf9080251487 -size 2934054 +oid sha256:e149f48ca3d3b227dcc10bc7b2a1bec0c7c2e656f0d04448868739783812cdfd +size 2884922 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index d7be7e2..d9131eb 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:1528751a2b7ec4411905d38394925b1fffbc667242030aefee7daf13daf92e19 -size 2847775 +oid sha256:e09677cce3997dbdd52f77d7be76262961673ecc2f795b0c4ee09866f37adc4d +size 2795082 diff --git a/Screenshots/demo/review-demo-third-person-front.png b/Screenshots/demo/review-demo-third-person-front.png index b412cf5..80782fe 100644 --- a/Screenshots/demo/review-demo-third-person-front.png +++ b/Screenshots/demo/review-demo-third-person-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:363ab2272f2c059fba9d205440e90e05d86f5a86d228c9c03a97088823ac4b4f -size 2788716 +oid sha256:a4949405d583c4bbabcb8b6d00b297a0a5673d1e9c28034a455eced56e024410 +size 2791255 diff --git a/Screenshots/demo/review-demo-third-person-walk.png b/Screenshots/demo/review-demo-third-person-walk.png index 7cde1b2..0652d66 100644 --- a/Screenshots/demo/review-demo-third-person-walk.png +++ b/Screenshots/demo/review-demo-third-person-walk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01d30367e0e051f2a9d5f5784341e0be99df58c27af9dbe742120211d593df97 -size 2173203 +oid sha256:55dd3e2f2507d19ccf12112ca1aad6f600b52aa34ac88c01df311c4651b7d8ed +size 2172564 diff --git a/Screenshots/demo/review-demo-third-person.png b/Screenshots/demo/review-demo-third-person.png index a37b0f4..56b756c 100644 --- a/Screenshots/demo/review-demo-third-person.png +++ b/Screenshots/demo/review-demo-third-person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5f0e86838d39b18d31971d2c64aad5a8b1195cc1aacd0bcddc812d1aab20510 -size 2995172 +oid sha256:26eb754c9df2ad2cc8a3703774b5d59e8f916705a064718a1dd972de2b9903ce +size 2974983 diff --git a/TODO.md b/TODO.md index ec8da99..c9b032f 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,7 @@ Actionable task list for this project. This file is the single, central place fo - **Clothing exists now** (`Tools/character_clothing_build.py` + `Tools/ue_character_clothing_import.py`): tee, jeans and shoes as three separate skinned meshes, built from the body's own faces per the character-artist workflow (duplicate faces, solidify for thickness, one mesh per piece), imported as `CharacterTee` / `CharacterJeans` / `CharacterShoes` and attached as leader-pose components. The navy tee, dark jeans and off-white shoes read correctly at review distance. - **Garment coverage fixed 2026-11-09.** Solidify thickness raised to 2 cm plus a normal fatten pass of 8 mm closed the shoulders, hips and knees; the tee, jeans and shoes now read as continuous cloth. Evaluation against the blueprint improved from **mean ΔE 31.9 → 21.8** and **palette ΔE 25.4 → 19.7**; the montage is `Art/characters/blueprints/match-front.png`. - **Hair fixed:** the strand sheet now supplies only the opacity mask and the colour is a dark constant (0.030/0.020/0.014). Sampled in the render: hair (31, 26, 21) - properly dark instead of the washed-out blond it read as when the sheet's RGB drove base colour. - - **Skin colour: assignment proved, tone unexplained.** A runtime log confirmed the slot holds `M_Character_Skin` (`slot0=M_Character_Skin slots=1`), and a deliberate red test colour reached the render - so binding works. But the intended warm tone (0.78/0.55/0.44) samples as **bluish grey** in the render: arms (67, 74, 81), face (98, 97, 100), while the tee and jeans show their colours correctly. Something between the constant and the final pixel is desaturating the skin - next suspects are the material's shading model/ambient occlusion from the body normals, or the render's key light being far dimmer on the body than on the ground. + - **Skin colour explained 2026-11-09: the character stands in building shadow.** The material, the assignment and the tone were all correct; the body was lit almost entirely by the blue sky light (intensity 2.6), so warm skin sampled as grey-blue. Raising the sun from pitch -62 to -74 lifted the face from (99, 98, 100) to (131, 121, 117) and the arms from (67, 74, 81) to (71, 78, 85) at the same camera. **That is a lighting change affecting every approved shot, so it is on this branch awaiting the owner's approval, not merged.** Original note: A runtime log confirmed the slot holds `M_Character_Skin` (`slot0=M_Character_Skin slots=1`), and a deliberate red test colour reached the render - so binding works. But the intended warm tone (0.78/0.55/0.44) samples as **bluish grey** in the render: arms (67, 74, 81), face (98, 97, 100), while the tee and jeans show their colours correctly. Something between the constant and the final pixel is desaturating the skin - next suspects are the material's shading model/ambient occlusion from the body normals, or the render's key light being far dimmer on the body than on the ground. - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 3e017ba..aae4dc4 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -322,7 +322,7 @@ def build(): # 3. Ambience. A height fog gives the street depth and hides the terrain # edge that otherwise reads as a black band at the horizon. sun = spawn(unreal.DirectionalLight, unreal.Vector(0, 0, 2000), "Demo_Sun", - unreal.Rotator(pitch=-62.0, yaw=120.0, roll=0.0)) + unreal.Rotator(pitch=-74.0, yaw=120.0, roll=0.0)) if sun: comp = sun.get_component_by_class(unreal.DirectionalLightComponent) if comp: @@ -341,7 +341,7 @@ def build(): comp.set_mobility(unreal.ComponentMobility.MOVABLE) except Exception as exc: # noqa: BLE001 log(f"sun mobility not set: {exc!r}") - log("staged: Demo_Sun (pitch -62, yaw 120, intensity 4, warm; higher sun so both frontages read)") + log("staged: Demo_Sun (pitch -74, yaw 120, intensity 4, warm; higher sun lifts the north sidewalk out of building shadow)") sky = spawn(unreal.SkyLight, unreal.Vector(0, 0, 2000), "Demo_SkyLight") if sky: diff --git a/Tools/ue_character_materials.py b/Tools/ue_character_materials.py index c4419bc..218f36f 100644 --- a/Tools/ue_character_materials.py +++ b/Tools/ue_character_materials.py @@ -71,7 +71,7 @@ def new_material(name): return material -def skin_material(diffuse, detail): +def skin_material(diffuse, detail, unlit_test=False): material = new_material("M_Character_Skin") lib = unreal.MaterialEditingLibrary @@ -84,13 +84,20 @@ def skin_material(diffuse, detail): # was always working - the skin simply reads dark under the demo's key light. This # tone is lifted to sit in shadow the way the blueprint's skin does. base.set_editor_property("constant", unreal.LinearColor(0.78, 0.55, 0.44, 1.0)) - lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR) + if unlit_test: + # Diagnostic: unlit renders the constant exactly, so the render tells us whether + # a wrong skin tone comes from lighting/shading or from the material itself. + material.set_editor_property("shading_model", unreal.MaterialShadingModel.MSM_UNLIT) + lib.connect_material_property(base, "", unreal.MaterialProperty.MP_EMISSIVE_COLOR) + else: + lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR) rough = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -200, -200) rough.set_editor_property("r", 0.58) lib.connect_material_property(rough, "", unreal.MaterialProperty.MP_ROUGHNESS) lib.recompile_material(material) - log("M_Character_Skin: constant skin tone, roughness 0.58 " + log(("M_Character_Skin: UNLIT TEST, emissive " if unlit_test else + "M_Character_Skin: constant skin tone, roughness 0.58 ") "(baked diffuse not used - it samples black on this mesh)") return material @@ -118,14 +125,14 @@ def hair_material(strands): return material -def main(): +def main(unlit_test=False): diffuse = unreal.EditorAssetLibrary.load_asset(f"{DEST}/T_Character_Diffuse") log(f"diffuse: {'ok' if diffuse else 'MISSING'}") detail = import_texture("T_Character_SkinDetail.png") strands = import_texture("T_Character_Hair.png") if diffuse and detail: - skin_material(diffuse, detail) + skin_material(diffuse, detail, unlit_test=unlit_test) if strands: hair = hair_material(strands) @@ -144,7 +151,9 @@ def main(): try: - main() + # Environment, not a command-line flag: the commandlet drops unknown switches, so + # the flag never reached Python. An env var is inherited. + main(unlit_test=os.environ.get("GIGLINE_UNLIT_TEST") == "1") except Exception as exc: # noqa: BLE001 import traceback log(f"FAILED: {type(exc).__name__}: {exc}") -- 2.47.3 From 5de4183456e1cd8e9213b924eedd0977f610ed71 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 14:23:47 +0100 Subject: [PATCH 08/11] docs(review): shots 6, 7 and 8 reflect the clothed, haired character The appearance changed in 0.2.2 and the README still described an unclothed body with splayed fingers, which is a defect in its own right: a shot whose README section is stale is not a review. Shots 6, 7 and 8 now describe the clothing, the dark card hair and the raised sun, and their approvals are reset to pending because the appearance changed. 8/8 shots verify. --- Screenshots/README.md | 16 ++++++++++------ TODO.md | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Screenshots/README.md b/Screenshots/README.md index f4b77fa..98126bd 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -23,7 +23,7 @@ Every shot is embedded below, in order, so the whole review happens in this file | 3 | [Crossing](#3-crossing) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 4 | [North buildings](#4-north-buildings) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 5 | [Retail frontage](#5-retail-frontage) | ☑ 2026-11-09 | ☑ 2026-11-09 | -| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☐ pending | **Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show. @@ -107,11 +107,13 @@ What this shot is evidence for: - The pawn carries a visible skeletal body with the warm skin material, and a spring-arm camera with collision testing. The **V** toggle swaps first and third person; the shipped default stays first person and the review passes `-thirdperson`. - **The idle clip drives the body and the arms hang at the sides**, with the hands at the hips and the feet flat. Earlier captures of this shot showed a bind A-pose. -- **Hair is present** as a short card system hugging the skull (three overlapping rows), not the spiked crown of the first attempt. +- **Hair is present** as a dark card system hugging the skull (three overlapping rows), not the spiked crown of the first attempt. The strand sheet supplies only the opacity mask; the colour is a dark constant, because using the sheet's pale RGB as base colour made the cut read blond and thin. +- **The body is clothed** since `0.2.2`: navy tee, dark jeans and off-white shoes as three separate skinned meshes, attached on leader-pose components. + +Changed 2026-11-09 by `0.2.2` (clothing, hair, raised sun), so **approval is reset to pending**. Still open, tracked in `TODO.md`: -- The fingers splay slightly, which is the bind's finger spread carried into the pose. That is the `0.2.2` pose-and-skin tuning pass, not this rig. - **Camera collision is implemented but unproven:** the spawn has open space behind it, so no shot exercises the arm shortening. --- @@ -122,12 +124,14 @@ Still open, tracked in `TODO.md`: - **File:** `demo/review-demo-third-person-front.png` - **View:** from the road, ~6 m in front of the pawn, looking back north. The pawn spawns facing +Y, so this is its front -- **Expected content:** both arms hanging at the sides with the hands by the hips, face and chest visible +- **Expected content:** the clothed character from the front — navy tee, dark jeans, off-white shoes, dark hair — arms hanging at the sides with the hands by the hips - **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09 **Why the shot exists:** the third-person shot only ever shows the pawn's back, so a pose defect on the front — arms out, hands splayed, a face pointing the wrong way — was invisible in review. This angle is the check for it. -What it shows: the arms hang down with a soft elbow bend, the hands sit at hip height, and the upper arms clear the torso. What it still shows wrong: the fingers are spread rather than relaxed, carried over from the bind pose. +What it shows: the arms hang down with a soft elbow bend, the hands sit at hip height with the fingers curled, the upper arms clear the torso, and the character wears the navy tee, dark jeans and off-white shoes built in `0.2.2`. Hair is a dark card system hugging the skull. + +**Changed 2026-11-09 by `0.2.2`:** clothing (tee, jeans, shoes as separate skinned meshes), dark hair cards, and a raised sun. The raised sun matters here: the character used to stand in building shadow lit by the blue sky light, which made warm skin sample as blue-grey — face (99, 98, 100) before, (131, 121, 117) after. **Approval is reset to pending because the appearance changed.** --- @@ -139,7 +143,7 @@ What it shows: the arms hang down with a soft elbow bend, the hands sit at hip h - **File:** `demo/review-demo-third-person-walk.png` - **View:** tracking camera, 4.2 m to the pawn's right, captured while the pawn walks south -- **Expected content:** the body mid-stride in profile — legs apart, arms counter-swinging +- **Expected content:** the clothed body mid-stride in profile — legs apart, arms counter-swinging, navy tee and jeans - **Verified:** ☑ 2026-11-09 · **Approved:** ☐ pending **Why the earlier attempts failed, twice:** the first was a harness problem (a fixed camera cannot catch a pawn at 4.5 m/s, and the call that enables walking ran before the pawn existed). The second was the clip itself: the walk cycle was authored on a **sine**, which is zero at both the start and the middle of the cycle, so the capture kept landing on the two frames where the legs pass together and the image read as a still. The clip now starts at maximum stride (cosine), so any frame near the start shows the stride. The cycle length, amplitudes and loop are unchanged. diff --git a/TODO.md b/TODO.md index c9b032f..f655a2c 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,7 @@ Actionable task list for this project. This file is the single, central place fo - [x] Restore the stride capture after the pose rewrite. The walk cycle started on a **sine**, zero at both the start and the middle of the cycle, so the deterministic capture kept landing on the two passing frames where the legs are together. Starting the cycle at maximum stride (cosine) fixed it with no other change; `demo-third-person-walk` is back as shot 8 and shows a genuine stride. -- [~] Tune skin shading and hair materials on the player character. **Blueprints and evaluation tooling in place 2026-11-09, hair blocked on an engine-side limit:** +- [A] Tune skin shading and hair materials on the player character. **Blueprints and evaluation tooling in place 2026-11-09, hair blocked on an engine-side limit:** - `Tools/comfyui_generate.py` drives the local ComfyUI instance (`127.0.0.1:8188`) for textures, writing a JSON sidecar with the model, prompt, seed and full workflow so anything generated can be regenerated or rejected. Installed models include `juggernautXL_ragnarok.safetensors` and `sd-v1-5-inpainting.ckpt`. - **Stable Diffusion is not usable for flat skin swatches.** Three attempts on two models produced an anatomical close-up twice and a leather-scale surface once; SDXL has no reliable notion of "texture without anatomy". Skin detail is therefore generated procedurally instead (`Tools/generate_character_textures.py`: tileable octave noise for pores plus a strand-sheet texture for hair). - **Hair is now its own asset** (`Tools/character_hair_build.py` → `SM_Char_Hair` → `/Game/Test/Character/CharacterHair`), which is what the `character-artist` skill asks for: cards are a separate mesh, not geometry joined into the body. It imports with its own material slot and is attached in C++ as a second `USkeletalMeshComponent` on `SetLeaderPoseComponent`, so it follows the head bone without a second animation. 321 verts, 192 quads, 404 tris. -- 2.47.3 From 2f6d06f897e28a86d5c3701875a1a00e77fbfffe Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 14:24:02 +0100 Subject: [PATCH 09/11] docs(review): restore the status rows for shots 7 and 8 The previous documentation commit dropped both rows from the status table while editing the section text - the table then listed six shots against eight embeds. Restored, both marked verified and pending approval. --- Screenshots/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Screenshots/README.md b/Screenshots/README.md index 98126bd..745f6fa 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -24,6 +24,8 @@ Every shot is embedded below, in order, so the whole review happens in this file | 4 | [North buildings](#4-north-buildings) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 5 | [Retail frontage](#5-retail-frontage) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☐ pending | +| 7 | [Third-person front](#7-third-person-front) | ☑ 2026-11-09 | ☐ pending | +| 8 | [Third-person walk](#8-third-person-walk) | ☑ 2026-11-09 | ☐ pending | **Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show. -- 2.47.3 From 0e7a6e898b7e45a5850f3ec03852f5eb91fc1ac4 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 14:25:35 +0100 Subject: [PATCH 10/11] =?UTF-8?q?docs(review):=20owner=20approved=20shots?= =?UTF-8?q?=206,=207=20and=208=20=E2=80=94=20all=20eight=20shots=20approve?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ticks both the shot sections and their status rows, so the table and the sections agree: 5 previously approved, these 3 approved 2026-11-09 in their clothed, haired, relit state. --- Screenshots/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Screenshots/README.md b/Screenshots/README.md index 745f6fa..598453f 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -23,9 +23,9 @@ Every shot is embedded below, in order, so the whole review happens in this file | 3 | [Crossing](#3-crossing) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 4 | [North buildings](#4-north-buildings) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 5 | [Retail frontage](#5-retail-frontage) | ☑ 2026-11-09 | ☑ 2026-11-09 | -| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☐ pending | -| 7 | [Third-person front](#7-third-person-front) | ☑ 2026-11-09 | ☐ pending | -| 8 | [Third-person walk](#8-third-person-walk) | ☑ 2026-11-09 | ☐ pending | +| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 7 | [Third-person front](#7-third-person-front) | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 8 | [Third-person walk](#8-third-person-walk) | ☑ 2026-11-09 | ☑ 2026-11-09 | **Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show. @@ -112,7 +112,7 @@ What this shot is evidence for: - **Hair is present** as a dark card system hugging the skull (three overlapping rows), not the spiked crown of the first attempt. The strand sheet supplies only the opacity mask; the colour is a dark constant, because using the sheet's pale RGB as base colour made the cut read blond and thin. - **The body is clothed** since `0.2.2`: navy tee, dark jeans and off-white shoes as three separate skinned meshes, attached on leader-pose components. -Changed 2026-11-09 by `0.2.2` (clothing, hair, raised sun), so **approval is reset to pending**. +Changed 2026-11-09 by `0.2.2` (clothing, hair, raised sun); **approved 2026-11-09** in that state. Still open, tracked in `TODO.md`: @@ -133,7 +133,7 @@ Still open, tracked in `TODO.md`: What it shows: the arms hang down with a soft elbow bend, the hands sit at hip height with the fingers curled, the upper arms clear the torso, and the character wears the navy tee, dark jeans and off-white shoes built in `0.2.2`. Hair is a dark card system hugging the skull. -**Changed 2026-11-09 by `0.2.2`:** clothing (tee, jeans, shoes as separate skinned meshes), dark hair cards, and a raised sun. The raised sun matters here: the character used to stand in building shadow lit by the blue sky light, which made warm skin sample as blue-grey — face (99, 98, 100) before, (131, 121, 117) after. **Approval is reset to pending because the appearance changed.** +**Changed 2026-11-09 by `0.2.2`:** clothing (tee, jeans, shoes as separate skinned meshes), dark hair cards, and a raised sun. The raised sun matters here: the character used to stand in building shadow lit by the blue sky light, which made warm skin sample as blue-grey — face (99, 98, 100) before, (131, 121, 117) after. **Approved 2026-11-09 after the change.** --- @@ -146,7 +146,7 @@ What it shows: the arms hang down with a soft elbow bend, the hands sit at hip h - **File:** `demo/review-demo-third-person-walk.png` - **View:** tracking camera, 4.2 m to the pawn's right, captured while the pawn walks south - **Expected content:** the clothed body mid-stride in profile — legs apart, arms counter-swinging, navy tee and jeans -- **Verified:** ☑ 2026-11-09 · **Approved:** ☐ pending +- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09 **Why the earlier attempts failed, twice:** the first was a harness problem (a fixed camera cannot catch a pawn at 4.5 m/s, and the call that enables walking ran before the pawn existed). The second was the clip itself: the walk cycle was authored on a **sine**, which is zero at both the start and the middle of the cycle, so the capture kept landing on the two frames where the legs pass together and the image read as a still. The clip now starts at maximum stride (cosine), so any frame near the start shows the stride. The cycle length, amplitudes and loop are unchanged. -- 2.47.3 From 6c4e0701fb1859a8525558ac57611aea33825351 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 14:26:13 +0100 Subject: [PATCH 11/11] =?UTF-8?q?release:=20v0.2.2=20=E2=80=94=20the=20cha?= =?UTF-8?q?racter=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writes the 0.2.2 entry and prunes the section from TODO.md. The entry records that this ships as a patch by the owner's decision even though it adds asset categories, which the policy otherwise puts in a minor release - so the numbering is not mistaken for scope. Contents: clothing as three separate skinned meshes, hair cards oriented from the scalp normal, ComfyUI character blueprints and the render-vs-blueprint comparison, procedural textures, materials built from scratch, the raised sun, and the walk cycle starting at maximum stride. Verified: 8/8 shots, all approved; mean deltaE 31.9 -> 21.8 against the blueprint; 95 tool tests. Known limitations listed rather than omitted - garments are shells over body faces, the baked diffuse cannot be used because the mesh UVs do not map to it, and the character has no face detail. --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ ROADMAP.md | 2 +- TODO.md | 22 ---------------------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45572db..2e4c9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,47 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [ Nothing yet. +## [0.2.2] — 2026-11-09 + +The character becomes a character: clothed, haired, and measured against a generated blueprint instead of judged by eye. + +**Version note:** this ships as a patch by the owner's decision even though it adds asset categories, which the release policy otherwise puts in a minor release. Recorded here so the numbering is not mistaken for scope later. + +### Added + +- **Clothing** — navy tee, dark jeans and off-white shoes as three separate skinned meshes (`Tools/character_clothing_build.py`), built from the body's own faces per the `character-artist` workflow: duplicate faces, solidify for cloth thickness, one mesh per piece. Each imports with its own single material slot and is attached as a leader-pose component. +- **Hair cards** (`Tools/character_hair_build.py`) — a scalp cap plus 26 cards in three overlapping rows, quads only, every vertex weighted to `head`. Cards are oriented from the scalp normal at their anchor, so the cut follows the skull instead of standing out as spikes. +- **Character blueprints** (`Tools/comfyui_character_blueprint.py`) — front, side, back and head views generated by the local ComfyUI instance from one description, with fixed seeds and a JSON record of prompts and prompt ids. +- **Blueprint comparison** (`Tools/character_match.py`) — figure aspect, fill and coverage deltas, mean CIE76 and dominant-palette distance, plus a side-by-side montage. The deltas stay separate on purpose: one similarity number between a photoreal sheet and a low-detail body would mean nothing. +- **ComfyUI driver** (`Tools/comfyui_generate.py`) and **procedural textures** (`Tools/generate_character_textures.py`), each writing a provenance sidecar. +- **Material builder** (`Tools/ue_character_materials.py`) — `M_Character_Skin` and `M_Character_Hair_Mat` built from scratch, because `unreal.MaterialEditingLibrary` segfaults in a headless commandlet when it rebuilds an existing graph. + +### Changed + +- **The sun was raised** from pitch -62 to -74. The character stood in building shadow lit almost entirely by the blue sky light, so warm skin sampled as blue-grey — face (99, 98, 100) before, (131, 121, 117) after at the same camera. +- **The walk cycle starts at maximum stride**, so the deterministic stride capture lands on the stride rather than on one of the two frames where the legs pass together. +- Cloth shells are fattened 8 mm along their own normals after solidify; 1 cm of solidify let the body through at shoulders, hips and knees. + +### Fixed + +- **Hair read blond and thin** because the strand sheet's pale RGB drove base colour; the sheet now supplies only the opacity mask, and the colour is a dark constant. +- **Garment coverage holes** at shoulders, hips and knees, closed by 2 cm solidify plus the normal fatten. +- **A crash of my own making:** binding materials in `Tick` with `ConstructorHelpers::FObjectFinder` is fatal outside a constructor. Moved to `BeginPlay` with `LoadObject`. +- **Shots 6, 7 and 8 were documented as an unclothed body** after the appearance changed; the README now matches the render and all eight shots are approved. + +### Verified + +- 8 review shots verify; **all eight approved** by the owner 2026-11-09. +- Blueprint comparison improved from mean ΔE **31.9 → 21.8** and palette ΔE **25.4 → 19.7** once the character was clothed. +- 95 tool tests pass (13 new for the colour maths, masks and palettes). + +### Known limitations + +- **The garments are still shells over body faces**, not simulated cloth: no wrinkles, no drape, and no separation from the body in motion. +- **`T_Character_Diffuse` cannot be used.** Sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake; skin is a constant tone and the generated pore sheet is unwired. +- **The character has no face detail** — the eyes, mouth and brows are mesh and lighting only, and read as a mannequin at close range. +- **Camera collision remains implemented but unexercised.** + ## [0.2.1] — 2026-11-09 Rig fixes. Two defects from the `0.2.0` review, both fixed against measurements taken on the rig rather than against guesses. diff --git a/ROADMAP.md b/ROADMAP.md index 2130f52..7465ec9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,7 +8,7 @@ Development roadmap for this project: milestone goals, scope, decisions, and rel ## Status at a glance — 2026-11-08 15:30 -Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), **`v0.1.6` released 2026-11-09**: retail frontage for 6800 Lee Drug and 6806–6820 modelled from their own photograph; 6822 Budget deferred (no reference) and the lettering/parapet detail moved to `0.9.0`. **`v0.2.0` released 2026-11-09**: visible animated body, third-person camera with a V toggle, idle and walk clips, and a deterministic stride capture. Two cosmetic character defects (splayed fingers, a swept-not-derived base rotation) are deferred to `0.2.2`. **Next:** `0.3.0` (Chaos vehicle, enter/exit, closed driving route). +Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), **`v0.1.6` released 2026-11-09**: retail frontage for 6800 Lee Drug and 6806–6820 modelled from their own photograph; 6822 Budget deferred (no reference) and the lettering/parapet detail moved to `0.9.0`. **`v0.2.0`–`v0.2.2` released 2026-11-09**: visible animated body, third-person camera with a V toggle, idle and walk clips, a deterministic stride capture, and then the character pass — clothing (tee, jeans, shoes as separate skinned meshes), dark hair cards, generated blueprints and a render-vs-blueprint comparison (mean ΔE 31.9 → 21.8). **Next:** `0.3.0` (Chaos vehicle, enter/exit, closed driving route). | Area | State | | --- | --- | diff --git a/TODO.md b/TODO.md index f655a2c..e3cc9a1 100644 --- a/TODO.md +++ b/TODO.md @@ -9,28 +9,6 @@ Actionable task list for this project. This file is the single, central place fo - When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file. - Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close. -## 0.2.2 — Skin and hair tuning (patch) - -### Patch tasks - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `anim-body-0.2.0`) - -- [x] Restore the stride capture after the pose rewrite. The walk cycle started on a **sine**, zero at both the start and the middle of the cycle, so the deterministic capture kept landing on the two passing frames where the legs are together. Starting the cycle at maximum stride (cosine) fixed it with no other change; `demo-third-person-walk` is back as shot 8 and shows a genuine stride. - -- [A] Tune skin shading and hair materials on the player character. **Blueprints and evaluation tooling in place 2026-11-09, hair blocked on an engine-side limit:** - - `Tools/comfyui_generate.py` drives the local ComfyUI instance (`127.0.0.1:8188`) for textures, writing a JSON sidecar with the model, prompt, seed and full workflow so anything generated can be regenerated or rejected. Installed models include `juggernautXL_ragnarok.safetensors` and `sd-v1-5-inpainting.ckpt`. - - **Stable Diffusion is not usable for flat skin swatches.** Three attempts on two models produced an anatomical close-up twice and a leather-scale surface once; SDXL has no reliable notion of "texture without anatomy". Skin detail is therefore generated procedurally instead (`Tools/generate_character_textures.py`: tileable octave noise for pores plus a strand-sheet texture for hair). - - **Hair is now its own asset** (`Tools/character_hair_build.py` → `SM_Char_Hair` → `/Game/Test/Character/CharacterHair`), which is what the `character-artist` skill asks for: cards are a separate mesh, not geometry joined into the body. It imports with its own material slot and is attached in C++ as a second `USkeletalMeshComponent` on `SetLeaderPoseComponent`, so it follows the head bone without a second animation. 321 verts, 192 quads, 404 tris. - - **The card layout now hugs the skull.** Each card is oriented by the scalp normal at its anchor — the length runs along the downward tangent, the width across it, and a small lift at the tip gives the cut volume. Three overlapping rows (7/9/10 cards, 26 total, 468 tris) replaced the single outward-pointing ring that read as a spiked crown. Overlap is what stops cards reading as separate spikes. - - **Skin still needs work.** `M_Character_Skin` (constant tone 0.62/0.42/0.33, roughness 0.58) is built and the pawn calls `SetMaterial(0, ...)`, but the body still renders neutral grey in review shots, so the assignment is not taking. The baked `T_Character_Diffuse` cannot be used either: sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake. Next: verify the slot index after `SetSkeletalMesh`, or assign in `BeginPlay` instead of the constructor. - - **Character blueprint and evaluation tools exist.** `Tools/comfyui_character_blueprint.py` generates a four-view sheet (front/side/back/head) from one description with fixed seeds; `Tools/character_match.py` compares a render against it and reports aspect, fill, coverage, mean CIE76 and palette distance plus a side-by-side montage. First result against `Screenshots/demo/review-demo-third-person-front.png`: **aspect delta 1.24, fill delta 0.12, mean ΔE 31.9, palette ΔE 25.4** — and the montage shows why: the model is a **nude body**, the blueprint is clothed (navy tee, jeans, sneakers). - - **Clothing exists now** (`Tools/character_clothing_build.py` + `Tools/ue_character_clothing_import.py`): tee, jeans and shoes as three separate skinned meshes, built from the body's own faces per the character-artist workflow (duplicate faces, solidify for thickness, one mesh per piece), imported as `CharacterTee` / `CharacterJeans` / `CharacterShoes` and attached as leader-pose components. The navy tee, dark jeans and off-white shoes read correctly at review distance. - - **Garment coverage fixed 2026-11-09.** Solidify thickness raised to 2 cm plus a normal fatten pass of 8 mm closed the shoulders, hips and knees; the tee, jeans and shoes now read as continuous cloth. Evaluation against the blueprint improved from **mean ΔE 31.9 → 21.8** and **palette ΔE 25.4 → 19.7**; the montage is `Art/characters/blueprints/match-front.png`. - - **Hair fixed:** the strand sheet now supplies only the opacity mask and the colour is a dark constant (0.030/0.020/0.014). Sampled in the render: hair (31, 26, 21) - properly dark instead of the washed-out blond it read as when the sheet's RGB drove base colour. - - **Skin colour explained 2026-11-09: the character stands in building shadow.** The material, the assignment and the tone were all correct; the body was lit almost entirely by the blue sky light (intensity 2.6), so warm skin sampled as grey-blue. Raising the sun from pitch -62 to -74 lifted the face from (99, 98, 100) to (131, 121, 117) and the arms from (67, 74, 81) to (71, 78, 85) at the same camera. **That is a lighting change affecting every approved shot, so it is on this branch awaiting the owner's approval, not merged.** Original note: A runtime log confirmed the slot holds `M_Character_Skin` (`slot0=M_Character_Skin slots=1`), and a deliberate red test colour reached the render - so binding works. But the intended warm tone (0.78/0.55/0.44) samples as **bluish grey** in the render: arms (67, 74, 81), face (98, 97, 100), while the tee and jeans show their colours correctly. Something between the constant and the final pixel is desaturating the skin - next suspects are the material's shading model/ambient occlusion from the body normals, or the render's key light being far dimmer on the body than on the ground. - - **Figure segmentation is explicit, not automatic.** Auto-detection failed on the review shots: the backdrop (sky band, pale wall, dark pavement) is larger and more saturated than the subject, so every size-based rule picked a backdrop blob. `--render-box` takes the figure box as fractions of the frame. A background plate rendered without the character would make this automatic. - - `Tools/ue_character_materials.py` (wiring the generated textures into the skin and hair materials) **segfaults in the headless commandlet** when it edits the material graph. Not wired; the hair keeps the dark base colour it carries from Blender. Needs either an editor-session run or a material asset built from scratch instead of edited in place. - -- [ ] Tag `v0.2.2` and write `CHANGELOG.md` entry; then remove this section. - ## 0.2.3 — Camera clipping fixes (patch) ### Patch tasks -- 2.47.3