Start animating blend/morph shapes

This commit is contained in:
Ammar Askar 2023-08-16 00:14:35 -04:00
parent 6df4f8b9dc
commit 12c875deba
4 changed files with 78 additions and 21 deletions

View file

@ -14,7 +14,7 @@ namespace OpenTS2.Content.DBPF.Scenegraph
AnimResource = animResource;
}
public AnimationClip CreateClipFromResource(Dictionary<string, string> bonesToRelativePaths)
public AnimationClip CreateClipFromResource(Dictionary<string, string> bonesToRelativePaths, Dictionary<string, List<string>> blendsToRelativePaths)
{
var clip = new AnimationClip();
// mark as legacy for now, this might need to change when we do IK animations.
@ -27,18 +27,47 @@ namespace OpenTS2.Content.DBPF.Scenegraph
if (bonesToRelativePaths.TryGetValue(channel.ChannelName, out var relativePathToBone))
{
CreateBoneCurvesForChannel(clip, channel, relativePathToBone);
} else if (blendsToRelativePaths.TryGetValue(channel.ChannelName, out var relativePathsToBlend))
{
foreach (var blendRelativePath in relativePathsToBlend)
{
CreateBlendCurveForChannel(clip, channel, blendRelativePath);
}
}
else
{
Debug.LogWarning($"Bone for animation channel {channel.ChannelName} not found");
Debug.LogWarning($"Bone or blend for animation channel {channel.ChannelName} not found");
}
// TODO: handle morph animations.
}
}
return clip;
}
private static void CreateBlendCurveForChannel(AnimationClip clip, AnimResourceConstBlock.SharedChannel channel,
string relativePathToBlend)
{
if (channel.Type != AnimResourceConstBlock.ChannelType.Float1)
{
throw new ArgumentException("Invalid channel type for blend shape animation");
}
// unity blend shapes are from 0 to 99, whereas sims has them as 0 to 1.0
// Multiply each keyframe value by 100.
var originalKeyFrames = CreateKeyFramesForComponent(channel.DurationTicks, channel.Components[0]);
var keyFrames = new Keyframe[originalKeyFrames.Length];
for (var i = 0; i < originalKeyFrames.Length; i++)
{
keyFrames[i] = new Keyframe(originalKeyFrames[i].time, originalKeyFrames[i].value * 100,
originalKeyFrames[i].inTangent, originalKeyFrames[i].outTangent);
}
var curve = new AnimationCurve(keyFrames);
var property = $"blendShape.{channel.ChannelName}";
Debug.Log($"path: {relativePathToBlend} property: {property}");
clip.SetCurve(relativePathToBlend, typeof(SkinnedMeshRenderer), property, curve);
}
private static void CreateBoneCurvesForChannel(AnimationClip clip, AnimResourceConstBlock.SharedChannel channel, string relativePathToBone)
{
if (channel.Type == AnimResourceConstBlock.ChannelType.EulerRotation)
@ -89,14 +118,16 @@ namespace OpenTS2.Content.DBPF.Scenegraph
unityKeyframes[i] = new Keyframe(keyFrameTimeEven, keyFrame.Data);
break;
case IKeyFrame.ContinuousKeyFrame keyFrame:
unityKeyframes[i] = new Keyframe(keyFrameTimeEven, keyFrame.Data,
GetTangentIn(i, component.KeyFrames), GetTangentOut(i, component.KeyFrames));
// TODO: these tangentin and tangentout values are based on the original gradient where the x-axis
// is in ticks. We use seconds for the x-axis so the gradients need to be changed accordingly.
unityKeyframes[i] = new Keyframe(keyFrameTimeEven, keyFrame.Data/*,
GetTangentIn(i, component.KeyFrames), GetTangentOut(i, component.KeyFrames)*/);
break;
case IKeyFrame.DiscontinuousKeyFrame keyFrame:
var time = (float)keyFrame.Time;
// TODO: fix in and out tangents for discontinuous frames.
unityKeyframes[i] = new Keyframe(ConvertTimeToSeconds(time), keyFrame.Data,
GetTangentIn(i, component.KeyFrames), GetTangentOut(i, component.KeyFrames));
unityKeyframes[i] = new Keyframe(ConvertTimeToSeconds(time), keyFrame.Data/*,
GetTangentIn(i, component.KeyFrames), GetTangentOut(i, component.KeyFrames)*/);
break;
default:
throw new ArgumentOutOfRangeException();
@ -106,10 +137,12 @@ namespace OpenTS2.Content.DBPF.Scenegraph
return unityKeyframes;
}
// Assuming 30fps for now.
private const float TicksToSeconds = (float)(AnimResourceConstBlock.FramesPerTick / 30.0);
private static float ConvertTimeToSeconds(float timeInTicks)
{
// Assuming 24fps for now.
return (float)(timeInTicks * AnimResourceConstBlock.FramesPerTick / 24.0);
return timeInTicks * TicksToSeconds;
}
private static float GetTangentIn(int frameIdx, IReadOnlyList<IKeyFrame> frames)

View file

