LifePlay-Revival/Plugins/LPAnimCore/Source/LPAnimCore/LPCoupleSim.h
edujiro5972 d8a0f859ce DC1 : plugin LPAnimCore (LPAnimRedirect, LPCoupleSim) ; LifePlay ne depend plus de LPAnimMeasure (CHG-234)
Plugins/LPAnimCore (Runtime, PreDefault, toutes configurations) recoit la partie runtime
de l'ancien LPAnimMeasure de l'addon. LifePlay.Build.cs : LPAnimMeasure -> LPAnimCore.
LifePlayEditorTools depend encore de LPAnimMeasure jusqu'a DC2. Pin addon 3e9c32f.
Validation : DC1-BUILD code 0, DC1-VAL PASS (tournees Mesure identiques a SB2-REF,
controle visuel utilisateur marche et ShowCouple).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:31:01 +02:00

215 lines
9.5 KiB
C++

// Geometrie commune du couple (lot S5-T2, voir Docs/Migration/etat/sandbox/S5/S5-CART.md).
// Une seule source de verite pour : la transformation mesh -> acteur, l'echantillonnage des poses,
// le padding et le recalage de la boite englobante, le placement des corps, et le glissement du couple.
//
// Deux appelants, qui partagent la geometrie mais gardent leurs reglages :
// - addon : ALPPairSandboxStage (LPSandbox/LPCoupleSim.cpp) -> FSweepOptions::Sandbox()
// - jeu : ULPShowCoupleSubsystem (LifePlay/LPShowCoupleSubsystem.cpp) -> FSweepOptions::Show()
//
// Tout ce qui differe d'un cote a l'autre passe par FBoxFitParams ou FSweepOptions : le code
// de mesure et de boite n'est plus duplique, mais aucune option n'est alignee d'office.
// Regle du module : aucune dependance vers LifePlay ni vers LPSandbox.
#pragma once
#include "CoreMinimal.h"
#include "Engine/EngineTypes.h"
#include "Templates/Function.h"
class AActor;
class ACharacter;
class UAnimSequence;
class UBoxComponent;
class UCharacterMovementComponent;
class UPrimitiveComponent;
class USkeletalMeshComponent;
class UWorld;
namespace LPCoupleSim
{
// Demi-hauteur de capsule par defaut (Person et sandbox ont la meme valeur nominale).
inline constexpr double DefaultCapsuleHalf = 88.0;
// Pas d'echantillonnage d'une AnimSequence (0, L/8, ... 7L/8).
inline constexpr int32 PoseSamples = 8;
// Normale au-dela de laquelle une surface est consideree comme un appui (marche, lit).
inline constexpr float WalkableNormalZ = 0.6f;
// Penetration en-dessous de laquelle on ne depenetre pas.
inline constexpr float MinPenetrationDepth = 1.f;
// Tolerance de comparaison de position avant teleportation.
inline constexpr float MoveEpsilon = 0.1f;
// Rotation du mesh squelettique sous l'acteur (lacet -90).
inline constexpr double MeshYawDegrees = -90.0;
// Parametres de mesure. Les valeurs par defaut sont celles du jeu ; la sandbox surcharge
// CapsuleHalf et FloorLocalZ (son sol est a z = +10, donc son plancher local vaut 18 - PlayerSpot.Z).
struct FBoxFitParams
{
double CapsuleHalf = DefaultCapsuleHalf;
// Voie "animations" : FitCoupleBox (sandbox) et FitBox (jeu).
FVector AnimPad = FVector(12.0, 12.0, 6.0);
double AnimMaxSize = 800.0;
// Voie "corps vivants" : FitToBodies (jeu).
FVector LivePad = FVector(4.0, 4.0, 4.0);
FVector LiveMinExtent = FVector(8.0, 8.0, 8.0);
double LiveMaxSize = 400.0;
double LiveAnchorRadius = 140.0;
// Fond de boite, en Z local de l'acteur : la plante des pieds est a -CapsuleHalf.
double FloorLocalZ = -DefaultCapsuleHalf;
double MinExtentZ = 20.0;
};
struct FBoxFit
{
FVector Center = FVector::ZeroVector;
FVector Extent = FVector::ZeroVector;
// false : mesure absente ou hors plafond ; l'appelant garde la boite precedente.
bool bValid = false;
};
// --- Niveau pur : aucun UObject, aucun UWorld. Testable sans monde. ---
// Transformation commune du repere du mesh vers celui de l'acteur.
LPANIMCORE_API FTransform MeshToActor(double CapsuleHalf);
// Espace composant a partir des transformations locales et de la table des parents
// (Parents[i] < i, ou INDEX_NONE pour la racine, comme dans un FReferenceSkeleton).
LPANIMCORE_API void ComposeComponentSpace(
const TArray<int32>& Parents, const TArray<FTransform>& Local, TArray<FTransform>& OutComponent);
// Ajoute la position de chaque os (espace composant) a la boite, via MeshToActor.
LPANIMCORE_API void AccumulatePoints(
const TArray<FTransform>& Component, const FTransform& ToActor, FBox& InOutBounds);
// Os ecartes de la mesure des corps vivants (extremites et accessoires : la boite suivrait une
// main tendue). La voie "animations" ne les ecarte pas : ecart E6 de la cartographie.
LPANIMCORE_API bool IsExcludedLiveBone(const FString& BoneName);
// Boite brute -> boite finale, voie "animations" : padding, plafond, recalage du fond sur FloorLocalZ.
LPANIMCORE_API FBoxFit FitFromAnimBounds(const FBox& Bounds, const FBoxFitParams& Params);
// Boite brute -> boite finale, voie "corps vivants" : padding, etendue minimale, plafond. Pas de recalage.
LPANIMCORE_API FBoxFit FitFromLiveBounds(const FBox& Bounds, const FBoxFitParams& Params);
// Glissement le long d'une surface. TimeFraction : 1 - Hit.Time pour la part de course restante.
// bFlattenZ : la sandbox annule la composante verticale (son Z est verrouille par ailleurs).
LPANIMCORE_API FVector SlideWish(
const FVector& Wish, const FVector& Normal, float TimeFraction, bool bFlattenZ);
// --- Niveau lecture : assets et composants, sans effet de bord. ---
// Demi-hauteur reelle du personnage, avec repli (une capsule non initialisee mesure 0).
LPANIMCORE_API double CapsuleHalfOf(const ACharacter* Who, double Fallback = DefaultCapsuleHalf);
// Echantillonne une AnimSequence et ajoute les os a la boite. Os animes seulement,
// GetBoneTransform en donnees compressees (raw = false), rejet des poses NaN.
LPANIMCORE_API bool AccumulateAnimBounds(
const UAnimSequence* Anim, const FTransform& ToActor, FBox& InOutBounds, int32 Samples = PoseSamples);
// Ajoute les os affiches d'un mesh squelettique a la boite : ancre pelvis / hip, rayon
// LiveAnchorRadius, os exclus. Le mesh doit etre a jour (appel apres le tick d'animation).
LPANIMCORE_API void AccumulateLiveBody(
const USkeletalMeshComponent* Mesh, const FBoxFitParams& Params, FBox& InOutBounds);
// --- Niveau effets de bord : boite, corps, balayage. ---
// Cree la boite si besoin (profil commun : objet Pawn, tout Ignore sauf WorldDynamic = Block,
// cachee, hors navigation) et applique l'etat de collision. Retourne la boite a stocker.
LPANIMCORE_API UBoxComponent* EnsureBox(
ACharacter* Lead, UBoxComponent* Existing, FName Name, bool bEnabled);
// Etendue, centre et rotation nulle. Sans effet si Fit n'est pas valide.
LPANIMCORE_API void ApplyBoxFit(UBoxComponent* Box, const FBoxFit& Fit);
// Place le mesh squelettique sous l'acteur (plante des pieds a -CapsuleHalf, lacet -90).
LPANIMCORE_API void PlaceBodyMesh(ACharacter* Body, double CapsuleHalf);
// --- Soudure du partenaire ---
// Etat de mouvement memorise avant gel, pour le rendre a la sortie du show.
struct FSavedMove
{
TWeakObjectPtr<UCharacterMovementComponent> Move;
TEnumAsByte<EMovementMode> Mode = MOVE_None;
float Gravity = 1.f;
bool bTick = true;
};
// Arrete le mouvement, le desactive et coupe son tick. Retourne l'etat d'avant.
LPANIMCORE_API FSavedMove SaveAndFreeze(ACharacter* Body);
// Rend l'etat memorise, puis vide la memoire.
LPANIMCORE_API void RestoreMove(FSavedMove& Saved);
// Attache le partenaire au porteur et le pose sur son point (relatif nul).
LPANIMCORE_API void AttachPartner(ACharacter* Lead, ACharacter* Partner);
// Detache en gardant la position monde.
LPANIMCORE_API void Unweld(ACharacter* Partner);
// --- Balayage du couple ---
// Tout ce qui differe entre la sandbox et le jeu. Partir de Sandbox() ou de Show().
struct FSweepOptions
{
// Nombre de passes de glissement.
int32 MaxIterations = 3;
// SweepMultiByChannel (jeu : lits et depenetration ont besoin de tous les contacts)
// ou SweepSingleByChannel (sandbox).
bool bMultiSweep = true;
// Annule la composante verticale du glissement (sandbox).
bool bFlattenZ = false;
// Un contact dont la normale depasse WalkableNormalZ laisse passer le pas entier (sandbox).
// Voie simple seulement.
bool bAcceptWalkableStep = false;
// Sortir des penetrations au lieu de s'arreter net. Voie multiple seulement.
bool bDepenetrate = true;
// Ignorer tous les ACharacter du monde (jeu : les PNJ ne bloquent pas le couple).
bool bIgnoreAllCharacters = true;
// Appui franchissable : le couple se pose dessus au lieu d'etre bloque (jeu : les lits).
TFunction<bool(const UPrimitiveComponent*)> IsWalkableSupport;
// Acteur a ignorer entierement (jeu : la coque de la chambre, nommee Wall / Wall_*).
TFunction<bool(const AActor*)> IsIgnoredShell;
// Appele pour chaque acteur ignore par IsIgnoredShell, afin que l'appelant journalise.
TFunction<void(const AActor*)> OnIgnoredShell;
// Reglages du jeu : 3 passes, balayage multiple, depenetration, PNJ et coque ignores.
static FSweepOptions Show()
{
FSweepOptions Out;
return Out;
}
// Reglages de la sandbox : 2 passes, balayage simple, arret net en penetration,
// marches franchissables, glissement a plat, aucun PNJ ignore.
static FSweepOptions Sandbox()
{
FSweepOptions Out;
Out.MaxIterations = 2;
Out.bMultiSweep = false;
Out.bFlattenZ = true;
Out.bAcceptWalkableStep = true;
Out.bDepenetrate = false;
Out.bIgnoreAllCharacters = false;
return Out;
}
};
struct FSweepResult
{
// Position d'acteur retenue (From si rien n'a bouge).
FVector Location = FVector::ZeroVector;
// Dernier bloqueur en penetration, "Acteur/Composant", pour le journal. Vide sinon.
FString Blocker;
FVector BlockerNormal = FVector::ZeroVector;
float BlockerDepth = 0.f;
bool bMoved = false;
};
// Resout un deplacement du couple : la boite glisse, From est la position d'acteur de depart
// (sandbox : la position courante du porteur ; jeu : la derniere position sure), Wish le
// deplacement voulu. La fonction ne deplace rien : l'appelant applique FSweepResult::Location.
LPANIMCORE_API FSweepResult ResolveCoupleMove(
UWorld* World, const UBoxComponent* Box, const ACharacter* Lead, const ACharacter* Partner,
const FVector& From, const FVector& Wish, const FSweepOptions& Options);
}