<|start_filename|>Runtime/Scripts/GltfBoundsAsset.cs<|end_filename|>
﻿// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Threading.Tasks;
using GLTFast.Loading;
using UnityEngine;

namespace GLTFast
{
    public class GltfBoundsAsset : GltfAsset {

        [Tooltip("Create a box collider encapsulating the glTF asset")]
        public bool createBoxCollider = true;

        [NonSerialized]
        public Bounds bounds;

        public override async Task<bool> Load(
            string url,
            IDownloadProvider downloadProvider=null,
            IDeferAgent deferAgent=null,
            IMaterialGenerator materialGenerator=null,
            ICodeLogger logger = null
            )
        {
            importer = new GltfImport(downloadProvider,deferAgent, materialGenerator);
            var success = await importer.Load(url);
            if(success) {
                var insta = (GameObjectBoundsInstantiator) GetDefaultInstantiator(logger);
                // Auto-Instantiate
                if (sceneId>=0) {
                    success = importer.InstantiateScene(insta, sceneId);
                    currentSceneId = success ? sceneId : (int?)null;
                } else {
                    success = importer.InstantiateMainScene(insta);
                    currentSceneId = importer.defaultSceneIndex;
                }

                sceneInstance = insta.sceneInstance;

                if(success) {
                    SetBounds(insta);
                }
            }
            return success;
        }

        public override bool InstantiateScene(int sceneIndex, ICodeLogger logger = null) {
            base.InstantiateScene(sceneIndex, logger);
            var instantiator = (GameObjectBoundsInstantiator)GetDefaultInstantiator(logger);
            var success = base.InstantiateScene(sceneIndex, instantiator);
            currentSceneId = success ? sceneIndex : (int?)null;
            sceneInstance = instantiator.sceneInstance;
            if (success) {
                SetBounds(instantiator);
            }
            return success;
        }

        protected override IInstantiator GetDefaultInstantiator(ICodeLogger logger) {
            return new GameObjectBoundsInstantiator(importer, transform, logger);
        }
        
        void SetBounds(GameObjectBoundsInstantiator insta) {
            var sceneBounds = insta.sceneInstance!=null ? insta.CalculateBounds() : null;
            if (sceneBounds.HasValue) {
                bounds = sceneBounds.Value;
                if (createBoxCollider) {
#if UNITY_PHYSICS
                    var boxCollider = gameObject.AddComponent<BoxCollider>();
                    boxCollider.center = bounds.center;
                    boxCollider.size = bounds.size;
#else
                    Debug.LogError("GltfBoundsAsset requires the built-in Physics package to be enabled (in the Package Manager)");
#endif
                }
            }
        }
    }
}


<|start_filename|>Runtime/Scripts/DOTS/EntityInstantiator.cs<|end_filename|>
// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#if UNITY_DOTS_HYBRID

using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;

namespace GLTFast {
    public class EntityInstantiator : IInstantiator {

        const float k_Epsilon = .00001f;

        protected ICodeLogger logger;
        
        protected IGltfReadable gltf;
        
        protected Entity parent;

        protected Dictionary<uint,Entity> nodes;

        EntityManager entityManager;
        EntityArchetype nodeArcheType;
        EntityArchetype sceneArcheType;

        public EntityInstantiator(IGltfReadable gltf, Entity parent, ICodeLogger logger = null) {
            this.gltf = gltf;
            this.parent = parent;
            this.logger = logger;
        }

        public virtual void Init() {
            nodes = new Dictionary<uint, Entity>();
            entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
            nodeArcheType = entityManager.CreateArchetype(
                typeof(Translation),
                typeof(Rotation),
                typeof(Parent),
                typeof(LocalToParent),
                typeof(LocalToWorld)
            );
            sceneArcheType = entityManager.CreateArchetype(
                typeof(Translation),
                typeof(Rotation),
                typeof(LocalToWorld)
            );
        }

        public void CreateNode(
            uint nodeIndex,
            Vector3 position,
            Quaternion rotation,
            Vector3 scale
        ) {
            var node = entityManager.CreateEntity(nodeArcheType);
            entityManager.SetComponentData(node,new Translation {Value = position});
            entityManager.SetComponentData(node,new Rotation {Value = rotation});
            SetEntityScale(node, scale);
            nodes[nodeIndex] = node;
        }

