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.
This commit is contained in:
zwitschi committed 2026-11-09 13:41:39 +01:00
1 parent 85a8437cbc
commit 86e9c5b693
50 files changed
+408 -46

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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<USkeletalMeshComponent>(
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<USkeletalMesh>(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<USpringArmComponent>(TEXT("ThirdPersonArm"));
@@ -826,6 +850,13 @@ void AGigLineCharacter::ToggleCamera()
{
HairMesh->SetHiddenInGame(!bThirdPerson);
}
for (USkeletalMeshComponent* Piece : Clothing)
{
if (Piece)
{
Piece->SetHiddenInGame(!bThirdPerson);
}
}
if (bThirdPerson)
{
UpdateBodyAnimation();
@@ -166,6 +166,16 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body")
TObjectPtr<USkeletalMeshComponent> 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<TObjectPtr<USkeletalMeshComponent>> Clothing;
UPROPERTY()
TObjectPtr<UAnimSequence> IdleAnimation;