using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using GLTF.Schema; using System.Linq; using Unity.Collections; using Unity.Mathematics; using UnityEngine; namespace GLTF { public static class GLTFHelpers { private struct PreviousGLTFSizes { public int PreviousBufferCount; public int PreviousBufferViewCount; public int PreviousAccessorCount; public int PreviousMeshCount; public int PreviousNodeCount; public int PreviousSceneCount; public int PreviousSkinCount; public int PreviousAnimationCount; public int PreviousCameraCount; public int PreviousMaterialCount; public int PreviousTextureCount; public int PreviousImageCount; public int PreviousSamplerCount; } /// /// Removes references to indexes that do not exist. /// /// The node to clean public static void RemoveUndefinedReferences(GLTFRoot root) { int accessorCount = root.Accessors?.Count ?? 0; int bufferCount = root.Buffers?.Count ?? 0; int bufferViewCount = root.BufferViews?.Count ?? 0; int cameraCount = root.Cameras?.Count ?? 0; int meshCount = root.Meshes?.Count ?? 0; int nodeCount = root.Nodes?.Count ?? 0; int samplersCount = root.Samplers?.Count ?? 0; int skinCount = root.Skins?.Count ?? 0; int textureCount = root.Textures?.Count ?? 0; if (root.Accessors != null) { foreach (Accessor accessor in root.Accessors) { if (accessor.BufferView != null && accessor.BufferView.Id >= bufferViewCount) { accessor.BufferView = null; } } } if (root.Animations != null) { foreach (GLTFAnimation animation in root.Animations) { if (animation.Samplers != null) { foreach (AnimationSampler animationSampler in animation.Samplers) { if (animationSampler.Input != null && animationSampler.Input.Id >= accessorCount) { animationSampler.Input = null; } if (animationSampler.Output != null && animationSampler.Output.Id >= accessorCount) { animationSampler.Output = null; } } } } } if (root.BufferViews != null) { foreach (BufferView bufferView in root.BufferViews) { if (bufferView.Buffer != null && bufferView.Buffer.Id >= bufferCount) { bufferView.Buffer = null; } } } if (root.Images != null) { foreach (GLTFImage image in root.Images) { if (image.BufferView != null && image.BufferView.Id >= bufferViewCount) { image.BufferView = null; } } } if (root.Materials != null) { foreach (GLTFMaterial material in root.Materials) { if (material.EmissiveTexture?.Index != null && material.EmissiveTexture.Index.Id >= textureCount) { material.EmissiveTexture.Index = null; } if (material.NormalTexture?.Index != null && material.NormalTexture.Index.Id >= textureCount) { material.NormalTexture.Index = null; } if (material.OcclusionTexture?.Index != null && material.OcclusionTexture.Index.Id >= textureCount) { material.OcclusionTexture.Index = null; } if (material.OcclusionTexture?.Index != null && material.OcclusionTexture.Index.Id >= textureCount) { material.OcclusionTexture.Index = null; } if (material.PbrMetallicRoughness != null) { if (material.PbrMetallicRoughness.BaseColorTexture?.Index != null && material.PbrMetallicRoughness.BaseColorTexture.Index.Id >= textureCount) { material.PbrMetallicRoughness.BaseColorTexture.Index = null; } if (material.PbrMetallicRoughness.MetallicRoughnessTexture?.Index != null && material.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id >= textureCount) { material.PbrMetallicRoughness.MetallicRoughnessTexture.Index = null; } } } } if (root.Meshes != null) { foreach (GLTFMesh mesh in root.Meshes) { if (mesh.Primitives != null) { foreach (MeshPrimitive primitive in mesh.Primitives) { if (primitive.Indices != null && primitive.Indices.Id >= accessorCount) { primitive.Indices = null; } if (primitive.Material != null && primitive.Material.Id >= accessorCount) { primitive.Material = null; } } } } } if (root.Nodes != null) { foreach (Node node in root.Nodes) { if (node.Camera != null && node.Camera.Id >= cameraCount) { node.Camera = null; } if (node.Children != null) { for (int i = node.Children.Count - 1; i > 0; i--) { if (node.Children[i].Id >= nodeCount) { node.Children.RemoveAt(i); } } } if (node.Mesh != null && node.Mesh.Id >= meshCount) { node.Mesh = null; } if (node.Skin != null && node.Skin.Id >= skinCount) { node.Skin = null; } } } if (root.Scenes != null) { foreach (GLTFScene scene in root.Scenes) { if (scene.Nodes != null) { for (int i = scene.Nodes.Count - 1; i > 0; i--) { if (scene.Nodes[i].Id >= nodeCount) { scene.Nodes.RemoveAt(i); } } } } } if (root.Skins != null) { foreach (Skin skin in root.Skins) { if (skin.Joints != null) { for (int i = skin.Joints.Count - 1; i > 0; i--) { if (skin.Joints[i].Id >= nodeCount) { skin.Joints.RemoveAt(i); } } } if (skin.Skeleton != null && skin.Skeleton.Id >= nodeCount) { skin.Skeleton = null; } } } if (root.Textures != null) { foreach (GLTFTexture texture in root.Textures) { if (texture.Sampler != null && texture.Sampler.Id >= samplersCount) { texture.Sampler = null; } } } } private static void ApplySparseAccessorsVec3(ref NumericArray resultArray, AttributeAccessor attributeAccessor, AttributeAccessor sparseValues, AttributeAccessor sparseIndices) { var sparseArrays = new NumericArray[2]; // Values LoadBufferView(sparseValues.AccessorId.Value.Sparse.Values.BufferView.Value, sparseValues.Offset, sparseValues.bufferData, out NativeArray bufferViewCache1); // Indices LoadBufferView(sparseIndices.AccessorId.Value.Sparse.Indices.BufferView.Value, sparseIndices.Offset, sparseIndices.bufferData, out NativeArray bufferViewCache2); Accessor.AsSparseFloat3Array(attributeAccessor.AccessorId.Value, ref sparseArrays[0], bufferViewCache1, 0); Accessor.AsSparseUIntArray(attributeAccessor.AccessorId.Value, ref sparseArrays[1], bufferViewCache2, 0); var before = new NumericArray(); before.AsFloat3s = new float3[resultArray.AsFloat3s.Length]; System.Array.Copy(resultArray.AsFloat3s, before.AsFloat3s, before.AsFloat3s.Length); for (int j = 0; j < sparseArrays[1].AsUInts.Length; j++) before.AsFloat3s[sparseArrays[1].AsUInts[j]] = sparseArrays[0].AsFloat3s[j]; resultArray = before; } /// /// Uses the accessor to parse the buffer into attributes needed to construct the mesh primitive /// /// A dictionary that contains a mapping of attribute name to data needed to parse /// /// Uses the accessor to parse the buffer into attributes needed to construct the mesh primitive /// /// A dictionary that contains a mapping of attribute name to data needed to parse public static void BuildMeshAttributes(ref Dictionary attributes,ref Dictionary sparseAccessors) { if (attributes.TryGetValue(SemanticProperties.POSITION, out var attributeAccessor)) { NumericArray resultArray = attributeAccessor.AccessorContent; LoadBufferView(attributeAccessor, out NativeArray bufferViewCache); attributeAccessor.AccessorId.Value.AsVertexArray(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); if (sparseAccessors.TryGetValue(SemanticProperties.POSITION, out var sparseData)) ApplySparseAccessorsVec3(ref resultArray, attributeAccessor, sparseData.sparseValues, sparseData.sparseIndices); attributeAccessor.AccessorContent = resultArray; } if (attributes.TryGetValue(SemanticProperties.INDICES, out var attributeAccessorIndices)) { NumericArray resultArray = attributeAccessorIndices.AccessorContent; LoadBufferView(attributeAccessorIndices, out NativeArray bufferViewCache); attributeAccessorIndices.AccessorId.Value.AsTriangles(ref resultArray, bufferViewCache); attributeAccessorIndices.AccessorContent = resultArray; } if (attributes.TryGetValue(SemanticProperties.NORMAL, out var attributeAccessorNormals)) { NumericArray resultArray = attributeAccessorNormals.AccessorContent; LoadBufferView(attributeAccessorNormals, out NativeArray bufferViewCache); attributeAccessorNormals.AccessorId.Value.AsNormalArray(ref resultArray, bufferViewCache, 0, attributeAccessorNormals.AccessorId.Value.Normalized); if (sparseAccessors.TryGetValue(SemanticProperties.NORMAL, out var sparseData)) ApplySparseAccessorsVec3(ref resultArray, attributeAccessorNormals, sparseData.sparseValues, sparseData.sparseIndices); attributeAccessorNormals.AccessorContent = resultArray; } for (int i = 0; i < SemanticProperties.TexCoord.Length; i++) { if (attributes.TryGetValue(SemanticProperties.TexCoord[i], out var attributeAccessorTexCoord)) { NumericArray resultArray = attributeAccessorTexCoord.AccessorContent; LoadBufferView(attributeAccessorTexCoord, out NativeArray bufferViewCache); attributeAccessorTexCoord.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, 0, attributeAccessorTexCoord.AccessorId.Value.Normalized); // if (sparseAccessors.TryGetValue(SemanticProperties.TexCoord[0], out var sparseData)) // ApplySparseAccessorsTexCoord(ref resultArray, attributeAccessor, sparseData.sparseValues, sparseData.sparseIndices); attributeAccessorTexCoord.AccessorContent = resultArray; } } for (int i = 0; i < SemanticProperties.Color.Length; i++) { if (attributes.TryGetValue(SemanticProperties.Color[i], out var attributeAccessorColor)) { NumericArray resultArray = attributeAccessorColor.AccessorContent; LoadBufferView(attributeAccessorColor, out NativeArray bufferViewCache); attributeAccessorColor.AccessorId.Value.AsColorArray(ref resultArray, bufferViewCache, 0, attributeAccessorColor.AccessorId.Value.Normalized); // if (sparseAccessors.TryGetValue(SemanticProperties.Color[0], out var sparseData)) // ApplySparseAccessorsColor(ref resultArray, attributeAccessor, sparseData.sparseValues, sparseData.sparseIndices); attributeAccessorColor.AccessorContent = resultArray; } } if (attributes.TryGetValue(SemanticProperties.TANGENT, out var attributeAccessorTangent)) { NumericArray resultArray = attributeAccessorTangent.AccessorContent; LoadBufferView(attributeAccessorTangent, out NativeArray bufferViewCache); attributeAccessorTangent.AccessorId.Value.AsTangentArray(ref resultArray, bufferViewCache, 0, attributeAccessorTangent.AccessorId.Value.Normalized); // if (sparseAccessors.TryGetValue(SemanticProperties.TANGENT, out var sparseData)) // ApplySparseAccessorsTangent(ref resultArray, attributeAccessor, sparseData.sparseValues, sparseData.sparseIndices); attributeAccessorTangent.AccessorContent = resultArray; } for (int i = 0; i < SemanticProperties.Weight.Length; i++) { if (attributes.TryGetValue(SemanticProperties.Weight[i], out var attributeAccessorWeight)) { NumericArray resultArray = attributeAccessorWeight.AccessorContent; LoadBufferView(attributeAccessorWeight, out NativeArray bufferViewCache); attributeAccessorWeight.AccessorId.Value.AsFloat4Array(ref resultArray, bufferViewCache, 0, attributeAccessorWeight.AccessorId.Value.Normalized); attributeAccessorWeight.AccessorContent = resultArray; } } for (int i = 0; i < SemanticProperties.Joint.Length; i++) { if (attributes.TryGetValue(SemanticProperties.Joint[0], out var attributeAccessorJoint)) { NumericArray resultArray = attributeAccessorJoint.AccessorContent; LoadBufferView(attributeAccessorJoint, out NativeArray bufferViewCache); attributeAccessorJoint.AccessorId.Value.AsFloat4Array(ref resultArray, bufferViewCache, 0, attributeAccessorJoint.AccessorId.Value.Normalized); attributeAccessorJoint.AccessorContent = resultArray; } } } public static void BuildTargetAttributes(ref Dictionary attributes, float importScale = 1f) { var hasScale = !Mathf.Approximately(importScale, 1f); foreach (var kvp in attributes) { var attributeAccessor = kvp.Value; NumericArray resultArray = attributeAccessor.AccessorContent; LoadBufferView(attributeAccessor, out NativeArray bufferViewCache); bool normalize = attributeAccessor.AccessorId.Value.Normalized; switch (kvp.Key) { case SemanticProperties.POSITION: if (hasScale) { float3 conversionScale = new float3(importScale, importScale, importScale); attributeAccessor.AccessorId.Value.AsFloat3ArrayConversion(ref resultArray, bufferViewCache, conversionScale, 0, normalize); } else attributeAccessor.AccessorId.Value.AsFloat3Array(ref resultArray, bufferViewCache, 0, normalize); break; case SemanticProperties.NORMAL: case SemanticProperties.TANGENT: attributeAccessor.AccessorId.Value.AsFloat3Array(ref resultArray, bufferViewCache, 0, normalize); break; default: throw new System.Exception($"Unrecognized morph target attribute {kvp.Key}"); } attributeAccessor.AccessorContent = resultArray; } } public static void BuildBindPoseSamplers(ref AttributeAccessor attributeAccessor) { NumericArray resultArray = attributeAccessor.AccessorContent; LoadBufferView(attributeAccessor, out NativeArray bufferViewCache); attributeAccessor.AccessorId.Value.AsMatrix4x4Array(ref resultArray, bufferViewCache, 0, attributeAccessor.AccessorId.Value.Normalized); attributeAccessor.AccessorContent = resultArray; } /// /// Uses the accessor to parse the buffer into arrays needed to construct the animation /// /// A dictionary mapping AttributeAccessor lists to their target types public static void BuildAnimationSamplers(ref Dictionary> samplers) { foreach (var samplerSet in samplers) { foreach (var attributeAccessor in samplerSet.Value) { NumericArray resultArray = attributeAccessor.AccessorContent; LoadBufferView(attributeAccessor, out NativeArray bufferViewCache); switch (attributeAccessor.AccessorId.Value.Type) { case GLTFAccessorAttributeType.SCALAR: attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache); break; case GLTFAccessorAttributeType.VEC2: attributeAccessor.AccessorId.Value.AsFloat2Array(ref resultArray, bufferViewCache); break; case GLTFAccessorAttributeType.VEC3: attributeAccessor.AccessorId.Value.AsFloat3Array(ref resultArray, bufferViewCache); break; 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; } attributeAccessor.AccessorContent = resultArray; } } } /// /// Merges the right root into the left root /// This function combines all of the lists of objects on each glTF root together and updates the relative indicies /// All properties all merged except Asset and Default, which will stay "mergeToRoot"'s value /// /// The node to merge into /// The node to merge from public static void MergeGLTF(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) { PreviousGLTFSizes previousGLTFSize = new PreviousGLTFSizes() { PreviousAccessorCount = mergeToRoot.Accessors?.Count ?? 0, PreviousBufferCount = mergeToRoot.Buffers?.Count ?? 0, PreviousAnimationCount = mergeToRoot.Animations?.Count ?? 0, PreviousBufferViewCount = mergeToRoot.BufferViews?.Count ?? 0, PreviousCameraCount = mergeToRoot.Cameras?.Count ?? 0, PreviousImageCount = mergeToRoot.Images?.Count ?? 0, PreviousMaterialCount = mergeToRoot.Materials?.Count ?? 0, PreviousMeshCount = mergeToRoot.Meshes?.Count ?? 0, PreviousNodeCount = mergeToRoot.Nodes?.Count ?? 0, PreviousSamplerCount = mergeToRoot.Samplers?.Count ?? 0, PreviousSceneCount = mergeToRoot.Scenes?.Count ?? 0, PreviousSkinCount = mergeToRoot.Skins?.Count ?? 0, PreviousTextureCount = mergeToRoot.Textures?.Count ?? 0 }; GLTFRoot mergeFromRootCopy = new GLTFRoot(mergeFromRoot); // for each type: // 1) add the right hand range to the left hand object // 2) update all ids to be based off of the appended size // merge extensions MergeExtensions(mergeToRoot, mergeFromRootCopy); // merge accessors, buffers, and bufferviews MergeAccessorsBufferViewsAndBuffers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); // merge materials, samplers, images, and textures MergeMaterialsImagesTexturesAndSamplers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); // merge meshes MergeMeshes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); // merge cameras MergeCameras(mergeToRoot, mergeFromRootCopy); // merge nodes MergeNodes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); // merge animation, and skin MergeAnimationsAndSkins(mergeToRoot, mergeFromRootCopy, previousGLTFSize); // merge scenes MergeScenes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); } /// /// Returns whether the input string is a Base64 uri. Images and buffers can both be encoded this way. /// /// The uri to check /// Whether the input string is base64 encoded public static bool IsBase64Uri(string uri) { const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,"; Regex regex = new Regex(Base64StringInitializer); return regex.Match(uri).Success; } private static void LoadBufferView(AttributeAccessor attributeAccessor, out byte[] bufferViewCache) { bufferViewCache = new byte[attributeAccessor.AccessorId.Value.BufferView.Value.ByteLength]; LoadBufferView(attributeAccessor.AccessorId.Value.BufferView.Value, attributeAccessor.Offset, attributeAccessor.bufferData, out NativeArray bufferViewCacheNative); bufferViewCacheNative.CopyTo(bufferViewCache); } private static void LoadBufferView(AttributeAccessor attributeAccessor, out NativeArray bufferViewCache) { LoadBufferView(attributeAccessor.AccessorId.Value.BufferView.Value, attributeAccessor.Offset, attributeAccessor.bufferData, out bufferViewCache); } internal static void LoadBufferView(BufferView bufferView, uint Offset, NativeArray nativeBuffer, out NativeArray bufferViewCache) { uint totalOffset = bufferView.ByteOffset + Offset; bufferViewCache = nativeBuffer.GetSubArray((int)totalOffset, (int)bufferView.ByteLength); } private static void MergeExtensions(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) { // avoid duplicates for extension merging if (mergeFromRoot.ExtensionsUsed != null) { if (mergeToRoot.ExtensionsUsed == null) { mergeToRoot.ExtensionsUsed = new List(mergeFromRoot.ExtensionsUsed.Count); } foreach (string extensionUsedToAdd in mergeFromRoot.ExtensionsUsed) { if (!mergeToRoot.ExtensionsUsed.Contains(extensionUsedToAdd)) { mergeToRoot.ExtensionsUsed.Add(extensionUsedToAdd); } } } if (mergeFromRoot.ExtensionsRequired != null) { if (mergeToRoot.ExtensionsRequired == null) { mergeToRoot.ExtensionsRequired = new List(mergeFromRoot.ExtensionsRequired.Count); } foreach (string extensionRequiredToAdd in mergeFromRoot.ExtensionsRequired) { if (!mergeToRoot.ExtensionsRequired.Contains(extensionRequiredToAdd)) { mergeToRoot.ExtensionsRequired.Add(extensionRequiredToAdd); } } } } private static void MergeAccessorsBufferViewsAndBuffers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { bool isGLB = false; if (mergeFromRoot.Buffers != null) { if (mergeToRoot.Buffers == null) { mergeToRoot.Buffers = new List(mergeFromRoot.Buffers.Count); } foreach (GLTFBuffer buffer in mergeFromRoot.Buffers) { if (buffer.Uri != null) { mergeToRoot.Buffers.Add(buffer); } else { isGLB = true; // assume glb is a uri is null } } } if (mergeFromRoot.BufferViews != null) { if (mergeToRoot.BufferViews == null) { mergeToRoot.BufferViews = new List(mergeFromRoot.BufferViews.Count); } mergeToRoot.BufferViews.AddRange(mergeFromRoot.BufferViews); for (int i = previousGLTFSizes.PreviousBufferViewCount; i < mergeToRoot.BufferViews.Count; ++i) { GLTFId bufferId = mergeToRoot.BufferViews[i].Buffer; if (!(isGLB && bufferId.Id == 0)) // if it is pointing a the special glb buffer (index 0 of a glb) then we dont want to adjust the buffer view, otherwise we do { // adjusting bufferview id based on merge amount bufferId.Id += previousGLTFSizes.PreviousBufferCount; bufferId.Root = mergeToRoot; } } } if (mergeFromRoot.Accessors != null) { if (mergeToRoot.Accessors == null) { mergeToRoot.Accessors = new List(mergeFromRoot.Accessors.Count); } mergeToRoot.Accessors.AddRange(mergeFromRoot.Accessors); for (int i = previousGLTFSizes.PreviousAccessorCount; i < mergeToRoot.Accessors.Count; ++i) { Accessor accessor = mergeToRoot.Accessors[i]; if (accessor.BufferView != null) { BufferViewId bufferViewId = accessor.BufferView; bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; bufferViewId.Root = mergeToRoot; } AccessorSparse accessorSparse = accessor.Sparse; if (accessorSparse != null) { BufferViewId indicesId = accessorSparse.Indices.BufferView; indicesId.Id += previousGLTFSizes.PreviousBufferViewCount; indicesId.Root = mergeToRoot; BufferViewId valuesId = accessorSparse.Values.BufferView; valuesId.Id += previousGLTFSizes.PreviousBufferViewCount; valuesId.Root = mergeToRoot; } } } } private static void MergeMaterialsImagesTexturesAndSamplers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { if (mergeFromRoot.Samplers != null) { if (mergeToRoot.Samplers == null) { mergeToRoot.Samplers = new List(mergeFromRoot.Samplers.Count); } mergeToRoot.Samplers.AddRange(mergeFromRoot.Samplers); } if (mergeFromRoot.Images != null) { if (mergeToRoot.Images == null) { mergeToRoot.Images = new List(mergeFromRoot.Images.Count); } mergeToRoot.Images.AddRange(mergeFromRoot.Images); for (int i = previousGLTFSizes.PreviousImageCount; i < mergeToRoot.Images.Count; ++i) { GLTFImage image = mergeToRoot.Images[i]; if (image.BufferView != null) { BufferViewId bufferViewId = image.BufferView; bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; bufferViewId.Root = mergeToRoot; } } } if (mergeFromRoot.Textures != null) { if (mergeToRoot.Textures == null) { mergeToRoot.Textures = new List(mergeFromRoot.Textures.Count); } mergeToRoot.Textures.AddRange(mergeFromRoot.Textures); for (int i = previousGLTFSizes.PreviousTextureCount; i < mergeToRoot.Textures.Count; ++i) { GLTFTexture texture = mergeToRoot.Textures[i]; if (texture.Sampler != null) { SamplerId samplerId = texture.Sampler; samplerId.Id += previousGLTFSizes.PreviousSamplerCount; samplerId.Root = mergeToRoot; } if (texture.Source != null) { ImageId samplerId = texture.Source; samplerId.Id += previousGLTFSizes.PreviousImageCount; samplerId.Root = mergeToRoot; } } } if (mergeFromRoot.Materials != null) { if (mergeToRoot.Materials == null) { mergeToRoot.Materials = new List(mergeFromRoot.Materials.Count); } mergeToRoot.Materials.AddRange(mergeFromRoot.Materials); for (int i = previousGLTFSizes.PreviousMaterialCount; i < mergeToRoot.Materials.Count; ++i) { GLTFMaterial material = mergeToRoot.Materials[i]; PbrMetallicRoughness pbrMetallicRoughness = material.PbrMetallicRoughness; if (pbrMetallicRoughness != null) { if (pbrMetallicRoughness.BaseColorTexture != null) { TextureId textureId = pbrMetallicRoughness.BaseColorTexture.Index; textureId.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } if (pbrMetallicRoughness.MetallicRoughnessTexture != null) { TextureId textureId = pbrMetallicRoughness.MetallicRoughnessTexture.Index; textureId.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } } MaterialCommonConstant commonConstant = material.CommonConstant; if (commonConstant?.LightmapTexture != null) { TextureId textureId = material.CommonConstant.LightmapTexture.Index; textureId.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } if (material.EmissiveTexture != null) { TextureId textureId = material.EmissiveTexture.Index; material.EmissiveTexture.Index.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } if (material.NormalTexture != null) { TextureId textureId = material.NormalTexture.Index; textureId.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } if (material.OcclusionTexture != null) { TextureId textureId = material.OcclusionTexture.Index; textureId.Id += previousGLTFSizes.PreviousTextureCount; textureId.Root = mergeToRoot; } } } } private static void MergeMeshes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { if (mergeFromRoot.Meshes == null) return; if (mergeToRoot.Meshes == null) { mergeToRoot.Meshes = new List(mergeFromRoot.Meshes.Count); } mergeToRoot.Meshes.AddRange(mergeFromRoot.Meshes); for (int i = previousGLTFSizes.PreviousMeshCount; i < mergeToRoot.Meshes.Count; ++i) { GLTFMesh mesh = mergeToRoot.Meshes[i]; if (mesh.Primitives != null) { foreach (MeshPrimitive primitive in mesh.Primitives) { foreach (var attributeAccessorPair in primitive.Attributes) { AccessorId accessorId = attributeAccessorPair.Value; accessorId.Id += previousGLTFSizes.PreviousAccessorCount; accessorId.Root = mergeToRoot; } if (primitive.Indices != null) { AccessorId accessorId = primitive.Indices; accessorId.Id += previousGLTFSizes.PreviousAccessorCount; accessorId.Root = mergeToRoot; } if (primitive.Material != null) { MaterialId materialId = primitive.Material; materialId.Id += previousGLTFSizes.PreviousMaterialCount; materialId.Root = mergeToRoot; } if (primitive.Targets != null) { foreach (Dictionary targetsDictionary in primitive.Targets) { foreach (var targetsPair in targetsDictionary) { AccessorId accessorId = targetsPair.Value; accessorId.Id += previousGLTFSizes.PreviousAccessorCount; accessorId.Root = mergeToRoot; } } } } } } } private static void MergeCameras(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) { if (mergeFromRoot.Cameras == null) return; if (mergeToRoot.Cameras == null) { mergeToRoot.Cameras = new List(mergeFromRoot.Cameras.Count); } mergeToRoot.Cameras.AddRange(mergeFromRoot.Cameras); } private static void MergeNodes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { if (mergeFromRoot.Nodes == null) return; if (mergeToRoot.Nodes == null) { mergeToRoot.Nodes = new List(mergeFromRoot.Nodes.Count); } mergeToRoot.Nodes.AddRange(mergeFromRoot.Nodes); for (int i = previousGLTFSizes.PreviousNodeCount; i < mergeToRoot.Nodes.Count; ++i) { Node node = mergeToRoot.Nodes[i]; if (node.Mesh != null) { MeshId meshId = node.Mesh; meshId.Id += previousGLTFSizes.PreviousMeshCount; node.Mesh.Root = mergeToRoot; } if (node.Camera != null) { CameraId cameraId = node.Camera; cameraId.Id += previousGLTFSizes.PreviousCameraCount; cameraId.Root = mergeToRoot; } if (node.Children != null) { foreach (NodeId child in node.Children) { child.Id += previousGLTFSizes.PreviousNodeCount; child.Root = mergeToRoot; } } if (node.Skin != null) { SkinId skinId = node.Skin; skinId.Id += previousGLTFSizes.PreviousSkinCount; skinId.Root = mergeToRoot; } } } private static void MergeAnimationsAndSkins(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { if (mergeFromRoot.Skins != null) { if (mergeToRoot.Skins == null) { mergeToRoot.Skins = new List(mergeFromRoot.Skins.Count); } mergeToRoot.Skins.AddRange(mergeFromRoot.Skins); for (int i = previousGLTFSizes.PreviousSkinCount; i < mergeToRoot.Skins.Count; ++i) { Skin skin = mergeToRoot.Skins[i]; if (skin.InverseBindMatrices != null) { skin.InverseBindMatrices.Id += previousGLTFSizes.PreviousAccessorCount; } if (skin.Skeleton != null) { skin.Skeleton.Id += previousGLTFSizes.PreviousNodeCount; } if (skin.Joints != null) { foreach (NodeId joint in skin.Joints) { joint.Id += previousGLTFSizes.PreviousNodeCount; } } } } if (mergeFromRoot.Animations != null) { if (mergeToRoot.Animations == null) { mergeToRoot.Animations = new List(mergeFromRoot.Animations.Count); } mergeToRoot.Animations.AddRange(mergeFromRoot.Animations); for (int i = previousGLTFSizes.PreviousAnimationCount; i < mergeToRoot.Animations.Count; ++i) { GLTFAnimation animation = mergeToRoot.Animations[i]; foreach (AnimationSampler sampler in animation.Samplers) { AccessorId inputId = sampler.Input; inputId.Id += previousGLTFSizes.PreviousAccessorCount; inputId.Root = mergeToRoot; AccessorId outputId = sampler.Output; outputId.Id += previousGLTFSizes.PreviousAccessorCount; outputId.Root = mergeToRoot; } foreach (AnimationChannel channel in animation.Channels) { AnimationSamplerId samplerId = channel.Sampler; samplerId.Id += previousGLTFSizes.PreviousSamplerCount; samplerId.Root = mergeToRoot; NodeId nodeId = channel.Target.Node; nodeId.Id += previousGLTFSizes.PreviousNodeCount; nodeId.Root = mergeToRoot; } } } } private static void MergeScenes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) { if (mergeFromRoot.Scenes == null) return; if (mergeToRoot.Scenes == null) { mergeToRoot.Scenes = new List(mergeFromRoot.Scenes.Count); } mergeToRoot.Scenes.AddRange(mergeFromRoot.Scenes); for (int i = previousGLTFSizes.PreviousSceneCount; i < mergeToRoot.Scenes.Count; ++i) { GLTFScene scene = mergeToRoot.Scenes[i]; foreach (NodeId nodeId in scene.Nodes) { nodeId.Id += previousGLTFSizes.PreviousNodeCount; nodeId.Root = mergeToRoot; } } } private static string UpdateCanonicalPath(string oldPath, string newCanonicalPath) { string fileName = Path.GetFileName(oldPath); return newCanonicalPath + Path.DirectorySeparatorChar + fileName; } public static NodeId FindCommonAncestor(IEnumerable nodes) { // build parentage GLTFRoot root = nodes.First().Root; Dictionary childToParent = new Dictionary(root.Nodes.Count); for (int i = 0; i < root.Nodes.Count; i++) { if (root.Nodes[i].Children == null) { continue; } foreach (NodeId child in root.Nodes[i].Children) { childToParent[child.Id] = i; } } // scan for common ancestor int? commonAncestorIndex = nodes .Select(n => n.Id) .Aggregate((int?)null, (elder, node) => FindCommonAncestor(elder, node)); return commonAncestorIndex != null ? new NodeId() { Id = commonAncestorIndex.Value, Root = root } : null; int? FindCommonAncestor(int? a, int? b) { // trivial cases if (a == null && b == null) { return null; } else if (a != null) { return a; } else if (b != null) { return b; } else if (AncestorOf(a.Value, b.Value)) { return a; } else { return FindCommonAncestor(childToParent[a.Value], b.Value); } } bool AncestorOf(int ancestor, int descendant) { while (childToParent.ContainsKey(descendant)) { if (childToParent[descendant] == ancestor) { return true; } descendant = childToParent[descendant]; } return false; } } } }