Sandbox / AnimToGenesis : LPDirectControlSubsystem (mannequin Quaternius, copie vers Genesis), ImportQuaternius, DiagnosePose

Phase Sandbox/AnimToGenesis (CHG-168..186).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
edujiro5972 2026-10-07 15:49:12 +02:00
parent e46d01d5d8
commit 884268479e
4 changed files with 173 additions and 6 deletions

View file

@ -7,6 +7,8 @@
#include "LPDirectControlSubsystem.h"
#include "LPAnimRedirect.h"
#include "Animation/AnimInstance.h"
#include "Animation/AnimationAsset.h"
#include "Blueprint/UserWidget.h"
@ -22,6 +24,7 @@
#include "EngineUtils.h"
#include "Framework/Application/SlateApplication.h"
#include "GameFramework/Character.h"
#include "GameFramework/GameModeBase.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/InputSettings.h"
#include "GameFramework/PlayerController.h"
@ -872,6 +875,13 @@ void ULPDirectControlSubsystem::Walk(float DeltaTime)
const float Forward = AxisValue(PC, ForwardAxis);
const float Right = AxisValue(PC, RightAxis);
const bool bMoving = !FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Right);
const AGameModeBase* Mode = World->GetAuthGameMode();
const bool bSandbox = Mode && Mode->GetClass()->GetName().Contains(TEXT("LPPairSandbox"));
if (Character && !bSandbox && LPAnimRedirect::IsGameTarget())
{
const float Speed = bMoving ? FMath::Max(Character->GetVelocity().Size(), 20.f) : 0.f;
WalkAnim(Character, Speed);
}
if (bMoving)
{
const FRotator Yaw(0.f, PC->GetControlRotation().Yaw, 0.f);
@ -947,10 +957,8 @@ void ULPDirectControlSubsystem::WalkAnim(ACharacter* Character, float Speed)
{
bMale = Male->GetPropertyValue_InContainer(Character);
}
const TCHAR* Path = bMale
? TEXT("/Game/Heavy/New2/Anims2/walk_M.walk_M")
: TEXT("/Game/Heavy/New2/Anims2/walk_F.walk_F");
UAnimationAsset* WalkCycle = LoadObject<UAnimationAsset>(nullptr, Path);
const FString Path = LPAnimRedirect::ClipPath(bMale ? TEXT("M_Walk_Loop") : TEXT("B_Walk_Loop"));
UAnimationAsset* WalkCycle = LoadObject<UAnimationAsset>(nullptr, *Path);
if (!WalkCycle)
{
return;

View file

@ -8,8 +8,8 @@
// .lpnear (MESH, PROMPT, ACTION). La liste est tenue ici, sur la personne ou le maillage le
// plus proche, et elle reste cachee. E l'affiche ; un second E, ou un clic, lance l'action.
// Le personnage n'est pas possede : la marche active bRunPhysicsWithNoController.
// Le cycle walk_F et la camera suiveuse ne sont plus appliques ici : ils cassaient
// le corps et la vue. L'essai est la sandbox interaction (LifePlay_interaction_sandbox.bat).
// La marche du jeu ne joue un cycle que si LPAnimTarget.ini vaut game (clips B_ / M_).
// sandbox laisse le corps au Blueprint. La camera suiveuse n'est pas branchee.
// Aucun Blueprint n'est modifie.
#pragma once

119
Tools/DiagnosePose.py Normal file
View file

@ -0,0 +1,119 @@
# Mesure la pose Idle (image 0) : mannequin, Bridget, Michael.
# But : voir si la main passe derriere le buste, et de quel angle le bras est roule.
import unreal
PAIRS = (
("source", "/Game/Quaternius/UAL/Idle_Loop.Idle_Loop", "/Game/Quaternius/UAL/UAL1_Standard.UAL1_Standard",
("pelvis", "spine_03", "clavicle_l", "upperarm_l", "lowerarm_l", "hand_l",
"clavicle_r", "upperarm_r", "lowerarm_r", "hand_r")),
("bridget", "/Game/Quaternius/UAL/B_Idle_Loop.B_Idle_Loop", "/Game/Heavy/Daz/Bridget/bridget_gp.bridget_gp",
("pelvis", "chestUpper", "lCollar", "lShldrBend", "lForearmBend", "lHand",
"rCollar", "rShldrBend", "rForearmBend", "rHand")),
("michael", "/Game/Quaternius/UAL/M_Idle_Loop.M_Idle_Loop", "/Game/Heavy/Daz/Michael/michael.michael",
("pelvis", "chestUpper", "lCollar", "lShldrBend", "lForearmBend", "lHand",
"rCollar", "rShldrBend", "rForearmBend", "rHand")),
)
FINGERS = (
"lThumb1", "lIndex1", "lMid1", "lRing1", "lPinky1",
"thumb_01_l", "index_01_l", "middle_01_l", "pinky_01_l",
"hand_l", "lHand",
)
def dot(a, b):
return a.x * b.x + a.y * b.y + a.z * b.z
def sub(a, b):
return unreal.MathLibrary.subtract_vector_vector(a, b)
def norm(v):
return unreal.MathLibrary.normal(v)
def cross(a, b):
return unreal.MathLibrary.cross_vector_vector(a, b)
def fmt(v):
return "(%.1f %.1f %.1f)" % (v.x, v.y, v.z)
def bone_names(mesh):
pose = mesh.skeleton.get_reference_pose()
names = unreal.AnimPoseExtensions.get_bone_names(pose)
return [str(n) for n in names]
def pose_at_zero(sequence):
for call in (
lambda: unreal.AnimPoseExtensions.get_anim_pose_at_time(sequence, 0.0),
lambda: unreal.AnimPoseExtensions.get_anim_pose_at_frame(sequence, 0),
):
try:
return call()
except Exception as exc:
unreal.log_warning("POSE api %s" % exc)
raise RuntimeError("pas de lecture de pose")
def bone_tm(pose, name):
return unreal.AnimPoseExtensions.get_bone_pose(pose, name, unreal.AnimPoseSpaces.WORLD)
def main():
unreal.log_warning("POSE debut")
for label, anim_path, mesh_path, bones in PAIRS:
anim = unreal.load_asset(anim_path)
mesh = unreal.load_asset(mesh_path)
if not anim or not mesh:
unreal.log_error("POSE absent %s" % label)
continue
names = bone_names(mesh)
pose = pose_at_zero(anim)
got = {}
for bone in bones:
if bone not in names:
unreal.log_warning("POSE %s os absent %s" % (label, bone))
continue
got[bone] = bone_tm(pose, bone).translation
pelvis = got.get("pelvis")
chest_name = "spine_03" if "spine_03" in got else "chestUpper"
chest = got.get(chest_name)
if not pelvis or not chest:
unreal.log_error("POSE %s sans buste" % label)
continue
up = norm(sub(chest, pelvis))
# Avant approximatif : produit du haut du buste et de l'axe gauche-droite des epaules.
left = got.get("clavicle_l") or got.get("upperarm_l") or got.get("lCollar") or got.get("lShldrBend")
right = got.get("clavicle_r") or got.get("upperarm_r") or got.get("rCollar") or got.get("rShldrBend")
if not left or not right:
unreal.log_error("POSE %s sans epaules" % label)
continue
side = norm(sub(left, right))
forward = norm(cross(side, up))
unreal.log_warning("POSE %s avant %s" % (label, fmt(forward)))
for side_name, hand_name, elbow_name in (
("gauche", "hand_l" if "hand_l" in got else "lHand", "lowerarm_l" if "lowerarm_l" in got else "lForearmBend"),
("droite", "hand_r" if "hand_r" in got else "rHand", "lowerarm_r" if "lowerarm_r" in got else "rForearmBend"),
):
hand = got.get(hand_name)
elbow = got.get(elbow_name)
if not hand:
continue
rel = sub(hand, chest)
ahead = dot(rel, forward)
unreal.log_warning(
"POSE %s main %s %s devant %.1f pos %s" % (label, side_name, hand_name, ahead, fmt(hand)))
if elbow:
unreal.log_warning("POSE %s coude %s %s" % (label, side_name, fmt(elbow)))
present = [n for n in FINGERS if n in names]
unreal.log_warning("POSE %s doigts %s" % (label, " ".join(present)))
unreal.log_warning("POSE fin")
unreal.SystemLibrary.quit_editor()
main()

40
Tools/ImportQuaternius.py Normal file
View file

@ -0,0 +1,40 @@
# Importe le mannequin et les animations CC0 de Quaternius (Universal Animation Library, sans root motion).
#
# Usage :
# UnrealEditor-Cmd.exe <projet> -run=pythonscript "-script=<projet>/Tools/ImportQuaternius.py" -unattended -nopause -nosplash
# Sortie : lignes "QUATERNIUS" du journal.
import unreal
glb = "D:/Game/Universal Animation Library[Standard]/Unreal-Godot/UAL1_Standard.glb"
dest = "/Game/Quaternius/UAL"
task = unreal.AssetImportTask()
task.filename = glb
task.destination_path = dest
task.automated = True
task.replace_existing = True
task.save = True
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
reported = list(task.imported_object_paths)
unreal.log_warning("QUATERNIUS tache %d objet(s)" % len(reported))
for path in reported:
unreal.log_warning("QUATERNIUS PATH %s" % path)
if not unreal.EditorAssetLibrary.does_directory_exist(dest):
unreal.log_error("QUATERNIUS dossier absent %s" % dest)
else:
unreal.EditorAssetLibrary.save_directory(dest, False, True)
assets = unreal.EditorAssetLibrary.list_assets(dest, True, False)
unreal.log_warning("QUATERNIUS dossier %d asset(s)" % len(assets))
for path in assets:
asset = unreal.load_asset(path)
kind = asset.get_class().get_name() if asset else "null"
extra = ""
if isinstance(asset, unreal.SkeletalMesh):
box = asset.get_bounds()
extra = " origin=(%.1f %.1f %.1f) extent=(%.1f %.1f %.1f)" % (
box.origin.x, box.origin.y, box.origin.z,
box.box_extent.x, box.box_extent.y, box.box_extent.z)
unreal.log_warning("QUATERNIUS ASSET %s %s%s" % (kind, path, extra))