diff --git a/Runtime/Scripts/AccessorData.cs b/Runtime/Scripts/AccessorData.cs index 79e3eba6..2ff52742 100644 --- a/Runtime/Scripts/AccessorData.cs +++ b/Runtime/Scripts/AccessorData.cs @@ -26,7 +26,8 @@ enum AccessorUsage Rotation = 1 << 11, Scale = 1 << 12, Weight = 1 << 13, - RequiredForInstantiation = 1 << 14 + RequiredForInstantiation = 1 << 14, + Pointer = 1 << 15, } abstract class AccessorDataBase diff --git a/Runtime/Scripts/AnimationCameraGameObject.cs b/Runtime/Scripts/AnimationCameraGameObject.cs new file mode 100644 index 00000000..06852664 --- /dev/null +++ b/Runtime/Scripts/AnimationCameraGameObject.cs @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace GLTFast { + public class AnimationCameraGameObject : MonoBehaviour { + public Camera targetCamera; + public bool orthographic; + public float localScale; + + public float nearClipPlane; + float nearClipPlaneOld; + public float farClipPlane; + float farClipPlaneOld; + + public float xMag; + public float yMag; + float xMagOld; + float yMagOld; + + public float fov; + float fovOld; + + void OnEnable() { + if (targetCamera == null) { + Debug.LogError("Target camera not set on animated camera!"); + } + } + + void LateUpdate() { + bool orthoChanged = false; + if(farClipPlane != farClipPlaneOld) { + if(orthographic) { + orthoChanged = true; + farClipPlane = farClipPlane >= 0 ? farClipPlane : float.MaxValue; + } + targetCamera.farClipPlane = localScale * farClipPlane; + farClipPlaneOld = farClipPlane; + } + + if(nearClipPlane != nearClipPlaneOld) { + targetCamera.nearClipPlane = localScale * nearClipPlane; + nearClipPlaneOld = nearClipPlane; + orthoChanged = true; + } + + if(xMag != xMagOld) { + xMagOld = xMag; + orthoChanged = true; + } + + if(yMag != yMagOld) { + yMagOld = yMag; + orthoChanged = true; + } + + if(fov != fovOld) { + targetCamera.fieldOfView = fov * Mathf.Rad2Deg; + fovOld = fov; + } + + if(orthographic && orthoChanged) { + targetCamera.projectionMatrix = Matrix4x4.Ortho( + -xMag, + xMag, + -yMag, + yMag, + nearClipPlane, + farClipPlane + ); + } + } + } +} diff --git a/Runtime/Scripts/AnimationCameraGameObject.cs.meta b/Runtime/Scripts/AnimationCameraGameObject.cs.meta new file mode 100644 index 00000000..c7c6d4db --- /dev/null +++ b/Runtime/Scripts/AnimationCameraGameObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 730587a08e30e08459e08e8178c1d83a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/AnimationData.cs b/Runtime/Scripts/AnimationData.cs new file mode 100644 index 00000000..b8cceeec --- /dev/null +++ b/Runtime/Scripts/AnimationData.cs @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors +// SPDX-License-Identifier: Apache-2.0 + +#if UNITY_ANIMATION + +using System; +using GLTFast.Schema; +using UnityEngine; + +namespace GLTFast { + + public enum TargetType { + Unknown = -1, + Camera, + Material, + Mesh, + Node, + } + + public class AnimationData { + public TargetType TargetType = TargetType.Unknown; + public Type AnimationClipType; + public GltfAccessorAttributeType AccessorType; + public string TargetProperty; + public int TargetId = -1; + public string[] PropertyNames; + + public static AnimationData TranslationData() { + var template = "localPosition."; + return new AnimationData + { + TargetType = TargetType.Node, + AnimationClipType = typeof(Transform), + AccessorType = GltfAccessorAttributeType.VEC3, + TargetProperty = "translationNative", + PropertyNames = new [] {$"{template}x", $"{template}y", $"{template}z"} + }; + } + + public static AnimationData RotationData() { + var template = "localRotation."; + return new AnimationData + { + TargetType = TargetType.Node, + AnimationClipType = typeof(Transform), + AccessorType = GltfAccessorAttributeType.VEC4, + TargetProperty = "rotationNative", + PropertyNames = new [] {$"{template}x", $"{template}y", $"{template}z", $"{template}w"} + }; + } + + public static AnimationData ScaleData() { + var template = "localScale."; + return new AnimationData + { + TargetType = TargetType.Node, + AnimationClipType = typeof(Transform), + AccessorType = GltfAccessorAttributeType.VEC3, + TargetProperty = "scaleNative", + PropertyNames = new [] {$"{template}x", $"{template}y", $"{template}z"} + }; + } + + public static AnimationData WeightData() { + return new AnimationData + { + TargetType = TargetType.Node, + AnimationClipType = typeof(MeshRenderer), + AccessorType = GltfAccessorAttributeType.SCALAR, + TargetProperty = "weights" + }; + } + + public static AnimationData GeneratePointerData(string pointerPath) { + var data = new AnimationData(); + + switch (pointerPath) { + case string p when p.StartsWith("/cameras/"): + data.TargetType = TargetType.Camera; + data.AnimationClipType = typeof(AnimationCameraGameObject); + data.TargetId = ParsePointerTargetId(pointerPath["/cameras/".Length..]); + data.TargetProperty = pointerPath[$"/cameras/{data.TargetId}/".Length..]; + break; + case string p when p.StartsWith("/materials/"): + data.TargetType = TargetType.Material; + data.AnimationClipType = typeof(Renderer); + data.TargetId = ParsePointerTargetId(pointerPath["/materials/".Length..]); + data.TargetProperty = pointerPath[$"/materials/{data.TargetId}/".Length..]; + break; + case string p when p.StartsWith("/meshes/"): + data.TargetType = TargetType.Mesh; + data.AnimationClipType = typeof(UnityEngine.MeshRenderer); + data.TargetId = ParsePointerTargetId(pointerPath["/meshes/".Length..]); + data.TargetProperty = pointerPath[$"/meshes/{data.TargetId}/".Length..]; + break; + case string p when p.StartsWith("/nodes/"): + data.TargetType = TargetType.Node; + if (pointerPath[^7..].Equals("weights")) { + data.AnimationClipType = typeof(UnityEngine.MeshRenderer); + } else { + data.AnimationClipType = typeof(Transform); + } + data.TargetId = ParsePointerTargetId(pointerPath["/nodes/".Length..]); + data.TargetProperty = pointerPath[$"/nodes/{data.TargetId}/".Length..]; + break; + } + + string template; + switch(data.TargetProperty) { + // Core + case "rotation": + template = "localRotation."; + data.PropertyNames = new[] {$"{template}x", $"{template}y", $"{template}z", $"{template}w"}; + data.AccessorType = GltfAccessorAttributeType.VEC4; + break; + case "scale": + template = "localScale."; + data.PropertyNames = new [] {$"{template}x", $"{template}y", $"{template}z"}; + data.AccessorType = GltfAccessorAttributeType.VEC3; + break; + case "translation": + template = "localPosition."; + data.PropertyNames = new [] {$"{template}x", $"{template}y", $"{template}z"}; + data.AccessorType = GltfAccessorAttributeType.VEC3; + break; + case "weights": + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "orthographic/xmag": + data.PropertyNames = new [] {"xMag"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "orthographic/ymag": + data.PropertyNames = new [] {"yMag"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "orthographic/zfar": + data.PropertyNames = new [] {"farClipPlane"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "orthographic/znear": + data.PropertyNames = new [] {"nearClipPlane"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "perspective/yfov": + data.PropertyNames = new [] {"fov"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "perspective/zfar": + data.PropertyNames = new [] {"farClipPlane"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "perspective/znear": + data.PropertyNames = new [] {"nearClipPlane"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "pbrMetallicRoughness/baseColorFactor": + template = "material.baseColorFactor."; + data.PropertyNames = new[] {$"{template}r", $"{template}g", $"{template}b", $"{template}a"}; + data.AccessorType = GltfAccessorAttributeType.VEC4; + break; + case "pbrMetallicRoughness/metallicFactor": + data.PropertyNames = new[] {"material.metallicFactor"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "pbrMetallicRoughness/roughnessFactor": + data.PropertyNames = new[] {"material.roughnessFactor"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "alphaCutoff": + data.PropertyNames = new[] {"material.alphaCutoff"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "emissiveFactor": + template = "material.emissiveFactor."; + data.PropertyNames = new[] {$"{template}r", $"{template}g", $"{template}b"}; + data.AccessorType = GltfAccessorAttributeType.VEC3; + break; + case "normalTexture/scale": + data.PropertyNames = new[] {"material.normalTexture_scale"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + case "occlusionTexture/strength": + data.PropertyNames = new[] {"material.occlusionTexture_strength"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + + // KHR_materials_transmission + case "extensions/KHR_materials_transmission/transmissionFactor": + data.PropertyNames = new[] {"material.transmissionFactor"}; + data.AccessorType = GltfAccessorAttributeType.SCALAR; + break; + + default: +#if DEBUG + Debug.LogWarning($"glTF animation pointer {pointerPath} is not supported."); +#endif + break; + } + + return data; + } + + public static int ParsePointerTargetId(string name) { + var split = name[..name.IndexOf("/")]; + if(int.TryParse(split, out var targetId)) { + return targetId; + } + return -1; + } + } +} +#endif + diff --git a/Runtime/Scripts/AnimationData.cs.meta b/Runtime/Scripts/AnimationData.cs.meta new file mode 100644 index 00000000..22111bfa --- /dev/null +++ b/Runtime/Scripts/AnimationData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7fbc1ab70546c564d8f6efad07671ac2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/AnimationUtils.cs b/Runtime/Scripts/AnimationUtils.cs index 744aff3c..a83d2fa2 100644 --- a/Runtime/Scripts/AnimationUtils.cs +++ b/Runtime/Scripts/AnimationUtils.cs @@ -18,27 +18,45 @@ static class AnimationUtils { const float k_TimeEpsilon = 0.00001f; - public static void AddTranslationCurves(AnimationClip clip, string animationPath, NativeArray times, NativeArray translations, InterpolationType interpolationType) { - // TODO: Refactor interface to use Unity.Mathematics types and remove this Reinterpret - var values = translations.Reinterpret(); - AddVec3Curves(clip, animationPath, "localPosition.", times, values, interpolationType); - } - - public static void AddScaleCurves(AnimationClip clip, string animationPath, NativeArray times, NativeArray translations, InterpolationType interpolationType) { - // TODO: Refactor interface to use Unity.Mathematics types and remove this Reinterpret - var values = translations.Reinterpret(); - AddVec3Curves(clip, animationPath, "localScale.", times, values, interpolationType); + public static void AddCurve(AnimationClip clip, string animationPath, AnimationData animData, NativeArray times, AccessorDataBase values, InterpolationType interpolationType) { + switch(animData.AccessorType) { + case GltfAccessorAttributeType.SCALAR: + var scalarVals = ((AccessorNativeData)values).data; + AddScalarCurve(clip, animationPath, animData.PropertyNames[0], animData.AnimationClipType, times, scalarVals, interpolationType); + break; + case GltfAccessorAttributeType.VEC2: + var float2Vals = ((AccessorNativeData)values).data.Reinterpret(); + AddVec2Curves(clip, animationPath, animData.PropertyNames, animData.AnimationClipType, times, float2Vals, interpolationType); + break; + case GltfAccessorAttributeType.VEC3: + var float3Vals = ((AccessorNativeData)values).data.Reinterpret(); + // Special case for translations (x is flipped in Unity). + bool flip = animData.TargetProperty.Equals("translation"); + AddVec3Curves(clip, animationPath, animData.PropertyNames, animData.AnimationClipType, times, float3Vals, interpolationType, flip); + break; + case GltfAccessorAttributeType.VEC4: + // Special cases for quaternions. + // Pointer rotation that requires y and z components flipping. + if (animData.TargetProperty.Equals("rotation")) { + var quaternionVals = ((AccessorNativeData)values).data.Reinterpret(); + AddQuaternionCurves(clip, animationPath, animData.PropertyNames, animData.AnimationClipType, times, quaternionVals, interpolationType, true); + break; + } + // An original rotation that already has the correct y and z. + else if(animData.TargetProperty.Equals("rotationNative")) { + var quaternionVals = ((AccessorNativeData)values).data.Reinterpret(); + AddQuaternionCurves(clip, animationPath, animData.PropertyNames, animData.AnimationClipType, times, quaternionVals, interpolationType); + break; + } + var float4Vals = ((AccessorNativeData)values).data.Reinterpret(); + AddVec4Curves(clip, animationPath, animData.PropertyNames, animData.AnimationClipType, times, float4Vals, interpolationType); + break; + } } - public static void AddRotationCurves(AnimationClip clip, string animationPath, NativeArray times, NativeArray quaternions, InterpolationType interpolationType) { - Profiler.BeginSample("AnimationUtils.AddRotationCurves"); - var rotX = new AnimationCurve(); - var rotY = new AnimationCurve(); - var rotZ = new AnimationCurve(); - var rotW = new AnimationCurve(); - - // TODO: Refactor interface to use Unity.Mathematics types and remove this Reinterpret - var values = quaternions.Reinterpret(); + public static void AddScalarCurve(AnimationClip clip, string animationPath, string propertyName, Type targetType, NativeArray times, NativeArray values, InterpolationType interpolationType) { + Profiler.BeginSample("AnimationUtils.AddScalarCurve"); + var curve = new AnimationCurve(); #if DEBUG uint duplicates = 0; @@ -49,10 +67,7 @@ public static void AddRotationCurves(AnimationClip clip, string animationPath, N for (var i = 0; i < times.Length; i++) { var time = times[i]; var value = values[i]; - rotX.AddKey( new Keyframe(time, value.value.x, float.PositiveInfinity, 0) ); - rotY.AddKey( new Keyframe(time, value.value.y, float.PositiveInfinity, 0) ); - rotZ.AddKey( new Keyframe(time, value.value.z, float.PositiveInfinity, 0) ); - rotW.AddKey( new Keyframe(time, value.value.w, float.PositiveInfinity, 0) ); + curve.AddKey( new Keyframe(time, value, float.PositiveInfinity, 0) ); } break; } @@ -62,17 +77,14 @@ public static void AddRotationCurves(AnimationClip clip, string animationPath, N var inTangent = values[i*3]; var value = values[i*3 + 1]; var outTangent = values[i*3 + 2]; - rotX.AddKey( new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, .5f ) ); - rotY.AddKey( new Keyframe(time, value.value.y, inTangent.value.y, outTangent.value.y, .5f, .5f ) ); - rotZ.AddKey( new Keyframe(time, value.value.z, inTangent.value.z, outTangent.value.z, .5f, .5f ) ); - rotW.AddKey( new Keyframe(time, value.value.w, inTangent.value.w, outTangent.value.w, .5f, .5f ) ); + curve.AddKey( new Keyframe(time, value, inTangent, outTangent, .5f, .5f ) ); } break; } default: { // LINEAR var prevTime = times[0]; var prevValue = values[0]; - var inTangent = new quaternion(new float4(0f)); + float inTangent = values[0]; for (var i = 1; i < times.Length; i++) { var time = times[i]; @@ -87,48 +99,30 @@ public static void AddRotationCurves(AnimationClip clip, string animationPath, N continue; } - // Ensure shortest path rotation ( see https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#interpolation-slerp ) - if (math.dot(prevValue, value) < 0) { - value.value = -value.value; - } - var dT = time - prevTime; - var dV = value.value - prevValue.value; - quaternion outTangent; + var dV = value - prevValue; + float outTangent; if (dT < k_TimeEpsilon) { - outTangent.value.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; - outTangent.value.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; - outTangent.value.z = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; - outTangent.value.w = (dV.w < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent = (dV < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; } else { outTangent = dV / dT; } - rotX.AddKey( new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, outTangent.value.x ) ); - rotY.AddKey( new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, outTangent.value.y ) ); - rotZ.AddKey( new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, outTangent.value.z ) ); - rotW.AddKey( new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, outTangent.value.w ) ); + curve.AddKey( new Keyframe(prevTime, prevValue, inTangent, outTangent ) ); inTangent = outTangent; prevTime = time; prevValue = value; } - rotX.AddKey( new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, 0 ) ); - rotY.AddKey( new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, 0 ) ); - rotZ.AddKey( new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, 0 ) ); - rotW.AddKey( new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, 0 ) ); + curve.AddKey( new Keyframe(prevTime, prevValue, inTangent, 0 ) ); break; } } - clip.SetCurve(animationPath, typeof(Transform), "localRotation.x", rotX); - clip.SetCurve(animationPath, typeof(Transform), "localRotation.y", rotY); - clip.SetCurve(animationPath, typeof(Transform), "localRotation.z", rotZ); - clip.SetCurve(animationPath, typeof(Transform), "localRotation.w", rotW); + clip.SetCurve(animationPath, targetType, propertyName, curve); Profiler.EndSample(); - #if DEBUG if (duplicates > 0) { ReportDuplicateKeyframes(); @@ -136,59 +130,90 @@ public static void AddRotationCurves(AnimationClip clip, string animationPath, N #endif } - public static string CreateAnimationPath(int nodeIndex, string[] nodeNames, int[] parentIndex) { - Profiler.BeginSample("AnimationUtils.CreateAnimationPath"); - var sb = new StringBuilder(); - do { - if (sb.Length > 0) { - sb.Insert(0,'/'); + public static void AddVec2Curves(AnimationClip clip, string animationPath, string[] propertyNames, Type targetType, NativeArray times, NativeArray values, InterpolationType interpolationType) { + Profiler.BeginSample("AnimationUtils.AddVec2Curves"); + var curveX = new AnimationCurve(); + var curveY = new AnimationCurve(); + +#if DEBUG + uint duplicates = 0; +#endif + + switch (interpolationType) { + case InterpolationType.Step: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + curveX.AddKey( new Keyframe(time, value.x, float.PositiveInfinity, 0) ); + curveY.AddKey( new Keyframe(time, value.y, float.PositiveInfinity, 0) ); + } + break; } - sb.Insert(0,nodeNames[nodeIndex]); - nodeIndex = parentIndex[nodeIndex]; - } while (nodeIndex>=0); - Profiler.EndSample(); - return sb.ToString(); - } + case InterpolationType.CubicSpline: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var inTangent = values[i*3]; + var value = values[i*3 + 1]; + var outTangent = values[i*3 + 2]; + curveX.AddKey( new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f ) ); + curveY.AddKey( new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f ) ); + } + break; + } + default: { // LINEAR + var prevTime = times[0]; + var prevValue = values[0]; + var inTangent = new float2(0f); - public static void AddMorphTargetWeightCurves( - AnimationClip clip, - string animationPath, - NativeArray times, - NativeArray values, - InterpolationType interpolationType, - string[] morphTargetNames = null - ) - { - Profiler.BeginSample("AnimationUtils.AddMorphTargetWeightCurves"); - int morphTargetCount; - if (morphTargetNames == null) { - morphTargetCount = values.Length / times.Length; - if (interpolationType == InterpolationType.CubicSpline) { - // 3 values per key (in-tangent, out-tangent and value) - morphTargetCount /= 3; + for (var i = 1; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + + if (prevTime >= time) { + // Time value is not increasing, so we ignore this keyframe + // This happened on some Sketchfab files (see #298) +#if DEBUG + duplicates++; +#endif + continue; + } + + var dT = time - prevTime; + var dV = value - prevValue; + float2 outTangent; + if (dT < k_TimeEpsilon) { + outTangent.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + } else { + outTangent = dV / dT; + } + + curveX.AddKey( new Keyframe(prevTime, prevValue.x, inTangent.x, outTangent.x ) ); + curveY.AddKey( new Keyframe(prevTime, prevValue.y, inTangent.y, outTangent.y ) ); + + inTangent = outTangent; + prevTime = time; + prevValue = value; + } + + curveX.AddKey( new Keyframe(prevTime, prevValue.x, inTangent.x, 0 ) ); + curveY.AddKey( new Keyframe(prevTime, prevValue.y, inTangent.y, 0 ) ); + + break; } } - else { - morphTargetCount = morphTargetNames.Length; - } - for (var i = 0; i < morphTargetCount; i++) { - var morphTargetName = morphTargetNames==null ? i.ToString() : morphTargetNames[i]; - AddScalarCurve( - clip, - animationPath, - morphTargetName, - i, - morphTargetCount, - times, - values, - interpolationType - ); - } + clip.SetCurve(animationPath, targetType, propertyNames[0], curveX); + clip.SetCurve(animationPath, targetType, propertyNames[1], curveY); Profiler.EndSample(); +#if DEBUG + if (duplicates > 0) { + ReportDuplicateKeyframes(); + } +#endif } - static void AddVec3Curves(AnimationClip clip, string animationPath, string propertyPrefix, NativeArray times, NativeArray values, InterpolationType interpolationType) { + public static void AddVec3Curves(AnimationClip clip, string animationPath, string[] propertyNames, Type targetType, NativeArray times, NativeArray values, InterpolationType interpolationType, bool flip = false) { Profiler.BeginSample("AnimationUtils.AddVec3Curves"); var curveX = new AnimationCurve(); var curveY = new AnimationCurve(); @@ -203,6 +228,9 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope for (var i = 0; i < times.Length; i++) { var time = times[i]; var value = values[i]; + if(flip) { + value.x *= -1; + } curveX.AddKey( new Keyframe(time, value.x, float.PositiveInfinity, 0) ); curveY.AddKey( new Keyframe(time, value.y, float.PositiveInfinity, 0) ); curveZ.AddKey( new Keyframe(time, value.z, float.PositiveInfinity, 0) ); @@ -215,6 +243,11 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope var inTangent = values[i*3]; var value = values[i*3 + 1]; var outTangent = values[i*3 + 2]; + if(flip) { + inTangent.x *= -1; + value.x *= -1; + outTangent.x *= -1; + } curveX.AddKey( new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f ) ); curveY.AddKey( new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f ) ); curveZ.AddKey( new Keyframe(time, value.z, inTangent.z, outTangent.z, .5f, .5f ) ); @@ -224,12 +257,17 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope default: { // LINEAR var prevTime = times[0]; var prevValue = values[0]; + if(flip) { + prevValue.x *= -1; + } var inTangent = new float3(0f); for (var i = 1; i < times.Length; i++) { var time = times[i]; var value = values[i]; - + if(flip) { + value.x *= -1; + } if (prevTime >= time) { // Time value is not increasing, so we ignore this keyframe // This happened on some Sketchfab files (see #298) @@ -267,9 +305,9 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope } } - clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}x", curveX); - clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}y", curveY); - clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}z", curveZ); + clip.SetCurve(animationPath, targetType, propertyNames[0], curveX); + clip.SetCurve(animationPath, targetType, propertyNames[1], curveY); + clip.SetCurve(animationPath, targetType, propertyNames[2], curveZ); Profiler.EndSample(); #if DEBUG if (duplicates > 0) { @@ -278,8 +316,279 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope #endif } - static void AddScalarCurve(AnimationClip clip, string animationPath, string propertyPrefix, int curveIndex, int valueStride, NativeArray times, NativeArray values, InterpolationType interpolationType) { - Profiler.BeginSample("AnimationUtils.AddScalarCurve"); + public static void AddVec4Curves(AnimationClip clip, string animationPath, string[] propertyNames, Type targetType, NativeArray times, NativeArray values, InterpolationType interpolationType) { + Profiler.BeginSample("AnimationUtils.AddVec4Curves"); + var curveX = new AnimationCurve(); + var curveY = new AnimationCurve(); + var curveZ = new AnimationCurve(); + var curveW = new AnimationCurve(); + +#if DEBUG + uint duplicates = 0; +#endif + + switch (interpolationType) { + case InterpolationType.Step: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + curveX.AddKey( new Keyframe(time, value.x, float.PositiveInfinity, 0) ); + curveY.AddKey( new Keyframe(time, value.y, float.PositiveInfinity, 0) ); + curveZ.AddKey( new Keyframe(time, value.z, float.PositiveInfinity, 0) ); + curveW.AddKey( new Keyframe(time, value.w, float.PositiveInfinity, 0) ); + } + break; + } + case InterpolationType.CubicSpline: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var inTangent = values[i*3]; + var value = values[i*3 + 1]; + var outTangent = values[i*3 + 2]; + curveX.AddKey( new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f ) ); + curveY.AddKey( new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f ) ); + curveZ.AddKey( new Keyframe(time, value.z, inTangent.z, outTangent.z, .5f, .5f ) ); + curveW.AddKey( new Keyframe(time, value.w, inTangent.w, outTangent.w, .5f, .5f ) ); + } + break; + } + default: { // LINEAR + var prevTime = times[0]; + var prevValue = values[0]; + var inTangent = new float4(0f); + + for (var i = 1; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + + if (prevTime >= time) { + // Time value is not increasing, so we ignore this keyframe + // This happened on some Sketchfab files (see #298) +#if DEBUG + duplicates++; +#endif + continue; + } + + var dT = time - prevTime; + var dV = value - prevValue; + float4 outTangent; + if (dT < k_TimeEpsilon) { + outTangent.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.z = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.w = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + } else { + outTangent = dV / dT; + } + + curveX.AddKey( new Keyframe(prevTime, prevValue.x, inTangent.x, outTangent.x ) ); + curveY.AddKey( new Keyframe(prevTime, prevValue.y, inTangent.y, outTangent.y ) ); + curveZ.AddKey( new Keyframe(prevTime, prevValue.z, inTangent.z, outTangent.z ) ); + curveW.AddKey( new Keyframe(prevTime, prevValue.w, inTangent.w, outTangent.w ) ); + + inTangent = outTangent; + prevTime = time; + prevValue = value; + } + + curveX.AddKey( new Keyframe(prevTime, prevValue.x, inTangent.x, 0 ) ); + curveY.AddKey( new Keyframe(prevTime, prevValue.y, inTangent.y, 0 ) ); + curveZ.AddKey( new Keyframe(prevTime, prevValue.z, inTangent.z, 0 ) ); + curveW.AddKey( new Keyframe(prevTime, prevValue.z, inTangent.w, 0 ) ); + + break; + } + } + clip.SetCurve(animationPath, targetType, propertyNames[0], curveX); + clip.SetCurve(animationPath, targetType, propertyNames[1], curveY); + clip.SetCurve(animationPath, targetType, propertyNames[2], curveZ); + clip.SetCurve(animationPath, targetType, propertyNames[3], curveW); + Profiler.EndSample(); +#if DEBUG + if (duplicates > 0) { + ReportDuplicateKeyframes(); + } +#endif + } + + public static void AddQuaternionCurves(AnimationClip clip, string animationPath, string[] propertyNames, Type targetType, NativeArray times, NativeArray values, InterpolationType interpolationType, bool flip = false) { + Profiler.BeginSample("AnimationUtils.AddQuaternionCurves"); + var rotX = new AnimationCurve(); + var rotY = new AnimationCurve(); + var rotZ = new AnimationCurve(); + var rotW = new AnimationCurve(); + +#if DEBUG + uint duplicates = 0; +#endif + + switch (interpolationType) { + case InterpolationType.Step: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + if(flip) { + value.value.y *= -1; + value.value.z *= -1; + } + rotX.AddKey( new Keyframe(time, value.value.x, float.PositiveInfinity, 0) ); + rotY.AddKey( new Keyframe(time, value.value.y, float.PositiveInfinity, 0) ); + rotZ.AddKey( new Keyframe(time, value.value.z, float.PositiveInfinity, 0) ); + rotW.AddKey( new Keyframe(time, value.value.w, float.PositiveInfinity, 0) ); + } + break; + } + case InterpolationType.CubicSpline: { + for (var i = 0; i < times.Length; i++) { + var time = times[i]; + var inTangent = values[i*3]; + var value = values[i*3 + 1]; + var outTangent = values[i*3 + 2]; + if (flip) { + inTangent.value.y *= -1; + inTangent.value.z *= -1; + value.value.y *= -1; + value.value.z *= -1; + outTangent.value.y *= -1; + outTangent.value.z *= -1; + } + rotX.AddKey( new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, .5f ) ); + rotY.AddKey( new Keyframe(time, value.value.y, inTangent.value.y, outTangent.value.y, .5f, .5f ) ); + rotZ.AddKey( new Keyframe(time, value.value.z, inTangent.value.z, outTangent.value.z, .5f, .5f ) ); + rotW.AddKey( new Keyframe(time, value.value.w, inTangent.value.w, outTangent.value.w, .5f, .5f ) ); + } + break; + } + default: { // LINEAR + var prevTime = times[0]; + var prevValue = values[0]; + if (flip) { + prevValue.value.y *= -1; + prevValue.value.z *= -1; + } + var inTangent = new quaternion(new float4(0f)); + + for (var i = 1; i < times.Length; i++) { + var time = times[i]; + var value = values[i]; + if (flip) { + value.value.y *= -1; + value.value.z *= -1; + } + + if (prevTime >= time) { + // Time value is not increasing, so we ignore this keyframe + // This happened on some Sketchfab files (see #298) +#if DEBUG + duplicates++; +#endif + continue; + } + + // Ensure shortest path rotation ( see https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#interpolation-slerp ) + if (math.dot(prevValue, value) < 0) { + value.value = -value.value; + } + + var dT = time - prevTime; + var dV = value.value - prevValue.value; + quaternion outTangent; + if (dT < k_TimeEpsilon) { + outTangent.value.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.value.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.value.z = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + outTangent.value.w = (dV.w < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity; + } else { + outTangent = dV / dT; + } + + rotX.AddKey( new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, outTangent.value.x ) ); + rotY.AddKey( new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, outTangent.value.y ) ); + rotZ.AddKey( new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, outTangent.value.z ) ); + rotW.AddKey( new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, outTangent.value.w ) ); + + inTangent = outTangent; + prevTime = time; + prevValue = value; + } + + rotX.AddKey( new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, 0 ) ); + rotY.AddKey( new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, 0 ) ); + rotZ.AddKey( new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, 0 ) ); + rotW.AddKey( new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, 0 ) ); + + break; + } + } + + clip.SetCurve(animationPath, targetType, propertyNames[0], rotX); + clip.SetCurve(animationPath, targetType, propertyNames[1], rotY); + clip.SetCurve(animationPath, targetType, propertyNames[2], rotZ); + clip.SetCurve(animationPath, targetType, propertyNames[3], rotW); + Profiler.EndSample(); + +#if DEBUG + if (duplicates > 0) { + ReportDuplicateKeyframes(); + } +#endif + } + + public static string CreateAnimationPath(int nodeIndex, string[] nodeNames, int[] parentIndex) { + Profiler.BeginSample("AnimationUtils.CreateAnimationPath"); + var sb = new StringBuilder(); + do { + if (sb.Length > 0) { + sb.Insert(0,'/'); + } + sb.Insert(0,nodeNames[nodeIndex]); + nodeIndex = parentIndex[nodeIndex]; + } while (nodeIndex>=0); + Profiler.EndSample(); + return sb.ToString(); + } + + public static void AddMorphTargetWeightCurves( + AnimationClip clip, + string animationPath, + NativeArray times, + NativeArray values, + InterpolationType interpolationType, + string[] morphTargetNames = null + ) + { + Profiler.BeginSample("AnimationUtils.AddMorphTargetWeightCurves"); + int morphTargetCount; + if (morphTargetNames == null) { + morphTargetCount = values.Length / times.Length; + if (interpolationType == InterpolationType.CubicSpline) { + // 3 values per key (in-tangent, out-tangent and value) + morphTargetCount /= 3; + } + } + else { + morphTargetCount = morphTargetNames.Length; + } + + for (var i = 0; i < morphTargetCount; i++) { + var morphTargetName = morphTargetNames==null ? i.ToString() : morphTargetNames[i]; + AddBlendCurve( + clip, + animationPath, + morphTargetName, + i, + morphTargetCount, + times, + values, + interpolationType + ); + } + Profiler.EndSample(); + } + + public static void AddBlendCurve(AnimationClip clip, string animationPath, string propertyPrefix, int curveIndex, int valueStride, NativeArray times, NativeArray values, InterpolationType interpolationType) { + Profiler.BeginSample("AnimationUtils.AddBlendCurve"); var curve = new AnimationCurve(); #if DEBUG diff --git a/Runtime/Scripts/Extensions.cs b/Runtime/Scripts/Extensions.cs index 3f9d0614..b79bcf09 100644 --- a/Runtime/Scripts/Extensions.cs +++ b/Runtime/Scripts/Extensions.cs @@ -49,6 +49,10 @@ public enum Extension /// KHR_materials_clearcoat glTF extension /// MaterialsClearcoat, + /// + /// KHR_animation_pointer glTF extension + /// + AnimationPointer, } /// @@ -100,6 +104,10 @@ public static class ExtensionName /// KHR_materials_clearcoat glTF extension /// public const string MaterialsClearcoat = "KHR_materials_clearcoat"; + /// + /// KHR_animation_pointer glTF extension + /// + public const string AnimationPointer = "KHR_animation_pointer"; /// /// Returns the official name of the glTF extension @@ -110,6 +118,8 @@ public static string GetName(this Extension extension) { switch (extension) { + case Extension.AnimationPointer: + return AnimationPointer; case Extension.DracoMeshCompression: return DracoMeshCompression; case Extension.LightsPunctual: diff --git a/Runtime/Scripts/GameObjectInstantiator.cs b/Runtime/Scripts/GameObjectInstantiator.cs index f54f9296..5cb9036f 100644 --- a/Runtime/Scripts/GameObjectInstantiator.cs +++ b/Runtime/Scripts/GameObjectInstantiator.cs @@ -396,6 +396,16 @@ string cameraName cam.nearClipPlane = nearClipPlane * localScale; cam.farClipPlane = farClipPlane * localScale; +#if UNITY_ANIMATION + var camAnim = cam.transform.parent.gameObject.AddComponent(); + camAnim.orthographic = false; + camAnim.localScale = localScale; + camAnim.targetCamera = cam; + camAnim.fov = cam.fieldOfView; + camAnim.nearClipPlane = nearClipPlane; + camAnim.farClipPlane = farClipPlane; +#endif + // // If the aspect ratio is given and does not match the // // screen's aspect ratio, the viewport rect is reduced // // to match the glTFs aspect ratio (box fit) @@ -433,6 +443,17 @@ string cameraName farValue ); +#if UNITY_ANIMATION + var camAnim = cam.transform.parent.gameObject.AddComponent(); + camAnim.orthographic = true; + camAnim.localScale = localScale; + camAnim.targetCamera = cam; + camAnim.xMag = horizontal; + camAnim.yMag = vertical; + camAnim.nearClipPlane = nearClipPlane; + camAnim.farClipPlane = farValue; +#endif + // // If the aspect ratio does not match the // // screen's aspect ratio, the viewport rect is reduced // // to match the glTFs aspect ratio (box fit) diff --git a/Runtime/Scripts/GltfImport.cs b/Runtime/Scripts/GltfImport.cs index f01fd75e..e0b7c6c5 100644 --- a/Runtime/Scripts/GltfImport.cs +++ b/Runtime/Scripts/GltfImport.cs @@ -160,6 +160,9 @@ public abstract class GltfImportBase : IGltfReadable, IGltfBuffers, IDisposable #endif // KTX_UNITY #if MESHOPT ExtensionName.MeshoptCompression, +#endif +#if UNITY_ANIMATION + ExtensionName.AnimationPointer, #endif ExtensionName.MaterialsPbrSpecularGlossiness, ExtensionName.MaterialsUnlit, @@ -270,6 +273,9 @@ public abstract class GltfImportBase : IGltfReadable, IGltfBuffers, IDisposable Matrix4x4[][] m_SkinsInverseBindMatrices; #if UNITY_ANIMATION AnimationClip[] m_AnimationClips; + Dictionary> m_CameraUsages; + Dictionary> m_MaterialUsages; + Dictionary> m_NodeUsages; #endif #if UNITY_EDITOR @@ -2064,6 +2070,64 @@ async Task Prepare() #if UNITY_ANIMATION if (Root.HasAnimation && m_Settings.AnimationMethod != AnimationMethod.None) { + // Enumerate for KHR_animation_pointer + bool pointerExtension = Root.extensionsUsed != null && Array.IndexOf(Root.extensionsUsed, ExtensionName.AnimationPointer) > -1; + if (pointerExtension) { + + m_CameraUsages = new Dictionary>(); + m_NodeUsages = new Dictionary>(); + + + for (int nodeId = 0; nodeId < Root.Nodes.Count; nodeId++) { + var node = Root.Nodes[nodeId]; + + if (Root.Nodes[nodeId].mesh > 0) { + if (!m_NodeUsages.ContainsKey(Root.Nodes[nodeId].mesh)) { + m_NodeUsages.Add(node.mesh, new List()); + } + m_NodeUsages[node.mesh].Add(nodeId); + } + + if(node.camera > 0) { + if(!m_CameraUsages.ContainsKey(node.camera)) { + m_CameraUsages.Add(node.camera, new List()); + } + m_CameraUsages[node.camera].Add(nodeId); + } + } + + var primMaterialLookup = new Dictionary>(); + + for (int meshId = 0; meshId < Root.Meshes.Count; meshId++) { + var mesh = Root.Meshes[meshId]; + + foreach (var primitive in mesh.Primitives) { + if (!primMaterialLookup.ContainsKey(primitive.material)) { + primMaterialLookup.Add(primitive.material, new List()); + } + primMaterialLookup[primitive.material].Add(meshId); + } + } + + m_MaterialUsages = new Dictionary>(); + + for (int materialId = 0; materialId < Root.Materials.Count; materialId++) { + if (primMaterialLookup.ContainsKey(materialId)) + { + if(!m_MaterialUsages.ContainsKey(materialId)) { + m_MaterialUsages.Add(materialId, new List()); + } + + foreach (var meshId in primMaterialLookup[materialId]) + { + if (m_NodeUsages.ContainsKey(meshId)) + { + m_MaterialUsages[materialId].AddRange(m_NodeUsages[meshId]); + } + } + } + } + } m_AnimationClips = new AnimationClip[Root.Animations.Count]; for (var i = 0; i < Root.Animations.Count; i++) { @@ -2087,29 +2151,57 @@ async Task Prepare() continue; } - var path = AnimationUtils.CreateAnimationPath(channel.Target.node,m_NodeNames,parentIndex); - var times = ((AccessorNativeData) m_AccessorData[sampler.input]).data; + AnimationData animationData = new AnimationData(); switch (channel.Target.GetPath()) { - case AnimationChannel.Path.Translation: { - var values= ((AccessorNativeData) m_AccessorData[sampler.output]).data; - AnimationUtils.AddTranslationCurves(m_AnimationClips[i], path, times, values, sampler.GetInterpolationType()); + case AnimationChannel.Path.Translation: + animationData = AnimationData.TranslationData(); + animationData.TargetId = channel.Target.node; break; - } - case AnimationChannel.Path.Rotation: { - var values= ((AccessorNativeData) m_AccessorData[sampler.output]).data; - AnimationUtils.AddRotationCurves(m_AnimationClips[i], path, times, values, sampler.GetInterpolationType()); + case AnimationChannel.Path.Rotation: + animationData = AnimationData.RotationData(); + animationData.TargetId = channel.Target.node; break; - } - case AnimationChannel.Path.Scale: { - var values= ((AccessorNativeData) m_AccessorData[sampler.output]).data; - AnimationUtils.AddScaleCurves(m_AnimationClips[i], path, times, values, sampler.GetInterpolationType()); + case AnimationChannel.Path.Scale: + animationData = AnimationData.ScaleData(); + animationData.TargetId = channel.Target.node; break; - } - case AnimationChannel.Path.Weights: { - var values= ((AccessorNativeData) m_AccessorData[sampler.output]).data; - var node = Root.Nodes[channel.Target.node]; + case AnimationChannel.Path.Weights: + animationData = AnimationData.WeightData(); + animationData.TargetId = channel.Target.node; + break; + case AnimationChannel.Path.Pointer: + var rawPointer = channel.Target.extensions.KHR_animation_pointer.pointer; + animationData = AnimationData.GeneratePointerData(rawPointer); + break; + default: + break; + } + + if (animationData.TargetId < 0) + break; + + var nodeList = new List(); + + switch(animationData.TargetType) { + case TargetType.Camera: + nodeList = m_CameraUsages[animationData.TargetId]; + break; + case TargetType.Material: + nodeList = m_MaterialUsages[animationData.TargetId]; + break; + case TargetType.Node: + nodeList.Add(animationData.TargetId); + break; + } + + foreach (var nodeId in nodeList) { + var path = AnimationUtils.CreateAnimationPath(nodeId,m_NodeNames,parentIndex); + + if(animationData.TargetProperty.Equals("weights")) { + var values = ((AccessorNativeData) m_AccessorData[sampler.output]).data; + var node = Root.Nodes[animationData.TargetId]; if (node.mesh < 0 || node.mesh >= Root.Meshes.Count) { break; } @@ -2146,12 +2238,14 @@ async Task Prepare() // HACK END break; } - case AnimationChannel.Path.Pointer: - m_Logger?.Warning(LogCode.AnimationTargetPathUnsupported,channel.Target.GetPath().ToString()); - break; - default: - m_Logger?.Error(LogCode.AnimationTargetPathUnsupported,channel.Target.GetPath().ToString()); - break; + AnimationUtils.AddCurve( + m_AnimationClips[i], + path, + animationData, + times, + m_AccessorData[sampler.output], + sampler.GetInterpolationType() + ); } } } @@ -3127,6 +3221,9 @@ async Task LoadAccessorData(RootBase gltf) case AnimationChannel.Path.Weights: SetAccessorUsage(accessorIndex,AccessorUsage.Weight); break; + case AnimationChannel.Path.Pointer: + SetAccessorUsage(accessorIndex,AccessorUsage.Pointer); + break; } } } @@ -3193,19 +3290,58 @@ async Task LoadAccessorData(RootBase gltf) } #if UNITY_ANIMATION case GltfAccessorAttributeType.SCALAR when m_AccessorUsage[i]==AccessorUsage.AnimationTimes || m_AccessorUsage[i]==AccessorUsage.Weight: - { - // JobHandle? jh; - var ads = new AccessorNativeData(); - GetScalarJob(gltf, i, out var times, out var jh); - if (times.HasValue) { - ads.data = times.Value; + { + // JobHandle? jh; + var ads = new AccessorNativeData(); + GetScalarJob(gltf, i, out var times, out var jh); + if (times.HasValue) { + ads.data = times.Value; + } + if (jh.HasValue) { + tmpList.Add(jh.Value); + } + m_AccessorData[i] = ads; + break; } - if (jh.HasValue) { + // Pointers + case GltfAccessorAttributeType.SCALAR when m_AccessorUsage[i]==AccessorUsage.Pointer: + { + var ads = new AccessorNativeData(); + GetScalarJob(gltf, i, out var times, out var jh); + if (times.HasValue) { + ads.data = times.Value; + } + if (jh.HasValue) { + tmpList.Add(jh.Value); + } + m_AccessorData[i] = ads; + break; + } + case GltfAccessorAttributeType.VEC2 when m_AccessorUsage[i]==AccessorUsage.Pointer: + { + var ads = new AccessorNativeData(); + GetVector2Job(gltf, i, out ads.data, out var jh); tmpList.Add(jh.Value); + m_AccessorData[i] = ads; + break; } - m_AccessorData[i] = ads; - break; - } + case GltfAccessorAttributeType.VEC3 when m_AccessorUsage[i]==AccessorUsage.Pointer: + { + var ads = new AccessorNativeData(); + GetVector3Job(gltf, i, out ads.data, out var jh, false); + tmpList.Add(jh.Value); + m_AccessorData[i] = ads; + break; + } + case GltfAccessorAttributeType.VEC4 when m_AccessorUsage[i]==AccessorUsage.Pointer: + { + var ads = new AccessorNativeData(); + GetVector4Job(gltf, i, out ads.data, out var jh); + tmpList.Add(jh.Value); + m_AccessorData[i] = ads; + break; + } + #endif } Profiler.EndSample(); @@ -3637,6 +3773,45 @@ unsafe void GetMatricesJob(RootBase gltf, int accessorIndex, out NativeArray vectors, out JobHandle? jobHandle) + { + Profiler.BeginSample("GetVector2Job"); + var accessor = gltf.Accessors[accessorIndex]; + var bufferView = GetBufferView(accessor.bufferView, accessor.byteOffset); + + Profiler.BeginSample("Alloc"); + vectors = new NativeArray(accessor.count, Allocator.Persistent); + Profiler.EndSample(); + + Assert.AreEqual(accessor.GetAttributeType(), GltfAccessorAttributeType.VEC2); + if (accessor.IsSparse) + { + m_Logger.Error(LogCode.SparseAccessor, "Vector2"); + } + + Profiler.BeginSample("CreateJob"); + switch (accessor.componentType) + { + case GltfComponentType.Float: + { + var job = new MemCopyJob + { + input = (float*)bufferView.GetUnsafeReadOnlyPtr(), + bufferSize = accessor.count * 8, + result = (float*)vectors.GetUnsafePtr() + }; + jobHandle = job.Schedule(); + break; + } + default: + m_Logger?.Error(LogCode.IndexFormatInvalid, accessor.componentType.ToString()); + jobHandle = null; + break; + } + Profiler.EndSample(); + Profiler.EndSample(); + } + unsafe void GetVector3Job(RootBase gltf, int accessorIndex, out NativeArray vectors, out JobHandle? jobHandle, bool flip) { Profiler.BeginSample("GetVector3Job"); @@ -3818,6 +3993,46 @@ unsafe void GetScalarJob(RootBase gltf, int accessorIndex, out NativeArray vectors, out JobHandle? jobHandle) + { + Profiler.BeginSample("GetVector4Job"); + // index + var accessor = gltf.Accessors[accessorIndex]; + var bufferView = GetBufferView(accessor.bufferView, accessor.byteOffset); + + Profiler.BeginSample("Alloc"); + vectors = new NativeArray(accessor.count, Allocator.Persistent); + Profiler.EndSample(); + + Assert.AreEqual(accessor.GetAttributeType(), GltfAccessorAttributeType.VEC4); + if (accessor.IsSparse) + { + m_Logger.Error(LogCode.SparseAccessor, "Vector4"); + } + + Profiler.BeginSample("CreateJob"); + switch (accessor.componentType) + { + case GltfComponentType.Float: + { + var job = new MemCopyJob + { + input = (float*)bufferView.GetUnsafeReadOnlyPtr(), + bufferSize = accessor.count * 16, + result = (float*)vectors.GetUnsafePtr() + }; + jobHandle = job.Schedule(); + break; + } + default: + m_Logger?.Error(LogCode.IndexFormatInvalid, accessor.componentType.ToString()); + jobHandle = null; + break; + } + Profiler.EndSample(); + Profiler.EndSample(); + } + #endif // UNITY_ANIMATION /// diff --git a/Runtime/Scripts/Schema/AnimationChannelTarget.cs b/Runtime/Scripts/Schema/AnimationChannelTarget.cs index 1fc6d082..6db37692 100644 --- a/Runtime/Scripts/Schema/AnimationChannelTarget.cs +++ b/Runtime/Scripts/Schema/AnimationChannelTarget.cs @@ -23,6 +23,8 @@ public class AnimationChannelTarget { AnimationChannel.Path m_Path; + public AnimationChannelExtensions extensions; + public AnimationChannel.Path GetPath() { if (m_Path != AnimationChannel.Path.Unknown) { @@ -43,5 +45,25 @@ internal void GltfSerialize(JsonWriter writer) { throw new NotImplementedException($"GltfSerialize missing on {GetType()}"); } } + + [Serializable] + public class AnimationChannelExtensions + { + public AnimationPointer KHR_animation_pointer; + + internal void GltfSerialize(JsonWriter writer) { + throw new NotImplementedException($"GltfSerialize missing on {GetType()}"); + } + } + + [Serializable] + public class AnimationPointer + { + public string pointer; + + internal void GltfSerialize(JsonWriter writer) { + throw new NotImplementedException($"GltfSerialize missing on {GetType()}"); + } + } } #endif // UNITY_ANIMATION