        void SetEntityScale(Entity node, Vector3 scale) {
            if (!scale.Equals(Vector3.one)) {
                if (Math.Abs(scale.x - scale.y) < k_Epsilon && Math.Abs(scale.x - scale.z) < k_Epsilon) {
                    entityManager.AddComponentData(node, new Scale { Value = scale.x });
                } else {
                    entityManager.AddComponentData(node, new NonUniformScale { Value = scale });
                }
            }
        }

        public void SetParent(uint nodeIndex, uint parentIndex) {
            entityManager.SetComponentData(nodes[nodeIndex],new Parent{Value = nodes[parentIndex]} );
        }

        public void SetNodeName(uint nodeIndex, string name) {
#if UNITY_EDITOR
            entityManager.SetName(nodes[nodeIndex], name ?? $"Node-{nodeIndex}");
#endif
        }

        public virtual void AddPrimitive(
            uint nodeIndex,
            string meshName,
            Mesh mesh,
            int[] materialIndices,
            uint[] joints = null,
            uint? rootJoint = null,
            float[] morphTargetWeights = null,
            int primitiveNumeration = 0
        ) {
            Entity node;
            if(primitiveNumeration==0) {
                // Use Node GameObject for first Primitive
                node = nodes[nodeIndex];
            } else {
                node = entityManager.CreateEntity(nodeArcheType);
                entityManager.SetComponentData(node,new Translation {Value = new float3(0,0,0)});
                entityManager.SetComponentData(node,new Rotation {Value = quaternion.identity});
                entityManager.SetComponentData(node, new Parent { Value = nodes[nodeIndex] });
            }
            
            var hasMorphTargets = mesh.blendShapeCount > 0;

            for (var index = 0; index < materialIndices.Length; index++) {
                var material = gltf.GetMaterial(materialIndices[index]) ?? gltf.GetDefaultMaterial();
                 
                RenderMeshUtility.AddComponents(node,entityManager,new RenderMeshDescription(mesh,material,subMeshIndex:index));
                 if(joints!=null || hasMorphTargets) {
                     if (joints != null) {
                         var bones = new Entity[joints.Length];
                         for (var j = 0; j < bones.Length; j++)
                         {
                             var jointIndex = joints[j];
                             bones[j] = nodes[jointIndex];
                         }
                         // TODO: Store bone entities array somewhere (pendant to SkinnedMeshRenderer.bones)
                     }
                     if (morphTargetWeights!=null) {
                         for (var i = 0; i < morphTargetWeights.Length; i++) {
                             var weight = morphTargetWeights[i];
                             // TODO set blend shape weight in proper component (pendant to SkinnedMeshRenderer.SetBlendShapeWeight(i, weight); )
                         }
                     }
                 }
            }
        }

        public void AddPrimitiveInstanced(
            uint nodeIndex,
            string meshName,
            Mesh mesh,
            int[] materialIndices,
            uint instanceCount,
            NativeArray<Vector3>? positions,
            NativeArray<Quaternion>? rotations,
            NativeArray<Vector3>? scales,
            int primitiveNumeration = 0
        ) {
            foreach (var materialIndex in materialIndices) {
                var material = gltf.GetMaterial(materialIndex) ?? gltf.GetDefaultMaterial();
                material.enableInstancing = true;
                var renderMeshDescription = new RenderMeshDescription(mesh, material, subMeshIndex:materialIndex);
                var prototype = entityManager.CreateEntity(nodeArcheType);
                RenderMeshUtility.AddComponents(prototype,entityManager,renderMeshDescription);
                if (scales.HasValue) {
                    entityManager.AddComponent<NonUniformScale>(prototype);
                }
                for (var i = 0; i < instanceCount; i++) {
                    var instance = i>0 ? entityManager.Instantiate(prototype) : prototype;
                    entityManager.SetComponentData(instance,new Translation {Value = positions?[i] ?? Vector3.zero });
                    entityManager.SetComponentData(instance,new Rotation {Value = rotations?[i] ?? Quaternion.identity});
                    entityManager.SetComponentData(instance, new Parent { Value = nodes[nodeIndex] });
                    if (scales.HasValue) {
                        entityManager.SetComponentData(instance, new NonUniformScale() { Value = scales.Value[i] });
                    }
                }
            }
        }

        public void AddCamera(uint nodeIndex, uint cameraIndex) {
            var camera = gltf.GetSourceCamera(cameraIndex);
            // TODO: Add camera support
        }

