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:
parent
e46d01d5d8
commit
884268479e
4 changed files with 173 additions and 6 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
119
Tools/DiagnosePose.py
Normal 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
40
Tools/ImportQuaternius.py
Normal 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))
|
||||
Loading…
Reference in a new issue