diff --git a/Editor/Scripts/GLTFImporter.cs b/Editor/Scripts/GLTFImporter.cs index f8dc9c45a..173762e82 100644 --- a/Editor/Scripts/GLTFImporter.cs +++ b/Editor/Scripts/GLTFImporter.cs @@ -317,65 +317,95 @@ string GetUniqueName(string desiredName) // Remove empty roots if (gltfScene && _removeEmptyRootObjects) { + // To avoid removing a Root object which is animated, we collect from all animation clips the paths that are animated. + var pathBindings = new List(); + foreach (var aniClip in animations) + { + var bindings = AnimationUtility.GetCurveBindings(aniClip); + var distinctPaths = bindings.Select( x => x.path).Distinct(); + pathBindings.AddRange(distinctPaths); + } + pathBindings = pathBindings.Distinct().ToList(); + var t = gltfScene.transform; var existingAnimator = t.GetComponent(); var hadAnimator = (bool)existingAnimator; var existingAvatar = existingAnimator ? existingAnimator.avatar : default; + var rootIsAnimated = false; if (existingAnimator) - DestroyImmediate(existingAnimator); + { + var firstChildName = t.childCount > 0 ? t.GetChild(0).gameObject.name : ""; + // check if the object is animated, when true, cancel here + rootIsAnimated = firstChildName != "" && pathBindings.Contains(firstChildName); + + if (!rootIsAnimated) + DestroyImmediate(existingAnimator); + } var animationPathPrefix = ""; var originalImportName = gltfScene.name; - while ( - gltfScene.transform.childCount == 1 && - gltfScene.GetComponents().Length == 1) // Transform component + if (!rootIsAnimated) { - var parent = gltfScene; - gltfScene = gltfScene.transform.GetChild(0).gameObject; - var importName = gltfScene.name; - t = gltfScene.transform; - t.parent = null; // To keep transform information in the new parent - DestroyImmediate(parent); // Get rid of the parent - if (animationPathPrefix != "") - animationPathPrefix += "/"; - animationPathPrefix += importName; - } - animationPathPrefix += "/"; + while ( + gltfScene.transform.childCount == 1 && + gltfScene.GetComponents().Length == 1) // Transform component + { + // check if the object is animated, when true, cancel here + if (pathBindings.Contains(animationPathPrefix != "" + ? $"{animationPathPrefix}/{gltfScene.transform.GetChild(0).gameObject.name}" + : gltfScene.transform.GetChild(0).gameObject.name)) + break; + + var parent = gltfScene; + gltfScene = gltfScene.transform.GetChild(0).gameObject; + var importName = gltfScene.name; - // Re-add animator if it was removed - if (hadAnimator) - { - var newAnimator = gltfScene.AddComponent(); - newAnimator.avatar = existingAvatar; - } - // Re-target animation clips - when we strip the root, all animations also change and have a different path now. - if (animations != null) - { - foreach (var clip in animations) - { - if (clip == null) continue; - - // change all animation clip targets - var bindings = AnimationUtility.GetCurveBindings(clip); - var curves = new AnimationCurve[bindings.Length]; - var newBindings = new EditorCurveBinding[bindings.Length]; - for (var index = 0; index < bindings.Length; index++) - { - var binding = bindings[index]; - curves[index] = AnimationUtility.GetEditorCurve(clip, binding); - - var newBinding = bindings[index]; - if (binding.path.StartsWith(animationPathPrefix, StringComparison.Ordinal)) - newBinding.path = binding.path.Substring(animationPathPrefix.Length); - newBindings[index] = newBinding; - } - - var emptyCurves = new AnimationCurve[curves.Length]; - AnimationUtility.SetEditorCurves(clip, bindings, emptyCurves); - AnimationUtility.SetEditorCurves(clip, newBindings, curves); - } - } + t = gltfScene.transform; + t.parent = null; // To keep transform information in the new parent + DestroyImmediate(parent); // Get rid of the parent + if (animationPathPrefix != "") + animationPathPrefix += "/"; + animationPathPrefix += importName; + } + + animationPathPrefix += "/"; + + // Re-add animator if it was removed + if (hadAnimator) + { + var newAnimator = gltfScene.AddComponent(); + newAnimator.avatar = existingAvatar; + } + + // Re-target animation clips - when we strip the root, all animations also change and have a different path now. + if (animations != null) + { + foreach (var clip in animations) + { + if (clip == null) continue; + + // change all animation clip targets + var bindings = AnimationUtility.GetCurveBindings(clip); + var curves = new AnimationCurve[bindings.Length]; + var newBindings = new EditorCurveBinding[bindings.Length]; + for (var index = 0; index < bindings.Length; index++) + { + var binding = bindings[index]; + curves[index] = AnimationUtility.GetEditorCurve(clip, binding); + + var newBinding = bindings[index]; + if (binding.path.StartsWith(animationPathPrefix, StringComparison.Ordinal)) + newBinding.path = binding.path.Substring(animationPathPrefix.Length); + newBindings[index] = newBinding; + } + + var emptyCurves = new AnimationCurve[curves.Length]; + AnimationUtility.SetEditorCurves(clip, bindings, emptyCurves); + AnimationUtility.SetEditorCurves(clip, newBindings, curves); + } + } + } // (!rootIsAnimated) gltfScene.name = originalImportName; } diff --git a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_LightsPunctualExtension.cs b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_LightsPunctualExtension.cs index 64f4a20f7..0a4be604f 100644 --- a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_LightsPunctualExtension.cs +++ b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_LightsPunctualExtension.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; -using GLTF.Math; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using GLTF.Extensions; +using GLTF.Utilities; +using UnityEngine; +using UnityGLTF.Extensions; +using Color = GLTF.Math.Color; namespace GLTF.Schema.KHR_lights_punctual { @@ -287,7 +290,7 @@ public IExtension Clone(GLTFRoot root) } } - public class KHR_LightsPunctualExtension : IExtension + public class KHR_LightsPunctualExtension : IExtension, IImportAnimationPointerRootExtension { public List Lights; @@ -314,6 +317,95 @@ public JProperty Serialize() ) ); } + + private static string[] GltfLightPropertyToUnityPropertyName(string gltfPropertyName) + { + switch (gltfPropertyName) + { + case "color": + return new string[] { "m_Color.r", "m_Color.g", "m_Color.b"}; + case "intensity": + return new string[] { "m_Intensity"}; + case "range": + return new string[] {"m_Range"}; + case "spot/innerConeAngle": + return new string[] {"m_InnerConeAngle"}; + case "spot/outerConeAngle": + return new string[] {"m_OuterConeAngle"}; + default: + return new string[] {gltfPropertyName}; + } + } + + private static AnimationPointerData.ImportValuesConversion GetConversion(string gltfPropertyName) + { + switch (gltfPropertyName) + { + case "color": + return (data, index) => + { + var col = data.primaryData.AccessorContent.AsFloat3s[index]; + var color = new Color(col[0], col[1], col[2], 1f).ToUnityColorRaw(); + return new float[] { color.r, color.g, color.b }; + //return new float[] { col.x, col.y, col.z }; + }; + case "intensity": + return (data, index) => new float[1] { data.primaryData.AccessorContent.AsFloats[index] / Mathf.PI }; + case "range": + return (data, index) => new float[1] { data.primaryData.AccessorContent.AsFloats[index] }; + case "spot/innerConeAngle": + return (data, index) => new float[] { data.primaryData.AccessorContent.AsFloats[index] * 2 / (Mathf.Deg2Rad * 0.8f)}; + case "spot/outerConeAngle": + return (data, index) => new float[] { data.primaryData.AccessorContent.AsFloats[index] * 2 / Mathf.Deg2Rad}; + default: + return null; + } + } + + public bool TryGetImportAnimationPointerData(GLTFRoot root, PointerPath pointerPath, out AnimationPointerData pointerData) + { + pointerData = new AnimationPointerData(); + pointerData.targetNodeIds = new int[0]; + pointerData.targetType = typeof(UnityEngine.Light); + + if (root.Nodes == null) + return false; + + var pointId = pointerPath.FindNext(PointerPath.PathElement.Index); + if (pointId == null) + return false; + + var property = pointerPath.FindNext(PointerPath.PathElement.Property); + if (property == null) + return false; + + pointerData.unityPropertyNames = GltfLightPropertyToUnityPropertyName(property.elementName); + pointerData.importAccessorContentConversion = GetConversion(property.elementName); + + List targetNodes = new List(); + for (int i = 0; i < root.Nodes.Count; i++) + { + var n = root.Nodes[i]; + if (n.Extensions != null && n.Extensions.TryGetValue(KHR_lights_punctualExtensionFactory.EXTENSION_NAME, out IExtension extension)) + { + if (!(extension is KHR_LightsPunctualNodeExtension lightExtension)) + continue; + + if (lightExtension.LightId.Id == pointId.index) + { + targetNodes.Add(i); + } + } + } + + if (targetNodes.Count > 0) + { + pointerData.targetNodeIds = targetNodes.ToArray(); + return true; + } + + return false; + } } public class PunctualLightId : GLTFId diff --git a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointer.cs b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointer.cs index eb24521a1..5a3f4a6be 100644 --- a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointer.cs +++ b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointer.cs @@ -22,6 +22,12 @@ public JProperty Serialize() if (path == null && clonedFrom != null) path = clonedFrom.path; return new JProperty(EXTENSION_NAME, new JObject(new JProperty("pointer", path))); } + + public void Deserialize(GLTFRoot root, JProperty extensionToken) + { + var extensionObject = (JObject) extensionToken.Value; + path = (string) extensionObject["pointer"]; + } public IExtension Clone(GLTFRoot root) { diff --git a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs new file mode 100644 index 000000000..625a1b04b --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json.Linq; +using UnityGLTF.Extensions; + +namespace GLTF.Schema +{ + public class KHR_animation_pointerExtensionFactory : ExtensionFactory + { + public const string EXTENSION_NAME = "KHR_animation_pointer"; + + + public KHR_animation_pointerExtensionFactory() + { + ExtensionName = EXTENSION_NAME; + } + + public override IExtension Deserialize(GLTFRoot root, JProperty extensionToken) + { + if (extensionToken != null) + { + var extensionObject = (JObject) extensionToken.Value; + string path = (string) extensionObject["pointer"]; + return new KHR_animation_pointer + { + path = path + }; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs.meta b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs.meta new file mode 100644 index 000000000..de883387d --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Extensions/KHR_animation_pointerExtensionFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7f3f2eb61a4b4dd088e62138724bac85 +timeCreated: 1707827260 \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/GLTFHelpers.cs b/Runtime/Plugins/GLTFSerialization/GLTFHelpers.cs index c1a9bc6c1..ac8d585df 100644 --- a/Runtime/Plugins/GLTFSerialization/GLTFHelpers.cs +++ b/Runtime/Plugins/GLTFSerialization/GLTFHelpers.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using GLTF.Schema; @@ -408,20 +409,28 @@ public static void BuildAnimationSamplers(ref Dictionary bufferViewCache); - switch (samplerSet.Key) + switch (attributeAccessor.AccessorId.Value.Type) { - case "time": - attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); + case GLTFAccessorAttributeType.SCALAR: + attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache); break; - case "translation": - case "scale": - attributeAccessor.AccessorId.Value.AsFloat3Array(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); + case GLTFAccessorAttributeType.VEC2: + attributeAccessor.AccessorId.Value.AsFloat2Array(ref resultArray, bufferViewCache); break; - case "rotation": - attributeAccessor.AccessorId.Value.AsFloat4Array(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); + case GLTFAccessorAttributeType.VEC3: + attributeAccessor.AccessorId.Value.AsFloat3Array(ref resultArray, bufferViewCache); break; - case "weights": - attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); + case GLTFAccessorAttributeType.VEC4: + attributeAccessor.AccessorId.Value.AsFloat4Array(ref resultArray, bufferViewCache); + break; + case GLTFAccessorAttributeType.MAT2: + Debug.LogWarning("Unsupported MAT2 animation sampler type"); + break; + case GLTFAccessorAttributeType.MAT3: + Debug.LogWarning("Unsupported MAT3 animation sampler type"); + break; + case GLTFAccessorAttributeType.MAT4: + attributeAccessor.AccessorId.Value.AsMatrix4x4Array(ref resultArray, bufferViewCache); break; } diff --git a/Runtime/Plugins/GLTFSerialization/Schema/Accessor.cs b/Runtime/Plugins/GLTFSerialization/Schema/Accessor.cs index 444d390ae..d39ee6bc4 100644 --- a/Runtime/Plugins/GLTFSerialization/Schema/Accessor.cs +++ b/Runtime/Plugins/GLTFSerialization/Schema/Accessor.cs @@ -1390,6 +1390,32 @@ public enum GLTFAccessorAttributeType MAT4 } + public static class GLTFAccessorAttributeTypeExtensions + { + public static int ComponentCount(this GLTFAccessorAttributeType attrType) + { + switch (attrType) + { + case GLTFAccessorAttributeType.SCALAR: + return 1; + case GLTFAccessorAttributeType.VEC2: + return 2; + case GLTFAccessorAttributeType.VEC3: + return 3; + case GLTFAccessorAttributeType.VEC4: + return 4; + case GLTFAccessorAttributeType.MAT2: + return 4; + case GLTFAccessorAttributeType.MAT3: + return 9; + case GLTFAccessorAttributeType.MAT4: + return 16; + } + + return 0; + } + } + /// [StructLayout(LayoutKind.Explicit)] public struct NumericArray diff --git a/Runtime/Plugins/GLTFSerialization/Schema/AnimationChannelTarget.cs b/Runtime/Plugins/GLTFSerialization/Schema/AnimationChannelTarget.cs index a119b40a2..1c8a68137 100644 --- a/Runtime/Plugins/GLTFSerialization/Schema/AnimationChannelTarget.cs +++ b/Runtime/Plugins/GLTFSerialization/Schema/AnimationChannelTarget.cs @@ -89,6 +89,7 @@ public enum GLTFAnimationChannelPath translation, rotation, scale, - weights + weights, + pointer } } diff --git a/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs b/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs new file mode 100644 index 000000000..d0b6af020 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs @@ -0,0 +1,21 @@ +using System; + +namespace GLTF.Schema +{ + public class AnimationPointerData + { + public string[] unityPropertyNames; + public Type targetType; + public int[] targetNodeIds; + + public delegate float[] ImportValuesConversion(AnimationPointerData data, int index); + public ImportValuesConversion importAccessorContentConversion; + + public string primaryPath = ""; + public AttributeAccessor primaryData; + + public string secondaryPath = ""; + public AttributeAccessor secondaryData; + } + +} \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs.meta b/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs.meta new file mode 100644 index 000000000..7a1bfe9c4 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Schema/AnimationPointerData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b46ea1356e184122834984053bbe1693 +timeCreated: 1708099053 \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Schema/GLTFProperty.cs b/Runtime/Plugins/GLTFSerialization/Schema/GLTFProperty.cs index 9a15ad4ce..2fe7b54ab 100644 --- a/Runtime/Plugins/GLTFSerialization/Schema/GLTFProperty.cs +++ b/Runtime/Plugins/GLTFSerialization/Schema/GLTFProperty.cs @@ -37,7 +37,8 @@ public static IReadOnlyList RegisteredExtensions { KHR_draco_mesh_compression_Factory.EXTENSION_NAME, new KHR_draco_mesh_compression_Factory() }, { KHR_texture_basisu_Factory.EXTENSION_NAME, new KHR_texture_basisu_Factory() }, { EXT_meshopt_compression_Factory.EXTENSION_NAME, new EXT_meshopt_compression_Factory() }, - { EXT_mesh_gpu_instancing_Factory.EXTENSION_NAME, new EXT_mesh_gpu_instancing_Factory() } + { EXT_mesh_gpu_instancing_Factory.EXTENSION_NAME, new EXT_mesh_gpu_instancing_Factory() }, + { KHR_animation_pointerExtensionFactory.EXTENSION_NAME, new KHR_animation_pointerExtensionFactory() } }; private static DefaultExtensionFactory _defaultExtensionFactory = new DefaultExtensionFactory(); diff --git a/Runtime/Plugins/GLTFSerialization/Schema/IExtension.cs b/Runtime/Plugins/GLTFSerialization/Schema/IExtension.cs index 90425ac05..1733f8fda 100644 --- a/Runtime/Plugins/GLTFSerialization/Schema/IExtension.cs +++ b/Runtime/Plugins/GLTFSerialization/Schema/IExtension.cs @@ -1,7 +1,17 @@ +using GLTF.Utilities; using Newtonsoft.Json.Linq; +using System; namespace GLTF.Schema { + /// + /// Additional interface for Root Extensions to support custom animation pointers + /// + public interface IImportAnimationPointerRootExtension + { + bool TryGetImportAnimationPointerData(GLTFRoot root, PointerPath pointerPath, out AnimationPointerData pointerData); + } + /// /// General interface for extensions /// diff --git a/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs b/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs new file mode 100644 index 000000000..f8e9f0621 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs @@ -0,0 +1,109 @@ +namespace GLTF.Utilities +{ + public class PointerPath + { + public enum PathElement { Root, RootExtension, Index, Extension, Child, Property } + public PathElement PathElementType { get; private set; } = PathElement.Root; + public int index { get; private set; } = -1; + public string elementName { get; private set; } = ""; + + public bool isValid { get; internal set; } = false; + + public PointerPath next { get; private set; } = null; + + public string ExtractPath() + { + return elementName+ (next != null ? "/" + next.ExtractPath() : ""); + } + + public PointerPath FindNext(PathElement pathElementType) + { + if (this.PathElementType == pathElementType) + return this; + + if (next == null) + return null; + return next.FindNext(pathElementType); + } + + private PointerPath() + { + isValid = true; + } + + public PointerPath(string fullPath) + { + if (string.IsNullOrEmpty(fullPath)) + return; + + + var splittedPath = fullPath.Split("/"); + var pathIndex = 0; + + if (string.IsNullOrEmpty(splittedPath[0])) + pathIndex++; + + if (splittedPath.Length <= pathIndex) + return; + + isValid = true; + + elementName = splittedPath[pathIndex]; + PathElementType = PathElement.Root; + + string GetCurrentAsString() + { + return splittedPath[pathIndex]; + } + + bool GetCurrentAsInt(out int result) + { + return int.TryParse(splittedPath[pathIndex], out result); + } + + PointerPath TravelHierarchy() + { + pathIndex++; + if (pathIndex >= splittedPath.Length) + return null; + + var result = new PointerPath(); + + if (GetCurrentAsInt(out int index)) + { + result.index = index; + result.PathElementType = PathElement.Index; + result.elementName = index.ToString(); + result.next = TravelHierarchy(); + return result; + } + + result.elementName = GetCurrentAsString(); + if (result.elementName == "extensions") + result.PathElementType = PathElement.Extension; + else + result.PathElementType = (pathIndex == splittedPath.Length-1) ? PathElement.Property : PathElement.Child; + if ((pathIndex < splittedPath.Length)) + result.next = TravelHierarchy(); + return result; + } + + if (elementName == "extensions") + { + pathIndex++; + if (pathIndex < splittedPath.Length) + { + elementName = GetCurrentAsString(); + PathElementType = PathElement.RootExtension; + next = TravelHierarchy(); + } + } + else + { + next = TravelHierarchy(); + } + + } + } + +} \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs.meta b/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs.meta new file mode 100644 index 000000000..8e7976ce7 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Utilities/AnimationPointerPath.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dc6bc893b2f9428b94cb9972e35f3e24 +timeCreated: 1708087555 \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs b/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs new file mode 100644 index 000000000..771a19f04 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs @@ -0,0 +1,51 @@ +using System.Collections; +using System.Collections.Generic; +using GLTF.Schema; + +namespace GLTF.Utilities +{ + public static class GltfRootExtensions + { + public static int[] GetAllNodeIdsWithMaterialId(this GLTFRoot root, int id) + { + List ids = new List(); + + for (int iMeshes = 0; iMeshes < root.Meshes.Count; iMeshes++) + { + if (root.Meshes[iMeshes].Primitives == null) + continue; + + for (int iPrimitives = 0; iPrimitives < root.Meshes[iMeshes].Primitives.Count; iPrimitives++) + { + if (root.Meshes[iMeshes].Primitives[iPrimitives].Material != null && root.Meshes[iMeshes].Primitives[iPrimitives].Material.Id == id) + { + for (int iNodes = 0; iNodes < root.Nodes.Count; iNodes++) + { + if (root.Nodes[iNodes].Mesh != null &&root.Nodes[iNodes].Mesh.Id == iMeshes) + { + ids.Add(iNodes); + } + } + break; + } + } + } + + return ids.ToArray(); + } + + public static int[] GetAllNodeIdsWithCameraId(this GLTFRoot root, int id) + { + List ids = new List(); + + for (int i = 0; i < root.Nodes.Count; i++) + { + if (root.Nodes[i].Camera != null && root.Nodes[i].Camera.Id == id) + ids.Add(i); + } + + return ids.ToArray(); + } + + } +} \ No newline at end of file diff --git a/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs.meta b/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs.meta new file mode 100644 index 000000000..266c7f2f4 --- /dev/null +++ b/Runtime/Plugins/GLTFSerialization/Utilities/GltfRootExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 14a6d519d0344f438f272a77e05a896c +timeCreated: 1707995699 \ No newline at end of file diff --git a/Runtime/Scripts/Extensions/KHR_animation_pointer_Resolver.cs b/Runtime/Scripts/Extensions/KHR_animation_pointer_Resolver.cs index 0568c4c50..52ccfe2dd 100644 --- a/Runtime/Scripts/Extensions/KHR_animation_pointer_Resolver.cs +++ b/Runtime/Scripts/Extensions/KHR_animation_pointer_Resolver.cs @@ -14,24 +14,6 @@ public void Add(KHR_animation_pointer anim) registered.Add(anim); } - // private struct MaterialMapping - // { - // public string propertyName; - // public string exportName; - // } - // - // private readonly Dictionary> mappings = new Dictionary>(); - // - // // TODO: should we use a static switch instead? - // public void RegisterMapping(Material mat, string propertyName, string exportedPropertyName) - // { - // if (!mappings.ContainsKey(mat)) - // { - // mappings.Add(mat, new List()); - // } - // mappings[mat].Add(new MaterialMapping() { propertyName = propertyName, exportName = exportedPropertyName }); - // } - public void Resolve(GLTFSceneExporter exporter) { foreach (var reg in registered) diff --git a/Runtime/Scripts/Plugins/AnimationPointer.meta b/Runtime/Scripts/Plugins/AnimationPointer.meta new file mode 100644 index 000000000..63e639d42 --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointer.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bdaa28f0f907479faf8ffcc4e5233a12 +timeCreated: 1708087054 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs b/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs new file mode 100644 index 000000000..049f90f28 --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs @@ -0,0 +1,177 @@ +using GLTF.Schema; +using UnityEngine; + +namespace UnityGLTF.Plugins +{ + internal static class AnimationPointerHelpers + { + private static readonly string[] MATERIAL_PROPERTY_COMPONENTS = {"x", "y", "z", "w"}; + private static readonly string[] MATERIAL_COLOR_PROPERTY_COMPONENTS = {"r", "g", "b", "a"}; + + internal static bool BuildImportMaterialAnimationPointerData(MaterialPropertiesRemapper remapper, Material material, + string gltfProperty, GLTFAccessorAttributeType valueType, out AnimationPointerData pointerData) + { + pointerData = new AnimationPointerData(); + if (!remapper.GetUnityPropertyName(material, gltfProperty, out string unityPropertyName, + out MaterialPointerPropertyMap propertyMap, out bool isSecondaryGltfProperty)) + return false; + + // e.g. Emission Factor and Emission Color are combined into a single property + if (isSecondaryGltfProperty && propertyMap.CombinePrimaryAndSecondaryOnImport) + return false; + + if (propertyMap.CombinePrimaryAndSecondaryOnImport) + { + if (propertyMap.CombinePrimaryAndSecondaryDataFunction == null) + return false; + pointerData.secondaryPath = propertyMap.GltfSecondaryPropertyName; + } + + var pointerDataCopy = pointerData; + + int primaryComponentCount = valueType.ComponentCount(); + + if (propertyMap.CombineComponentResult != MaterialPointerPropertyMap.CombineResultType.SameAsPrimary) + { + valueType = propertyMap.OverrideCombineResultType; + } + + switch (valueType) + { + case GLTFAccessorAttributeType.SCALAR: + pointerData.unityPropertyNames = GetAnimationChannelProperties(unityPropertyName, 1, isSecondaryGltfProperty ? 1 : 0 ); + pointerData.importAccessorContentConversion = (data, frame) => MaterialValueConversion(data.primaryData.AccessorContent, frame, primaryComponentCount, propertyMap, pointerDataCopy); + break; + case GLTFAccessorAttributeType.VEC2: + pointerData.unityPropertyNames = GetAnimationChannelProperties(unityPropertyName, 2, isSecondaryGltfProperty ? 2 : 0); + pointerData.importAccessorContentConversion = (data, frame) => MaterialValueConversion(data.primaryData.AccessorContent, frame, primaryComponentCount, propertyMap, pointerDataCopy); + break; + case GLTFAccessorAttributeType.VEC3: + pointerData.unityPropertyNames = GetAnimationChannelProperties(unityPropertyName, 3, isColor: propertyMap.IsColor); + pointerData.importAccessorContentConversion = (data, frame) => MaterialValueConversion(data.primaryData.AccessorContent, frame, primaryComponentCount, propertyMap, pointerDataCopy); + break; + case GLTFAccessorAttributeType.VEC4: + pointerData.unityPropertyNames = GetAnimationChannelProperties(unityPropertyName, 4, isColor: propertyMap.IsColor); + pointerData.importAccessorContentConversion = (data, frame) => MaterialValueConversion(data.primaryData.AccessorContent, frame, primaryComponentCount, propertyMap, pointerDataCopy); + break; + default: + return false; + } + + return true; + } + + internal static string[] GetAnimationChannelProperties(string propertyName, int componentCount, int componentOffset = 0, bool isColor = false) + { + var result = new string[ componentCount]; + if (componentCount == 1) + { + result[0] = $"material.{propertyName}"; + return result; + } + + for (int iComponent = 0; iComponent < componentCount; iComponent++) + { + if (isColor) + result[iComponent] = $"material.{propertyName}.{MATERIAL_COLOR_PROPERTY_COMPONENTS[iComponent+componentOffset]}"; + else + result[iComponent] = $"material.{propertyName}.{MATERIAL_PROPERTY_COMPONENTS[iComponent+componentOffset]}"; + } + + return result; + } + + internal static float[] MaterialValueConversion(NumericArray data, int index, int componentCount, MaterialPointerPropertyMap map, AnimationPointerData pointerData) + { + int resultComponentCount = componentCount; + if (map.CombineComponentResult == MaterialPointerPropertyMap.CombineResultType.Override) + { + resultComponentCount = map.OverrideCombineResultType.ComponentCount(); + } + float[] result = new float[resultComponentCount]; + + + switch (componentCount) + { + case 1: + result[0] = data.AsFloats[index]; + break; + case 2: + var frameData2 = data.AsFloat2s[index]; + result[0] = frameData2.x; + result[1] = frameData2.y; + break; + case 3: + var frameData3 = data.AsFloat3s[index]; + if (map.PropertyType == MaterialPointerPropertyMap.PropertyTypeOption.SRGBColor) + { + Color gammaColor = new Color(frameData3.x, frameData3.y, frameData3.z).gamma; + result[0] = gammaColor.r; + result[1] = gammaColor.g; + result[2] = gammaColor.b; + } + else + { + result[0] = frameData3.x; + result[1] = frameData3.y; + result[2] = frameData3.z; + } + break; + case 4: + var frameData4 = data.AsFloat4s[index]; + if (map.PropertyType == MaterialPointerPropertyMap.PropertyTypeOption.SRGBColor) + { + Color gammaColor = new Color(frameData4.x, frameData4.y, frameData4.z, frameData4.z).gamma; + result[0] = gammaColor.r; + result[1] = gammaColor.g; + result[2] = gammaColor.b; + result[3] = gammaColor.a; + } + else + { + result[0] = frameData4.x; + result[1] = frameData4.y; + result[2] = frameData4.z; + result[3] = frameData4.w; + } + + break; + } + + if (map.CombinePrimaryAndSecondaryOnImport && map.CombinePrimaryAndSecondaryDataFunction != null) + { + float[] secondary = new float[0]; + + if (pointerData.secondaryData != null && pointerData.secondaryData.AccessorContent.AsFloats != null) + { + NumericArray secondaryData = pointerData.secondaryData.AccessorContent; + switch (pointerData.secondaryData.AccessorId.Value.Type) + { + case GLTFAccessorAttributeType.SCALAR: + secondary = new float[] { secondaryData.AsFloats[index] }; + break; + case GLTFAccessorAttributeType.VEC2: + var s2 = secondaryData.AsFloat2s[index]; + secondary = new float[] { s2.x, s2.y }; + break; + case GLTFAccessorAttributeType.VEC3: + var s3 = secondaryData.AsFloat3s[index]; + secondary = new float[] { s3.x, s3.y, s3.z }; + break; + case GLTFAccessorAttributeType.VEC4: + var s4 = secondaryData.AsFloat4s[index]; + secondary = new float[] { s4.x, s4.y, s4.z, s4.w }; + break; + default: + return result; + } + } + + result = map.CombinePrimaryAndSecondaryDataFunction(result, secondary); + } + + return result; + } + } + +} \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs.meta b/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs.meta new file mode 100644 index 000000000..6712dc08f --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointer/AnimationPointerUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 26f0996784b744b3a0d324be1c95fe4f +timeCreated: 1707897204 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs b/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs new file mode 100644 index 000000000..284594b45 --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using GLTF.Schema; +using UnityEngine; + +namespace UnityGLTF.Plugins +{ + public class MaterialPointerPropertyMap + { + public enum PropertyTypeOption {LinearColor, SRGBColor, Texture, LinearTexture, TextureTransform, Float} + public enum CombineResultType { SameAsPrimary, Override} + + public PropertyTypeOption PropertyType = PropertyTypeOption.Float; + + public string[] PropertyNames + { + get => _propertyNames; + set + { + _propertyNames = value; + CreatePropertyIds(); + } + } + + public string GltfPropertyName = null; + public string GltfSecondaryPropertyName = null; + + public bool IsColor => PropertyType == PropertyTypeOption.LinearColor || + PropertyType == PropertyTypeOption.SRGBColor; + public bool IsTexture => PropertyType == PropertyTypeOption.Texture || + PropertyType == PropertyTypeOption.LinearTexture; + + /// + /// When Data is splitted into primary and secondary, the data gets combined on import. + /// Don't forget to set CombinePrimaryAndSecondaryDataFunction if you set this to true. + /// + public bool CombinePrimaryAndSecondaryOnImport = false; + + /// + /// Possibility to override the result type of the combined data. + /// E.g. for combining two Vec2 properties into Vec4 (as for texture transform) + /// + public CombineResultType CombineComponentResult = CombineResultType.SameAsPrimary; + public GLTFAccessorAttributeType OverrideCombineResultType = GLTFAccessorAttributeType.VEC4; + + // Export settings + public bool ExportKeepColorAlpha = true; + public bool ExportConvertToLinearColor = false; + public bool ExportFlipValueRange = false; + public float ExportValueMultiplier = 1f; + public string ExtensionName = null; + + // The arrays contains the components of the primary and secondary properties. e.g. for a Vector3, the arrays will contain 3 elements + public delegate float[] CombinePrimaryAndSecondaryData(float[] primary, float[] secondary); + + /// + /// Function to combine primary and secondary data on import. + /// This used by the AnimationPointer system to combine data from two glTF properties into a single Unity property + /// The arrays contains the components of the primary and secondary properties. e.g. for a Vector3, the arrays will contain 3 elements + /// + public CombinePrimaryAndSecondaryData CombinePrimaryAndSecondaryDataFunction = null; + + private string[] _propertyNames = new string[0]; + internal int[] propertyIds = new int[0]; + internal int[] propertyTextureIds = new int[0]; + + public MaterialPointerPropertyMap(PropertyTypeOption propertyType) + { + PropertyType = propertyType; + switch (PropertyType) + { + case PropertyTypeOption.LinearColor: + break; + case PropertyTypeOption.SRGBColor: + ExportConvertToLinearColor = true; + break; + case PropertyTypeOption.Texture: + break; + case PropertyTypeOption.LinearTexture: + break; + case PropertyTypeOption.TextureTransform: + SetupAsTextureTransform(); + break; + case PropertyTypeOption.Float: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void CreatePropertyIds() + { + propertyIds = new int[PropertyNames.Length]; + for (int i = 0; i < PropertyNames.Length; i++) + propertyIds[i] = Shader.PropertyToID(PropertyNames[i]); + + if (PropertyType == PropertyTypeOption.TextureTransform) + { + propertyTextureIds = new int[PropertyNames.Length]; + for (int i = 0; i < PropertyNames.Length; i++) + { + var pWithoutST = PropertyNames[i].Remove(PropertyNames[i].Length - 3, 3); + + propertyTextureIds[i] = Shader.PropertyToID(pWithoutST); + } + } + } + + private static float[] CombineTextureTransform(float[] primary, float[] secondary) + { + var result = new float[4]; + result[0] = primary[0]; + result[1] = primary[1]; + result[2] = secondary[0]; + result[3] = 1f - secondary[1] - primary[1]; + return result; + } + + private void SetupAsTextureTransform() + { + PropertyType = PropertyTypeOption.TextureTransform; + CombinePrimaryAndSecondaryDataFunction = CombineTextureTransform; + CombinePrimaryAndSecondaryOnImport = true; + CombineComponentResult = MaterialPointerPropertyMap.CombineResultType.Override; + OverrideCombineResultType = GLTFAccessorAttributeType.VEC4; + } + } + + public class MaterialPropertiesRemapper + { + public enum ImportExportUsageOption {ImportOnly, ExportOnly, ImportAndExport} + + private Dictionary importMaps = + new Dictionary(); + private Dictionary exportMaps = new Dictionary (); + + public void AddMap(MaterialPointerPropertyMap map, ImportExportUsageOption importExport = ImportExportUsageOption.ImportAndExport) + { + if (importExport == ImportExportUsageOption.ImportOnly || + importExport == ImportExportUsageOption.ImportAndExport) + { + if (importMaps.ContainsKey(map.GltfPropertyName)) + { + Debug.LogError("MaterialPropertiesRemapper: Import Map with the same glTF property name already exists: " + map.GltfPropertyName); + return; + } + importMaps.Add(map.GltfPropertyName, map); + } + if (importExport == ImportExportUsageOption.ExportOnly || + importExport == ImportExportUsageOption.ImportAndExport) + { + for (int i = 0; i < map.PropertyNames.Length; i++) + { + if (exportMaps.ContainsKey(map.PropertyNames[i])) + { + Debug.LogError("MaterialPropertiesRemapper: Export Map with the same unity property name already exists: " + map.PropertyNames[i]); + continue; + } + + exportMaps.Add(map.PropertyNames[i], map); + } + } + } + + public bool GetMapFromUnityMaterial(Material mat, string unityPropertyName, out MaterialPointerPropertyMap map) + { + map = null; + if (!exportMaps.TryGetValue(unityPropertyName, out map)) + return false; + + if (map.PropertyType == MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + bool valid = false; + for (int i = 0; i < map.propertyTextureIds.Length; i++) + valid |= (mat.HasProperty(map.propertyTextureIds[i]) && mat.GetTexture(map.propertyTextureIds[i])); + if (!valid) + { + map = null; + return false; + } + } + else + { + bool valid = false; + for (int i = 0; i < map.propertyIds.Length; i++) + valid |= (mat.HasProperty(map.propertyIds[i])); + if (!valid) + { + map = null; + return false; + } + } + + return true; + + } + + public bool GetUnityPropertyName(Material mat, string gltfPropertyName, out string propertyName, + out MaterialPointerPropertyMap map, out bool isSecondary) + { + foreach (var kvp in importMaps) + { + var currentMap = kvp.Value; + if (currentMap.GltfPropertyName != gltfPropertyName && currentMap.GltfSecondaryPropertyName != gltfPropertyName) + continue; + + for (int i = 0; i < currentMap.PropertyNames.Length; i++) + { + if (currentMap.PropertyType == MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + for (int j = 0; j < currentMap.PropertyNames.Length; j++) + { + if (mat.HasProperty(currentMap.propertyTextureIds[j])) + { + map = currentMap; + propertyName = currentMap.PropertyNames[j]; + isSecondary = currentMap.GltfSecondaryPropertyName == gltfPropertyName; + return true; + } + } + } + else if (mat.HasProperty(currentMap.propertyIds[i])) + { + map = currentMap; + propertyName = currentMap.PropertyNames[i]; + isSecondary = currentMap.GltfSecondaryPropertyName == gltfPropertyName; + return true; + } + } + } + + map = null; + propertyName = ""; + isSecondary = false; + return false; + } + } + + public class DefaultMaterialPropertiesRemapper : MaterialPropertiesRemapper + { + public DefaultMaterialPropertiesRemapper() + { + var baseColor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.SRGBColor) + { + PropertyNames = new[] { "_Color", "_BaseColor", "_BaseColorFactor", "baseColorFactor" }, + GltfPropertyName = "pbrMetallicRoughness/baseColorFactor", + }; + AddMap(baseColor); + + var smoothness = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_Smoothness", "_Glossiness" }, + ExportFlipValueRange = true, + GltfPropertyName = "pbrMetallicRoughness/roughnessFactor", + }; + AddMap(smoothness, ImportExportUsageOption.ExportOnly); + + var roughness = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_Roughness", "_RoughnessFactor", "roughnessFactor" }, + GltfPropertyName = "pbrMetallicRoughness/roughnessFactor" + }; + AddMap(roughness); + + var metallic = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_Metallic", "_MetallicFactor", "metallicFactor" }, + GltfPropertyName = "pbrMetallicRoughness/metallicFactor" + }; + AddMap(metallic); + + var baseColorTexture = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + PropertyNames = new[] { "_MainTex_ST", "_BaseMap_ST", "_BaseColorTexture_ST", "baseColorTexture_ST" }, + GltfPropertyName = + $"pbrMetallicRoughness/baseColorTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}", + GltfSecondaryPropertyName = + $"pbrMetallicRoughness/baseColorTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}", + ExtensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME, + }; + AddMap(baseColorTexture); + + var emissiveFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.SRGBColor) + { + PropertyNames = new[] { "_EmissionColor", "_EmissiveFactor", "emissiveFactor" }, + GltfPropertyName = "emissiveFactor", + GltfSecondaryPropertyName = + $"extensions/{KHR_materials_emissive_strength_Factory.EXTENSION_NAME}/{nameof(KHR_materials_emissive_strength.emissiveStrength)}", + ExtensionName = KHR_materials_emissive_strength_Factory.EXTENSION_NAME, + CombinePrimaryAndSecondaryOnImport = true, + ExportKeepColorAlpha = false, + CombinePrimaryAndSecondaryDataFunction = (primary, secondary) => + { + float strength = (secondary != null && secondary.Length > 0) ? secondary[0] : 1f; + var result = new float[primary.Length]; + for (int i = 0; i < 3; i++) + result[i] = primary[i] * strength; + if (result.Length == 4) + result[3] = primary[3]; + + Color color = result.Length == 3 ? new Color(result[0], result[1], result[2]) : new Color(result[0], result[1], result[2], result[3]); + color = color.gamma; + result[0] = color.r; + result[1] = color.g; + result[2] = color.b; + if (result.Length == 4) + result[3] = color.a; + return result; + } + }; + AddMap(emissiveFactor); + + var emissiveTexture = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + PropertyNames = new[] { "_EmissionMap_ST", "_EmissiveTexture_ST", "emissiveTexture_ST" }, + GltfPropertyName = + $"emissiveTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}", + GltfSecondaryPropertyName = + $"emissiveTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}", + ExtensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME + }; + AddMap(emissiveTexture); + + /*var roughnessTex = new MaterialPointerPropertyMap + { + propertyNames = new[] { "_BumpMap_ST", "_NormalTexture_ST", "normalTexture_ST" }, + isTexture = true, + isTextureTransform = true, + gltfPropertyName = $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}", + gltfSecondaryPropertyName = $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}", + extensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME + }; + + maps.Add(roughnessTex); */ + + var alphaCutoff = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_AlphaCutoff", "alphaCutoff", "_Cutoff" }, + GltfPropertyName = "alphaCutoff" + }; + AddMap(alphaCutoff); + + var normalScale = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_BumpScale", "_NormalScale", "normalScale", "normalTextureScale" }, + GltfPropertyName = "normalTexture/scale" + }; + AddMap(normalScale); + + var normalTexture = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + PropertyNames = new[] { "_BumpMap_ST", "_NormalTexture_ST", "normalTexture_ST" }, + GltfPropertyName = + $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}", + GltfSecondaryPropertyName = + $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}", + ExtensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME + }; + AddMap(normalTexture); + + var occlusionStrength = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_OcclusionStrength", "occlusionStrength", "occlusionTextureStrength" }, + GltfPropertyName = "occlusionTexture/strength" + }; + AddMap(occlusionStrength); + + var occlusionTexture = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform) + { + PropertyNames = new[] { "_OcclusionMap_ST", "_OcclusionTexture_ST", "occlusionTexture_ST" }, + GltfPropertyName = + $"occlusionTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}", + GltfSecondaryPropertyName = + $"occlusionTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}", + ExtensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME + }; + AddMap(occlusionTexture); + + // KHR_materials_transmission + var transmissionFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_TransmissionFactor", "transmissionFactor" }, + GltfPropertyName = + $"extensions/{KHR_materials_transmission_Factory.EXTENSION_NAME}/{nameof(KHR_materials_transmission.transmissionFactor)}", + ExtensionName = KHR_materials_transmission_Factory.EXTENSION_NAME + }; + AddMap(transmissionFactor); + + // KHR_materials_volume + var thicknessFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_ThicknessFactor", "thicknessFactor" }, + GltfPropertyName = + $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.thicknessFactor)}", + ExtensionName = KHR_materials_volume_Factory.EXTENSION_NAME + }; + AddMap(thicknessFactor); + + var attenuationDistance = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_AttenuationDistance", "attenuationDistance" }, + GltfPropertyName = + $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.attenuationDistance)}", + ExtensionName = KHR_materials_volume_Factory.EXTENSION_NAME + }; + AddMap(attenuationDistance); + + var attenuationColor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.LinearColor) + { + PropertyNames = new[] { "_AttenuationColor", "attenuationColor" }, + GltfPropertyName = + $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.attenuationColor)}", + ExtensionName = KHR_materials_volume_Factory.EXTENSION_NAME, + ExportKeepColorAlpha = false, + }; + AddMap(attenuationColor); + + // KHR_materials_ior + var ior = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_IOR", "ior" }, + GltfPropertyName = + $"extensions/{KHR_materials_ior_Factory.EXTENSION_NAME}/{nameof(KHR_materials_ior.ior)}", + ExtensionName = KHR_materials_ior_Factory.EXTENSION_NAME + }; + AddMap(ior); + + // KHR_materials_iridescence + var iridescenceFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_IridescenceFactor", "iridescenceFactor" }, + GltfPropertyName = + $"extensions/{KHR_materials_iridescence_Factory.EXTENSION_NAME}/{nameof(KHR_materials_iridescence.iridescenceFactor)}", + ExtensionName = KHR_materials_iridescence_Factory.EXTENSION_NAME + }; + AddMap(iridescenceFactor); + + // KHR_materials_specular + var specularFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.Float) + { + PropertyNames = new[] { "_SpecularFactor", "specularFactor" }, + GltfPropertyName = + $"extensions/{KHR_materials_specular_Factory.EXTENSION_NAME}/{nameof(KHR_materials_specular.specularFactor)}", + ExtensionName = KHR_materials_specular_Factory.EXTENSION_NAME + }; + AddMap(specularFactor); + + var specularColorFactor = new MaterialPointerPropertyMap(MaterialPointerPropertyMap.PropertyTypeOption.LinearColor) + { + PropertyNames = new[] { "_SpecularColorFactor", "specularColorFactor" }, + GltfPropertyName = + $"extensions/{KHR_materials_specular_Factory.EXTENSION_NAME}/{nameof(KHR_materials_specular.specularColorFactor)}", + ExtensionName = KHR_materials_specular_Factory.EXTENSION_NAME, + ExportKeepColorAlpha = false, + }; + AddMap(specularColorFactor); + + + // TODO KHR_materials_clearcoat + // case "_ClearcoatFactor": + // case "clearcoatFactor": + // propertyName = $"extensions/{KHR_materials_clearcoat_Factory.EXTENSION_NAME}/{nameof(KHR_materials_clearcoat.clearcoatFactor)}"; + // extensionName = KHR_materials_clearcoat_Factory.EXTENSION_NAME; + // break; + // case "_ClearcoatRoughnessFactor": + // case "clearcoatRoughnessFactor": + // propertyName = $"extensions/{KHR_materials_clearcoat_Factory.EXTENSION_NAME}/{nameof(KHR_materials_clearcoat.clearcoatRoughnessFactor)}"; + // extensionName = KHR_materials_clearcoat_Factory.EXTENSION_NAME; + // break; + + // TODO KHR_materials_sheen + // case "_SheenColorFactor": + // case "sheenColorFactor": + // propertyName = $"extensions/{KHR_materials_sheen_Factory.EXTENSION_NAME}/{nameof(KHR_materials_sheen.sheenColorFactor)}"; + // extensionName = KHR_materials_sheen_Factory.EXTENSION_NAME; + // keepColorAlpha = false; + // break; + // case "_SheenRoughnessFactor": + // case "sheenRoughnessFactor": + // propertyName = $"extensions/{KHR_materials_sheen_Factory.EXTENSION_NAME}/{nameof(KHR_materials_sheen.sheenRoughnessFactor)}"; + // extensionName = KHR_materials_sheen_Factory.EXTENSION_NAME; + // break; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs.meta b/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs.meta new file mode 100644 index 000000000..dcb021e0d --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointer/MaterialPropertiesRemapper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e35adb0d169a4046908867445f09f321 +timeCreated: 1708078060 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointerExport.cs b/Runtime/Scripts/Plugins/AnimationPointerExport.cs index 8569a2ff2..2fd609db4 100644 --- a/Runtime/Scripts/Plugins/AnimationPointerExport.cs +++ b/Runtime/Scripts/Plugins/AnimationPointerExport.cs @@ -13,6 +13,7 @@ public override GLTFExportPluginContext CreateInstance(ExportContext context) public class AnimationPointerExportContext: GLTFExportPluginContext { + public MaterialPropertiesRemapper materialPropertiesRemapper = new DefaultMaterialPropertiesRemapper(); } } \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointerImport.cs b/Runtime/Scripts/Plugins/AnimationPointerImport.cs new file mode 100644 index 000000000..7588b7d0d --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointerImport.cs @@ -0,0 +1,17 @@ +namespace UnityGLTF.Plugins +{ + public class AnimationPointerImport: GLTFImportPlugin + { + public override string DisplayName => "KHR_animation_pointer"; + public override string Description => "Animate arbitrary material and object properties. Without this extension, only node transforms and blend shape weights can be animated."; + public override GLTFImportPluginContext CreateInstance(GLTFImportContext context) + { + return new AnimationPointerImportContext(); + } + } + + public class AnimationPointerImportContext: GLTFImportPluginContext + { + public MaterialPropertiesRemapper materialPropertiesRemapper = new DefaultMaterialPropertiesRemapper(); + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/AnimationPointerImport.cs.meta b/Runtime/Scripts/Plugins/AnimationPointerImport.cs.meta new file mode 100644 index 000000000..637b82fdd --- /dev/null +++ b/Runtime/Scripts/Plugins/AnimationPointerImport.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 473c2b5b3de547139304b32beee641e9 +timeCreated: 1707313653 \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/Core/ImportContext.cs b/Runtime/Scripts/Plugins/Core/ImportContext.cs index e1198b483..40a9bf119 100644 --- a/Runtime/Scripts/Plugins/Core/ImportContext.cs +++ b/Runtime/Scripts/Plugins/Core/ImportContext.cs @@ -51,8 +51,8 @@ internal GLTFImportContext(GLTFSettings settings) { Plugins = InitializePlugins(settings); } - - public bool TryGetPlugin(out GLTFImportPluginContext o) where T: GLTFImportPluginContext + + public bool TryGetPlugin(out T o) where T: GLTFImportPluginContext { foreach (var plugin in Plugins) { diff --git a/Runtime/Scripts/SceneExporter/ExporterAnimationPointer.cs b/Runtime/Scripts/SceneExporter/ExporterAnimationPointer.cs index f55a895dc..0b061b0b8 100644 --- a/Runtime/Scripts/SceneExporter/ExporterAnimationPointer.cs +++ b/Runtime/Scripts/SceneExporter/ExporterAnimationPointer.cs @@ -3,6 +3,7 @@ using System.Linq; using GLTF.Schema; using GLTF.Schema.KHR_lights_punctual; +using GLTF.Utilities; using UnityEngine; using UnityGLTF.Extensions; using UnityGLTF.JsonPointer; @@ -16,32 +17,12 @@ public partial class GLTFSceneExporter internal readonly List pointerResolvers = new List(); private readonly KHR_animation_pointer_Resolver animationPointerResolver = new KHR_animation_pointer_Resolver(); -#region Shader Property Names - // ReSharper disable InconsistentNaming - - private static readonly int _MainTex = Shader.PropertyToID("_MainTex"); - private static readonly int _BaseMap = Shader.PropertyToID("_BaseMap"); - private static readonly int _BaseColorTexture = Shader.PropertyToID("_BaseColorTexture"); - private static readonly int baseColorTexture = Shader.PropertyToID("baseColorTexture"); - private static readonly int _EmissionMap = Shader.PropertyToID("_EmissionMap"); - private static readonly int _EmissiveTexture = Shader.PropertyToID("_EmissiveTexture"); - private static readonly int emissiveTexture = Shader.PropertyToID("emissiveTexture"); - private static readonly int _BumpMap = Shader.PropertyToID("_BumpMap"); - private static readonly int _NormalTexture = Shader.PropertyToID("_NormalTexture"); - private static readonly int normalTexture = Shader.PropertyToID("normalTexture"); - private static readonly int _OcclusionMap = Shader.PropertyToID("_OcclusionMap"); - private static readonly int _OcclusionTexture = Shader.PropertyToID("_OcclusionTexture"); - private static readonly int occlusionTexture = Shader.PropertyToID("occlusionTexture"); - - // ReSharper restore InconsistentNaming -#endregion - public void RegisterResolver(IJsonPointerResolver resolver) { if (!pointerResolvers.Contains(resolver)) pointerResolvers.Add(resolver); } - + /// /// AddAnimationData should be called within the event. /// @@ -66,7 +47,7 @@ public void RegisterResolver(IJsonPointerResolver resolver) public void AddAnimationData(Object animatedObject, string propertyName, GLTFAnimation animation, float[] times, object[] values) { if (!animatedObject) return; - + // TODO should skip property switches that are not supported without KHR_animation_pointer // TODO should probably start with the transform check below and stop afterwards if KHR_animation_pointer is off @@ -95,204 +76,40 @@ public void AddAnimationData(Object animatedObject, string propertyName, GLTFAni string extensionName = null; var propertyType = values[0]?.GetType(); + var animationPointerExportContext = _plugins.FirstOrDefault(x => x is AnimationPointerExportContext) as AnimationPointerExportContext; + switch (animatedObject) { case Material material: // Debug.Log("material: " + material + ", propertyName: " + propertyName); // mapping from known Unity property names to glTF property names - switch (propertyName) + if (animationPointerExportContext == null || + animationPointerExportContext.materialPropertiesRemapper == null) { - case "_Color": - case "_BaseColor": - case "_BaseColorFactor": - case "baseColorFactor": - propertyName = "pbrMetallicRoughness/baseColorFactor"; - convertToLinearColor = true; - break; - case "_Smoothness": - case "_Glossiness": - propertyName = "pbrMetallicRoughness/roughnessFactor"; - flipValueRange = true; - break; - case "_Roughness": - case "_RoughnessFactor": - case "roughnessFactor": - propertyName = "pbrMetallicRoughness/roughnessFactor"; - break; - case "_Metallic": - case "_MetallicFactor": - case "metallicFactor": - propertyName = "pbrMetallicRoughness/metallicFactor"; - break; - case "_MainTex_ST": - case "_BaseMap_ST": - case "_BaseColorTexture_ST": - case "baseColorTexture_ST": - if (!(material.HasProperty(_MainTex) && material.GetTexture(_MainTex)) && - !(material.HasProperty(_BaseMap) && material.GetTexture(_BaseMap)) && - !(material.HasProperty(_BaseColorTexture) && material.GetTexture(_BaseColorTexture)) && - !(material.HasProperty(baseColorTexture) && material.GetTexture(baseColorTexture))) return; - propertyName = $"pbrMetallicRoughness/baseColorTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}"; - secondPropertyName = $"pbrMetallicRoughness/baseColorTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}"; - isTextureTransform = true; - extensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME; - break; - case "_EmissionColor": - case "_EmissiveFactor": - case "emissiveFactor": - propertyName = "emissiveFactor"; - secondPropertyName = $"extensions/{KHR_materials_emissive_strength_Factory.EXTENSION_NAME}/{nameof(KHR_materials_emissive_strength.emissiveStrength)}"; - extensionName = KHR_materials_emissive_strength_Factory.EXTENSION_NAME; - keepColorAlpha = false; - convertToLinearColor = true; - break; - case "_EmissionMap_ST": - case "_EmissiveTexture_ST": - case "emissiveTexture_ST": - if (!(material.HasProperty(_EmissionMap) && material.GetTexture(_EmissionMap)) && - !(material.HasProperty(_EmissiveTexture) && material.GetTexture(_EmissiveTexture)) && - !(material.HasProperty(emissiveTexture) && material.GetTexture(emissiveTexture))) return; - propertyName = $"emissiveTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}"; - secondPropertyName = $"emissiveTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}"; - isTextureTransform = true; - extensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME; - break; - case "_Cutoff": - case "_AlphaCutoff": - case "alphaCutoff": - propertyName = "alphaCutoff"; - break; - case "_BumpScale": - case "_NormalScale": - case "normalScale": - case "normalTextureScale": - propertyName = "normalTexture/scale"; - break; - case "_BumpMap_ST": - case "_NormalTexture_ST": - case "normalTexture_ST": - if (!(material.HasProperty(_BumpMap) && material.GetTexture(_BumpMap)) && - !(material.HasProperty(_NormalTexture) && material.GetTexture(_NormalTexture)) && - !(material.HasProperty(normalTexture) && material.GetTexture(normalTexture))) return; - propertyName = $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}"; - secondPropertyName = $"normalTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}"; - isTextureTransform = true; - extensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME; - break; - case "_OcclusionStrength": - case "occlusionStrength": - case "occlusionTextureStrength": - propertyName = "occlusionTexture/strength"; - break; - case "_OcclusionMap_ST": - case "_OcclusionTexture_ST": - case "occlusionTexture_ST": - if (!(material.HasProperty(_OcclusionMap) && material.GetTexture(_OcclusionMap)) && - !(material.HasProperty(_OcclusionTexture) && material.GetTexture(_OcclusionTexture)) && - !(material.HasProperty(occlusionTexture) && material.GetTexture(occlusionTexture))) return; - propertyName = $"occlusionTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.SCALE}"; - secondPropertyName = $"occlusionTexture/extensions/{ExtTextureTransformExtensionFactory.EXTENSION_NAME}/{ExtTextureTransformExtensionFactory.OFFSET}"; - isTextureTransform = true; - extensionName = ExtTextureTransformExtensionFactory.EXTENSION_NAME; - break; - - // TODO metallic/roughness _ST - - // KHR_materials_transmission - case "_TransmissionFactor": - case "transmissionFactor": - propertyName = $"extensions/{KHR_materials_transmission_Factory.EXTENSION_NAME}/{nameof(KHR_materials_transmission.transmissionFactor)}"; - extensionName = KHR_materials_transmission_Factory.EXTENSION_NAME; - break; - - // KHR_materials_volume - case "_ThicknessFactor": - case "thicknessFactor": - propertyName = $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.thicknessFactor)}"; - extensionName = KHR_materials_volume_Factory.EXTENSION_NAME; - break; - case "_AttenuationDistance": - case "attenuationDistance": - propertyName = $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.attenuationDistance)}"; - extensionName = KHR_materials_volume_Factory.EXTENSION_NAME; - break; - case "_AttenuationColor": - case "attenuationColor": - propertyName = $"extensions/{KHR_materials_volume_Factory.EXTENSION_NAME}/{nameof(KHR_materials_volume.attenuationColor)}"; - extensionName = KHR_materials_volume_Factory.EXTENSION_NAME; - keepColorAlpha = false; - break; - - // KHR_materials_ior - case "_IOR": - case "ior": - propertyName = $"extensions/{KHR_materials_ior_Factory.EXTENSION_NAME}/{nameof(KHR_materials_ior.ior)}"; - extensionName = KHR_materials_ior_Factory.EXTENSION_NAME; - break; - - // KHR_materials_iridescence - case "_IridescenceFactor": - case "iridescenceFactor": - propertyName = $"extensions/{KHR_materials_iridescence_Factory.EXTENSION_NAME}/{nameof(KHR_materials_iridescence.iridescenceFactor)}"; - extensionName = KHR_materials_iridescence_Factory.EXTENSION_NAME; - break; - case "_IridescenceIor": - case "iridescenceIor": - propertyName = $"extensions/{KHR_materials_iridescence_Factory.EXTENSION_NAME}/{nameof(KHR_materials_iridescence.iridescenceIor)}"; - extensionName = KHR_materials_iridescence_Factory.EXTENSION_NAME; - break; - case "_IridescenceThicknessMinimum": - case "iridescenceThicknessMinimum": - propertyName = $"extensions/{KHR_materials_iridescence_Factory.EXTENSION_NAME}/{nameof(KHR_materials_iridescence.iridescenceThicknessMinimum)}"; - extensionName = KHR_materials_iridescence_Factory.EXTENSION_NAME; - break; - case "_IridescenceThicknessMaximum": - case "iridescenceThicknessMaximum": - propertyName = $"extensions/{KHR_materials_iridescence_Factory.EXTENSION_NAME}/{nameof(KHR_materials_iridescence.iridescenceThicknessMaximum)}"; - extensionName = KHR_materials_iridescence_Factory.EXTENSION_NAME; - break; - - // KHR_materials_specular - case "_SpecularFactor": - case "specularFactor": - propertyName = $"extensions/{KHR_materials_specular_Factory.EXTENSION_NAME}/{nameof(KHR_materials_specular.specularFactor)}"; - extensionName = KHR_materials_specular_Factory.EXTENSION_NAME; - break; - case "_SpecularColorFactor": - case "specularColorFactor": - propertyName = $"extensions/{KHR_materials_specular_Factory.EXTENSION_NAME}/{nameof(KHR_materials_specular.specularColorFactor)}"; - extensionName = KHR_materials_specular_Factory.EXTENSION_NAME; - keepColorAlpha = false; - break; + if (animationPointerExportContext == null) + Debug.Log(LogType.Error, "No AnimationPointerExportContext found in GLTFSceneExporter. Skipping animation"); + else + Debug.Log(LogType.Error, "No MaterialPropertiesRemapper found in AnimationPointerExportContext. Skipping animation"); + return; + } + + if (!animationPointerExportContext.materialPropertiesRemapper.GetMapFromUnityMaterial(material, propertyName, out MaterialPointerPropertyMap map)) + { + Debug.Log(LogType.Warning, "Unknown property name on Material " + material + ": " + propertyName); - // TODO KHR_materials_clearcoat - // case "_ClearcoatFactor": - // case "clearcoatFactor": - // propertyName = $"extensions/{KHR_materials_clearcoat_Factory.EXTENSION_NAME}/{nameof(KHR_materials_clearcoat.clearcoatFactor)}"; - // extensionName = KHR_materials_clearcoat_Factory.EXTENSION_NAME; - // break; - // case "_ClearcoatRoughnessFactor": - // case "clearcoatRoughnessFactor": - // propertyName = $"extensions/{KHR_materials_clearcoat_Factory.EXTENSION_NAME}/{nameof(KHR_materials_clearcoat.clearcoatRoughnessFactor)}"; - // extensionName = KHR_materials_clearcoat_Factory.EXTENSION_NAME; - // break; - - // TODO KHR_materials_sheen - // case "_SheenColorFactor": - // case "sheenColorFactor": - // propertyName = $"extensions/{KHR_materials_sheen_Factory.EXTENSION_NAME}/{nameof(KHR_materials_sheen.sheenColorFactor)}"; - // extensionName = KHR_materials_sheen_Factory.EXTENSION_NAME; - // keepColorAlpha = false; - // break; - // case "_SheenRoughnessFactor": - // case "sheenRoughnessFactor": - // propertyName = $"extensions/{KHR_materials_sheen_Factory.EXTENSION_NAME}/{nameof(KHR_materials_sheen.sheenRoughnessFactor)}"; - // extensionName = KHR_materials_sheen_Factory.EXTENSION_NAME; - // break; - default: - Debug.Log(LogType.Warning, "Unknown property name on Material " + material + ": " + propertyName); - break; + return; } + + secondPropertyName = map.GltfSecondaryPropertyName; + propertyName = map.GltfPropertyName; + extensionName = map.ExtensionName; + + flipValueRange = map.ExportFlipValueRange; + valueMultiplier = map.ExportValueMultiplier == 1f ? null : map.ExportValueMultiplier; + isTextureTransform = map.PropertyType == MaterialPointerPropertyMap.PropertyTypeOption.TextureTransform; + keepColorAlpha = map.ExportKeepColorAlpha; + convertToLinearColor = map.ExportConvertToLinearColor; + break; case Light light: extensionName = KHR_lights_punctualExtensionFactory.EXTENSION_NAME; @@ -312,7 +129,7 @@ public void AddAnimationData(Object animatedObject, string propertyName, GLTFAni propertyName = $"spot/outerConeAngle"; break; case "m_InnerSpotAngle": - valueMultiplier = Mathf.Deg2Rad / 2; + valueMultiplier = Mathf.Deg2Rad / 2 * 0.8f; propertyName = $"spot/innerConeAngle"; break; case "m_Range": diff --git a/Runtime/Scripts/SceneImporter/ImporterAnimation.cs b/Runtime/Scripts/SceneImporter/ImporterAnimation.cs index cf5892c93..960c9df6c 100644 --- a/Runtime/Scripts/SceneImporter/ImporterAnimation.cs +++ b/Runtime/Scripts/SceneImporter/ImporterAnimation.cs @@ -5,9 +5,12 @@ using System.Threading.Tasks; using GLTF; using GLTF.Schema; +using GLTF.Schema.KHR_lights_punctual; +using GLTF.Utilities; using UnityEngine; using UnityGLTF.Cache; using UnityGLTF.Extensions; +using UnityGLTF.Plugins; namespace UnityGLTF { @@ -264,10 +267,46 @@ protected async Task ConstructClip(Transform root, int animationI if (_options.AnimationMethod == AnimationMethod.Legacy) clip.legacy = true; + int[] nodeIds = new int[0]; + + AnimationPointerImportContext pointerImportContext = null; + + AttributeAccessor FindSecondaryChannel(string animationPointerPath) + { + foreach (AnimationChannel secondAnimationChannel in animation.Channels) + { + if (secondAnimationChannel.Target.Extensions == null || + !secondAnimationChannel.Target.Extensions.TryGetValue(KHR_animation_pointer.EXTENSION_NAME, + out IExtension secondaryExt)) + continue; + if (secondaryExt is KHR_animation_pointer secondaryPointer) + { + AnimationSamplerCacheData secondarySamplerCache = + animationCache.Samplers[secondAnimationChannel.Sampler.Id]; + if (secondaryPointer.path == animationPointerPath) + { + return secondarySamplerCache.Output; + } + } + } + + return null; + } + foreach (AnimationChannel channel in animation.Channels) { + bool usesPointer = false; + IExtension pointerExtension = null; AnimationSamplerCacheData samplerCache = animationCache.Samplers[channel.Sampler.Id]; - if (channel.Target.Node == null) + if (channel.Target.Extensions != null && channel.Target.Extensions.TryGetValue( + KHR_animation_pointer.EXTENSION_NAME, + out pointerExtension)) + { + if (Context.TryGetPlugin(out pointerImportContext)) + usesPointer = true; + } + + if (!usesPointer && channel.Target.Node == null) { // If a channel doesn't have a target node, then just skip it. // This is legal and is present in one of the asset generator models, but means that animation doesn't actually do anything. @@ -275,96 +314,284 @@ protected async Task ConstructClip(Transform root, int animationI // Model 08 continue; } - var node = await GetNode(channel.Target.Node.Id, cancellationToken); - string relativePath = RelativePathFrom(node.transform, root); - - NumericArray input = samplerCache.Input.AccessorContent, - output = samplerCache.Output.AccessorContent; + string relativePath = null; + AnimationPointerData pointerData = null; string[] propertyNames; - var known = Enum.TryParse(channel.Target.Path, out GLTFAnimationChannelPath path); - if (!known) continue; - switch (path) + GLTFAnimationChannelPath path = GLTFAnimationChannelPath.translation; + + if (usesPointer && pointerExtension != null) + { + KHR_animation_pointer pointer = pointerExtension as KHR_animation_pointer; + if (pointer == null || pointer.path == null) + continue; + + path = GLTFAnimationChannelPath.pointer; + relativePath = pointer.path; + var pointerHierarchy = new PointerPath(relativePath); + if (!pointerHierarchy.isValid) + continue; + + switch (pointerHierarchy.PathElementType) + { + case PointerPath.PathElement.RootExtension: + if (!_gltfRoot.Extensions.TryGetValue(pointerHierarchy.elementName, out IExtension hierarchyExtension)) + continue; + + // Check if the extension support animation pointers + if (hierarchyExtension is IImportAnimationPointerRootExtension rootExtension) + { + // Let the extension handle the pointer data and create the nodeIds and unity properties + if (rootExtension.TryGetImportAnimationPointerData(_gltfRoot, pointerHierarchy, out pointerData)) + nodeIds = pointerData.targetNodeIds; + else + continue; + } + pointerData.primaryData = samplerCache.Output; + break; + case PointerPath.PathElement.Root: + var rootType = pointerHierarchy.elementName; + var rootIndex = pointerHierarchy.FindNext(PointerPath.PathElement.Index); + if (rootIndex == null) + continue; + + switch (rootType) + { + case "nodes": + var pointerPropertyElement = pointerHierarchy.FindNext(PointerPath.PathElement.Property); + if (pointerPropertyElement == null) + continue; + + pointerData = new AnimationPointerData(); + pointerData.targetNodeIds = new int[] {rootIndex.index}; + nodeIds = pointerData.targetNodeIds; + + // Convert translate, scale, rotation from pointer path to to GLTFAnimationChannelPath, so we can use the same code path as the non-animation-pointer channels + if (!GLTFAnimationChannelPath.TryParse(pointerPropertyElement.elementName, out path)) + continue; + + break; + case "materials": + nodeIds = _gltfRoot.GetAllNodeIdsWithMaterialId(rootIndex.index); + if (nodeIds.Length == 0) + continue; + var materialPath = pointerHierarchy.FindNext(PointerPath.PathElement.Index).next; + if (materialPath == null) + continue; + + var gltfPropertyPath = materialPath.ExtractPath(); + var mat = _assetCache.MaterialCache[rootIndex.index]; + if (!mat.UnityMaterial) + continue; + + if (!AnimationPointerHelpers.BuildImportMaterialAnimationPointerData(pointerImportContext.materialPropertiesRemapper, mat.UnityMaterial, gltfPropertyPath, samplerCache.Output.AccessorId.Value.Type, out pointerData)) + continue; + + pointerData.primaryData = samplerCache.Output; + pointerData.primaryPath = pointer.path; + if (!string.IsNullOrEmpty(pointerData.secondaryPath)) + { + // When an property has potentially a second Sampler, we need to find it. e.g. like EmissionFactor and EmissionStrength + string secondaryPath = $"/{pointerHierarchy.elementName}/{rootIndex.index.ToString()}/{pointerData.secondaryPath}"; + pointerData.secondaryData = FindSecondaryChannel(secondaryPath); + } + break; + case "cameras": + int cameraId = rootIndex.index; + pointerData = new AnimationPointerData(); + pointerData.targetType = typeof(Camera); + pointerData.primaryData = samplerCache.Output; + + string gltfCameraPropertyPath = rootIndex.next.ExtractPath(); + switch (gltfCameraPropertyPath) + { + case "orthographic/ymag": + pointerData.secondaryPath = $"/{pointerHierarchy.elementName}/{rootIndex.index.ToString()}/orthographic/xmag"; + pointerData.unityPropertyNames = new string[] { "orthographic size" }; + pointerData.secondaryData = FindSecondaryChannel(pointerData.secondaryPath); + pointerData.importAccessorContentConversion = (data, frame) => + { + var xmag = data.secondaryData.AccessorContent.AsFloats[frame]; + var ymag = data.primaryData.AccessorContent.AsFloats[frame]; + return new float[] {Mathf.Max(xmag, ymag)}; + }; + break; + case "orthographic/xmag": + continue; + case "perspective/znear": + case "orthographic/znear": + pointerData.unityPropertyNames = new string[] { "near clip plane" }; + pointerData.importAccessorContentConversion = (data, frame) => + new float[] {data.primaryData.AccessorContent.AsFloats[frame]}; + break; + case "perspective/zfar": + case "orthographic/zfar": + pointerData.unityPropertyNames = new string[] { "far clip plane" }; + pointerData.importAccessorContentConversion = (data, frame) => + new float[] {data.primaryData.AccessorContent.AsFloats[frame]}; + break; + case "perspective/yfov": + pointerData.unityPropertyNames = new string[] { "field of view" }; + pointerData.importAccessorContentConversion = (data, frame) => + { + var fov = data.primaryData.AccessorContent.AsFloats[frame] * Mathf.Rad2Deg; + return new float[] {fov}; + }; + break; + case "backgroundColor": + pointerData.unityPropertyNames = new string[] { "background color.r", "background color.g", "background color.b", "background color.a" }; + pointerData.importAccessorContentConversion = (data, frame) => + { + var color = data.primaryData.AccessorContent.AsFloat4s[frame].ToUnityColorRaw(); + return new float[] {color.r, color.g, color.b, color.a}; + }; + break; + default: + Debug.Log(LogType.Warning, "Unknown property name on Camera " + cameraId.ToString() + ": " + gltfCameraPropertyPath); + break; + } + + nodeIds = _gltfRoot.GetAllNodeIdsWithCameraId(cameraId); + break; + default: + continue; + //throw new NotImplementedException(); + } + break; + default: + continue; + } + + if (pointerData == null) + continue; + } + else { - case GLTFAnimationChannelPath.translation: - propertyNames = new string[] { "localPosition.x", "localPosition.y", "localPosition.z" }; + if (channel.Target == null || channel.Target.Node == null) + continue; + nodeIds = new int[] {channel.Target.Node.Id}; + } + + // In case an animated material are referenced from many nodes, whe need to create a curve for each node. (e.g. Materials) + foreach (var nodeId in nodeIds) + { + var node = await GetNode(nodeId, cancellationToken); + var targetNode = _gltfRoot.Nodes[nodeId]; + relativePath = RelativePathFrom(node.transform, root); + NumericArray input = samplerCache.Input.AccessorContent; + NumericArray output = samplerCache.Output.AccessorContent; + + if (!usesPointer) + { + var known = Enum.TryParse(channel.Target.Path, out path); + if (!known) continue; + } + else + { + if (pointerData.targetType == null) + pointerData.targetType = targetNode.Skin != null + ? typeof(SkinnedMeshRenderer) + : typeof(MeshRenderer); + } + + switch (path) + { + case GLTFAnimationChannelPath.pointer: + if (pointerData.importAccessorContentConversion == null) + continue; + SetAnimationCurve(clip, relativePath, pointerData.unityPropertyNames, input, output, + samplerCache.Interpolation, pointerData.targetType, + (data, frame) => pointerData.importAccessorContentConversion(pointerData, frame)); + break; + case GLTFAnimationChannelPath.translation: + propertyNames = new string[] { "localPosition.x", "localPosition.y", "localPosition.z" }; #if UNITY_EDITOR - // TODO technically this should be postprocessing in the ScriptedImporter instead, - // but performance is much better if we do it when constructing the clips - var factor = Context?.ImportScaleFactor ?? 1f; + // TODO technically this should be postprocessing in the ScriptedImporter instead, + // but performance is much better if we do it when constructing the clips + var factor = Context?.ImportScaleFactor ?? 1f; #endif - SetAnimationCurve(clip, relativePath, propertyNames, input, output, - samplerCache.Interpolation, typeof(Transform), - (data, frame) => - { - var position = data.AsFloat3s[frame].ToUnityVector3Convert(); + SetAnimationCurve(clip, relativePath, propertyNames, input, output, + samplerCache.Interpolation, typeof(Transform), + (data, frame) => + { + var position = data.AsFloat3s[frame].ToUnityVector3Convert(); #if UNITY_EDITOR - return new float[] { position.x * factor, position.y * factor, position.z * factor}; + return new float[] + { position.x * factor, position.y * factor, position.z * factor }; #else return new float[] { position.x, position.y, position.z }; #endif - }); - break; - - case GLTFAnimationChannelPath.rotation: - propertyNames = new string[] { "localRotation.x", "localRotation.y", "localRotation.z", "localRotation.w" }; - - SetAnimationCurve(clip, relativePath, propertyNames, input, output, - samplerCache.Interpolation, typeof(Transform), - (data, frame) => - { - var rotation = data.AsFloat4s[frame]; - var quaternion = rotation.ToUnityQuaternionConvert(); - return new float[] { quaternion.x, quaternion.y, quaternion.z, quaternion.w }; - }); - - break; - - case GLTFAnimationChannelPath.scale: - propertyNames = new string[] { "localScale.x", "localScale.y", "localScale.z" }; - - SetAnimationCurve(clip, relativePath, propertyNames, input, output, - samplerCache.Interpolation, typeof(Transform), - (data, frame) => - { - var scale = data.AsFloat3s[frame].ToUnityVector3Raw(); - return new float[] { scale.x, scale.y, scale.z }; - }); - break; - - case GLTFAnimationChannelPath.weights: - var mesh = channel.Target.Node.Value.Mesh.Value; - var primitives = mesh.Primitives; - if (primitives[0].Targets == null) break; - var targetCount = primitives[0].Targets.Count; - for (int primitiveIndex = 0; primitiveIndex < primitives.Count; primitiveIndex++) - { - // see SceneImporter:156 - // blend shapes are always called "Morphtarget" - var targetNames = mesh.TargetNames; - propertyNames = new string[targetCount]; - for (var i = 0; i < targetCount; i++) - propertyNames[i] = _options.ImportBlendShapeNames ? ("blendShape." + ((targetNames != null && targetNames.Count > i) ? targetNames[i] : ("Morphtarget" + i))) : "blendShape."+i.ToString(); - var frameFloats = new float[targetCount]; - - var blendShapeFrameWeight = _options.BlendShapeFrameWeight; + }); + break; + + case GLTFAnimationChannelPath.rotation: + propertyNames = new string[] + { "localRotation.x", "localRotation.y", "localRotation.z", "localRotation.w" }; + bool flipRotation = (targetNode.Extensions != null + && targetNode.Extensions.ContainsKey(KHR_lights_punctualExtensionFactory.EXTENSION_NAME) + && Context.TryGetPlugin(out _)); SetAnimationCurve(clip, relativePath, propertyNames, input, output, - samplerCache.Interpolation, typeof(SkinnedMeshRenderer), + samplerCache.Interpolation, typeof(Transform), (data, frame) => { - var allValues = data.AsFloats; - for (var k = 0; k < targetCount; k++) - frameFloats[k] = allValues[frame * targetCount + k] * blendShapeFrameWeight; - - return frameFloats; + var rotation = data.AsFloat4s[frame]; + var quaternion = rotation.ToUnityQuaternionConvert(); + if (flipRotation) + quaternion *= Quaternion.Euler(0, 180, 0); + return new float[] { quaternion.x, quaternion.y, quaternion.z, quaternion.w }; }); - } - break; + break; + case GLTFAnimationChannelPath.scale: + propertyNames = new string[] { "localScale.x", "localScale.y", "localScale.z" }; - default: - Debug.Log(LogType.Warning, $"Cannot read GLTF animation path (File: {_gltfFileName})"); - break; - } // switch target type + SetAnimationCurve(clip, relativePath, propertyNames, input, output, + samplerCache.Interpolation, typeof(Transform), + (data, frame) => + { + var scale = data.AsFloat3s[frame].ToUnityVector3Raw(); + return new float[] { scale.x, scale.y, scale.z }; + }); + break; + + case GLTFAnimationChannelPath.weights: + var mesh = targetNode.Mesh.Value; + var primitives = mesh.Primitives; + if (primitives[0].Targets == null) break; + var targetCount = primitives[0].Targets.Count; + for (int primitiveIndex = 0; primitiveIndex < primitives.Count; primitiveIndex++) + { + // see SceneImporter:156 + // blend shapes are always called "Morphtarget" + var targetNames = mesh.TargetNames; + propertyNames = new string[targetCount]; + for (var i = 0; i < targetCount; i++) + propertyNames[i] = _options.ImportBlendShapeNames + ? ("blendShape." + ((targetNames != null && targetNames.Count > i) + ? targetNames[i] + : ("Morphtarget" + i))) + : "blendShape." + i.ToString(); + var frameFloats = new float[targetCount]; + + var blendShapeFrameWeight = _options.BlendShapeFrameWeight; + SetAnimationCurve(clip, relativePath, propertyNames, input, output, + samplerCache.Interpolation, typeof(SkinnedMeshRenderer), + (data, frame) => + { + var allValues = data.AsFloats; + for (var k = 0; k < targetCount; k++) + frameFloats[k] = allValues[frame * targetCount + k] * blendShapeFrameWeight; + + return frameFloats; + }); + } + break; + default: + Debug.Log(LogType.Warning, $"Cannot read GLTF animation path (File: {_gltfFileName})"); + break; + } // switch target type + } // foreach nodeIds + + await YieldOnTimeoutAndThrowOnLowMemory(); } // foreach channel // EnsureQuaternionContinuity results in unwanted tangents on the first and last keyframes > custom Solution in SetAnimationCurve diff --git a/Runtime/Scripts/SceneImporter/ImporterMaterials.cs b/Runtime/Scripts/SceneImporter/ImporterMaterials.cs index 413a0fd74..2caddc926 100644 --- a/Runtime/Scripts/SceneImporter/ImporterMaterials.cs +++ b/Runtime/Scripts/SceneImporter/ImporterMaterials.cs @@ -670,6 +670,7 @@ void SetTransformKeyword() } } + // ?? uniformMapper.EmissiveFactor = QualitySettings.activeColorSpace == ColorSpace.Linear ? def.EmissiveFactor.ToUnityColorLinear() : def.EmissiveFactor.ToUnityColorLinear(); var emissiveExt = GetEmissiveStrength(def);