123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using Newtonsoft.Json;
- using Siccity.GLTFUtility.Converters;
- using UnityEngine;
- using UnityEngine.Scripting;
- using Newtonsoft.Json.Linq;
- namespace Siccity.GLTFUtility {
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#animation
- /// <summary> Contains info for a single animation clip </summary>
- [Preserve] public class GLTFAnimation {
- /// <summary> Connects the output values of the key frame animation to a specific node in the hierarchy </summary>
- [JsonProperty(Required = Required.Always)] public Channel[] channels;
- [JsonProperty(Required = Required.Always)] public Sampler[] samplers;
- public string name;
- public JObject extras;
- #region Classes
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#animation-sampler
- [Preserve] public class Sampler {
- /// <summary> The index of an accessor containing keyframe input values, e.g., time. </summary>
- [JsonProperty(Required = Required.Always)] public int input;
- /// <summary> The index of an accessor containing keyframe output values. </summary>
- [JsonProperty(Required = Required.Always)] public int output;
- /// <summary> Valid names include: "LINEAR", "STEP", "CUBICSPLINE" </summary>
- [JsonConverter(typeof(EnumConverter))] public InterpolationMode interpolation = InterpolationMode.LINEAR;
- }
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#channel
- /// <summary> Connects the output values of the key frame animation to a specific node in the hierarchy </summary>
- [Preserve] public class Channel {
- /// <summary> Target sampler index </summary>
- [JsonProperty(Required = Required.Always)] public int sampler;
- /// <summary> Target sampler index </summary>
- [JsonProperty(Required = Required.Always)] public Target target;
- }
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#target
- /// <summary> Identifies which node and property to animate </summary>
- [Preserve] public class Target {
- /// <summary> Target node index.</summary>
- public int? node;
- /// <summary> Which property to animate. Valid names are: "translation", "rotation", "scale", "weights" </summary>
- [JsonProperty(Required = Required.Always)] public string path;
- }
- [Preserve] public class ImportResult {
- public AnimationClip clip;
- }
- #endregion
- public ImportResult Import(GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) {
- bool multiRoots = nodes.Where(x => x.IsRoot).Count() > 1;
- ImportResult result = new ImportResult();
- result.clip = new AnimationClip();
- result.clip.name = name;
- result.clip.frameRate = importSettings.animationSettings.frameRate;
- result.clip.legacy = importSettings.animationSettings.useLegacyClips;
- if (result.clip.legacy && importSettings.animationSettings.looping)
- {
- result.clip.wrapMode = WrapMode.Loop;
- }
- for (int i = 0; i < channels.Length; i++) {
- Channel channel = channels[i];
- if (samplers.Length <= channel.sampler) {
- Debug.LogWarning($"GLTFUtility: Animation channel points to sampler at index {channel.sampler} which doesn't exist. Skipping animation clip.");
- continue;
- }
- Sampler sampler = samplers[channel.sampler];
- // Get interpolation mode
- InterpolationMode interpolationMode = importSettings.animationSettings.interpolationMode;
- if (interpolationMode == InterpolationMode.ImportFromFile) {
- interpolationMode = sampler.interpolation;
- }
- if (interpolationMode == InterpolationMode.CUBICSPLINE) Debug.LogWarning("Animation interpolation mode CUBICSPLINE not fully supported, result might look different.");
- string relativePath = "";
- GLTFNode.ImportResult node = nodes[channel.target.node.Value];
- while (node != null && !node.IsRoot) {
- if (string.IsNullOrEmpty(relativePath)) relativePath = node.transform.name;
- else relativePath = node.transform.name + "/" + relativePath;
- if (node.parent.HasValue) node = nodes[node.parent.Value];
- else node = null;
- }
- // If file has multiple root nodes, a new parent will be created for them as a final step of the import process. This parent fucks up the curve relative paths.
- // Add node.transform.name to path if there are multiple roots. This is not the most elegant fix but it works.
- // See GLTFNodeExtensions.GetRoot
- if (multiRoots) relativePath = node.transform.name + "/" + relativePath;
- System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
- float[] keyframeInput = accessors[sampler.input].ReadFloat().ToArray();
- switch (channel.target.path) {
- case "translation":
- Vector3[] pos = accessors[sampler.output].ReadVec3().ToArray();
- AnimationCurve posX = new AnimationCurve();
- AnimationCurve posY = new AnimationCurve();
- AnimationCurve posZ = new AnimationCurve();
- for (int k = 0; k < keyframeInput.Length; k++) {
- posX.AddKey(CreateKeyframe(k, keyframeInput, pos, x => -x.x, interpolationMode));
- posY.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.y, interpolationMode));
- posZ.AddKey(CreateKeyframe(k, keyframeInput, pos, x => x.z, interpolationMode));
- }
- result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", posX);
- result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", posY);
- result.clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", posZ);
- break;
- case "rotation":
- Vector4[] rot = accessors[sampler.output].ReadVec4().ToArray();
- AnimationCurve rotX = new AnimationCurve();
- AnimationCurve rotY = new AnimationCurve();
- AnimationCurve rotZ = new AnimationCurve();
- AnimationCurve rotW = new AnimationCurve();
- for (int k = 0; k < keyframeInput.Length; k++) {
- // The Animation window in Unity shows keyframes incorrectly converted to euler. This is only to deceive you. The quaternions underneath work correctly
- rotX.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.x, interpolationMode));
- rotY.AddKey(CreateKeyframe(k, keyframeInput, rot, x => -x.y, interpolationMode));
- rotZ.AddKey(CreateKeyframe(k, keyframeInput, rot, x => -x.z, interpolationMode));
- rotW.AddKey(CreateKeyframe(k, keyframeInput, rot, x => x.w, interpolationMode));
- }
- result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", rotX);
- result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", rotY);
- result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", rotZ);
- result.clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", rotW);
- break;
- case "scale":
- Vector3[] scale = accessors[sampler.output].ReadVec3().ToArray();
- AnimationCurve scaleX = new AnimationCurve();
- AnimationCurve scaleY = new AnimationCurve();
- AnimationCurve scaleZ = new AnimationCurve();
- for (int k = 0; k < keyframeInput.Length; k++) {
- scaleX.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.x, interpolationMode));
- scaleY.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.y, interpolationMode));
- scaleZ.AddKey(CreateKeyframe(k, keyframeInput, scale, x => x.z, interpolationMode));
- }
- result.clip.SetCurve(relativePath, typeof(Transform), "localScale.x", scaleX);
- result.clip.SetCurve(relativePath, typeof(Transform), "localScale.y", scaleY);
- result.clip.SetCurve(relativePath, typeof(Transform), "localScale.z", scaleZ);
- break;
- case "weights":
- GLTFNode.ImportResult skinnedMeshNode = nodes[channel.target.node.Value];
- SkinnedMeshRenderer skinnedMeshRenderer = skinnedMeshNode.transform.GetComponent<SkinnedMeshRenderer>();
- int numberOfBlendShapes = skinnedMeshRenderer.sharedMesh.blendShapeCount;
- AnimationCurve[] blendShapeCurves = new AnimationCurve[numberOfBlendShapes];
- for(int j = 0; j < numberOfBlendShapes; ++j) {
- blendShapeCurves[j] = new AnimationCurve();
- }
- float[] weights = accessors[sampler.output].ReadFloat().ToArray();
- float[] weightValues = new float[keyframeInput.Length];
- float[] previouslyKeyedValues = new float[numberOfBlendShapes];
- // Reference for my future self:
- // keyframeInput.Length = number of keyframes
- // keyframeInput[ k ] = timestamp of keyframe
- // weights.Length = number of keyframes * number of blendshapes
- // weights[ j ] = actual animated weight of a specific blend shape
- // (index into weights[] array accounts for keyframe index and blend shape index)
- for(int k = 0; k < keyframeInput.Length; ++k) {
- for(int j = 0; j < numberOfBlendShapes; ++j) {
- int weightIndex = (k * numberOfBlendShapes) + j;
- weightValues[k] = weights[weightIndex];
- bool addKey = true;
- if(importSettings.animationSettings.compressBlendShapeKeyFrames) {
- if(k == 0 || !Mathf.Approximately(weightValues[k], previouslyKeyedValues[j])) {
- if(k > 0) {
- weightValues[k-1] = previouslyKeyedValues[j];
- blendShapeCurves[j].AddKey(CreateKeyframe(k-1, keyframeInput, weightValues, x => x, interpolationMode));
- }
- addKey = true;
- previouslyKeyedValues[j] = weightValues[k];
- } else {
- addKey = false;
- }
- }
- if(addKey) {
- blendShapeCurves[j].AddKey(CreateKeyframe(k, keyframeInput, weightValues, x => x, interpolationMode));
- }
- }
- }
- for(int j = 0; j < numberOfBlendShapes; ++j) {
- string propertyName = "blendShape." + skinnedMeshRenderer.sharedMesh.GetBlendShapeName(j);
- result.clip.SetCurve(relativePath, typeof(SkinnedMeshRenderer), propertyName, blendShapeCurves[j]);
- }
- break;
- }
- }
- return result;
- }
- public static Keyframe CreateKeyframe<T>(int index, float[] timeArray, T[] valueArray, Func<T, float> getValue, InterpolationMode interpolationMode) {
- float time = timeArray[index];
- Keyframe keyframe;
- #pragma warning disable CS0618
- if (interpolationMode == InterpolationMode.STEP) {
- keyframe = new Keyframe(time, getValue(valueArray[index]), float.PositiveInfinity, float.PositiveInfinity, 1, 1);
- } else if (interpolationMode == InterpolationMode.CUBICSPLINE) {
- // @TODO: Find out what the right math is to calculate the tangent/weight values.
- float inTangent = getValue(valueArray[index * 3]);
- float outTangent = getValue(valueArray[(index * 3) + 2]);
- keyframe = new Keyframe(time, getValue(valueArray[(index * 3) + 1]), inTangent, outTangent, 1, 1);
- } else { // LINEAR
- keyframe = new Keyframe(time, getValue(valueArray[index]), 0, 0, 0, 0);
- }
- #pragma warning restore CS0618
- return keyframe;
- }
- }
- public static class GLTFAnimationExtensions {
- public static GLTFAnimation.ImportResult[] Import(this List<GLTFAnimation> animations, GLTFAccessor.ImportResult[] accessors, GLTFNode.ImportResult[] nodes, ImportSettings importSettings) {
- if (animations == null) return null;
- GLTFAnimation.ImportResult[] results = new GLTFAnimation.ImportResult[animations.Count];
- for (int i = 0; i < results.Length; i++) {
- results[i] = animations[i].Import(accessors, nodes, importSettings);
- if (string.IsNullOrEmpty(results[i].clip.name)) results[i].clip.name = "animation" + i;
- }
- return results;
- }
- }
- }
|