avatar/Assets/VRCFury/Scripts/Editor/VF/Builder/ClipBuilder.cs

232 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace VF.Builder {
public class ClipBuilder {
//private static float ONE_FRAME = 1 / 60f;
private readonly GameObject baseObject;
public ClipBuilder(GameObject baseObject) {
this.baseObject = baseObject;
}
public static ObjectReferenceKeyframe[] OneFrame(Object obj) {
var f1 = new ObjectReferenceKeyframe {
time = 0,
value = obj
};
var f2 = new ObjectReferenceKeyframe {
time = 1/60f,
value = obj
};
return new[]{ f1, f2 };
}
public static AnimationCurve OneFrame(float value) {
return AnimationCurve.Constant(0, 0, value);
}
public static AnimationCurve FromFrames(params Keyframe[] keyframes) {
for (var i = 0; i < keyframes.Length; i++) {
keyframes[i].time /= 60f;
}
return new AnimationCurve(keyframes);
}
public static AnimationCurve FromSeconds(params Keyframe[] keyframes) {
return new AnimationCurve(keyframes);
}
public static int GetLengthInFrames(AnimationClip clip) {
var maxTime = AnimationUtility.GetCurveBindings(clip)
.Select(binding => AnimationUtility.GetEditorCurve(clip, binding))
.Select(curve => curve.keys.Max(key => key.time))
.DefaultIfEmpty(0)
.Max();
maxTime = Math.Max(maxTime, AnimationUtility.GetObjectReferenceCurveBindings(clip)
.Select(binding => AnimationUtility.GetObjectReferenceCurve(clip, binding))
.Select(curve => curve.Max(key => key.time))
.DefaultIfEmpty(0)
.Max());
return (int)Math.Round(maxTime / 60f);
}
public void MergeSingleFrameClips(AnimationClip target, params Tuple<float, AnimationClip>[] sources) {
foreach (var binding in sources.SelectMany(tuple => AnimationUtility.GetCurveBindings(tuple.Item2)).Distinct()) {
var exists = AnimationUtility.GetFloatValue(baseObject, binding, out var defaultValue);
if (!exists) continue;
var outputCurve = new AnimationCurve();
foreach (var source in sources) {
var sourceCurve = AnimationUtility.GetEditorCurve(source.Item2, binding);
if (sourceCurve.keys.Length == 1) {
outputCurve.AddKey(new Keyframe(source.Item1, sourceCurve.keys[0].value, 0f, 0f));
} else if (sourceCurve.keys.Length == 0) {
outputCurve.AddKey(new Keyframe(source.Item1, defaultValue, 0f, 0f));
} else {
throw new Exception("Source curve didn't contain exactly 1 key: " + sourceCurve.keys.Length);
}
}
AnimationUtility.SetEditorCurve(target, binding, outputCurve);
}
foreach (var binding in sources.SelectMany(tuple => AnimationUtility.GetObjectReferenceCurveBindings(tuple.Item2)).Distinct()) {
var exists = AnimationUtility.GetObjectReferenceValue(baseObject, binding, out var defaultValue);
if (!exists) continue;
var outputCurve = new List<ObjectReferenceKeyframe>();
foreach (var source in sources) {
var sourceCurve = AnimationUtility.GetObjectReferenceCurve(source.Item2, binding);
if (sourceCurve.Length == 1) {
outputCurve.Add(new ObjectReferenceKeyframe { time = source.Item1, value = sourceCurve[0].value });
} else if (sourceCurve.Length == 0) {
outputCurve.Add(new ObjectReferenceKeyframe { time = source.Item1, value = defaultValue });
} else {
throw new Exception("Source curve didn't contain exactly 1 key: " + sourceCurve.Length);
}
}
AnimationUtility.SetObjectReferenceCurve(target, binding, outputCurve.ToArray());
}
}
public void Enable(AnimationClip clip, GameObject obj, bool active = true) {
clip.SetCurve(GetPath(obj), typeof(GameObject), "m_IsActive", OneFrame(active ? 1 : 0));
}
public void Scale(AnimationClip clip, GameObject obj, AnimationCurve curve) {
foreach (var axis in new[]{"x","y","z"}) {
clip.SetCurve(GetPath(obj), typeof(Transform), "m_LocalScale." + axis, curve);
}
}
public void Scale(AnimationClip clip, GameObject obj, float x, float y, float z) {
clip.SetCurve(GetPath(obj), typeof(Transform), "m_LocalScale.x", OneFrame(x));
clip.SetCurve(GetPath(obj), typeof(Transform), "m_LocalScale.y", OneFrame(y));
clip.SetCurve(GetPath(obj), typeof(Transform), "m_LocalScale.z", OneFrame(z));
}
public void BlendShape(AnimationClip clip, SkinnedMeshRenderer skin, string blendShape, AnimationCurve curve) {
clip.SetCurve(GetPath(skin.gameObject), typeof(SkinnedMeshRenderer), "blendShape." + blendShape, curve);
}
public void BlendShape(AnimationClip clip, SkinnedMeshRenderer skin, string blendShape, float value) {
BlendShape(clip, skin, blendShape, OneFrame(value));
}
public void Material(AnimationClip clip, GameObject obj, int matSlot, Material mat) {
foreach (var renderer in obj.GetComponents<Renderer>()) {
Material(clip, renderer, matSlot, mat);
}
}
private void Material(AnimationClip clip, Renderer renderer, int matSlot, Material mat) {
var binding = new EditorCurveBinding {
path = GetPath(renderer.gameObject),
propertyName = "m_Materials.Array.data[" + matSlot + "]",
type = renderer.GetType()
};
AnimationUtility.SetObjectReferenceCurve(clip, binding, new[] {
new ObjectReferenceKeyframe() {
time = 0,
value = mat
}
});
}
public string GetPath(GameObject obj) {
return GetPath(obj.transform);
}
public string GetPath(Transform transform) {
var parts = new List<string>();
var current = transform;
while (current != baseObject.transform) {
if (current == null) {
throw new Exception("Animated object wasn't a child of the root GameObject: " + string.Join("/", parts));
}
parts.Insert(0, current.name);
current = current.parent;
}
return string.Join("/", parts);
}
public static bool IsEmptyMotion(Motion motion, GameObject avatarRoot = null) {
var isEmpty = true;
AnimatorIterator.ForEachClip(motion, clip => {
isEmpty &= IsEmptyClip(clip, avatarRoot);
});
return isEmpty;
}
private static bool IsEmptyClip(AnimationClip clip, GameObject avatarRoot = null) {
if (!IsStaticClip(clip)) return false;
foreach (var binding in AnimationUtility.GetCurveBindings(clip)) {
if (!avatarRoot) return false;
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve.length == 0) continue;
var val = curve.keys[0].value;
var exists = AnimationUtility.GetFloatValue(avatarRoot, binding, out var existingValue);
if (!exists || val != existingValue) return false;
}
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) {
if (!avatarRoot) return false;
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
if (curve.Length == 0) continue;
var val = curve[0].value;
var exists = AnimationUtility.GetObjectReferenceValue(avatarRoot, binding, out var existingValue);
if (!exists || val != existingValue) return false;
}
return true;
}
public static bool IsStaticMotion(Motion motion) {
var isStatic = true;
AnimatorIterator.ForEachClip(motion, clip => {
isStatic &= IsStaticClip(clip);
});
return isStatic;
}
private static bool IsStaticClip(AnimationClip clip) {
var isStatic = true;
foreach (var binding in AnimationUtility.GetCurveBindings(clip)) {
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve.keys.All(key => key.time != 0)) isStatic = false;
if (curve.keys.Select(k => k.value).Distinct().Count() > 1) isStatic = false;
}
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) {
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
if (curve.All(key => key.time != 0)) isStatic = false;
if (curve.Select(k => k.value).Distinct().Count() > 1) isStatic = false;
}
return isStatic;
}
public static Tuple<AnimationClip, AnimationClip> SplitRangeClip(Motion motion) {
if (!(motion is AnimationClip clip)) return null;
var times = new HashSet<float>();
foreach (var binding in AnimationUtility.GetCurveBindings(clip)) {
times.UnionWith(AnimationUtility.GetEditorCurve(clip, binding).keys.Select(key => key.time));
}
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) {
times.UnionWith(AnimationUtility.GetObjectReferenceCurve(clip, binding).Select(key => key.time));
}
if (times.Count != 2) return null;
times.Remove(0);
if (times.Count != 1) return null;
var startClip = new AnimationClip();
var endClip = new AnimationClip();
foreach (var binding in AnimationUtility.GetCurveBindings(clip)) {
foreach (var key in AnimationUtility.GetEditorCurve(clip, binding).keys) {
AnimationUtility.SetEditorCurve(key.time == 0 ? startClip : endClip, binding, OneFrame(key.value));
}
}
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) {
foreach (var key in AnimationUtility.GetObjectReferenceCurve(clip, binding)) {
AnimationUtility.SetObjectReferenceCurve(key.time == 0 ? startClip : endClip, binding, OneFrame(key.value));
}
}
return Tuple.Create(startClip, endClip);
}
}
}