@ -280,7 +280,7 @@ namespace OpenTS2.Content.DBPF.Scenegraph
// Now we can actually attach the subset of morphs this mesh uses for unity. Only catch is unity requires
// the frameWeight parameter to be increasing, so we just start at 99% and increment. Not sure if it should
// be equally distributed yet.
var weight = 99f;
var weight = 99.9f;
foreach (var animation in animations)
{
if (animation == null)

View file

@ -37,17 +37,17 @@ public class ScenegraphGMDCTest : MonoBehaviour
var driveOff = ContentProvider.Get().GetAsset<ScenegraphAnimationAsset>(
new ResourceKey("o-vehiclePizza-driveOff_anim", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
var clip = driveOff.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths);
var clip = driveOff.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths, scenegraphComponent.BlendNamesToRelativePaths);
anim.AddClip(clip, "driveOff");
var drive = ContentProvider.Get().GetAsset<ScenegraphAnimationAsset>(
new ResourceKey("o-vehiclePizza-drive_anim", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
clip = drive.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths);
clip = drive.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths, scenegraphComponent.BlendNamesToRelativePaths);
anim.AddClip(clip, "drive");
var stop = ContentProvider.Get().GetAsset<ScenegraphAnimationAsset>(
new ResourceKey("o-vehiclePizza-stop_anim", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
clip = stop.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths);
clip = stop.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths, scenegraphComponent.BlendNamesToRelativePaths);
anim.AddClip(clip, "stop");
}
@ -58,7 +58,7 @@ public class ScenegraphGMDCTest : MonoBehaviour
var recline = ContentProvider.Get().GetAsset<ScenegraphAnimationAsset>(
new ResourceKey("o2a-chairRecliner-recline_anim", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_ANIM));
var clip = recline.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths);
var clip = recline.CreateClipFromResource(scenegraphComponent.BoneNamesToRelativePaths, scenegraphComponent.BlendNamesToRelativePaths);
anim.AddClip(clip, "recline");
}

View file

@ -71,7 +71,7 @@ namespace OpenTS2.Scenes
}
BindBonesInMeshes();
ComputeRelativeBonePaths();
ComputeRelativeBoneAndBlendPaths();
}
catch (Exception e)
{
@ -85,28 +85,45 @@ namespace OpenTS2.Scenes
/// "vehiclepizza/root_trans/root_rot/body_trans/body_rot". This is used for animating bones as unity
/// needs their relative paths for animations.
/// </summary>
public Dictionary<string, string> BoneNamesToRelativePaths = new Dictionary<string, string>();
private Dictionary<string, Transform> _boneNamesToTransform = new Dictionary<string, Transform>();
public readonly Dictionary<string, string> BoneNamesToRelativePaths = new Dictionary<string, string>();
private readonly Dictionary<string, Transform> _boneNamesToTransform = new Dictionary<string, Transform>();
/// <summary>
/// A mapping of blend shapes such as "recliningbend" and "slot_0_indent" to the paths relative to the
/// scenegraph component "chair_living/fabric". This can include multiple components, hence the list of strings.
/// </summary>
public readonly Dictionary<string, List<string>> BlendNamesToRelativePaths = new Dictionary<string, List<string>>();
private readonly Dictionary<Transform, List<string>> _unityObjectsToBlends = new Dictionary<Transform, List<string>>();
private void ComputeRelativeBonePaths()
private void ComputeRelativeBoneAndBlendPaths()
{
// For now I'm just going down the children and computing the relative path, but we can probably do this
// more cleanly when we build the nodes.
TraverseChildrenGameObjectsAndAddRelativeBones("", transform.GetChild(0));
TraverseChildrenGameObjectsAndAddRelativeBonesAndPaths("", transform.GetChild(0));
}
private void TraverseChildrenGameObjectsAndAddRelativeBones(string pathSoFar, Transform parentObj)
private void TraverseChildrenGameObjectsAndAddRelativeBonesAndPaths(string pathSoFar, Transform parentObj)
{
if (_boneNamesToTransform.ContainsKey(parentObj.name))
{
BoneNamesToRelativePaths[parentObj.name] = $"{pathSoFar}{parentObj.name}";
}
if (_unityObjectsToBlends.TryGetValue(parentObj, out var blends))
{
foreach (var blend in blends)
{
if (!BlendNamesToRelativePaths.ContainsKey(blend))
{
BlendNamesToRelativePaths[blend] = new List<string>();
}
BlendNamesToRelativePaths[blend].Add($"{pathSoFar}{parentObj.name}");
}
}
pathSoFar += $"{parentObj.name}/";
for (var i = 0; i < parentObj.childCount; i++)
{
var child = parentObj.GetChild(i);
TraverseChildrenGameObjectsAndAddRelativeBones(pathSoFar, child);
TraverseChildrenGameObjectsAndAddRelativeBonesAndPaths(pathSoFar, child);
}
}
@ -348,6 +365,13 @@ namespace OpenTS2.Scenes
{
_meshesToBindBonesFor.Enqueue((primitive.Value, skinnedRenderer));
}
var blends = new List<string>();
for (var i = 0; i < skinnedRenderer.sharedMesh.blendShapeCount; i++)
{
blends.Add(skinnedRenderer.sharedMesh.GetBlendShapeName(i));
}
_unityObjectsToBlends[primitiveObject.transform] = blends;
}
else
{