This commit is contained in:
Nahuel Rocchetti 2023-08-25 09:08:16 -03:00
commit 0229014e46
3 changed files with 121 additions and 24 deletions

View file

@ -57,16 +57,16 @@ namespace OpenTS2.Components
public ScenegraphComponent Scenegraph { get; private set; }
/// <summary>
/// This is the unity object that all the animation rigging constraints get placed into.
/// This is the unity object that all the animation rigs get placed into.
/// </summary>
private GameObject _animationRig;
private GameObject _animationRigs;
private Animator _animator;
private RigBuilder _rigBuilder;
/// <summary>
/// Set of IK chains that have already been applied to this sim so they don't get re-applied.
/// </summary>
private readonly HashSet<uint> _appliedIKChains = new HashSet<uint>();
private readonly Dictionary<uint, RigLayer> _appliedIKChains = new Dictionary<uint, RigLayer>();
/// <summary>
/// These are basically parent bones of IK chains, this is needed so that if an animation updates the position
@ -90,13 +90,13 @@ namespace OpenTS2.Components
boneRenderer.transforms = boneTransforms.ToArray();
// Add an animator component to the auskel.
var animator = skeleton.AddComponent<Animator>();
_animator = skeleton.AddComponent<Animator>();
// Add a rig builder.
_rigBuilder = skeleton.AddComponent<RigBuilder>();
// Add a child rig to the skeleton.
_animationRig = new GameObject("UnityAnimationRig", typeof(Rig));
_animationRig.transform.SetParent(skeleton.transform, worldPositionStays: false);
_animationRigs = new GameObject("UnityAnimationRigs");
_animationRigs.transform.SetParent(skeleton.transform, worldPositionStays: false);
// HACK: In the auskel model, for some reason the bones that represent the IK goals for the hands are
// parented to the root_trans of the skeleton. This is done even though in animations the IK goal bones
@ -120,8 +120,8 @@ namespace OpenTS2.Components
AddGizmosAroundInverseKinmaticsPositions();
_rigBuilder.layers.Add(new RigLayer(_animationRig.GetComponent<Rig>()));
_rigBuilder.layers[0].Initialize(animator);
//_rigBuilder.layers.Add(new RigLayer(_animationRig.GetComponent<Rig>()));
//_rigBuilder.layers[0].Initialize(animator);
_rigBuilder.Build();
}
@ -155,14 +155,35 @@ namespace OpenTS2.Components
Destroy(spherePrimitive);
}
public void AddInverseKinematicsFromAnimation(AnimResourceConstBlock anim)
public void AdjustInverseKinematicWeightsForAnimation(AnimResourceConstBlock anim)
{
// TODO: This should eventually live in the animation controller for the sim. This is here for easy testing
// for now.
var auSkelTarget = anim.AnimTargets.FirstOrDefault(t => t.TagName.ToLower() == "auskel");
if (auSkelTarget == null)
{
return;
}
AddInverseKinematicsFromAnimation(auSkelTarget);
// Disable all current rigs.
foreach (var rigLayer in _appliedIKChains.Values)
{
rigLayer.active = false;
}
// Activate the ones present in the current animation.
foreach (var chain in auSkelTarget.IKChains)
{
if (!_appliedIKChains.TryGetValue(chain.NameCrc, out var rigLayer))
{
continue;
}
rigLayer.active = true;
}
}
private void AddInverseKinematicsFromAnimation(AnimResourceConstBlock.AnimTarget auSkelTarget)
{
AddInverseKinematicsFromIKChains(auSkelTarget.IKChains);
}
@ -170,10 +191,13 @@ namespace OpenTS2.Components
{
foreach (var chain in ikChains)
{
if (_appliedIKChains.Contains(chain.NameCrc))
if (_appliedIKChains.ContainsKey(chain.NameCrc))
{
continue;
}
var rigObject = new GameObject($"ik-rig-{chain.NameCrc:X}", typeof(Rig));
rigObject.transform.SetParent(_animationRigs.transform);
var rig = rigObject.GetComponent<Rig>();
Debug.Assert(chain.BeginBoneCrc != 0, "ikChain without a beginning bone");
Debug.Assert(chain.EndBoneCrc != 0, "ikChain without an end bone");
@ -222,7 +246,7 @@ namespace OpenTS2.Components
}
ikChainObj.GetComponent<TwoBoneIKConstraint>().data = twoBoneConstraint;
ikChainObj.transform.parent = _animationRig.transform;
ikChainObj.transform.parent = rigObject.transform;
if (target.Rotation2Crc != 0)
{
@ -237,10 +261,13 @@ namespace OpenTS2.Components
space = OverrideTransformData.Space.Local,
};
rotationConstraint.GetComponent<OverrideTransform>().data = rotationOverrideConstraint;
rotationConstraint.transform.parent = _animationRig.transform;
rotationConstraint.transform.parent = rigObject.transform;
}
_appliedIKChains.Add(chain.NameCrc);
var rigLayer = new RigLayer(rig);
_rigBuilder.layers.Add(rigLayer);
rigLayer.Initialize(_animator);
_appliedIKChains[chain.NameCrc] = rigLayer;
}
_rigBuilder.Build();

View file

