diff --git a/GLTFSerialization/GLTFSerialization/Schema/GLTFId.cs b/GLTFSerialization/GLTFSerialization/Schema/GLTFId.cs index 6b9422b5d..9a5b5ee0a 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/GLTFId.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/GLTFId.cs @@ -209,6 +209,31 @@ public static MeshId Deserialize(GLTFRoot root, JsonReader reader) } } + public class AnimationId : GLTFId + { + public AnimationId() + { + } + + public AnimationId(AnimationId id, GLTFRoot newRoot) : base(id, newRoot) + { + } + + public override GLTFAnimation Value + { + get { return Root.Animations[Id]; } + } + + public static AnimationId Deserialize(GLTFRoot root, JsonReader reader) + { + return new AnimationId + { + Id = reader.ReadAsInt32().Value, + Root = root + }; + } + } + public class NodeId : GLTFId { public NodeId() diff --git a/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/Extensions/SchemaExtensions.cs b/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/Extensions/SchemaExtensions.cs index c0d10aa1e..3c7e8b463 100644 --- a/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/Extensions/SchemaExtensions.cs +++ b/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/Extensions/SchemaExtensions.cs @@ -417,7 +417,7 @@ public static void ConvertVector3CoordinateSpace(ref AttributeAccessor attribute } /// - /// Converts and copies based on the specified coordinate scale + /// Converts and copies based on the specified coordinate space /// /// The array to convert and copy /// The specified coordinate space @@ -453,7 +453,7 @@ public static void ConvertVector4CoordinateSpace(ref AttributeAccessor attribute } /// - /// Converts and copies based on the specified coordinate scale + /// Converts and copies based on the specified coordinate space /// /// The array to convert and copy /// The specified coordinate space @@ -472,6 +472,87 @@ public static Vector4[] ConvertVector4CoordinateSpaceAndCopy(Vector4[] array, GL return returnArray; } + + /// + /// Converts and copies based on the specified coordinate space + /// + /// The array to convert and copy + /// The copied and converted Quaternion array + public static Quaternion[] ConvertQuaternionCoordinateSpaceAndCopy(Quaternion[] array) + { + var returnArray = new Quaternion[array.Length]; + + for (var i = 0; i < array.Length; i++) + { + var unityQuat = array[i]; + Vector3 fromAxisOfRotation = new Vector3(unityQuat.x, unityQuat.y, unityQuat.z); + float axisFlipScale = CoordinateSpaceConversionRequiresHandednessFlip ? -1.0f : 1.0f; + Vector3 toAxisOfRotation = axisFlipScale * Vector3.Scale(fromAxisOfRotation, CoordinateSpaceConversionScale.ToUnityVector3Raw()); + + returnArray[i] = new Quaternion(toAxisOfRotation.x, toAxisOfRotation.y, toAxisOfRotation.z, unityQuat.w); + } + + return returnArray; + } + + /// + /// Converts and copies based on the specified coordinate space + /// + /// The array to convert and copy + /// The copied and converted Matrix4x4 array + public static Matrix4x4[] ConvertMatrix4x4CoordinateSpaceAndCopy(Matrix4x4[] array) + { + var returnArr = new Matrix4x4[array.Length]; + + for (int i = 0; i < array.Length; ++i) + { + Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); + Matrix4x4 convert = Matrix4x4.Scale(coordinateSpaceConversionScale); + returnArr[i] = convert * array[i] * convert; + } + + return returnArr; + } + + /// + /// Extract the joint values + /// + /// The array to extract from and copy + /// Copied Vector4 with joints + public static int[] ExtractJointAndCopy(BoneWeight[] array) + { + var returnArray = new int[array.Length * 4]; + + for (var i = 0; i < array.Length; i++) + { + returnArray[i * 4] = array[i].boneIndex0; + returnArray[i * 4 + 1] = array[i].boneIndex1; + returnArray[i * 4 + 2] = array[i].boneIndex2; + returnArray[i * 4 + 3] = array[i].boneIndex3; + } + + return returnArray; + } + + /// + /// Extract the weight values + /// + /// The array to extract from and copy + /// Copied Vector4 with weights + public static Vector4[] ExtractWeightAndCopy(BoneWeight[] array) + { + var returnArray = new Vector4[array.Length]; + + for (var i = 0; i < array.Length; i++) + { + returnArray[i].x = array[i].weight0; + returnArray[i].y = array[i].weight1; + returnArray[i].z = array[i].weight2; + returnArray[i].w = array[i].weight3; + } + + return returnArray; + } /// /// Rewinds the indicies into Unity coordinate space from glTF space diff --git a/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/GLTFSceneExporter.cs b/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/GLTFSceneExporter.cs index a63448de5..0f8ade26e 100644 --- a/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/GLTFSceneExporter.cs +++ b/UnityGLTF/Assets/UnityGLTF/Runtime/Scripts/GLTFSceneExporter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; using GLTF.Schema; +using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityGLTF.Extensions; @@ -48,6 +49,38 @@ private struct ImageInfo public Texture2D texture; public TextureMapType textureMapType; } + + private class PropertyCurveBindings + { + public string name; + public List curveBindings; + + public PropertyCurveBindings(string name, EditorCurveBinding curveBinding) + { + this.name = name; + this.curveBindings = new List + { + curveBinding + }; + } + } + + private class CurveBindingGroup + { + public string path; + public Type type; + public List properties; + + public CurveBindingGroup(string path, Type type, PropertyCurveBindings property) + { + this.path = path; + this.type = type; + this.properties = new List + { + property + }; + } + } private Transform[] _rootTransforms; private GLTFRoot _root; @@ -79,6 +112,10 @@ protected struct PrimKey private readonly Dictionary _primOwner = new Dictionary(); private readonly Dictionary _meshToPrims = new Dictionary(); + private readonly Dictionary _existedNodes = new Dictionary(); + private readonly Dictionary _existedAnimations = new Dictionary(); + private readonly Dictionary _existedSkins = new Dictionary(); + // Settings public static bool ExportNames = true; public static bool ExportFullPath = true; @@ -125,7 +162,9 @@ public GLTFSceneExporter(Transform[] rootTransforms, ExportOptions options) Nodes = new List(), Samplers = new List(), Scenes = new List(), - Textures = new List() + Textures = new List(), + Skins = new List(), + Animations = new List() }; _imageInfos = new List(); @@ -492,13 +531,54 @@ private NodeId ExportNode(Transform nodeTransform) }; _root.Nodes.Add(node); + if (!_existedNodes.ContainsKey(nodeTransform.GetInstanceID())) + { + _existedNodes.Add(nodeTransform.GetInstanceID(), id); + } + // children that are primitives get put in a mesh - GameObject[] primitives, nonPrimitives; - FilterPrimitives(nodeTransform, out primitives, out nonPrimitives); - if (primitives.Length > 0) + GameObject[] meshPrimitives, skinnedMeshPrimitives, nonPrimitives; + FilterPrimitives(nodeTransform, out meshPrimitives, out skinnedMeshPrimitives, out nonPrimitives); + + // children that are not primitives get added as child nodes + if (nonPrimitives.Length > 0) + { + node.Children = new List(nonPrimitives.Length); + List postProcess = new List(); + foreach (var child in nonPrimitives) + { + GameObject[] childMeshPrimitives, childSkinnedMeshPrimitives, childNonPrimitives; + FilterPrimitives(child.transform, out childMeshPrimitives, out childSkinnedMeshPrimitives, out childNonPrimitives); + if (childMeshPrimitives.Length + childSkinnedMeshPrimitives.Length > 0) + { + postProcess.Add(child); + continue; + } + + node.Children.Add(ExportNode(child.transform)); + } + + foreach (var child in postProcess) + { + node.Children.Add(ExportNode(child.transform)); + } + } + + if (meshPrimitives.Length + skinnedMeshPrimitives.Length > 0) { - node.Mesh = ExportMesh(nodeTransform.name, primitives); + if (skinnedMeshPrimitives.Length > 0) + { + ExportSkin(nodeTransform.name, skinnedMeshPrimitives, node); + } + if (meshPrimitives.Length > 0) + { + node.Mesh = ExportMesh(nodeTransform.name, meshPrimitives); + } + + var primitives = new List(); + primitives.AddRange(meshPrimitives); + primitives.AddRange(skinnedMeshPrimitives); // associate unity meshes with gltf mesh id foreach (var prim in primitives) { @@ -516,13 +596,16 @@ private NodeId ExportNode(Transform nodeTransform) } } - // children that are not primitives get added as child nodes - if (nonPrimitives.Length > 0) + var animator = nodeTransform.gameObject.GetComponent(); + if (animator != null) { - node.Children = new List(nonPrimitives.Length); - foreach (var child in nonPrimitives) - { - node.Children.Add(ExportNode(child.transform)); + if (animator.runtimeAnimatorController != null) + { + var animationClips = animator.runtimeAnimatorController.animationClips; + if (animationClips != null && animationClips.Length != 0) + { + ExportAnimations(animationClips, nodeTransform); + } } } @@ -597,30 +680,51 @@ private static bool ContainsValidRenderer (GameObject gameObject) || (gameObject.GetComponent() != null); } - private void FilterPrimitives(Transform transform, out GameObject[] primitives, out GameObject[] nonPrimitives) + private void FilterPrimitives(Transform transform, out GameObject[] meshPrimitives, out GameObject[] skinnedMeshPrimitives, out GameObject[] nonPrimitives) { var childCount = transform.childCount; - var prims = new List(childCount + 1); + var meshPrims = new List(childCount + 1); + var skinnedMeshPrims = new List(childCount + 1); var nonPrims = new List(childCount); // add another primitive if the root object also has a mesh - if (transform.gameObject.activeSelf) + var gameObject = transform.gameObject; + if (gameObject.activeSelf) { - if (ContainsValidRenderer(transform.gameObject)) + if (ContainsValidRenderer(gameObject)) { - prims.Add(transform.gameObject); + if (gameObject.GetComponent() != null) + { + skinnedMeshPrims.Add(gameObject); + } + else + { + meshPrims.Add(gameObject); + } } } for (var i = 0; i < childCount; i++) { var go = transform.GetChild(i).gameObject; if (IsPrimitive(go)) - prims.Add(go); + { + if (go.GetComponent() != null) + { + skinnedMeshPrims.Add(go); + } + else + { + meshPrims.Add(go); + } + } else + { nonPrims.Add(go); + } } - primitives = prims.ToArray(); + meshPrimitives = meshPrims.ToArray(); + skinnedMeshPrimitives = skinnedMeshPrims.ToArray(); nonPrimitives = nonPrims.ToArray(); } @@ -637,7 +741,110 @@ private static bool IsPrimitive(GameObject gameObject) && gameObject.transform.localRotation == Quaternion.identity && gameObject.transform.localScale == Vector3.one && ContainsValidRenderer(gameObject); + } + + private void ExportSkin(string name, GameObject[] primitives, Node node) + { + foreach (var prim in primitives) + { + var smr = prim.GetComponent(); + if (_existedSkins.TryGetValue(smr.rootBone.GetInstanceID(), out SkinId skinId)) + { + node.Skin = skinId; + node.Mesh = ExportMesh(prim.name, new GameObject[] { prim }); + continue; + } + + var skin = new Skin + { + Joints = new List() + }; + + var mesh = smr.sharedMesh; + if (mesh.bindposes.Length != 0) + { + skin.InverseBindMatrices = ExportAccessor(SchemaExtensions.ConvertMatrix4x4CoordinateSpaceAndCopy(mesh.bindposes)); + } + + var baseId = _root.Nodes.Count; + foreach (var bone in smr.bones) + { + var translation = bone.localPosition; + var rotation = bone.localRotation; + var scale = bone.localScale; + + NodeId nodeId = null; + if (!_existedNodes.TryGetValue(bone.GetInstanceID(), out nodeId)) + { + var boneNode = new Node + { + Name = ExportNames ? bone.gameObject.name : null, + Translation = new GLTF.Math.Vector3(translation.x, translation.y, translation.z), + Rotation = new GLTF.Math.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w), + Scale = new GLTF.Math.Vector3(scale.x, scale.y, scale.z), + }; + + if (bone.childCount > 0) + { + boneNode.Children = new List(); + for (var i = 0; i < bone.childCount; ++i) + { + var childIndex = Array.IndexOf(smr.bones, bone.GetChild(i)); + if (-1 == childIndex) + { + continue; + } + boneNode.Children.Add( + new NodeId + { + Id = childIndex + baseId, + Root = _root + } + ); + } + } + + nodeId = new NodeId + { + Id = _root.Nodes.Count, + Root = _root + }; + + _root.Nodes.Add(boneNode); + _existedNodes.Add(bone.GetInstanceID(), nodeId); + } + + skin.Joints.Add(nodeId); + } + + NodeId rootBoneId = null; + if (!_existedNodes.TryGetValue(smr.rootBone.GetInstanceID(), out rootBoneId)) + { + rootBoneId = new NodeId + { + Id = baseId, + Root = _root + }; + _existedNodes.Add(smr.rootBone.GetInstanceID(), rootBoneId); + } + + skin.Skeleton = rootBoneId; + + skinId = new SkinId + { + Id = _root.Skins.Count, + Root = _root + }; + + _root.Skins.Add(skin); + + node.Skin = skinId; + + node.Mesh = ExportMesh(prim.name, new GameObject[] { prim }); + + _existedSkins.Add(smr.rootBone.GetInstanceID(), skinId); + } } private MeshId ExportMesh(string name, GameObject[] primitives) @@ -707,6 +914,260 @@ private MeshId ExportMesh(string name, GameObject[] primitives) return id; } + private void ExportAnimations(AnimationClip[] animationClips, Transform rootNodeTransform) + { + if (null == animationClips) + { + return; + } + + foreach (var animationClip in animationClips) + { + var animInstId = animationClip.GetInstanceID(); + AnimationId existedAnimId; + if (!_existedAnimations.TryGetValue(animInstId, out existedAnimId)) + { + var animId = ExportAnimationClip(animationClip, rootNodeTransform); + _existedAnimations.Add(animInstId, animId); + } + } + } + + private AnimationId ExportAnimationClip(AnimationClip animationClip, Transform rootNodeTransform) + { + var curveBindings = AnimationUtility.GetCurveBindings(animationClip); + + var curveBindingGroups = CurveMarshalling(curveBindings); + + var animation = new GLTFAnimation() + { + Name = animationClip.name, + Channels = new List(), + Samplers = new List() + }; + + var frameCount = Mathf.CeilToInt(animationClip.length * animationClip.frameRate); + var timestamps = new float[frameCount]; + for (var i = 0; i < frameCount; ++i) + { + var timestamp = i / animationClip.frameRate; + timestamps[i] = timestamp; + } + + var timesstampsId = ExportAccessor(timestamps); + + foreach (var curveBindingGroup in curveBindingGroups) + { + var bone = rootNodeTransform.Find(curveBindingGroup.path); + if (bone == null) + { + continue; + } + + NodeId nodeId = null; + if (!_existedNodes.TryGetValue(bone.GetInstanceID(), out nodeId)) + { + continue; + } + + foreach(var property in curveBindingGroup.properties) + { + GLTFAnimationChannelPath path; + AccessorId accessorId = ExportCurve(animationClip, property.name, property.curveBindings, frameCount, out path); + if (accessorId == null) + { + continue; + } + var sampler = new AnimationSampler() + { + Input = timesstampsId, + Interpolation = InterpolationType.LINEAR, + Output = accessorId + }; + + var samplerId = new SamplerId() + { + Id = animation.Samplers.Count, + Root = _root + }; + + animation.Samplers.Add(sampler); + + var channel = new AnimationChannel() + { + Sampler = samplerId, + Target = new AnimationChannelTarget() + { + Node = nodeId, + Path = path + } + }; + + animation.Channels.Add(channel); + } + } + + var id = new AnimationId + { + Id = _root.Animations.Count, + Root = _root + }; + + _root.Animations.Add(animation); + + return id; + } + + private AccessorId ExportCurve(AnimationClip animationClip, string propertyName, List curveBindings, int frameCount, out GLTFAnimationChannelPath path) + { + switch (propertyName) + { + case "m_LocalPosition": + { + var data = new Vector3[frameCount]; + for (var i = 0; i < frameCount; ++i) + { + var time = i / animationClip.frameRate; + + var curveIndex = 0; + var value = new float[3]; + foreach (var curveBinding in curveBindings) + { + var curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding); + if (curve != null) value[curveIndex++] = curve.Evaluate(time); + } + + data[i] = new Vector3(value[0], value[1], value[2]); + } + + path = GLTFAnimationChannelPath.translation; + return ExportAccessor(SchemaExtensions.ConvertVector3CoordinateSpaceAndCopy(data, SchemaExtensions.CoordinateSpaceConversionScale)); + } + case "m_LocalRotation": + { + var data = new Quaternion[frameCount]; + for (var i = 0; i < frameCount; ++i) + { + var time = i / animationClip.frameRate; + + var curveIndex = 0; + var value = new float[4]; + foreach (var curveBinding in curveBindings) + { + var curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding); + if (curve != null) value[curveIndex++] = curve.Evaluate(time); + } + + data[i] = new Quaternion(value[0], value[1], value[2], value[3]); + } + + path = GLTFAnimationChannelPath.rotation; + return ExportAccessor(SchemaExtensions.ConvertQuaternionCoordinateSpaceAndCopy(data)); + } + case "m_LocalScale": + { + var data = new Vector3[frameCount]; + for (var i = 0; i < frameCount; ++i) + { + var time = i / animationClip.frameRate; + + var curveIndex = 0; + var value = new float[3]; + foreach (var curveBinding in curveBindings) + { + var curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding); + if (curve != null) value[curveIndex++] = curve.Evaluate(time); + } + + data[i] = new Vector3(value[0], value[1], value[2]); + } + + path = GLTFAnimationChannelPath.scale; + return ExportAccessor(data); + } + case "localEulerAnglesRaw": + { + throw new Exception("Parsing of localEulerAnglesRaw is not supported."); + } + } + + Debug.LogError("Unrecognized property name: " + propertyName); + path = GLTFAnimationChannelPath.translation; + return null; + } + + private List CurveMarshalling(EditorCurveBinding[] curveBindings) + { + List curveGroups = new List(); + foreach (var curveBinding in curveBindings) + { + bool bFoundGroup = false; + foreach (var curveGroup in curveGroups) + { + if ((curveGroup.path == curveBinding.path) && + (curveGroup.type == curveBinding.type)) + { + bFoundGroup = true; + + bool bFoundCurveBindingSlot = false; + foreach (var property in curveGroup.properties) + { + if (property.name == Path.GetFileNameWithoutExtension(curveBinding.propertyName)) + { + property.curveBindings.Add(curveBinding); + bFoundCurveBindingSlot = true; + } + } + + if (!bFoundCurveBindingSlot) + { + curveGroup.properties.Add(new PropertyCurveBindings(Path.GetFileNameWithoutExtension(curveBinding.propertyName), curveBinding)); + } + } + } + + if (!bFoundGroup) + { + var property = new PropertyCurveBindings(Path.GetFileNameWithoutExtension(curveBinding.propertyName), curveBinding); + curveGroups.Add(new CurveBindingGroup(curveBinding.path, curveBinding.type, property)); + } + } + + foreach (var curveGroup in curveGroups) + { + curveGroup.properties.Sort((l, r) => + { + Func getPriority = (string propertyName) => + { + int priority = -1; + if (propertyName == "m_LocalPosition") + { + priority = 0; + } + else if (propertyName == "m_LocalRotation") + { + priority = 1; + } + else if (propertyName == "localEulerAnglesRaw") + { + priority = 2; + } + else if (propertyName == "m_LocalScale") + { + priority = 3; + } + return priority; + }; + + var lp = getPriority(l.name); + var rp = getPriority(r.name); + return lp < rp ? -1 : 1; + }); + } + + return curveGroups; + } + // a mesh *might* decode to multiple prims if there are submeshes private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) { @@ -742,7 +1203,7 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) { prims[i] = new MeshPrimitive(primVariations[i], _root) { - Material = ExportMaterial(materialsObj[i]) + Material = ExportMaterial(materialsObj[i], renderer) }; } @@ -750,8 +1211,9 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) } AccessorId aPosition = null, aNormal = null, aTangent = null, - aTexcoord0 = null, aTexcoord1 = null, aColor0 = null; - + aTexcoord0 = null, aTexcoord1 = null, aTexcoord2 = null, aTexcoord3 = null, + aColor0 = null, aJoint0 = null, aWeight0 = null; + aPosition = ExportAccessor(SchemaExtensions.ConvertVector3CoordinateSpaceAndCopy(meshObj.vertices, SchemaExtensions.CoordinateSpaceConversionScale)); if (meshObj.normals.Length != 0) @@ -766,9 +1228,21 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) if (meshObj.uv2.Length != 0) aTexcoord1 = ExportAccessor(SchemaExtensions.FlipTexCoordArrayVAndCopy(meshObj.uv2)); + if (meshObj.uv3.Length != 0) + aTexcoord2 = ExportAccessor(SchemaExtensions.FlipTexCoordArrayVAndCopy(meshObj.uv3)); + + if (meshObj.uv4.Length != 0) + aTexcoord3 = ExportAccessor(SchemaExtensions.FlipTexCoordArrayVAndCopy(meshObj.uv4)); + if (meshObj.colors.Length != 0) aColor0 = ExportAccessor(meshObj.colors); + if (meshObj.boneWeights.Length != 0) + { + aJoint0 = ExportAccessor(SchemaExtensions.ExtractJointAndCopy(meshObj.boneWeights), SemanticProperties.JOINTS_0); + aWeight0 = ExportAccessor(SchemaExtensions.ExtractWeightAndCopy(meshObj.boneWeights)); + } + MaterialId lastMaterialId = null; for (var submesh = 0; submesh < meshObj.subMeshCount; submesh++) @@ -780,7 +1254,7 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) if (topology == MeshTopology.Triangles) SchemaExtensions.FlipTriangleFaces(indices); primitive.Mode = GetDrawMode(topology); - primitive.Indices = ExportAccessor(indices, true); + primitive.Indices = ExportAccessor(indices, SemanticProperties.INDICES); primitive.Attributes = new Dictionary(); primitive.Attributes.Add(SemanticProperties.POSITION, aPosition); @@ -793,12 +1267,20 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) primitive.Attributes.Add(SemanticProperties.TEXCOORD_0, aTexcoord0); if (aTexcoord1 != null) primitive.Attributes.Add(SemanticProperties.TEXCOORD_1, aTexcoord1); + if (aTexcoord2 != null) + primitive.Attributes.Add(SemanticProperties.TEXCOORD_2, aTexcoord2); + if (aTexcoord3 != null) + primitive.Attributes.Add(SemanticProperties.TEXCOORD_3, aTexcoord3); if (aColor0 != null) primitive.Attributes.Add(SemanticProperties.COLOR_0, aColor0); + if (aJoint0 != null) + primitive.Attributes.Add(SemanticProperties.JOINTS_0, aJoint0); + if (aWeight0 != null) + primitive.Attributes.Add(SemanticProperties.WEIGHTS_0, aWeight0); if (submesh < materialsObj.Length) { - primitive.Material = ExportMaterial(materialsObj[submesh]); + primitive.Material = ExportMaterial(materialsObj[submesh], renderer); lastMaterialId = primitive.Material; } else @@ -816,7 +1298,7 @@ private MeshPrimitive[] ExportPrimitive(GameObject gameObject, GLTFMesh mesh) return prims; } - private MaterialId ExportMaterial(Material materialObj) + private MaterialId ExportMaterial(Material materialObj, MeshRenderer renderer) { MaterialId id = GetMaterialId(_root, materialObj); if (id != null) @@ -852,7 +1334,7 @@ private MaterialId ExportMaterial(Material materialObj) material.DoubleSided = materialObj.HasProperty("_Cull") && materialObj.GetInt("_Cull") == (float)CullMode.Off; - if(materialObj.IsKeywordEnabled("_EMISSION")) + if (materialObj.IsKeywordEnabled("_EMISSION")) { if (materialObj.HasProperty("_EmissionColor")) { @@ -865,7 +1347,7 @@ private MaterialId ExportMaterial(Material materialObj) if (emissionTex != null) { - if(emissionTex is Texture2D) + if (emissionTex is Texture2D) { material.EmissiveTexture = ExportTextureInfo(emissionTex, TextureMapType.Emission); @@ -885,7 +1367,7 @@ private MaterialId ExportMaterial(Material materialObj) if (normalTex != null) { - if(normalTex is Texture2D) + if (normalTex is Texture2D) { material.NormalTexture = ExportNormalTextureInfo(normalTex, TextureMapType.Bump, materialObj); ExportTextureTransform(material.NormalTexture, materialObj, "_BumpMap"); @@ -902,7 +1384,7 @@ private MaterialId ExportMaterial(Material materialObj) var occTex = materialObj.GetTexture("_OcclusionMap"); if (occTex != null) { - if(occTex is Texture2D) + if (occTex is Texture2D) { material.OcclusionTexture = ExportOcclusionTextureInfo(occTex, TextureMapType.Occlusion, materialObj); ExportTextureTransform(material.OcclusionTexture, materialObj, "_OcclusionMap"); @@ -1089,7 +1571,7 @@ private PbrMetallicRoughness ExportPBRMetallicRoughness(Material material) if (mainTex != null) { - if(mainTex is Texture2D) + if (mainTex is Texture2D) { pbr.BaseColorTexture = ExportTextureInfo(mainTex, TextureMapType.Main); ExportTextureTransform(pbr.BaseColorTexture, material, "_MainTex"); @@ -1119,7 +1601,7 @@ private PbrMetallicRoughness ExportPBRMetallicRoughness(Material material) if (mrTex != null) { - if(mrTex is Texture2D) + if (mrTex is Texture2D) { pbr.MetallicRoughnessTexture = ExportTextureInfo(mrTex, TextureMapType.MetallicGloss); ExportTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicGlossMap"); @@ -1136,7 +1618,7 @@ private PbrMetallicRoughness ExportPBRMetallicRoughness(Material material) if (mgTex != null) { - if(mgTex is Texture2D) + if (mgTex is Texture2D) { pbr.MetallicRoughnessTexture = ExportTextureInfo(mgTex, TextureMapType.SpecGloss); ExportTextureTransform(pbr.MetallicRoughnessTexture, material, "_SpecGlossMap"); @@ -1232,13 +1714,13 @@ private TextureId ExportTexture(Texture textureObj, TextureMapType textureMapTyp } if (_shouldUseInternalBufferForImages) - { + { texture.Source = ExportImageInternalBuffer(textureObj, textureMapType); - } - else - { + } + else + { texture.Source = ExportImage(textureObj, textureMapType); - } + } texture.Sampler = ExportSampler(textureObj); _textures.Add(textureObj); @@ -1301,13 +1783,12 @@ private ImageId ExportImage(Texture texture, TextureMapType texturMapType) private ImageId ExportImageInternalBuffer(UnityEngine.Texture texture, TextureMapType texturMapType) { - if (texture == null) { - throw new Exception("texture can not be NULL."); + throw new Exception("texture can not be NULL."); } - var image = new GLTFImage(); + var image = new GLTFImage(); image.MimeType = "image/png"; var byteOffset = _bufferWriter.BaseStream.Position; @@ -1425,7 +1906,7 @@ private SamplerId ExportSampler(Texture texture) return samplerId; } - private AccessorId ExportAccessor(int[] arr, bool isIndices = false) + private AccessorId ExportAccessor(float[] arr) { uint count = (uint)arr.Length; @@ -1438,6 +1919,66 @@ private AccessorId ExportAccessor(int[] arr, bool isIndices = false) accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.SCALAR; + var min = arr[0]; + var max = arr[0]; + + for (var i = 1; i < count; i++) + { + var cur = arr[i]; + + if (cur < min) + { + min = cur; + } + if (cur > max) + { + max = cur; + } + } + + AlignToBoundary(_bufferWriter.BaseStream, 0x00); + uint byteOffset = CalculateAlignment((uint)_bufferWriter.BaseStream.Position, 4); + + accessor.ComponentType = GLTFComponentType.Float; + + foreach (var v in arr) + { + _bufferWriter.Write(v); + } + + accessor.Min = new List { min }; + accessor.Max = new List { max }; + + uint byteLength = CalculateAlignment((uint)_bufferWriter.BaseStream.Position - byteOffset, 4); + + accessor.BufferView = ExportBufferView(byteOffset, byteLength); + + var id = new AccessorId + { + Id = _root.Accessors.Count, + Root = _root + }; + _root.Accessors.Add(accessor); + + return id; + } + + private AccessorId ExportAccessor(int[] arr, string property) + { + uint count = (uint)arr.Length; + + if (count == 0) + { + throw new Exception("Accessors can not have a count of 0."); + } + + bool isJoints = property == SemanticProperties.JOINTS_0; + bool isIndices = property == SemanticProperties.INDICES; + + var accessor = new Accessor(); + accessor.Count = isJoints ? count / 4 : count; + accessor.Type = isJoints ? GLTFAccessorAttributeType.VEC4 : GLTFAccessorAttributeType.SCALAR; + int min = arr[0]; int max = arr[0]; @@ -1513,8 +2054,11 @@ private AccessorId ExportAccessor(int[] arr, bool isIndices = false) } } - accessor.Min = new List { min }; - accessor.Max = new List { max }; + if (accessor.Type == GLTFAccessorAttributeType.SCALAR) + { + accessor.Min = new List { min }; + accessor.Max = new List { max }; + } uint byteLength = CalculateAlignment((uint)_bufferWriter.BaseStream.Position - byteOffset, 4); @@ -1778,6 +2322,95 @@ private AccessorId ExportAccessor(Vector4[] arr) return id; } + + private AccessorId ExportAccessor(Quaternion[] arr) + { + uint count = (uint)arr.Length; + + if (count == 0) + { + throw new Exception("Accessors can not have a count of 0."); + } + + var accessor = new Accessor(); + accessor.ComponentType = GLTFComponentType.Float; + accessor.Count = count; + accessor.Type = GLTFAccessorAttributeType.VEC4; + + float minX = arr[0].x; + float minY = arr[0].y; + float minZ = arr[0].z; + float minW = arr[0].w; + float maxX = arr[0].x; + float maxY = arr[0].y; + float maxZ = arr[0].z; + float maxW = arr[0].w; + + for (var i = 1; i < count; i++) + { + var cur = arr[i]; + + if (cur.x < minX) + { + minX = cur.x; + } + if (cur.y < minY) + { + minY = cur.y; + } + if (cur.z < minZ) + { + minZ = cur.z; + } + if (cur.w < minW) + { + minW = cur.w; + } + if (cur.x > maxX) + { + maxX = cur.x; + } + if (cur.y > maxY) + { + maxY = cur.y; + } + if (cur.z > maxZ) + { + maxZ = cur.z; + } + if (cur.w > maxW) + { + maxW = cur.w; + } + } + + accessor.Min = new List { minX, minY, minZ, minW }; + accessor.Max = new List { maxX, maxY, maxZ, maxW }; + + AlignToBoundary(_bufferWriter.BaseStream, 0x00); + uint byteOffset = CalculateAlignment((uint)_bufferWriter.BaseStream.Position, 4); + + foreach (var vec in arr) + { + _bufferWriter.Write(vec.x); + _bufferWriter.Write(vec.y); + _bufferWriter.Write(vec.z); + _bufferWriter.Write(vec.w); + } + + uint byteLength = CalculateAlignment((uint)_bufferWriter.BaseStream.Position - byteOffset, 4); + + accessor.BufferView = ExportBufferView(byteOffset, byteLength); + + var id = new AccessorId + { + Id = _root.Accessors.Count, + Root = _root + }; + _root.Accessors.Add(accessor); + + return id; + } private AccessorId ExportAccessor(Color[] arr) { @@ -1868,6 +2501,57 @@ private AccessorId ExportAccessor(Color[] arr) return id; } + private AccessorId ExportAccessor(Matrix4x4[] arr) + { + var count = (uint)arr.Length; + + if (count == 0) + { + throw new Exception("Accessors can not have a count of 0."); + } + + var accessor = new Accessor(); + accessor.ComponentType = GLTFComponentType.Float; + accessor.Count = count; + accessor.Type = GLTFAccessorAttributeType.MAT4; + + AlignToBoundary(_bufferWriter.BaseStream, 0x00); + uint byteOffset = CalculateAlignment((uint)_bufferWriter.BaseStream.Position, 4); + + foreach (var mat in arr) + { + _bufferWriter.Write(mat.m00); + _bufferWriter.Write(mat.m10); + _bufferWriter.Write(mat.m20); + _bufferWriter.Write(mat.m30); + _bufferWriter.Write(mat.m01); + _bufferWriter.Write(mat.m11); + _bufferWriter.Write(mat.m21); + _bufferWriter.Write(mat.m31); + _bufferWriter.Write(mat.m02); + _bufferWriter.Write(mat.m12); + _bufferWriter.Write(mat.m22); + _bufferWriter.Write(mat.m32); + _bufferWriter.Write(mat.m03); + _bufferWriter.Write(mat.m13); + _bufferWriter.Write(mat.m23); + _bufferWriter.Write(mat.m33); + } + + uint byteLength = CalculateAlignment((uint)_bufferWriter.BaseStream.Position - byteOffset, 4); + + accessor.BufferView = ExportBufferView(byteOffset, byteLength); + + var id = new AccessorId + { + Id = _root.Accessors.Count, + Root = _root + }; + _root.Accessors.Add(accessor); + + return id; + } + private BufferViewId ExportBufferView(uint byteOffset, uint byteLength) { var bufferView = new BufferView