mirror of
https://github.com/LazyDuchess/OpenTS2.git
synced 2025-01-22 16:21:47 -05:00
Start animating blend/morph shapes
This commit is contained in:
parent
6df4f8b9dc
commit
12c875deba
4 changed files with 78 additions and 21 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue