mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
AnimationClip Recovery Improvements (#1210)
* - improved Slope/Tangent calculation - recover Slopes for all Float curves - tweaked Dense and Constant curves generation - fixed erroneously generating Float curves out of PPtr curves, on MonoBehaviours and some Engine components * remove duplicated bindings cache * don't get stuck on broken Transform animation, and minor tweaks * implementing change requests * implementing new change requests * edit comment
This commit is contained in:
parent
be4e38218e
commit
c3586ab772
@ -1,5 +1,6 @@
|
||||
using AssetRipper.Assets.Cloning;
|
||||
using AssetRipper.Assets.Collections;
|
||||
using AssetRipper.Assets.Generics;
|
||||
using AssetRipper.Checksum;
|
||||
using AssetRipper.IO.Endian;
|
||||
using AssetRipper.Processing.AnimationClips.Editor;
|
||||
@ -44,120 +45,129 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
|
||||
public static void Process(IAnimationClip clip, PathChecksumCache checksumCache)
|
||||
{
|
||||
AnimationClipConverter converter = new AnimationClipConverter(clip, checksumCache);
|
||||
AnimationClipConverter converter = new(clip, checksumCache);
|
||||
converter.ProcessInner();
|
||||
}
|
||||
|
||||
private void ProcessInner()
|
||||
{
|
||||
if (m_clip.Has_MuscleClip_C74() && m_clip.Has_ClipBindingConstant_C74())
|
||||
if (m_clip.Has_ClipBindingConstant_C74())
|
||||
{
|
||||
IClip clip = m_clip.MuscleClip_C74.Clip.Data;
|
||||
IAnimationClipBindingConstant bindings = m_clip.ClipBindingConstant_C74;
|
||||
|
||||
IReadOnlyList<StreamedFrame> streamedFrames = GenerateFramesFromStreamedClip(clip.StreamedClip);
|
||||
float lastDenseFrame = clip.DenseClip.FrameCount / clip.DenseClip.SampleRate;
|
||||
float lastSampleFrame = streamedFrames.Count > 1 ? streamedFrames[streamedFrames.Count - 2].Time : 0.0f;
|
||||
float lastFrame = Math.Max(lastDenseFrame, lastSampleFrame);
|
||||
ProcessStreams(streamedFrames);
|
||||
|
||||
int streamedCurveCount = clip.StreamedClip.CurveCount();
|
||||
ProcessDenses(clip.DenseClip, streamedCurveCount);
|
||||
|
||||
ProcessStreams(streamedFrames, bindings, clip.DenseClip.SampleRate);
|
||||
ProcessDenses(clip, bindings);
|
||||
if (clip.Has_ConstantClip())
|
||||
{
|
||||
ProcessConstant(clip, clip.ConstantClip, bindings, lastFrame);
|
||||
}
|
||||
if (m_clip.Has_MuscleClipInfo_C74())
|
||||
{
|
||||
m_clip.MuscleClipInfo_C74.Initialize(m_clip.MuscleClip_C74);
|
||||
int preConstantCurves = streamedCurveCount + (int)clip.DenseClip.CurveCount;
|
||||
float lastConstantTime = CalculateLastConstantTime(streamedFrames, m_clip.MuscleClip_C74.StopTime);
|
||||
ProcessConstant(clip.ConstantClip, preConstantCurves, lastConstantTime);
|
||||
}
|
||||
|
||||
m_clip.MuscleClipInfo_C74.Initialize(m_clip.MuscleClip_C74);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessStreams(IReadOnlyList<StreamedFrame> streamFrames, IAnimationClipBindingConstant bindings, float sampleRate)
|
||||
private void ProcessStreams(IReadOnlyList<StreamedFrame> streamedFrames)
|
||||
{
|
||||
Span<float> curveValues = [0, 0, 0, 0];
|
||||
Span<float> inSlopeValues = [0, 0, 0, 0];
|
||||
Span<float> outSlopeValues = [0, 0, 0, 0];
|
||||
float interval = 1.0f / sampleRate;
|
||||
|
||||
// first (index [0]) stream frame is for slope calculation for the first real frame (index [1])
|
||||
// last one (index [count - 1]) is +Infinity
|
||||
// it is made for slope processing, but we don't need them
|
||||
bool frameIndex0 = true;
|
||||
for (int frameIndex = 0; frameIndex < streamFrames.Count - 1; frameIndex++)
|
||||
if (streamedFrames.Count > 1)
|
||||
{
|
||||
StreamedFrame frame = streamFrames[frameIndex];
|
||||
for (int curveIndex = 0; curveIndex < frame.Curves.Length;)
|
||||
{
|
||||
StreamedCurveKey curve = frame.Curves[curveIndex];
|
||||
IGenericBinding binding = bindings.FindBinding(curve.Index);
|
||||
streamedFrames[0].Time = 0f; // fix first frame for PPtrCurves to have Time=0 instead of float.MinValue
|
||||
}
|
||||
int frameCount = streamedFrames.Count - 1; // last StreamedFrame is dummy so must be skipped
|
||||
for (int frameIdx = 0; frameIdx < frameCount; frameIdx++)
|
||||
{
|
||||
// last real frame doesn't need outSlope calculation, and will have inSlope from previous iteration
|
||||
bool doSlopeCalc = frameIdx != frameCount - 1;
|
||||
|
||||
StreamedFrame frame = streamedFrames[frameIdx];
|
||||
for (int curveIdx = 0; curveIdx < frame.Curves.Length;)
|
||||
{
|
||||
int curveID = frame.Curves[curveIdx].Index;
|
||||
IGenericBinding binding = GetBinding(curveID);
|
||||
string path = GetCurvePath(binding.Path);
|
||||
StreamedCurveKey curve;
|
||||
if (binding.IsTransform())
|
||||
{
|
||||
if (frameIndex0)
|
||||
int transformDim = binding.TransformType().GetDimension();
|
||||
if (frameIdx == 0) // first StreamedFrame only contains PPtrCurves, skip
|
||||
{
|
||||
curveIndex = GetNextCurve(frame, curveIndex);
|
||||
curveIdx += transformDim;
|
||||
continue;
|
||||
}
|
||||
GetPreviousFrame(streamFrames, curve.Index, frameIndex, out int prevFrameIndex, out int prevCurveIndex);
|
||||
int dimension = binding.TransformType().GetDimension();
|
||||
for (int key = 0; key < dimension; key++)
|
||||
for (int offset = 0; offset < transformDim; offset++)
|
||||
{
|
||||
StreamedCurveKey keyCurve = frame.Curves[curveIndex];//index out of bounds
|
||||
StreamedFrame prevFrame = streamFrames[prevFrameIndex];
|
||||
StreamedCurveKey prevKeyCurve = prevFrame.Curves[prevCurveIndex + key];
|
||||
float deltaTime = frame.Time - prevFrame.Time;
|
||||
curveValues[key] = keyCurve.Value;
|
||||
inSlopeValues[key] = prevKeyCurve.CalculateNextInSlope(deltaTime, keyCurve.Value);
|
||||
outSlopeValues[key] = keyCurve.OutSlope;
|
||||
curveIndex = GetNextCurve(frame, curveIndex);
|
||||
curve = frame.Curves[curveIdx];
|
||||
if (doSlopeCalc)
|
||||
{
|
||||
if (TryGetNextFrame(streamedFrames, frameIdx, curveID, out StreamedFrame? nextFrame, out int nextCurveIdx))
|
||||
{
|
||||
StreamedCurveKey nextCurve = nextFrame.Curves[nextCurveIdx + offset];
|
||||
curve.CalculateSlopes(frame.Time, nextFrame.Time, nextCurve);
|
||||
}
|
||||
}
|
||||
curveValues[offset] = curve.Value;
|
||||
inSlopeValues[offset] = curve.InSlope;
|
||||
outSlopeValues[offset] = curve.OutSlope;
|
||||
curveIdx++;
|
||||
}
|
||||
|
||||
AddTransformCurve(frame.Time, binding.TransformType(), curveValues, inSlopeValues, outSlopeValues, 0, path);
|
||||
continue;
|
||||
}
|
||||
else if (binding.CustomType == (byte)BindingCustomType.None)
|
||||
curve = frame.Curves[curveIdx];
|
||||
if (!binding.IsPPtrCurve()) // Skip slope calculation for PPtrCurves
|
||||
{
|
||||
if (frameIndex0)
|
||||
if (frameIdx == 0) // first StreamedFrame only contains PPtrCurves, skip
|
||||
{
|
||||
curveIndex = GetNextCurve(frame, curveIndex);
|
||||
curveIdx++;
|
||||
continue;
|
||||
}
|
||||
AddDefaultCurve(binding, path, frame.Time, frame.Curves[curveIndex].Value);
|
||||
curveIndex = GetNextCurve(frame, curveIndex);
|
||||
if (doSlopeCalc)
|
||||
{
|
||||
if (TryGetNextFrame(streamedFrames, frameIdx, curveID, out StreamedFrame? nextFrame, out int nextCurveIdx))
|
||||
{
|
||||
StreamedCurveKey nextCurve = nextFrame.Curves[nextCurveIdx];
|
||||
curve.CalculateSlopes(frame.Time, nextFrame.Time, nextCurve);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (binding.CustomType == (byte)BindingCustomType.None)
|
||||
{
|
||||
AddDefaultCurve(binding, path, frame.Time, curve.Value, curve.InSlope, curve.OutSlope);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCustomCurve(bindings, binding, path, frame.Time, frame.Curves[curveIndex].Value);
|
||||
curveIndex = GetNextCurve(frame, curveIndex);
|
||||
AddCustomCurve(binding, path, frame.Time, curve.Value, curve.InSlope, curve.OutSlope);
|
||||
}
|
||||
}
|
||||
if (frameIndex0)
|
||||
{
|
||||
frameIndex0 = false;
|
||||
curveIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDenses(IClip clip, IAnimationClipBindingConstant bindings)
|
||||
private void ProcessDenses(DenseClip dense, int preDenseCurves)
|
||||
{
|
||||
DenseClip dense = clip.DenseClip;
|
||||
ReadOnlySpan<float> slopeValues = [0, 0, 0, 0]; // no slopes - 0 values
|
||||
|
||||
float[] rentedArray = ArrayPool<float>.Shared.Rent(dense.SampleArray.Count);
|
||||
dense.SampleArray.CopyTo(rentedArray);
|
||||
ReadOnlySpan<float> curveValues = new ReadOnlySpan<float>(rentedArray, 0, dense.SampleArray.Count);
|
||||
ReadOnlySpan<float> curveValues = new(rentedArray, 0, dense.SampleArray.Count);
|
||||
|
||||
ReadOnlySpan<float> slopeValues = [0, 0, 0, 0]; // no slopes - 0 values
|
||||
|
||||
int streamCount = clip.StreamedClip.CurveCount();
|
||||
for (int frameIndex = 0; frameIndex < dense.FrameCount; frameIndex++)
|
||||
{
|
||||
float time = frameIndex / dense.SampleRate;
|
||||
float time = frameIndex / dense.SampleRate + dense.BeginTime;
|
||||
int frameOffset = frameIndex * (int)dense.CurveCount;
|
||||
for (int curveIndex = 0; curveIndex < dense.CurveCount;)
|
||||
{
|
||||
int index = streamCount + curveIndex;
|
||||
IGenericBinding binding = bindings.FindBinding(index);
|
||||
int index = preDenseCurves + curveIndex;
|
||||
IGenericBinding binding = GetBinding(index);
|
||||
string path = GetCurvePath(binding.Path);
|
||||
int framePosition = frameOffset + curveIndex;
|
||||
if (binding.IsTransform())
|
||||
@ -172,7 +182,7 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCustomCurve(bindings, binding, path, time, dense.SampleArray[framePosition]);
|
||||
AddCustomCurve(binding, path, time, dense.SampleArray[framePosition]);
|
||||
curveIndex++;
|
||||
}
|
||||
}
|
||||
@ -180,25 +190,22 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
ArrayPool<float>.Shared.Return(rentedArray);
|
||||
}
|
||||
|
||||
private void ProcessConstant(IClip clip, IConstantClip constant, IAnimationClipBindingConstant bindings, float lastFrame)
|
||||
private void ProcessConstant(IConstantClip constant, int preConstantCurves, float lastFrame)
|
||||
{
|
||||
float[] rentedArray = ArrayPool<float>.Shared.Rent(constant.Data.Count);
|
||||
constant.Data.CopyTo(rentedArray);
|
||||
ReadOnlySpan<float> curveValues = new ReadOnlySpan<float>(rentedArray, 0, constant.Data.Count);
|
||||
ReadOnlySpan<float> curveValues = new(rentedArray, 0, constant.Data.Count);
|
||||
|
||||
ReadOnlySpan<float> slopeValues = [0, 0, 0, 0]; // no slopes - 0 values
|
||||
|
||||
int streamCount = clip.StreamedClip.CurveCount();
|
||||
int denseCount = (int)clip.DenseClip.CurveCount;
|
||||
|
||||
// only first and last frames
|
||||
float time = 0.0f;
|
||||
for (int i = 0; i < 2; i++, time += lastFrame)
|
||||
float time = 0f;
|
||||
int Is1or2Frames = time == lastFrame ? 1 : 2; // a constant curve can be made with 1 or 2 frames
|
||||
for (int i = 0; i < Is1or2Frames; i++, time += lastFrame)
|
||||
{
|
||||
for (int curveIndex = 0; curveIndex < constant.Data.Count;)
|
||||
{
|
||||
int index = streamCount + denseCount + curveIndex;
|
||||
IGenericBinding binding = bindings.FindBinding(index);
|
||||
int index = preConstantCurves + curveIndex;
|
||||
IGenericBinding binding = GetBinding(index);
|
||||
string path = GetCurvePath(binding.Path);
|
||||
if (binding.IsTransform())
|
||||
{
|
||||
@ -212,7 +219,7 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCustomCurve(bindings, binding, path, time, constant.Data[curveIndex]);
|
||||
AddCustomCurve(binding, path, time, constant.Data[curveIndex]);
|
||||
curveIndex++;
|
||||
}
|
||||
}
|
||||
@ -220,35 +227,24 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
ArrayPool<float>.Shared.Return(rentedArray);
|
||||
}
|
||||
|
||||
private void AddCustomCurve(IAnimationClipBindingConstant bindings, IGenericBinding binding, string path, float time, float value)
|
||||
private void AddCustomCurve(IGenericBinding binding, string path, float time, float value, float inTangent = 0, float outTangent = 0)
|
||||
{
|
||||
bool ProcessStreams_frameIndex0 = time != FrameIndex0Time;
|
||||
switch ((BindingCustomType)binding.CustomType)
|
||||
{
|
||||
case BindingCustomType.AnimatorMuscle:
|
||||
if (ProcessStreams_frameIndex0)
|
||||
{
|
||||
AddAnimatorMuscleCurve(binding, time, value);
|
||||
}
|
||||
|
||||
AddAnimatorMuscleCurve(binding, time, value, inTangent, outTangent);
|
||||
break;
|
||||
|
||||
default:
|
||||
string attribute = m_customCurveResolver.ToAttributeName((BindingCustomType)binding.CustomType, binding.Attribute, path);
|
||||
CurveData curve = new(path, attribute, binding.GetClassID(), binding.Script.TryGetAsset(m_clip.Collection));
|
||||
if (binding.IsPPtrCurve())
|
||||
{
|
||||
if (!ProcessStreams_frameIndex0)
|
||||
{
|
||||
time = 0.0f;
|
||||
}
|
||||
|
||||
CurveData curve = new CurveData(path, attribute, binding.GetClassID(), binding.Script.TryGetAsset(m_clip.Collection));
|
||||
AddPPtrKeyframe(curve, bindings, time, (int)value);
|
||||
AddPPtrKeyframe(curve, time, (int)value);
|
||||
}
|
||||
else if (ProcessStreams_frameIndex0)
|
||||
else
|
||||
{
|
||||
CurveData curve = new CurveData(path, attribute, binding.GetClassID(), binding.Script.TryGetAsset(m_clip.Collection));
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
AddFloatKeyframe(curve, time, value, inTangent, outTangent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -411,24 +407,20 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDefaultCurve(IGenericBinding binding, string path, float time, float value)
|
||||
private void AddDefaultCurve(IGenericBinding binding, string path, float time, float value, float inTangent = 0, float outTangent = 0)
|
||||
{
|
||||
switch (binding.GetClassID())
|
||||
{
|
||||
case ClassIDType.GameObject:
|
||||
{
|
||||
AddGameObjectCurve(binding, path, time, value);
|
||||
}
|
||||
AddGameObjectCurve(binding, path, time, value);
|
||||
break;
|
||||
|
||||
case ClassIDType.MonoBehaviour:
|
||||
{
|
||||
AddScriptCurve(binding, path, time, value);
|
||||
}
|
||||
AddScriptCurve(binding, path, time, value, inTangent, outTangent);
|
||||
break;
|
||||
|
||||
default:
|
||||
AddEngineCurve(binding, path, time, value);
|
||||
AddEngineCurve(binding, path, time, value, inTangent, outTangent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -437,19 +429,18 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
{
|
||||
if (GameObject.TryGetPath(binding.Attribute, out string? propertyName))
|
||||
{
|
||||
CurveData curve = new CurveData(path, propertyName, ClassIDType.GameObject);
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
return;
|
||||
CurveData curve = new(path, propertyName, ClassIDType.GameObject);
|
||||
AddFloatKeyframe(curve, time, value, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// that means that dev exported animation clip with missing component
|
||||
CurveData curve = new CurveData(path, GetReversedPath(MissedPropertyPrefix, binding.Attribute), ClassIDType.GameObject);
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
CurveData curve = new(path, GetReversedPath(MissedPropertyPrefix, binding.Attribute), ClassIDType.GameObject);
|
||||
AddFloatKeyframe(curve, time, value, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddScriptCurve(IGenericBinding binding, string path, float time, float value)
|
||||
private void AddScriptCurve(IGenericBinding binding, string path, float time, float value, float inTangent, float outTangent)
|
||||
{
|
||||
if (binding.Script.TryGetAsset(m_clip.Collection) is IMonoScript script)
|
||||
{
|
||||
@ -461,33 +452,45 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
propertyName = GetReversedPath(ScriptPropertyPrefix, binding.Attribute);
|
||||
}
|
||||
|
||||
CurveData curve = new CurveData(path, propertyName, ClassIDType.MonoBehaviour, binding.Script.TryGetAsset(m_clip.Collection));
|
||||
CurveData curve = new(path, propertyName, ClassIDType.MonoBehaviour, binding.Script.TryGetAsset(m_clip.Collection));
|
||||
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
}
|
||||
|
||||
private void AddEngineCurve(IGenericBinding binding, string path, float time, float value)
|
||||
{
|
||||
if (!FieldHashes.TryGetPath(binding.GetClassID(), binding.Attribute, out string? propertyName))
|
||||
if (binding.IsPPtrCurve())
|
||||
{
|
||||
CurveData curve = new(path, GetReversedPath(TypeTreePropertyPrefix, binding.Attribute), binding.GetClassID());
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
AddPPtrKeyframe(curve, time, (int)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurveData curve = new(path, propertyName, binding.GetClassID());
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
AddFloatKeyframe(curve, time, value, inTangent, outTangent);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAnimatorMuscleCurve(IGenericBinding binding, float time, float value)
|
||||
private void AddEngineCurve(IGenericBinding binding, string path, float time, float value, float inTangent, float outTangent)
|
||||
{
|
||||
string attributeString = HumanoidMuscleTypeExtensions.ToAttributeString(binding.GetHumanoidMuscle(Version));
|
||||
CurveData curve = new CurveData(string.Empty, attributeString, ClassIDType.Animator);
|
||||
AddFloatKeyframe(curve, time, value);
|
||||
if (!FieldHashes.TryGetPath(binding.GetClassID(), binding.Attribute, out string? propertyName))
|
||||
{
|
||||
propertyName = GetReversedPath(TypeTreePropertyPrefix, binding.Attribute);
|
||||
}
|
||||
|
||||
CurveData curve = new(path, propertyName, binding.GetClassID());
|
||||
|
||||
if (binding.IsPPtrCurve())
|
||||
{
|
||||
AddPPtrKeyframe(curve, time, (int)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddFloatKeyframe(curve, time, value, inTangent, outTangent);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFloatKeyframe(in CurveData curveData, float time, float value)
|
||||
private void AddAnimatorMuscleCurve(IGenericBinding binding, float time, float value, float inTangent, float outTangent)
|
||||
{
|
||||
string attributeString = HumanoidMuscleTypeExtensions.ToAttributeString(binding.GetHumanoidMuscle(Version));
|
||||
CurveData curve = new(string.Empty, attributeString, ClassIDType.Animator);
|
||||
AddFloatKeyframe(curve, time, value, inTangent, outTangent);
|
||||
}
|
||||
|
||||
private void AddFloatKeyframe(in CurveData curveData, float time, float value, float inTangent, float outTangent)
|
||||
{
|
||||
if (!m_floats.TryGetValue(curveData, out IFloatCurve? curve))
|
||||
{
|
||||
@ -502,10 +505,10 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
}
|
||||
|
||||
IKeyframe_Single floatKey = curve.Curve.Curve.AddNew();
|
||||
floatKey.SetValues(Version, time, value, DefaultFloatWeight);
|
||||
floatKey.SetValues(Version, time, value, inTangent, outTangent, DefaultFloatWeight);
|
||||
}
|
||||
|
||||
private void AddPPtrKeyframe(in CurveData curveData, IAnimationClipBindingConstant bindings, float time, int index)
|
||||
private void AddPPtrKeyframe(in CurveData curveData, float time, int index)
|
||||
{
|
||||
if (!m_pptrs.TryGetValue(curveData, out IPPtrCurve? curve))
|
||||
{
|
||||
@ -524,41 +527,62 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
m_pptrs.Add(curveData, curve);
|
||||
}
|
||||
|
||||
IPPtr_Object value = bindings.PptrCurveMapping[index];
|
||||
IPPtr_Object value = ClipBindingConstant.PptrCurveMapping[index];
|
||||
IPPtrKeyframe key = curve.Curve.AddNew();
|
||||
key.Time = time;
|
||||
key.Value.CopyValues(value, new PPtrConverter(m_clip));
|
||||
}
|
||||
|
||||
private static void GetPreviousFrame(IReadOnlyList<StreamedFrame> streamFrames, int curveID, int currentFrame, out int frameIndex, out int curveIndex)
|
||||
private IGenericBinding GetBinding(int index)
|
||||
{
|
||||
for (frameIndex = currentFrame - 1; frameIndex >= 0; frameIndex--)
|
||||
if (m_bindingsCache.TryGetValue(index, out IGenericBinding? binding))
|
||||
{
|
||||
StreamedFrame frame = streamFrames[frameIndex];
|
||||
for (curveIndex = 0; curveIndex < frame.Curves.Length; curveIndex++)
|
||||
return binding;
|
||||
}
|
||||
int curves = 0;
|
||||
AccessListBase<IGenericBinding> bindings = ClipBindingConstant.GenericBindings;
|
||||
for (int i = 0; i < bindings.Count; i++)
|
||||
{
|
||||
binding = bindings[i];
|
||||
if (binding.GetClassID() == ClassIDType.Transform)
|
||||
{
|
||||
StreamedCurveKey curve = frame.Curves[curveIndex];
|
||||
curves += binding.TransformType().GetDimension();
|
||||
}
|
||||
else
|
||||
{
|
||||
curves += 1;
|
||||
}
|
||||
if (curves > index)
|
||||
{
|
||||
m_bindingsCache[index] = binding;
|
||||
if (binding.IsTransform() && binding.TransformType().GetDimension() < 1)
|
||||
{
|
||||
// If an animation was malformed, this avoids the possibility of an infinite FOR loop when processing Transform bindings
|
||||
throw new IndexOutOfRangeException("Transform AnimationCurve can't have Dimension less than 1.");
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
throw new ArgumentException($"Binding with index {index} hasn't been found", nameof(index));
|
||||
}
|
||||
|
||||
private static bool TryGetNextFrame(IReadOnlyList<StreamedFrame> streamedFrames, int currentFrame, int curveID, [MaybeNullWhen(false)] out StreamedFrame nextFrame, out int curveIdx)
|
||||
{
|
||||
for (int frameIndex = currentFrame + 1; frameIndex < streamedFrames.Count; frameIndex++)
|
||||
{
|
||||
nextFrame = streamedFrames[frameIndex];
|
||||
for (curveIdx = 0; curveIdx < nextFrame.Curves.Length; curveIdx++)
|
||||
{
|
||||
StreamedCurveKey curve = nextFrame.Curves[curveIdx];
|
||||
if (curve.Index == curveID)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Exception($"There is no curve with index {curveID} in any of previous frames");
|
||||
}
|
||||
|
||||
private static int GetNextCurve(StreamedFrame frame, int currentCurve)
|
||||
{
|
||||
StreamedCurveKey curve = frame.Curves[currentCurve];
|
||||
int i = currentCurve + 1;
|
||||
for (; i < frame.Curves.Length; i++)
|
||||
{
|
||||
if (frame.Curves[i].Index != curve.Index)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
nextFrame = null;
|
||||
curveIdx = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetCurvePath(uint hash)
|
||||
@ -580,7 +604,7 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
AssetCollection collection = m_clip.Collection;
|
||||
CopyDataToBuffer(clip, collection, buffer);
|
||||
|
||||
EndianSpanReader reader = new EndianSpanReader(buffer, collection.EndianType);
|
||||
EndianSpanReader reader = new(buffer, collection.EndianType);
|
||||
while (reader.Position < reader.Length)
|
||||
{
|
||||
StreamedFrame frame = new();
|
||||
@ -619,6 +643,34 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateLastConstantTime(IReadOnlyList<StreamedFrame> streamedFrames, float stopTime)
|
||||
{
|
||||
if (stopTime == 0f || streamedFrames.Count <= 1) // streamedFrames[streamedFrames.Count-1] has dummy Infinity Time
|
||||
{
|
||||
return stopTime;
|
||||
}
|
||||
float sampleRate = m_clip.SampleRate_C74;
|
||||
// using frame indexes for precise calculation
|
||||
int lastFrame = (int)float.Round(stopTime * sampleRate);
|
||||
StreamedFrame lastStreamedFrame = streamedFrames[streamedFrames.Count - 2];
|
||||
// careful of streamedFrames[0], has Time=float.MinValue
|
||||
int lastSFFrame = lastStreamedFrame.Time > 0 ? (int)float.Round(lastStreamedFrame.Time * sampleRate) : 0;
|
||||
if (lastFrame - lastSFFrame == 1)
|
||||
{
|
||||
// check if last StreamedFrame has a PPtrCurve, because it adds 1 extra frame to MuscleClip.StopTime
|
||||
foreach (StreamedCurveKey curve in lastStreamedFrame.Curves)
|
||||
{
|
||||
IGenericBinding binding = GetBinding(curve.Index);
|
||||
if (binding.IsPPtrCurve())
|
||||
{
|
||||
// careful of streamedFrames[0], has Time=float.MinValue
|
||||
return lastStreamedFrame.Time > 0 ? lastStreamedFrame.Time : 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
return stopTime;
|
||||
}
|
||||
|
||||
private static string GetReversedPath([ConstantExpected] string prefix, uint hash)
|
||||
{
|
||||
return Crc32Algorithm.ReverseAscii(hash, $"{prefix}0x{hash:X}_");
|
||||
@ -631,11 +683,6 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
private const string ScriptPropertyPrefix = "script_";
|
||||
private const string TypeTreePropertyPrefix = "typetree_";
|
||||
|
||||
/// <summary>
|
||||
/// Used to detect when a StreamedFrame is from index 0.
|
||||
/// </summary>
|
||||
private const float FrameIndex0Time = float.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The default weight for a keyframe.
|
||||
/// </summary>
|
||||
@ -655,5 +702,7 @@ namespace AssetRipper.Processing.AnimationClips
|
||||
private readonly PathChecksumCache m_checksumCache;
|
||||
private readonly IAnimationClip m_clip;
|
||||
private readonly CustomCurveResolver m_customCurveResolver;
|
||||
private readonly Dictionary<int, IGenericBinding> m_bindingsCache = new(); //cache results from GetBinding(curveID)
|
||||
private IAnimationClipBindingConstant ClipBindingConstant => m_clip.ClipBindingConstant_C74!; //This class only supports 4.3 and newer.
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace AssetRipper.Processing.AnimationClips
|
||||
{
|
||||
public sealed partial class CustomCurveResolver
|
||||
public partial struct CustomCurveResolver
|
||||
{
|
||||
public CustomCurveResolver(IAnimationClip clip)
|
||||
{
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
using AssetRipper.IO.Endian;
|
||||
using AssetRipper.SourceGenerated.Subclasses.AnimationCurve_Single;
|
||||
using AssetRipper.SourceGenerated.Subclasses.Keyframe_Single;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AssetRipper.Processing.AnimationClips.Editor
|
||||
@ -12,96 +10,98 @@ namespace AssetRipper.Processing.AnimationClips.Editor
|
||||
{
|
||||
Index = index;
|
||||
Coefficient = coefficient;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static StreamedCurveKey CalculateStreamedFrame(IAnimationCurve_Single curve, int lhsIndex, int rhsIndex, float timeOffset)
|
||||
{
|
||||
IReadOnlyList<IKeyframe_Single> keyframes = curve.Curve;
|
||||
IKeyframe_Single lhs = keyframes[lhsIndex];
|
||||
int curveKeyIndex = lhsIndex;
|
||||
IKeyframe_Single rhs = keyframes[rhsIndex];
|
||||
float frameTime = lhs.Time + timeOffset;
|
||||
//TimeEnd = rhs.Time + timeOffset;
|
||||
float deltaTime = rhs.Time - lhs.Time;
|
||||
if (deltaTime < 0.00009999999747378752)
|
||||
{
|
||||
deltaTime = 0.000099999997f;
|
||||
}
|
||||
float deltaValue = rhs.Value - lhs.Value;
|
||||
float inverseTime = 1.0f / (deltaTime * deltaTime);
|
||||
float outTangent = lhs.OutSlope * deltaTime;
|
||||
float inTangent = rhs.InSlope * deltaTime;
|
||||
float curveKeyCoefX = (inTangent + outTangent - deltaValue - deltaValue) * inverseTime / deltaTime;
|
||||
float curveKeyCoefY = inverseTime * (deltaValue + deltaValue + deltaValue - outTangent - outTangent - inTangent);
|
||||
float curveKeyCoefZ = lhs.OutSlope;
|
||||
float curveKeyValue = lhs.Value;
|
||||
if (lhs.OutSlope == float.PositiveInfinity || rhs.InSlope == float.PositiveInfinity)
|
||||
{
|
||||
curveKeyCoefX = 0.0f;
|
||||
curveKeyCoefY = 0.0f;
|
||||
curveKeyCoefZ = 0.0f;
|
||||
curveKeyValue = lhs.Value;
|
||||
}
|
||||
Vector3 curveKeyCoef = new Vector3(curveKeyCoefX, curveKeyCoefY, curveKeyCoefZ);
|
||||
StreamedCurveKey curveKey = new StreamedCurveKey(curveKeyIndex, curveKeyCoef, curveKeyValue);
|
||||
return curveKey;
|
||||
RightSidedLimit = LeftSidedLimit = value;
|
||||
}
|
||||
|
||||
public void Read(ref EndianSpanReader reader)
|
||||
{
|
||||
Index = reader.ReadInt32();
|
||||
Coefficient = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
Value = reader.ReadSingle();
|
||||
Coefficient = new(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||
RightSidedLimit = reader.ReadSingle();
|
||||
LeftSidedLimit = RightSidedLimit;
|
||||
}
|
||||
|
||||
public void CalculateSlopes(float time, float nextTime, StreamedCurveKey nextKey)
|
||||
{
|
||||
if (Coefficient != default) // != (0,0,0) => regular slopes
|
||||
{
|
||||
OutSlope = Coefficient.Z;
|
||||
float deltaX = nextTime - time;
|
||||
float _CoeffX = Coefficient.X * deltaX * deltaX;
|
||||
float _CoeffY = Coefficient.Y * deltaX;
|
||||
nextKey.InSlope = 3 * _CoeffX + 2 * _CoeffY + Coefficient.Z; // may need some kind of rounding if wanting to get an inSlope of exactly 0
|
||||
//nextKey.inSlope = float.Round(nextKey.inSlope, 4); // amount of decimal places may be more dynamic
|
||||
if (nextKey.Coefficient == default) // calculate LSL only if next slope calculation needs it
|
||||
{
|
||||
nextKey.LeftSidedLimit = (_CoeffX + _CoeffY + Coefficient.Z) * deltaX + RightSidedLimit;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (RightSidedLimit != LeftSidedLimit)
|
||||
{
|
||||
// check percentage difference
|
||||
float diff = RightSidedLimit > LeftSidedLimit ? RightSidedLimit - LeftSidedLimit : LeftSidedLimit - RightSidedLimit;
|
||||
float AbsVal = RightSidedLimit < 0 ? -RightSidedLimit : RightSidedLimit;
|
||||
float AbsLSV = LeftSidedLimit < 0 ? -LeftSidedLimit : LeftSidedLimit;
|
||||
float div = AbsVal > AbsLSV ? AbsVal : AbsLSV;
|
||||
const float ROUNDING_ERROR = 1e-5f; // arbitrary small value
|
||||
// normally the difference between Right and Left Sided Limits should be "big"/much greater than a rounding error.
|
||||
// if the check fails and INCORRECTLY skips this IF block, the next statements
|
||||
// will still produce a good approximation for the expected curve
|
||||
if (diff/div > ROUNDING_ERROR)
|
||||
{
|
||||
OutSlope = float.NegativeInfinity;
|
||||
nextKey.InSlope = 0f;
|
||||
nextKey.LeftSidedLimit = RightSidedLimit;
|
||||
RightSidedLimit = LeftSidedLimit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (RightSidedLimit == nextKey.RightSidedLimit)
|
||||
{
|
||||
OutSlope = 0f;
|
||||
nextKey.InSlope = 0f;
|
||||
nextKey.LeftSidedLimit = RightSidedLimit;
|
||||
return;
|
||||
}
|
||||
OutSlope = float.PositiveInfinity;
|
||||
nextKey.InSlope = 0f;
|
||||
// don't do nextKey.LeftSidedLimit=RightSidedLimit here, because having 2 consecutive keys
|
||||
// with outSlope +Inf and -Inf is illegal (Editor corrects it)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate value between two KeyframeTpl Float at given time
|
||||
/// Index for its GenericBinding inside AnimationClip's BindingConstant.
|
||||
/// </summary>
|
||||
public int Index { get; private set; }
|
||||
/// <summary>
|
||||
/// Coefficients of the Cubic Bezier Equation between this Key and the next, for the same Curve.
|
||||
/// </summary>
|
||||
public Vector3 Coefficient { get; private set; }
|
||||
/// <summary>
|
||||
/// Value of the binded property during this keyframe.
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// This value could change after Slope calculation.
|
||||
/// </remarks>
|
||||
public float Value { get => RightSidedLimit; private set => RightSidedLimit = value; }
|
||||
/// <summary>
|
||||
/// Value of the key, when approached from its right side.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This calculates a unit interval cubic Hermite spline.<br />
|
||||
/// <see href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline"/><br />
|
||||
/// <see href="https://en.wikipedia.org/wiki/Hermite_interpolation"/>
|
||||
/// outSlope=-Infinity creates a discontinuity on the curve,
|
||||
/// making the CurveKey value differ from its left side.
|
||||
/// </remarks>
|
||||
/// <param name="deltaTimeFraction">(time - leftTime) / (rightTime - leftTime)</param>
|
||||
/// <param name="leftValue">lhs.Value</param>
|
||||
/// <param name="leftTangent">lhs.OutSlope * (rightTime - leftTime)</param>
|
||||
/// <param name="rightValue">rhs.Value</param>
|
||||
/// <param name="rightTangent">rhs.OutSlope * (rightTime - leftTime)</param>
|
||||
/// <returns>Value between two keyframes</returns>
|
||||
public static float HermiteInterpolate(float deltaTimeFraction, float leftValue, float leftTangent, float rightValue, float rightTangent)
|
||||
{
|
||||
float tt = deltaTimeFraction * deltaTimeFraction;
|
||||
float ttt = tt * deltaTimeFraction;
|
||||
float tttx2 = ttt * 2.0f;
|
||||
float ttx3 = tt * 3.0f;
|
||||
float v1 = (deltaTimeFraction + ttt - 2.0f * tt) * leftTangent + (tttx2 - ttx3 + 1.0f) * leftValue;
|
||||
float v2 = (ttt - tt) * rightTangent;
|
||||
float v3 = ttx3 - tttx2;
|
||||
return v1 + v2 + v3 * rightValue;
|
||||
}
|
||||
|
||||
public float CalculateNextInSlope(float deltaTime, float nextValue)
|
||||
{
|
||||
if (deltaTime >= 3.40282347e38f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (deltaTime < 0.00009999999747378752)
|
||||
{
|
||||
deltaTime = 0.000099999997f;
|
||||
}
|
||||
float deltaValue = nextValue - Value;
|
||||
float inverseTime = 1.0f / (deltaTime * deltaTime);
|
||||
float outTangent = OutSlope * deltaTime;
|
||||
float inTangent = deltaValue + deltaValue + deltaValue - outTangent - outTangent - Coefficient.Y / inverseTime;
|
||||
return inTangent / deltaTime;
|
||||
}
|
||||
|
||||
public float OutSlope => Coefficient.Z;
|
||||
|
||||
public int Index { get; set; }
|
||||
public Vector3 Coefficient { get; set; }
|
||||
public float Value { get; set; }
|
||||
private float RightSidedLimit { get; set; }
|
||||
/// <summary>
|
||||
/// Value of the key, when approached from its left side
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// outSlope=-Infinity creates a discontinuity on the curve,
|
||||
/// making the CurveKey value differ from its right side.
|
||||
/// </remarks>
|
||||
private float LeftSidedLimit { get; set; }
|
||||
public float InSlope { get; private set; }
|
||||
public float OutSlope { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
using AssetRipper.Assets.Generics;
|
||||
using AssetRipper.SourceGenerated.Extensions.Enums.AnimationClip.GenericBinding;
|
||||
using AssetRipper.SourceGenerated.Subclasses.AnimationClipBindingConstant;
|
||||
using AssetRipper.SourceGenerated.Subclasses.GenericBinding;
|
||||
|
||||
namespace AssetRipper.SourceGenerated.Extensions
|
||||
{
|
||||
public static class AnimationClipBindingConstantExtensions
|
||||
{
|
||||
public static IGenericBinding FindBinding(this IAnimationClipBindingConstant constant, int index)
|
||||
{
|
||||
int curves = 0;
|
||||
AccessListBase<IGenericBinding> bindings = constant.GenericBindings;
|
||||
|
||||
for (int i = 0; i < bindings.Count; i++)
|
||||
{
|
||||
IGenericBinding gb = bindings[i];
|
||||
if (gb.GetClassID() == ClassIDType.Transform)
|
||||
{
|
||||
curves += gb.TransformType().GetDimension();
|
||||
}
|
||||
else
|
||||
{
|
||||
curves += 1;
|
||||
}
|
||||
|
||||
if (curves > index)
|
||||
{
|
||||
return gb;
|
||||
}
|
||||
}
|
||||
throw new ArgumentException($"Binding with index {index} hasn't been found", nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user