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 "LPDirectControlSubsystem.h"
|
||||||
|
|
||||||
|
#include "LPAnimRedirect.h"
|
||||||
|
|
||||||
#include "Animation/AnimInstance.h"
|
#include "Animation/AnimInstance.h"
|
||||||
#include "Animation/AnimationAsset.h"
|
#include "Animation/AnimationAsset.h"
|
||||||
#include "Blueprint/UserWidget.h"
|
#include "Blueprint/UserWidget.h"
|
||||||
|
|
@ -22,6 +24,7 @@
|
||||||
#include "EngineUtils.h"
|
#include "EngineUtils.h"
|
||||||
#include "Framework/Application/SlateApplication.h"
|
#include "Framework/Application/SlateApplication.h"
|
||||||
#include "GameFramework/Character.h"
|
#include "GameFramework/Character.h"
|
||||||
|
#include "GameFramework/GameModeBase.h"
|
||||||
#include "GameFramework/CharacterMovementComponent.h"
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "GameFramework/InputSettings.h"
|
#include "GameFramework/InputSettings.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
|
|
@ -872,6 +875,13 @@ void ULPDirectControlSubsystem::Walk(float DeltaTime)
|
||||||
const float Forward = AxisValue(PC, ForwardAxis);
|
const float Forward = AxisValue(PC, ForwardAxis);
|
||||||
const float Right = AxisValue(PC, RightAxis);
|
const float Right = AxisValue(PC, RightAxis);
|
||||||
const bool bMoving = !FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Right);
|
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)
|
if (bMoving)
|
||||||
{
|
{
|
||||||
const FRotator Yaw(0.f, PC->GetControlRotation().Yaw, 0.f);
|
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);
|
bMale = Male->GetPropertyValue_InContainer(Character);
|
||||||
}
|
}
|
||||||
const TCHAR* Path = bMale
|
const FString Path = LPAnimRedirect::ClipPath(bMale ? TEXT("M_Walk_Loop") : TEXT("B_Walk_Loop"));
|
||||||
? TEXT("/Game/Heavy/New2/Anims2/walk_M.walk_M")
|
UAnimationAsset* WalkCycle = LoadObject<UAnimationAsset>(nullptr, *Path);
|
||||||
: TEXT("/Game/Heavy/New2/Anims2/walk_F.walk_F");
|
|
||||||
UAnimationAsset* WalkCycle = LoadObject<UAnimationAsset>(nullptr, Path);
|
|
||||||
if (!WalkCycle)
|
if (!WalkCycle)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
// .lpnear (MESH, PROMPT, ACTION). La liste est tenue ici, sur la personne ou le maillage le
|
// .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.
|
// 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 personnage n'est pas possede : la marche active bRunPhysicsWithNoController.
|
||||||
// Le cycle walk_F et la camera suiveuse ne sont plus appliques ici : ils cassaient
|
// La marche du jeu ne joue un cycle que si LPAnimTarget.ini vaut game (clips B_ / M_).
|
||||||
// le corps et la vue. L'essai est la sandbox interaction (LifePlay_interaction_sandbox.bat).
|
// sandbox laisse le corps au Blueprint. La camera suiveuse n'est pas branchee.
|
||||||
// Aucun Blueprint n'est modifie.
|
// Aucun Blueprint n'est modifie.
|
||||||
|
|
||||||
#pragma once
|
#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