@ -1,4 +1,7 @@
using OpenTS2.Common;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using OpenTS2.Common;
using OpenTS2.Components;
using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
@ -10,6 +13,15 @@ namespace OpenTS2.Engine.Tests
{
public class SimAnimationTest : MonoBehaviour
{
public string animationName = "";
[TextArea(minLines: 10, maxLines: 10)]
public string matchedNames = "";
private readonly Dictionary<string, ScenegraphAnimationAsset> _animations = new Dictionary<string, ScenegraphAnimationAsset>();
private Animation _animationObj;
private SimCharacterComponent _sim;
private void Start()
{
var contentProvider = ContentProvider.Get();
@ -19,24 +31,78 @@ namespace OpenTS2.Engine.Tests
Filesystem.GetPackagesInDirectory(Filesystem.GetDataPathForProduct(ProductFlags.BaseGame) +
"/Res/Sims3D"));
var sim = SimCharacterComponent.CreateNakedBaseSim();
// Load all animations involving auskel and put them in the dictionary.
foreach (var animationAsset in contentProvider.GetAssetsOfType<ScenegraphAnimationAsset>(TypeIDs.SCENEGRAPH_ANIM))
{
var auSkelTarget = animationAsset.AnimResource.AnimTargets.FirstOrDefault(t => t.TagName.ToLower() == "auskel");
if (auSkelTarget == null)
{
continue;
}
_animations[animationAsset.AnimResource.ScenegraphResource.ResourceName] = animationAsset;
}
UpdateAnimationsListAndGetSelection();
_sim = SimCharacterComponent.CreateNakedBaseSim();
const string animationName = "a-pose-neutral-stand_anim";
var anim = contentProvider.GetAsset<ScenegraphAnimationAsset>(
new ResourceKey(animationName, GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
sim.AddInverseKinematicsFromAnimation(anim.AnimResource);
_sim.AdjustInverseKinematicWeightsForAnimation(anim.AnimResource);
// Add the test animation.
var animationObj = sim.GetComponentInChildren<Animation>();
var clip = anim.CreateClipFromResource(sim.Scenegraph.BoneNamesToRelativePaths, sim.Scenegraph.BlendNamesToRelativePaths);
animationObj.AddClip(clip, animationName);
_animationObj = _sim.GetComponentInChildren<Animation>();
var clip = anim.CreateClipFromResource(_sim.Scenegraph.BoneNamesToRelativePaths, _sim.Scenegraph.BlendNamesToRelativePaths);
_animationObj.AddClip(clip, animationName);
//const string testAnimation = "a-blowHorn_anim";
const string testAnimation = "a2o-punchingBag-punch-start_anim";
anim = contentProvider.GetAsset<ScenegraphAnimationAsset>(
new ResourceKey(testAnimation, GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
clip = anim.CreateClipFromResource(sim.Scenegraph.BoneNamesToRelativePaths, sim.Scenegraph.BlendNamesToRelativePaths);
animationObj.AddClip(clip, testAnimation);
clip = anim.CreateClipFromResource(_sim.Scenegraph.BoneNamesToRelativePaths, _sim.Scenegraph.BlendNamesToRelativePaths);
_animationObj.AddClip(clip, testAnimation);
_sim.AdjustInverseKinematicWeightsForAnimation(anim.AnimResource);
}
private void OnValidate()
{
// Check what new animations match.
var anim = UpdateAnimationsListAndGetSelection();
if (anim == null)
{
return;
}
// Play the neutral animation for one second to reset positions to default.
_animationObj.Play("a-pose-neutral-stand_anim");
var animName = anim.AnimResource.ScenegraphResource.ResourceName;
if (_animationObj.GetClip(animName) == null)
{
var clip = anim.CreateClipFromResource(_sim.Scenegraph.BoneNamesToRelativePaths,
_sim.Scenegraph.BlendNamesToRelativePaths);
_animationObj.AddClip(clip, animName);
}
StartCoroutine(PlayClipFrameDelayed(animName, anim));
}
IEnumerator PlayClipFrameDelayed(string animName, ScenegraphAnimationAsset anim)
{
yield return new WaitForFixedUpdate();
_sim.AdjustInverseKinematicWeightsForAnimation(anim.AnimResource);
_animationObj.Play(animName);
}
private ScenegraphAnimationAsset UpdateAnimationsListAndGetSelection()
{
var matchedAnimations = _animations.Where(pair => pair.Key.StartsWith(animationName));
var tenMatchedAnimations = matchedAnimations.Take(10).ToArray();
// Display 10 matching animations.
matchedNames = string.Join("\n", tenMatchedAnimations.Select(pair => pair.Key));
return tenMatchedAnimations.Length == 0 ? null : tenMatchedAnimations[0].Value;
}
}
}

View file

@ -22,10 +22,14 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
/// </summary>
public const double FramesPerTick = 0.03;
public ScenegraphResource ScenegraphResource;
public AnimTarget[] AnimTargets;
public AnimResourceConstBlock(PersistTypeInfo blockTypeInfo, AnimTarget[] animTargets) : base(blockTypeInfo)
public AnimResourceConstBlock(PersistTypeInfo blockTypeInfo, ScenegraphResource scenegraphResource,
AnimTarget[] animTargets) : base(blockTypeInfo)
{
ScenegraphResource = scenegraphResource;
AnimTargets = animTargets;
}
@ -639,7 +643,7 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
}
*/
return new AnimResourceConstBlock(blockTypeInfo, animTargets);
return new AnimResourceConstBlock(blockTypeInfo, resource, animTargets);
}
// For some reason this format pads to 4-byte boundaries sometimes...we deal with that here.