        public void AddScene(
            string name,
            uint[] nodeIndices
#if UNITY_ANIMATION
            ,AnimationClip[] animationClips
#endif // UNITY_ANIMATION
            )
        {
            var sceneEntity = entityManager.CreateEntity(parent==Entity.Null ? sceneArcheType : nodeArcheType);
            entityManager.SetComponentData(sceneEntity,new Translation {Value = new float3(0,0,0)});
            entityManager.SetComponentData(sceneEntity,new Rotation {Value = quaternion.identity});
#if UNITY_EDITOR
            entityManager.SetName(sceneEntity, name ?? "Scene");
#endif
            if (parent != Entity.Null) {
                entityManager.SetComponentData(sceneEntity, new Parent { Value = parent });
            }

            var sceneParent = new Parent { Value = sceneEntity };
            
            foreach(var nodeIndex in nodeIndices) {
                entityManager.SetComponentData(nodes[nodeIndex], sceneParent);
            }

#if UNITY_ANIMATION
            if (animationClips != null) {
                // TODO: Add animation support
            }
#endif // UNITY_ANIMATION
        }
    }
}

#endif // UNITY_DOTS_HYBRID


<|start_filename|>Runtime/Scripts/DefaultDownloadProvider.cs<|end_filename|>
﻿// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

namespace GLTFast.Loading {
    public class DefaultDownloadProvider : IDownloadProvider {
        public async  Task<IDownload> Request(Uri url) {
            var req = new AwaitableDownload(url);
            while (req.MoveNext()) {
                await Task.Yield();
            }
            return req;
        }

        public async Task<ITextureDownload> RequestTexture(Uri url,bool nonReadable) {
            var req = new AwaitableTextureDownload(url,nonReadable);
            while (req.MoveNext()) {
                await Task.Yield();
            }
            return req;
        }
    }

    public class AwaitableDownload : IDownload {
        const string GLB_MIME = "model/gltf-binary";
        const string GLTF_MIME = "model/gltf+json";

        protected UnityWebRequest request;
        protected UnityWebRequestAsyncOperation asynOperation;


        public AwaitableDownload() {}

        public AwaitableDownload(Uri url) {
            Init(url);
        }

        protected void Init(Uri url) {
            request = UnityWebRequest.Get(url);
            asynOperation = request.SendWebRequest();
        }

        public object Current { get { return asynOperation; } }
        public bool MoveNext() { return !asynOperation.isDone; }
        public void Reset() {}

#if UNITY_2020_1_OR_NEWER
        public bool success => request.isDone && request.result == UnityWebRequest.Result.Success;
#else
        public bool success => request.isDone && !request.isNetworkError && !request.isHttpError;
#endif

        public string error { get { return request.error; } }
        public byte[] data { get { return request.downloadHandler.data; } }
        public string text { get { return request.downloadHandler.text; } }
        public bool? isBinary
        {
            get
            {
                if (success)
                {
                    string contentType = request.GetResponseHeader("Content-Type");
                    if (contentType == GLB_MIME)
                        return true;
                    if (contentType == GLTF_MIME)
                        return false;
                }
                return null;
            }
        }
    }

    public class AwaitableTextureDownload : AwaitableDownload, ITextureDownload {

        public AwaitableTextureDownload():base() {}
        public AwaitableTextureDownload(Uri url):base(url) {}

        public AwaitableTextureDownload(Uri url, bool nonReadable) {
            Init(url,nonReadable);
        }

        protected static UnityWebRequest CreateRequest(Uri url, bool nonReadable) {
            return UnityWebRequestTexture.GetTexture(url,nonReadable);
        }

        protected void Init(Uri url, bool nonReadable) {
            request = CreateRequest(url,nonReadable);
            asynOperation = request.SendWebRequest();
        }

        public Texture2D texture {
            get {
                return (request.downloadHandler as  DownloadHandlerTexture ).texture;
            }
        }
    }
}


<|start_filename|>Runtime/Scripts/StopWatch.cs<|end_filename|>
﻿// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using UnityEngine;

namespace GLTFast.Utils {

    public class StopWatch : MonoBehaviour {
        
        [Tooltip("Maximum time a frame can take. Frame times will not exceed this value even if the actual frame time was longer.")]
        [SerializeField]
        float maxDeltaTime = 10;
        
        float m_StartTime = -1;
        float m_MinFrameTime = float.MaxValue;
        float m_MaxFrameTime = float.MinValue;
        float m_Duration = -1;
        float m_FPSDuration;
        int m_StartFrameCount;
        int m_EndFrameCount;
        bool m_Running;
        bool m_ConsiderLastFrame;

        public float now => (Time.realtimeSinceStartup-m_StartTime)*1000;

        public bool active => m_Running || m_Duration>=0;
        public float lastDuration => m_Duration;
        public int frameCount => m_EndFrameCount-m_StartFrameCount;
        public float averageFrameTime => m_FPSDuration / frameCount;
        public float minFrameTime => m_MinFrameTime;
        public float maxFrameTime => m_MaxFrameTime;

        void Awake() {
            Time.maximumDeltaTime = maxDeltaTime;
        }

        public void StartTime() {
            m_StartTime = Time.realtimeSinceStartup;
            m_Duration = 0;
            m_FPSDuration = 0;
            m_MinFrameTime = float.MaxValue;
            m_MaxFrameTime = float.MinValue;
            m_StartFrameCount = Time.frameCount;
            m_EndFrameCount = m_StartFrameCount;
            m_Running = true;
        }

        public void StopTime() {
            m_Running = false;
            m_Duration = now;
            m_ConsiderLastFrame = true;
        }

        public string GetTextReport() {
            return string.Format(
                "{3} ms (fps avg: {0} min: {1} ms max: {2} ms)"
                , averageFrameTime.ToString("0.00")
                , minFrameTime < float.MaxValue ? minFrameTime.ToString("0.00") : "-"
                , maxFrameTime > float.MinValue ? maxFrameTime.ToString("0.00") : "-"
                , lastDuration >= 0 ? lastDuration.ToString("0.00") : "-"
            );
        }

        void Update() {
            if(m_Running || m_ConsiderLastFrame) {
                m_Duration = now;
                if (m_StartFrameCount < Time.frameCount) {
                    var delta = Time.deltaTime * 1000;
                    m_FPSDuration += delta;
                    UpdateFrameTimes(delta);
                }
                m_ConsiderLastFrame = false;
            }
        }

        void UpdateFrameTimes(float delta) {
            var currentFrame = Time.frameCount;
            // Skip handling frame twice (happened when StopTime was called)
            if (m_EndFrameCount >= currentFrame) return;
            m_EndFrameCount = currentFrame;
            if(m_EndFrameCount > m_StartFrameCount) {
                m_MinFrameTime = Mathf.Min(m_MinFrameTime, delta );
                m_MaxFrameTime = Mathf.Max(m_MaxFrameTime, delta );
            }
        }
    }
}


<|start_filename|>Runtime/Scripts/JsonParser.cs<|end_filename|>
﻿// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Profiling;

namespace GLTFast {
    
    using Schema;
    
    class JsonParser {
        internal static Root ParseJson(string json) {
            // JsonUtility sometimes creates non-null default instances of objects-type members
            // even though there are none in the original JSON.
            // This work-around makes sure not existent JSON nodes will be null in the result.
#if MEASURE_TIMINGS
            var stopWatch = new Stopwatch();
            stopWatch.Start();
#endif
            // Step one: main JSON parsing
            Profiler.BeginSample("JSON main");
            var root = JsonUtility.FromJson<Root>(json);
            Profiler.EndSample();

            // Step two:
            // detect, if a secondary null-check is necessary.
            Profiler.BeginSample("JSON extension check");
            bool check = false;
            if(root.materials!=null) {
                for (int i = 0; i < root.materials.Length; i++) {
                    var mat = root.materials[i];
                    // mat.extension is always set (not null), because JsonUtility constructs a default
                    // if any of mat.extension's members is not null, it is because there was
                    // a legit extensions node in JSON => we have to check which ones
                    if (mat.extensions.KHR_materials_unlit != null) {
                        check = true;
                    } else {
                        // otherwise dump the wrongfully constructed MaterialExtension
                        mat.extensions = null;
                    }
                }
            }
            if(root.accessors != null) {
                for (int i = 0; i < root.accessors.Length; i++) {
                    var accessor = root.accessors[i];
                    if (accessor.sparse.indices == null || accessor.sparse.values == null) {
                        // If indices and values members are null, `sparse` is likely
                        // an auto-instance by the JsonUtility and not present in JSON.
                        // Therefore we remove it:
                        accessor.sparse = null;
                    }
#if GLTFAST_SAFE
                    else {
                        // This is very likely a valid sparse accessor.
                        // However, an empty sparse property ( "sparse": {} ) would break
                        // glTFast, so better do a thorough follow-up check
                        check = true;
                    }
#endif // GLTFAST_SAFE
                }
            }
#if DRACO_UNITY
            if(!check && root.meshes!=null) {
                foreach (var mesh in root.meshes) {
                    if (mesh.primitives != null) {
                        foreach (var primitive in mesh.primitives) {
                            if (primitive.extensions?.KHR_draco_mesh_compression != null) {
                                check = true;
                                break;
                            }
                        }
                    }
                }
            }
#endif
            Profiler.EndSample();

            // Step three:
            // If we have to make an explicit check, parse the JSON again with a
            // different, minimal Root class, where class members are serialized to
            // the type string. In case the string is null, there's no JSON node.
            // Otherwise the string would be empty ("").
            if(check) {
                Profiler.BeginSample("JSON secondary");
                var fakeRoot = JsonUtility.FromJson<FakeSchema.Root>(json);

                if (root.materials != null) {
                    for (var i = 0; i < root.materials.Length; i++) {
                        var mat = root.materials[i];
                        if (mat.extensions == null) continue;
                        Assert.AreEqual(mat.name, fakeRoot.materials[i].name);
                        var fake = fakeRoot.materials[i].extensions;
                        if (fake.KHR_materials_unlit == null) {
                            mat.extensions.KHR_materials_unlit = null;
                        }

                        if (fake.KHR_materials_pbrSpecularGlossiness == null) {
                            mat.extensions.KHR_materials_pbrSpecularGlossiness = null;
                        }

                        if (fake.KHR_materials_transmission == null) {
                            mat.extensions.KHR_materials_transmission = null;
                        }

                        if (fake.KHR_materials_clearcoat == null) {
                            mat.extensions.KHR_materials_clearcoat = null;
                        }

                        if (fake.KHR_materials_sheen == null) {
                            mat.extensions.KHR_materials_sheen = null;
                        }
                    }
                }

#if GLTFAST_SAFE
                if (root.accessors != null) {
                    for (var i = 0; i < root.accessors.Length; i++) {
                        var sparse = fakeRoot.accessors[i].sparse;
                        if (sparse?.indices == null || sparse.values == null) {
                            root.accessors[i].sparse = null;
                        }
                    }
                }
#endif

#if DRACO_UNITY
                if (root.meshes != null) {
                    for (var i = 0; i < root.meshes.Length; i++) {
                        var mesh = root.meshes[i];
                        Assert.AreEqual(mesh.name, fakeRoot.meshes[i].name);
                        for (var j = 0; j < mesh.primitives.Length; j++) {
                            var primitive = mesh.primitives[j];
                            if (primitive.extensions == null ) continue;
                            var fake = fakeRoot.meshes[i].primitives[j];
                            if (fake.extensions.KHR_draco_mesh_compression == null) {
                                // TODO: Differentiate Primitive extensions here
                                // since Draco is the only primitive extension, we
                                // remove the whole extensions property.
                                // primitive.extensions.KHR_draco_mesh_compression = null;
                                primitive.extensions = null;
                            }
                        }
                    }
                }
#endif
                Profiler.EndSample();
            }
            
            // Step four:
            // Further null checks on nodes' extensions
            if (root.nodes != null) {
                for (int i = 0; i < root.nodes.Length; i++) {
                    var e = root.nodes[i].extensions;
                    if (e != null) {
                        if (e.EXT_mesh_gpu_instancing?.attributes == null) {
                            root.nodes[i].extensions = null;
                        }
                    }
                }
            }
#if MEASURE_TIMINGS
            stopWatch.Stop();
            var elapsedSeconds = stopWatch.ElapsedMilliseconds / 1000f;
            var throughput = json.Length / elapsedSeconds;
            Debug.Log($"JSON throughput: {throughput} bytes/sec ({json.Length} bytes in {elapsedSeconds} seconds)");
#endif
            return root;
        }
    }
}


<|start_filename|>Editor/Scripts/EditorImportSettings.cs<|end_filename|>
﻿// Copyright 2020-2022 <NAME>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using UnityEngine.Serialization;

namespace GLTFast.Editor {

    /// <summary>
    /// Editor Import specific settings (not relevant at runtime)
    /// </summary>
    [Serializable]
    public class EditorImportSettings {
        
        /// <summary>
        /// Creates a secondary UV set on all meshes, if there is none present already.
        /// Often used for lightmaps. 
        /// </summary>
        public bool generateSecondaryUVSet;
    }
}