O7e : ajustement de la coiffure sur le haut du crane (second essai)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1d0887f694
commit
6b41904fe4
4 changed files with 73 additions and 23 deletions
|
|
@ -592,10 +592,10 @@ aucune pondération ; matériaux sans texture reliée. Blender absent de la mach
|
|||
|
||||
1. import FBX en maillages statiques séparés (Python, `AssetImportTask`) dans un dossier de travail ;
|
||||
2. nouvelle commandlet `LPHairFromStaticMesh` (`Source/LifePlayEditorTools`) :
|
||||
- mesure la tête de Bridget (`bridget_gp`, 6 691 sommets rattachés à `head` ou à ses descendants : 18,1 × 22,5 cm)
|
||||
et la tête de référence (24,4 × 20,7 cm) ;
|
||||
- place la coiffure : échelle par axe (largeur 0,740, profondeur 1,088, hauteur 0,914), centre et sommet du crâne
|
||||
alignés ; option `-Uniform`, retouches `-Scale`, `-OffsetZ`, `-OffsetY` ;
|
||||
- mesure la tête de Bridget (`bridget_gp`, 6 691 sommets rattachés à `head` ou à ses descendants) et la tête de
|
||||
référence, sur le haut du crâne (voir second essai) ;
|
||||
- place la coiffure : échelle par axe, centre et sommet du crâne alignés, marge `-Margin` ; options `-Uniform`,
|
||||
`-Cranium`, retouches `-Scale`, `-OffsetZ`, `-OffsetY` ;
|
||||
- vérifie la pose de référence de l'os `head` : identique entre le corps et le squelette cible (aucune correction) ;
|
||||
- convertit (`FStaticToSkeletalMeshConverter` du moteur) sur une copie du squelette de `FloraHair_New`
|
||||
(23 os Genesis 8), tous les sommets liés à `head` ; essai à blanc `-DryRun` ;
|
||||
|
|
@ -610,6 +610,14 @@ aucune pondération ; matériaux sans texture reliée. Blender absent de la mach
|
|||
Assets : `Content/Heavy/New5/Hairs/Pocolov17` (+ `_Skeleton`, `_Cap`, `_Strands`, 3 textures ; ≈ 1,7 Mo). Build
|
||||
`LifePlay_Build_5.5_O7e` : `Pocolov17 ajoutee (index 126, F, module lp_Revival)`.
|
||||
|
||||
**Second essai** (premier essai en jeu : coiffure trop étroite, calotte enfoncée dans le crâne, crâne visible ; mèches
|
||||
en cartes noires opaques) : les boîtes complètes des têtes incluent oreilles, nez et cou ; celles de la tête Pocolov
|
||||
sont très larges (24,4 cm) alors que son crâne est plus petit que celui de Bridget. L'ajustement se fait désormais sur
|
||||
le **haut du crâne** (sommets à moins de 0,3 × profondeur sous le sommet ; Bridget 15,2 × 19,6 cm, Pocolov
|
||||
13,4 × 16,1 cm) avec une marge de 5 % : échelle 1,194 / 1,276 / 1,235 au lieu de 0,740 / 1,088 / 0,914 ; largeur
|
||||
finale 29 cm (Flora : 31 cm). `Thicken` des mèches ramené de 10 à 1 (le masque Pocolov est large, 10 le saturait).
|
||||
Premier essai sauvegardé dans `O7e\essai1`.
|
||||
|
||||
**Limites connues** : pas de morphs de forme (la coiffure ne suit pas les morphs du visage) ; rigide sur la tête
|
||||
(pas de mouvement des mèches) ; ajustement automatique à contrôler visuellement.
|
||||
|
||||
|
|
|
|||
|
|
@ -930,3 +930,4 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
|
|||
| CHG-083 | 2026-10-02 | O7e | `11_Audit_Optimisation.md` §16.6 | étude en lecture seule de l'intégration d'une coiffure Fab (demande utilisateur) : liste des coiffures = valeur par défaut `GI.HairstyleTable` (126 entrées, 35 absentes déjà en 4.27), suivi par noms d'os Genesis 8, morphs de forme facultatifs, sauvegardes par nom ; 34 coiffures Fab, ≈ 12 maillages utilisables | — (documentation) |
|
||||
| CHG-084 | 2026-10-02 | H1 | nouveaux `Source/LifePlay/LPHairstyleSubsystem.h/.cpp`, module `Content/Modules/lp_Revival` (`lp_Revival.lpmod`, `Hairstyles/h1_testhair.lphair`) ; asset de test `Content/Heavy/New5/Hairs/H1_TestHair` (copie de `FloraHair_New`, hors dépôt) ; `11_Audit_Optimisation.md` §16.7 | liste des coiffures ouverte aux mods (demande utilisateur) : fichiers `.lphair` ajoutés en fin de `GI.HairstyleTable` sans modifier de Blueprint ; vérifié au journal : doublons refusés, 1 coiffure ajoutée (table 126 → 127) ; build `LifePlay_Build_5.5_H1` ; validé en jeu le 2026-10-02 (nouvelle partie et liste des villes, coiffure ajoutée en fin de liste, suivi des animations, module désactivé puis réactivé, Continue) | supprimer le module `lp_Revival` (ou le désactiver) ; `git checkout H1-avant -- Source` |
|
||||
| CHG-085 | 2026-10-02 | O7e | nouvelle commandlet `Source/LifePlayEditorTools/LPHairFromStaticMeshCommandlet.h/.cpp` (+ `SkeletalMeshUtilitiesCommon`, `StaticMeshDescription` dans `LifePlayEditorTools.Build.cs`) ; assets `Content/Heavy/New5/Hairs/Pocolov17*` (hors dépôt) ; `Content/Modules/lp_Revival/Hairstyles/pocolov17.lphair` (remplace `h1_testhair.lphair`, `H1_TestHair` retiré) ; `Licenses/Free_3D_Models_Credits.txt` ; `11_Audit_Optimisation.md` §16.8 | première coiffure Fab (Pocolov Hair 17, CC BY 4.0, téléchargée avec l'accord de l'utilisateur) : maillage statique rattaché au squelette Genesis 8, ajusté à la tête de Bridget, matériaux `DazHair` ; build `LifePlay_Build_5.5_O7e` : coiffure ajoutée (index 126) ; test en jeu en attente | retirer `pocolov17.lphair` ; sauvegardes dans `D:\Game\LifePlay_Backup_5.5\O7e` ; `git checkout O7e-avant -- Source Content/Modules Licenses` |
|
||||
| CHG-086 | 2026-10-02 | O7e | `LPHairFromStaticMeshCommandlet` : ajustement sur le haut du crâne (`-Cranium`, `-Margin`) ; `Pocolov17` et `_Skeleton` régénérés (premier essai dans `O7e\essai1`), `Pocolov17_Strands` : `Thicken` 10 → 1 ; `11_Audit_Optimisation.md` §16.8 | premier essai en jeu (capture utilisateur) : coiffure trop étroite enfoncée dans le crâne, mèches opaques ; échelle 0,74 → 1,19 en largeur ; build `LifePlay_Build_5.5_O7e` reconstruit ; test en jeu en attente | `Tools`/assets : remettre `O7e\essai1` ; `git checkout O7e-apres -- Source/LifePlayEditorTools` |
|
||||
|
|
|
|||
|
|
@ -30,11 +30,10 @@ namespace
|
|||
return LoadObject<T>(nullptr, *Full);
|
||||
}
|
||||
|
||||
/** Boite des sommets du corps rattaches (influence dominante) a l'os HeadIndex ou a un de ses descendants. */
|
||||
FBox HeadBox(const USkeletalMesh* Body, int32 HeadIndex, int32& OutCount)
|
||||
/** Sommets du corps rattaches (influence dominante) a l'os HeadIndex ou a un de ses descendants. */
|
||||
TArray<FVector> HeadPoints(const USkeletalMesh* Body, int32 HeadIndex)
|
||||
{
|
||||
FBox Box(ForceInit);
|
||||
OutCount = 0;
|
||||
TArray<FVector> Points;
|
||||
const FReferenceSkeleton& Ref = Body->GetRefSkeleton();
|
||||
auto IsUnderHead = [&](int32 Bone)
|
||||
{
|
||||
|
|
@ -63,11 +62,42 @@ namespace
|
|||
const int32 Bone = Section.BoneMap.IsValidIndex(V.InfluenceBones[Best]) ? Section.BoneMap[V.InfluenceBones[Best]] : INDEX_NONE;
|
||||
if (IsUnderHead(Bone))
|
||||
{
|
||||
Box += FVector(V.Position);
|
||||
++OutCount;
|
||||
Points.Add(FVector(V.Position));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Points;
|
||||
}
|
||||
|
||||
/** Sommets d'un maillage statique (donnees sources, LOD 0). */
|
||||
TArray<FVector> StaticPoints(UStaticMesh* Mesh)
|
||||
{
|
||||
TArray<FVector> Points;
|
||||
if (const FMeshDescription* Desc = Mesh->GetMeshDescription(0))
|
||||
{
|
||||
TVertexAttributesConstRef<FVector3f> Positions = FStaticMeshConstAttributes(*Desc).GetVertexPositions();
|
||||
for (const FVertexID Vertex : Desc->Vertices().GetElementIDs())
|
||||
{
|
||||
Points.Add(FVector(Positions[Vertex]));
|
||||
}
|
||||
}
|
||||
return Points;
|
||||
}
|
||||
|
||||
/** Boite du haut du crane : sommets situes a moins de DepthFraction x profondeur de la tete sous le sommet. Exclut
|
||||
* oreilles, nez et cou, dont les proportions varient d'une tete de reference a l'autre. */
|
||||
FBox CraniumBox(const TArray<FVector>& Points, double DepthFraction)
|
||||
{
|
||||
FBox Full(Points);
|
||||
const double Floor = Full.Max.Z - DepthFraction * Full.GetSize().Y;
|
||||
FBox Box(ForceInit);
|
||||
for (const FVector& Point : Points)
|
||||
{
|
||||
if (Point.Z >= Floor)
|
||||
{
|
||||
Box += Point;
|
||||
}
|
||||
}
|
||||
return Box;
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +143,8 @@ int32 ULPHairFromStaticMeshCommandlet::Main(const FString& Params)
|
|||
const double ExtraScale = P.Contains(TEXT("Scale")) ? FCString::Atod(*P[TEXT("Scale")]) : 1.0;
|
||||
const double OffsetZ = P.Contains(TEXT("OffsetZ")) ? FCString::Atod(*P[TEXT("OffsetZ")]) : 0.0;
|
||||
const double OffsetY = P.Contains(TEXT("OffsetY")) ? FCString::Atod(*P[TEXT("OffsetY")]) : 0.0;
|
||||
const double Margin = P.Contains(TEXT("Margin")) ? FCString::Atod(*P[TEXT("Margin")]) : 1.05;
|
||||
const double CraniumDepth = P.Contains(TEXT("Cranium")) ? FCString::Atod(*P[TEXT("Cranium")]) : 0.3;
|
||||
const bool bDryRun = Switches.Contains(TEXT("DryRun"));
|
||||
if (StaticPath.IsEmpty() || FitHeadPath.IsEmpty() || OutPath.IsEmpty())
|
||||
{
|
||||
|
|
@ -144,27 +176,33 @@ int32 ULPHairFromStaticMeshCommandlet::Main(const FString& Params)
|
|||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Os '%s' absent du corps (%d) ou du squelette de reference (%d)"), *BoneName.ToString(), BodyHead, HairHead);
|
||||
return 1;
|
||||
}
|
||||
int32 HeadVerts = 0;
|
||||
const FBox BodyBox = HeadBox(Body, BodyHead, HeadVerts);
|
||||
const FBox FitBox = FitHead->GetBoundingBox();
|
||||
if (HeadVerts == 0 || FitBox.GetSize().X <= 0.0)
|
||||
const TArray<FVector> BodyPointsList = HeadPoints(Body, BodyHead);
|
||||
const TArray<FVector> FitPointsList = StaticPoints(FitHead);
|
||||
const int32 HeadVerts = BodyPointsList.Num();
|
||||
if (HeadVerts == 0 || FitPointsList.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Mesure impossible : %d sommets de tete sur le corps, largeur de la tete de reference %.2f"), HeadVerts, FitBox.GetSize().X);
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Mesure impossible : %d sommets de tete sur le corps, %d sur la tete de reference"), HeadVerts, FitPointsList.Num());
|
||||
return 1;
|
||||
}
|
||||
// Echelle par axe : largeur (X) et profondeur (Y) des deux tetes ; hauteur : moyenne des deux (la boite de la tete de
|
||||
// reference inclut le cou, sa hauteur n'est pas comparable). -Uniform : echelle unique calee sur la largeur.
|
||||
// Ajustement sur le haut du crane (largeur, profondeur, centre) et le sommet : la boite complete inclut oreilles,
|
||||
// nez et cou, et donnait une coiffure trop etroite enfoncee dans le crane (premier essai O7e).
|
||||
const FBox BodyFull(BodyPointsList);
|
||||
const FBox FitFull(FitPointsList);
|
||||
const FBox BodyBox = CraniumBox(BodyPointsList, CraniumDepth);
|
||||
const FBox FitBox = CraniumBox(FitPointsList, CraniumDepth);
|
||||
// Echelle par axe (largeur X, profondeur Y), hauteur : moyenne des deux ; -Uniform : echelle unique (largeur).
|
||||
// Marge : la calotte reste au-dessus du cuir chevelu.
|
||||
const double ScaleX = BodyBox.GetSize().X / FitBox.GetSize().X;
|
||||
const double ScaleY = Switches.Contains(TEXT("Uniform")) ? ScaleX : BodyBox.GetSize().Y / FitBox.GetSize().Y;
|
||||
const FVector Scale = FVector(ScaleX, ScaleY, (ScaleX + ScaleY) * 0.5) * ExtraScale;
|
||||
const FVector Scale = FVector(ScaleX, ScaleY, (ScaleX + ScaleY) * 0.5) * Margin * ExtraScale;
|
||||
const FVector From = TopCenter(FitBox);
|
||||
const FVector To = TopCenter(BodyBox) + FVector(0.0, OffsetY, OffsetZ);
|
||||
const FTransform BodyHeadCS = FAnimationRuntime::GetComponentSpaceTransformRefPose(Body->GetRefSkeleton(), BodyHead);
|
||||
const FTransform HairHeadCS = FAnimationRuntime::GetComponentSpaceTransformRefPose(HairRef, HairHead);
|
||||
const FTransform Correction = BodyHeadCS.Inverse() * HairHeadCS;
|
||||
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Tete du corps (%d sommets) : %s, taille %s"), HeadVerts, *BodyBox.ToString(), *BodyBox.GetSize().ToString());
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Tete de reference : %s, taille %s"), *FitBox.ToString(), *FitBox.GetSize().ToString());
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Tete du corps (%d sommets) : taille %s ; haut du crane %s, taille %s"), HeadVerts, *BodyFull.GetSize().ToString(), *BodyBox.ToString(), *BodyBox.GetSize().ToString());
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Tete de reference : taille %s ; haut du crane %s, taille %s"), *FitFull.GetSize().ToString(), *FitBox.ToString(), *FitBox.GetSize().ToString());
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Ajustement : echelle %s, %s -> %s"), *Scale.ToString(), *From.ToString(), *To.ToString());
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Os '%s' : corps %s, squelette cible %s, correction %s"), *BoneName.ToString(),
|
||||
*BodyHeadCS.GetLocation().ToString(), *HairHeadCS.GetLocation().ToString(), Correction.Equals(FTransform::Identity, 0.01) ? TEXT("aucune") : *Correction.ToString());
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
// Cette commandlet :
|
||||
// 1. mesure la tete du corps (-Body, sommets rattaches a l'os -Bone ou a ses descendants) et la tete de reference
|
||||
// fournie avec la coiffure (-FitHead) ;
|
||||
// 2. place la coiffure (-Static) sur la tete du corps : echelle par axe (largeur, profondeur ; -Uniform : largeur
|
||||
// seule), alignement du centre et du sommet du crane, puis correction si l'os de liaison du squelette cible n'a pas la meme pose de reference que
|
||||
// 2. place la coiffure (-Static) sur la tete du corps : echelle par axe mesuree sur le haut du crane, sans
|
||||
// oreilles, nez ni cou (largeur, profondeur ; -Uniform : largeur seule), alignement du centre et du sommet du crane, puis correction si l'os de liaison du squelette cible n'a pas la meme pose de reference que
|
||||
// celui du corps ;
|
||||
// 3. cree un squelette (copie de celui de -RefHair, une coiffure existante) et le SkeletalMesh -Out, tous les sommets
|
||||
// lies rigidement a l'os -Bone (FStaticToSkeletalMeshConverter du moteur) ;
|
||||
|
|
@ -17,8 +17,11 @@
|
|||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPHairFromStaticMesh -Static=/Game/.../Hair_17 -FitHead=/Game/.../Head
|
||||
// -Out=/Game/Heavy/New5/Hairs/Pocolov17 [-Body=/Game/Heavy/Daz/Bridget/bridget_gp]
|
||||
// [-RefHair=/Game/Heavy/New4/Hairs/FloraHair_New] [-Bone=head] [-Scale=1.0] [-OffsetZ=0] [-OffsetY=0] [-Uniform] [-DryRun]
|
||||
// [-RefHair=/Game/Heavy/New4/Hairs/FloraHair_New] [-Bone=head] [-Scale=1.0] [-OffsetZ=0] [-OffsetY=0] [-Margin=1.05] [-Cranium=0.3]
|
||||
// [-Uniform] [-DryRun]
|
||||
// -Scale, -OffsetZ, -OffsetY : retouches manuelles appliquees apres l'ajustement automatique (cm).
|
||||
// -Cranium : epaisseur mesuree sous le sommet du crane, en fraction de la profondeur de la tete (defaut 0,3) ;
|
||||
// -Margin : agrandissement de securite de la calotte (defaut 1,05).
|
||||
|
||||
#pragma once
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue