O7e : premiere coiffure Fab (Pocolov Hair 17) et commandlet LPHairFromStaticMesh
Maillage statique rattache au squelette Genesis 8 (os head), ajuste a la tete de Bridget, materiaux DazHair ; declaree par lp_Revival/Hairstyles/pocolov17.lphair. Attribution CC BY 4.0 dans Licenses/Free_3D_Models_Credits.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4fe9fab0ba
commit
1d0887f694
6 changed files with 305 additions and 1 deletions
|
|
@ -579,6 +579,40 @@ table (à remplacer par une coiffure Fab, lot O7e). Les 88 coiffures présentes
|
|||
**Limite** : le maillage doit être importé dans le projet et cuit avec le jeu ; un mod ne peut pas encore apporter
|
||||
son propre maillage (étape H3 : paquets `.pak` montés depuis `Modules/<mod>/`).
|
||||
|
||||
### 16.8 O7e : première coiffure Fab, Pocolov Hair 17 (2026-10-02)
|
||||
|
||||
Source : *Pocolov Hair 17* (Pocolov Studio 2019, artiste Thang), bibliothèque Fab de l'utilisateur, `pocolov-hair-17.zip`
|
||||
(1,4 Mo, FBX), licence **CC BY 4.0** : attribution dans `Licenses/Free_3D_Models_Credits.txt` et dans le fichier `.lphair`.
|
||||
Copie de l'archive et des assets d'import dans `D:\Game\LifePlay_Backup_5.5\O7e`.
|
||||
|
||||
**Constat** : maillage **statique** (`Hair_17`, 8 250 triangles) livré avec une tête de référence (`Head`) ; aucun os,
|
||||
aucune pondération ; matériaux sans texture reliée. Blender absent de la machine.
|
||||
|
||||
**Chaîne d'intégration** (reproductible pour d'autres coiffures statiques) :
|
||||
|
||||
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` ;
|
||||
- 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` ;
|
||||
3. matériaux `Pocolov17_Cap` et `Pocolov17_Strands`, enfants de `Daz/Bridget/Hairs/DazHair` (paramètres du jeu
|
||||
`HairColor`, `HairThickness`) : `MapDiffuse` = mèches grises, `MapOpacity` = lisière de la calotte / mèches ;
|
||||
`Thicken` 1 et 10 comme Flora ;
|
||||
4. dossier de travail retiré du projet (sauvegardé) ; dépendances contrôlées : la coiffure ne référence que son
|
||||
squelette et ses deux matériaux ;
|
||||
5. déclaration `Modules/lp_Revival/Hairstyles/pocolov17.lphair` (H1) ; la coiffure de test `H1_TestHair` est retirée
|
||||
(sauvegardée).
|
||||
|
||||
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)`.
|
||||
|
||||
**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.
|
||||
|
||||
## 17. Couche de données : stratégie (2026-10-02)
|
||||
|
||||
Proposition utilisateur : convertir les fichiers texte de `Modules` en fichiers JSON groupés par type, avec un calque de
|
||||
|
|
|
|||
|
|
@ -929,3 +929,4 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
|
|||
| CHG-082 | 2026-10-02 | O7d | `Tools/BuildCollections.py` : collections écrites dans `Saved/Collections` (locales) au lieu de `Content/Collections` (partagées) ; 13 fichiers déplacés vers `D:\Game\LifePlay_Backup_5.5\O7d\Content_Collections` puis régénérés (identiques) ; `11_Audit_Optimisation.md` §16.5, `08_Guide_Developpeur.md` §2.1 | avertissement à l'ouverture de l'éditeur (utilisateur) : une collection partagée exige un fournisseur de contrôle de révision, désactivé dans ce projet ; une collection locale n'en utilise pas ; validé par l'utilisateur le 2026-10-02 (plus d'avertissement) | `git checkout O7d-apres -- Tools/BuildCollections.py` et remettre les fichiers sauvegardés |
|
||||
| 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` |
|
||||
|
|
|
|||
|
|
@ -24,3 +24,6 @@ In no particular order, these are the authors of the original (most have been he
|
|||
- o4saken
|
||||
- frankyaye
|
||||
- Joel Palmius
|
||||
|
||||
LifePlay Revival (UE 5.5) additions:
|
||||
- Pocolov Hair 17 by Pocolov (Pocolov Studio 2019, artist Thang), Creative Commons Attribution 4.0 (CC BY 4.0), https://www.fab.com/listings/7f6f687d-bb13-40c6-8b69-64044ededc1d . Changes: bound to the Genesis 8 skeleton (head bone), fitted to the Bridget head, DazHair materials (Content/Heavy/New5/Hairs/Pocolov17*, declared by Content/Modules/lp_Revival/Hairstyles/pocolov17.lphair).
|
||||
|
|
|
|||
226
Source/LifePlayEditorTools/LPHairFromStaticMeshCommandlet.cpp
Normal file
226
Source/LifePlayEditorTools/LPHairFromStaticMeshCommandlet.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// Portage UE 5.5 - voir LPHairFromStaticMeshCommandlet.h
|
||||
|
||||
#include "LPHairFromStaticMeshCommandlet.h"
|
||||
|
||||
#include "AnimationRuntime.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "MeshDescription.h"
|
||||
#include "Misc/PackageName.h"
|
||||
#include "Rendering/SkeletalMeshLODModel.h"
|
||||
#include "Rendering/SkeletalMeshModel.h"
|
||||
#include "StaticMeshAttributes.h"
|
||||
#include "StaticToSkeletalMeshConverter.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPHairFromStatic, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
T* LoadByPath(const FString& Path)
|
||||
{
|
||||
FString Full = Path;
|
||||
if (!Full.Contains(TEXT(".")))
|
||||
{
|
||||
Full += TEXT(".") + FPackageName::GetShortName(Path);
|
||||
}
|
||||
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)
|
||||
{
|
||||
FBox Box(ForceInit);
|
||||
OutCount = 0;
|
||||
const FReferenceSkeleton& Ref = Body->GetRefSkeleton();
|
||||
auto IsUnderHead = [&](int32 Bone)
|
||||
{
|
||||
for (; Bone != INDEX_NONE; Bone = Ref.GetParentIndex(Bone))
|
||||
{
|
||||
if (Bone == HeadIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const FSkeletalMeshLODModel& LOD = Body->GetImportedModel()->LODModels[0];
|
||||
for (const FSkelMeshSection& Section : LOD.Sections)
|
||||
{
|
||||
for (const FSoftSkinVertex& V : Section.SoftVertices)
|
||||
{
|
||||
int32 Best = 0;
|
||||
for (int32 i = 1; i < MAX_TOTAL_INFLUENCES; ++i)
|
||||
{
|
||||
if (V.InfluenceWeights[i] > V.InfluenceWeights[Best])
|
||||
{
|
||||
Best = i;
|
||||
}
|
||||
}
|
||||
const int32 Bone = Section.BoneMap.IsValidIndex(V.InfluenceBones[Best]) ? Section.BoneMap[V.InfluenceBones[Best]] : INDEX_NONE;
|
||||
if (IsUnderHead(Bone))
|
||||
{
|
||||
Box += FVector(V.Position);
|
||||
++OutCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Box;
|
||||
}
|
||||
|
||||
/** Point de reference d'une tete : centre en X et Y, sommet du crane en Z. */
|
||||
FVector TopCenter(const FBox& Box)
|
||||
{
|
||||
return FVector(Box.GetCenter().X, Box.GetCenter().Y, Box.Max.Z);
|
||||
}
|
||||
|
||||
bool SaveAsset(UObject* Asset)
|
||||
{
|
||||
UPackage* Package = Asset->GetOutermost();
|
||||
Package->MarkPackageDirty();
|
||||
const FString Filename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
|
||||
FSavePackageArgs SaveArgs;
|
||||
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||||
SaveArgs.Error = GWarn;
|
||||
const bool bOk = UPackage::SavePackage(Package, Asset, *Filename, SaveArgs);
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT(" %s %s"), bOk ? TEXT("sauvegarde :") : TEXT("ECHEC de sauvegarde :"), *Filename);
|
||||
return bOk;
|
||||
}
|
||||
}
|
||||
|
||||
ULPHairFromStaticMeshCommandlet::ULPHairFromStaticMeshCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsEditor = true;
|
||||
IsServer = false;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPHairFromStaticMeshCommandlet::Main(const FString& Params)
|
||||
{
|
||||
TArray<FString> Tokens, Switches;
|
||||
TMap<FString, FString> P;
|
||||
ParseCommandLine(*Params, Tokens, Switches, P);
|
||||
const FString StaticPath = P.FindRef(TEXT("Static"));
|
||||
const FString FitHeadPath = P.FindRef(TEXT("FitHead"));
|
||||
const FString OutPath = P.FindRef(TEXT("Out"));
|
||||
const FString BodyPath = P.Contains(TEXT("Body")) ? P[TEXT("Body")] : TEXT("/Game/Heavy/Daz/Bridget/bridget_gp");
|
||||
const FString RefHairPath = P.Contains(TEXT("RefHair")) ? P[TEXT("RefHair")] : TEXT("/Game/Heavy/New4/Hairs/FloraHair_New");
|
||||
const FName BoneName(P.Contains(TEXT("Bone")) ? *P[TEXT("Bone")] : TEXT("head"));
|
||||
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 bool bDryRun = Switches.Contains(TEXT("DryRun"));
|
||||
if (StaticPath.IsEmpty() || FitHeadPath.IsEmpty() || OutPath.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Usage : -Static=<StaticMesh> -FitHead=<StaticMesh> -Out=</Game/...> [-Body=] [-RefHair=] [-Bone=head] [-Scale=] [-OffsetZ=] [-OffsetY=] [-DryRun]"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
UStaticMesh* Static = LoadByPath<UStaticMesh>(StaticPath);
|
||||
UStaticMesh* FitHead = LoadByPath<UStaticMesh>(FitHeadPath);
|
||||
USkeletalMesh* Body = LoadByPath<USkeletalMesh>(BodyPath);
|
||||
USkeletalMesh* RefHair = LoadByPath<USkeletalMesh>(RefHairPath);
|
||||
if (!Static || !FitHead || !Body || !RefHair || !RefHair->GetSkeleton())
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Asset introuvable : Static=%d FitHead=%d Body=%d RefHair=%d"), !!Static, !!FitHead, !!Body, RefHair && RefHair->GetSkeleton());
|
||||
return 1;
|
||||
}
|
||||
if (FPackageName::DoesPackageExist(OutPath))
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("%s existe deja : rien n'est ecrase"), *OutPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1. Mesures.
|
||||
const int32 BodyHead = Body->GetRefSkeleton().FindBoneIndex(BoneName);
|
||||
const FReferenceSkeleton& HairRef = RefHair->GetSkeleton()->GetReferenceSkeleton();
|
||||
const int32 HairHead = HairRef.FindBoneIndex(BoneName);
|
||||
if (BodyHead == INDEX_NONE || HairHead == INDEX_NONE)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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.
|
||||
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 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("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());
|
||||
|
||||
// 2. Copie de travail de la coiffure, sommets places sur la tete du corps.
|
||||
UStaticMesh* Work = DuplicateObject<UStaticMesh>(Static, GetTransientPackage(), NAME_None);
|
||||
FMeshDescription* Desc = Work ? Work->GetMeshDescription(0) : nullptr;
|
||||
if (!Desc)
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Donnees sources de %s indisponibles"), *StaticPath);
|
||||
return 1;
|
||||
}
|
||||
FBox Placed(ForceInit);
|
||||
TVertexAttributesRef<FVector3f> Positions = FStaticMeshAttributes(*Desc).GetVertexPositions();
|
||||
for (const FVertexID Vertex : Desc->Vertices().GetElementIDs())
|
||||
{
|
||||
const FVector OnBody = (FVector(Positions[Vertex]) - From) * Scale + To;
|
||||
const FVector Final = Correction.TransformPosition(OnBody);
|
||||
Positions[Vertex] = FVector3f(Final);
|
||||
Placed += Final;
|
||||
}
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("Coiffure placee : %s (avant : %s)"), *Placed.ToString(), *Static->GetBoundingBox().ToString());
|
||||
if (bDryRun)
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("-DryRun : rien n'est cree"));
|
||||
return 0;
|
||||
}
|
||||
Work->CommitMeshDescription(0);
|
||||
Work->Build(true);
|
||||
|
||||
// 3. Squelette (copie de celui de la coiffure de reference) et SkeletalMesh.
|
||||
const FString OutName = FPackageName::GetShortName(OutPath);
|
||||
UPackage* SkeletonPackage = CreatePackage(*(OutPath + TEXT("_Skeleton")));
|
||||
USkeleton* Skeleton = DuplicateObject<USkeleton>(RefHair->GetSkeleton(), SkeletonPackage, *(OutName + TEXT("_Skeleton")));
|
||||
Skeleton->SetFlags(RF_Public | RF_Standalone);
|
||||
Skeleton->SetPreviewMesh(nullptr);
|
||||
|
||||
UPackage* MeshPackage = CreatePackage(*OutPath);
|
||||
USkeletalMesh* Hair = NewObject<USkeletalMesh>(MeshPackage, *OutName, RF_Public | RF_Standalone);
|
||||
if (!FStaticToSkeletalMeshConverter::InitializeSkeletalMeshFromStaticMesh(Hair, Work, Skeleton->GetReferenceSkeleton(), BoneName))
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Conversion en SkeletalMesh refusee par le moteur"));
|
||||
return 1;
|
||||
}
|
||||
Hair->SetSkeleton(Skeleton);
|
||||
Skeleton->MergeAllBonesToBoneTree(Hair);
|
||||
Skeleton->SetPreviewMesh(Hair);
|
||||
|
||||
// 4. Controle puis sauvegarde.
|
||||
const int32 Bones = Hair->GetRefSkeleton().GetNum();
|
||||
const FBox Bounds = Hair->GetImportedBounds().GetBox();
|
||||
UE_LOG(LogLPHairFromStatic, Warning, TEXT("%s : %d os, %d materiau(x), bornes %s"), *OutName, Bones, Hair->GetMaterials().Num(), *Bounds.ToString());
|
||||
if (Bones != HairRef.GetNum() || Hair->GetRefSkeleton().FindBoneIndex(BoneName) == INDEX_NONE)
|
||||
{
|
||||
UE_LOG(LogLPHairFromStatic, Error, TEXT("Squelette inattendu : rien n'est sauvegarde"));
|
||||
return 1;
|
||||
}
|
||||
return SaveAsset(Skeleton) && SaveAsset(Hair) ? 0 : 1;
|
||||
}
|
||||
37
Source/LifePlayEditorTools/LPHairFromStaticMeshCommandlet.h
Normal file
37
Source/LifePlayEditorTools/LPHairFromStaticMeshCommandlet.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Portage UE 5.5 - lot O7e (Docs/Migration/11_Audit_Optimisation.md §16.8).
|
||||
//
|
||||
// Les coiffures LifePlay sont des SkeletalMesh dont les os portent les noms Genesis 8 : Person les fait suivre le corps
|
||||
// par SetLeaderPoseComponent (correspondance par nom d'os). Une coiffure de Fab est le plus souvent un maillage statique,
|
||||
// modele autour d'une tete de reference qui n'est pas celle de Bridget.
|
||||
//
|
||||
// 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
|
||||
// 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) ;
|
||||
// 4. sauvegarde. Aucun asset existant n'est modifie (les sources sont dupliquees en memoire).
|
||||
//
|
||||
// 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]
|
||||
// -Scale, -OffsetZ, -OffsetY : retouches manuelles appliquees apres l'ajustement automatique (cm).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPHairFromStaticMeshCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPHairFromStaticMeshCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPHairFromStaticMeshCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
|
|
@ -39,7 +39,10 @@ public class LifePlayEditorTools : ModuleRules
|
|||
"UMG",
|
||||
"UMGEditor",
|
||||
// Portage 5.5 (L7h) : LPCheckMeshesCommandlet lit les donnees sources des UStaticMesh
|
||||
"MeshDescription"
|
||||
"MeshDescription",
|
||||
// O7e : LPHairFromStaticMeshCommandlet (conversion maillage statique -> SkeletalMesh)
|
||||
"StaticMeshDescription",
|
||||
"SkeletalMeshUtilitiesCommon"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue