/// <summary>
        /// Apply dungeon texture table.
        /// </summary>
        /// <param name="dungeonTextureTable">Dungeon texture table changes to apply.</param>
        public void SetDungeonTextures(int[] dungeonTextureTable)
        {
            if (defaultTextures.Count == 0)
            {
                return;
            }

            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return;
            }

            // Get new material array
            Material[] materials = new Material[defaultTextures.Count];
            DFLocation.ClimateBaseType climateIndex = Game.GameManager.Instance.PlayerGPS.ClimateSettings.ClimateType;
            for (int i = 0; i < defaultTextures.Count; i++)
            {
                MaterialReader.ReverseTextureKey(defaultTextures[i], out int archive, out int record, out _);
                archive      = DungeonTextureTables.ApplyTextureTable(archive, dungeonTextureTable, climateIndex);
                materials[i] = dfUnity.MaterialReader.GetMaterial(archive, record);
            }

            // Assign material array
            if (materials != null)
            {
                GetComponent <MeshRenderer>().sharedMaterials = materials;
            }
        }
        /// <summary>
        /// Apply dungeon texture table.
        /// </summary>
        /// <param name="dungeonTextureTable">Dungeon texture table changes to apply.</param>
        public void SetDungeonTextures(int[] dungeonTextureTable)
        {
            if (defaultTextures.Count == 0)
            {
                return;
            }

            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return;
            }

            // Get new material array
            int archive, record, frame;

            Material[] materials = new Material[defaultTextures.Count];
            for (int i = 0; i < defaultTextures.Count; i++)
            {
                MaterialReader.ReverseTextureKey(defaultTextures[i], out archive, out record, out frame);
                switch (archive)
                {
                case 119:
                    archive = dungeonTextureTable[0];
                    break;

                case 120:
                    archive = dungeonTextureTable[1];
                    break;

                case 122:
                    archive = dungeonTextureTable[2];
                    break;

                case 123:
                    archive = dungeonTextureTable[3];
                    break;

                case 124:
                    archive = dungeonTextureTable[4];
                    break;

                case 168:
                    archive = dungeonTextureTable[5];
                    break;
                }
                materials[i] = dfUnity.MaterialReader.GetMaterial(archive, record);
            }

            // Assign material array
            if (materials != null)
            {
                GetComponent <MeshRenderer>().sharedMaterials = materials;
            }
        }
Example #3
0
        /// <summary>
        /// Sets billboard material with a custom texture.
        /// </summary>
        /// <param name="texture">Texture2D to set on material.</param>
        /// <param name="size">Size of billboard quad in normal units (not Daggerfall units).</param>
        /// <returns>Material.</returns>
        public Material SetMaterial(Texture2D texture, Vector2 size, bool isLightArchive = false)
        {
            // Get DaggerfallUnity
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return(null);
            }

            // Get references
            meshRenderer = GetComponent <MeshRenderer>();

            // Create material
            Material material = MaterialReader.CreateStandardMaterial(MaterialReader.CustomBlendMode.Cutout);

            material.mainTexture = texture;

            // Create mesh
            Mesh mesh = dfUnity.MeshReader.GetSimpleBillboardMesh(size);

            // Set summary
            summary.FlatType = FlatTypes.Decoration;
            summary.Size     = size;

            // Assign mesh and material
            MeshFilter meshFilter = GetComponent <MeshFilter>();
            Mesh       oldMesh    = meshFilter.sharedMesh;

            if (mesh)
            {
                meshFilter.sharedMesh       = mesh;
                meshRenderer.sharedMaterial = material;
            }
            if (oldMesh)
            {
                // The old mesh is no longer required
#if UNITY_EDITOR
                DestroyImmediate(oldMesh);
#else
                Destroy(oldMesh);
#endif
            }

            // General billboard shadows if enabled
            meshRenderer.shadowCastingMode = (DaggerfallUnity.Settings.GeneralBillboardShadows && !isLightArchive) ? ShadowCastingMode.TwoSided : ShadowCastingMode.Off;

            return(material);
        }
        /// <summary>
        /// Removes from cache all assets that have not been accessed from the time in minutes defined by
        /// <see cref="SettingsManager.AssetCacheThreshold"/>. If equals to <c>0</c> assets are never removed from cache.
        /// </summary>
        internal void PruneCache()
        {
            if (Settings.AssetCacheThreshold == 0)
            {
                return;
            }

            float time      = Time.realtimeSinceStartup;
            float threshold = Settings.AssetCacheThreshold * 60;

            MaterialReader.PruneCache(time, threshold);
            if (ModManager.Instance)
            {
                ModManager.Instance.PruneCache(time, threshold);
            }

            RaiseOnPruneCacheEvent(time, threshold);
        }
        /// <summary>
        /// Rebuild materials back to default with no climate modifier.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        public void DisableClimate(DaggerfallUnity dfUnity)
        {
            if (defaultTextures.Count == 0)
            {
                return;
            }

            // Get new material array
            int archive, record, frame;

            Material[] materials = new Material[defaultTextures.Count];
            for (int i = 0; i < defaultTextures.Count; i++)
            {
                MaterialReader.ReverseTextureKey(defaultTextures[i], out archive, out record, out frame);
                materials[i] = dfUnity.MaterialReader.GetMaterial(archive, record);
            }

            // Assign material array
            if (materials != null)
            {
                GetComponent <MeshRenderer>().sharedMaterials = materials;
            }
        }
        /// <summary>
        /// Sets new Daggerfall material and recreates mesh.
        /// Will use an atlas if specified in DaggerfallUnity singleton.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        /// <param name="archive">Texture archive index.</param>
        /// <param name="record">Texture record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <param name="dungeon">This is a dungeon billboard.</param>
        /// <returns>Material.</returns>
        public Material SetMaterial(DaggerfallUnity dfUnity, int archive, int record, int frame, bool dungeon)
        {
            Shader shader;

            //if (archive == 210 && dungeon) // Dungeon light billboards use unlit billboard shader
            if (archive == 210) // All light billboards use unlit billboard shader
            {
                shader = Shader.Find(dfUnity.MaterialReader.DefaultUnlitBillboardShaderName);
            }
            else
            {
                shader = Shader.Find(dfUnity.MaterialReader.DefaultBillboardShaderName);
            }

            // Get references
            meshRenderer = GetComponent <MeshRenderer>();

            Vector2  size;
            Mesh     mesh     = null;
            Material material = null;

            if (dfUnity.MaterialReader.AtlasTextures)
            {
                material = dfUnity.MaterialReader.GetMaterialAtlas(
                    archive,
                    0,
                    4,
                    2048,
                    out summary.AtlasRects,
                    out summary.AtlasIndices,
                    4,
                    true,
                    0,
                    false,
                    shader);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.AtlasRects[summary.AtlasIndices[record].startIndex],
                    archive,
                    record,
                    out size,
                    dungeon);
                summary.AtlasedMaterial = true;
                if (summary.AtlasIndices[record].frameCount > 1)
                {
                    summary.AnimatedMaterial = true;
                }
                else
                {
                    summary.AnimatedMaterial = false;
                }
            }
            else
            {
                material = dfUnity.MaterialReader.GetMaterial(
                    archive,
                    record,
                    frame,
                    0,
                    out summary.Rect,
                    4,
                    true,
                    shader);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.Rect,
                    archive,
                    record,
                    out size,
                    dungeon);
                summary.AtlasedMaterial  = false;
                summary.AnimatedMaterial = false;
            }

            // Set summary
            summary.InDungeon = dungeon;
            summary.FlatType  = MaterialReader.GetFlatType(archive);
            summary.Archive   = archive;
            summary.Record    = record;
            summary.Size      = size;

            // Set editor flat types
            if (summary.FlatType == FlatTypes.Editor)
            {
                summary.EditorFlatType = MaterialReader.GetEditorFlatType(summary.Record);
            }

            // Assign mesh and material
            MeshFilter meshFilter = GetComponent <MeshFilter>();
            Mesh       oldMesh    = meshFilter.sharedMesh;

            if (mesh)
            {
                meshFilter.sharedMesh       = mesh;
                meshRenderer.sharedMaterial = material;
            }
            if (oldMesh)
            {
                // The old mesh is no longer required
                DestroyImmediate(oldMesh);
            }

            return(material);
        }
        /// <summary>
        /// Creates mesh and material for this enemy.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        /// <param name="archive">Texture archive index derived from type and gender.</param>
        private void AssignMeshAndMaterial(DaggerfallUnity dfUnity, int archive)
        {
            // Get mesh filter
            if (meshFilter == null)
            {
                meshFilter = GetComponent <MeshFilter>();
            }

            // Vertices for a 1x1 unit quad
            // This is scaled to correct size depending on facing and orientation
            float hx = 0.5f, hy = 0.5f;

            Vector3[] vertices = new Vector3[4];
            vertices[0] = new Vector3(hx, hy, 0);
            vertices[1] = new Vector3(-hx, hy, 0);
            vertices[2] = new Vector3(hx, -hy, 0);
            vertices[3] = new Vector3(-hx, -hy, 0);

            // Indices
            int[] indices = new int[6]
            {
                0, 1, 2,
                3, 2, 1,
            };

            // Normals
            Vector3 normal = Vector3.Normalize(Vector3.up + Vector3.forward);

            Vector3[] normals = new Vector3[4];
            normals[0] = normal;
            normals[1] = normal;
            normals[2] = normal;
            normals[3] = normal;

            // Create mesh
            Mesh mesh = new Mesh();

            mesh.name      = string.Format("MobileEnemyMesh");
            mesh.vertices  = vertices;
            mesh.triangles = indices;
            mesh.normals   = normals;

            // Assign mesh
            meshFilter.sharedMesh = mesh;

            // Seek textures from mods
            TextureReplacement.SetEnemyImportedTextures(archive, GetComponent <MeshFilter>(), ref summary.ImportedTextures);

            // Create material
            Material material;

            if (summary.ImportedTextures.HasImportedTextures)
            {
                material = MaterialReader.CreateStandardMaterial(MaterialReader.CustomBlendMode.Cutout);
            }
            else
            {
                material = dfUnity.MaterialReader.GetMaterialAtlas(
                    archive,
                    0,
                    4,
                    1024,
                    out summary.AtlasRects,
                    out summary.AtlasIndices,
                    4,
                    true,
                    0,
                    false,
                    true);
            }

            // Set new enemy material
            GetComponent <MeshRenderer>().sharedMaterial = material;
        }
Example #8
0
        /// <summary>
        /// Sets new Daggerfall material and recreates mesh.
        /// Will use an atlas if specified in DaggerfallUnity singleton.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        /// <param name="archive">Texture archive index.</param>
        /// <param name="record">Texture record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <returns>Material.</returns>
        public Material SetMaterial(int archive, int record, int frame = 0)
        {
            // Get DaggerfallUnity
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return(null);
            }

            // Get references
            meshRenderer = GetComponent <MeshRenderer>();

            Vector2  size;
            Vector2  scale;
            Mesh     mesh     = null;
            Material material = null;

            if (material = TextureReplacement.GetStaticBillboardMaterial(gameObject, archive, record, ref summary, out scale))
            {
                mesh  = dfUnity.MeshReader.GetBillboardMesh(summary.Rect, archive, record, out size);
                size *= scale;
                summary.AtlasedMaterial  = false;
                summary.AnimatedMaterial = summary.ImportedTextures.FrameCount > 1;
            }
            else if (dfUnity.MaterialReader.AtlasTextures)
            {
                material = dfUnity.MaterialReader.GetMaterialAtlas(
                    archive,
                    0,
                    4,
                    2048,
                    out summary.AtlasRects,
                    out summary.AtlasIndices,
                    4,
                    true,
                    0,
                    false,
                    true);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.AtlasRects[summary.AtlasIndices[record].startIndex],
                    archive,
                    record,
                    out size);
                summary.AtlasedMaterial = true;
                if (summary.AtlasIndices[record].frameCount > 1)
                {
                    summary.AnimatedMaterial = true;
                }
                else
                {
                    summary.AnimatedMaterial = false;
                }
            }
            else
            {
                material = dfUnity.MaterialReader.GetMaterial(
                    archive,
                    record,
                    frame,
                    0,
                    out summary.Rect,
                    4,
                    true,
                    true);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.Rect,
                    archive,
                    record,
                    out size);
                summary.AtlasedMaterial  = false;
                summary.AnimatedMaterial = false;
            }

            // Set summary
            summary.FlatType = MaterialReader.GetFlatType(archive);
            summary.Archive  = archive;
            summary.Record   = record;
            summary.Size     = size;

            // Set editor flat types
            if (summary.FlatType == FlatTypes.Editor)
            {
                summary.EditorFlatType = MaterialReader.GetEditorFlatType(summary.Record);
            }

            // Set NPC flat type based on archive
            if (RDBLayout.IsNPCFlat(summary.Archive))
            {
                summary.FlatType = FlatTypes.NPC;
            }

            // Assign mesh and material
            MeshFilter meshFilter = GetComponent <MeshFilter>();
            Mesh       oldMesh    = meshFilter.sharedMesh;

            if (mesh)
            {
                meshFilter.sharedMesh       = mesh;
                meshRenderer.sharedMaterial = material;
            }
            if (oldMesh)
            {
                // The old mesh is no longer required
#if UNITY_EDITOR
                DestroyImmediate(oldMesh);
#else
                Destroy(oldMesh);
#endif
            }

            // General billboard shadows if enabled
            bool isLightArchive = (archive == TextureReader.LightsTextureArchive);
            meshRenderer.shadowCastingMode = (DaggerfallUnity.Settings.GeneralBillboardShadows && !isLightArchive) ? ShadowCastingMode.TwoSided : ShadowCastingMode.Off;

            // Add NPC trigger collider
            if (summary.FlatType == FlatTypes.NPC)
            {
                Collider col = gameObject.AddComponent <BoxCollider>();
                col.isTrigger = true;
            }

            return(material);
        }
        /// <summary>
        /// Sets new Daggerfall material and recreates mesh.
        /// Will use an atlas if specified in DaggerfallUnity singleton.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        /// <param name="archive">Texture archive index.</param>
        /// <param name="record">Texture record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <param name="dungeon">This is a dungeon billboard.</param>
        /// <returns>Material.</returns>
        public Material SetMaterial(int archive, int record, int frame, bool dungeon)
        {
            // Get DaggerfallUnity
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return(null);
            }

            // Get references
            meshRenderer = GetComponent <MeshRenderer>();

            Vector2  size;
            Mesh     mesh     = null;
            Material material = null;

            if (dfUnity.MaterialReader.AtlasTextures)
            {
                material = dfUnity.MaterialReader.GetMaterialAtlas(
                    archive,
                    0,
                    4,
                    2048,
                    out summary.AtlasRects,
                    out summary.AtlasIndices,
                    4,
                    true,
                    0,
                    false);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.AtlasRects[summary.AtlasIndices[record].startIndex],
                    archive,
                    record,
                    out size,
                    dungeon);
                summary.AtlasedMaterial = true;
                if (summary.AtlasIndices[record].frameCount > 1)
                {
                    summary.AnimatedMaterial = true;
                }
                else
                {
                    summary.AnimatedMaterial = false;
                }
            }
            else
            {
                material = dfUnity.MaterialReader.GetMaterial(
                    archive,
                    record,
                    frame,
                    0,
                    out summary.Rect,
                    4,
                    true);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.Rect,
                    archive,
                    record,
                    out size,
                    dungeon);
                summary.AtlasedMaterial  = false;
                summary.AnimatedMaterial = false;
            }

            // Update material properties
            MaterialReader.SetBlendMode(material, MaterialReader.CustomBlendMode.Cutout);

            // Set summary
            summary.InDungeon = dungeon;
            summary.FlatType  = MaterialReader.GetFlatType(archive);
            summary.Archive   = archive;
            summary.Record    = record;
            summary.Size      = size;

            // Set editor flat types
            if (summary.FlatType == FlatTypes.Editor)
            {
                summary.EditorFlatType = MaterialReader.GetEditorFlatType(summary.Record);
            }

            // Assign mesh and material
            MeshFilter meshFilter = GetComponent <MeshFilter>();
            Mesh       oldMesh    = meshFilter.sharedMesh;

            if (mesh)
            {
                meshFilter.sharedMesh       = mesh;
                meshRenderer.sharedMaterial = material;
            }
            if (oldMesh)
            {
                // The old mesh is no longer required
                DestroyImmediate(oldMesh);
            }

            // Standalone billboards never cast shadows
            meshRenderer.shadowCastingMode = ShadowCastingMode.Off;

            return(material);
        }
Example #10
0
        /// <summary>
        /// Gets Unity Mesh from previously combined model data.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleon for loading content.</param>
        /// <param name="combiner">ModelCombiner to build from.</param>
        /// <param name="cachedMaterialsOut">Array of cached materials in order of submesh.</param>
        /// <param name="textureKeysOut">Array of original texture keys in order of submesh.</param>
        /// <param name="hasAnimationsOut">True if one or more materials have animations.</param>
        /// <param name="solveTangents">Solve tangents for this mesh.</param>
        /// <param name="lightmapUVs">Add secondary lightmap UVs to this mesh.</param>
        /// <returns>Mesh object or null.</returns>
        public Mesh GetCombinedMesh(
            DaggerfallUnity dfUnity,
            ModelCombiner combiner,
            out CachedMaterial[] cachedMaterialsOut,
            out int[] textureKeysOut,
            out bool hasAnimationsOut,
            bool solveTangents = false,
            bool lightmapUVs   = false)
        {
            cachedMaterialsOut = null;
            hasAnimationsOut   = false;
            textureKeysOut     = null;

            // Ready check
            if (!IsReady)
            {
                return(null);
            }

            // Get combined model
            ModelCombiner.CombinedModel combinedModel;
            if (!combiner.GetCombinedModel(out combinedModel))
            {
                return(null);
            }

            // Load materials
            cachedMaterialsOut = new CachedMaterial[combinedModel.SubMeshes.Length];
            textureKeysOut     = new int[combinedModel.SubMeshes.Length];
            for (int i = 0; i < combinedModel.SubMeshes.Length; i++)
            {
                int archive = combinedModel.SubMeshes[i].TextureArchive;
                int record  = combinedModel.SubMeshes[i].TextureRecord;
                textureKeysOut[i] = MaterialReader.MakeTextureKey((short)archive, (byte)record, (byte)0);

                // Add material to array
                CachedMaterial cachedMaterial;
                dfUnity.MaterialReader.GetCachedMaterial(archive, record, 0, out cachedMaterial);
                cachedMaterialsOut[i] = cachedMaterial;

                // Set animation flag
                if (cachedMaterial.singleFrameCount > 1 && !hasAnimationsOut)
                {
                    hasAnimationsOut = true;
                }
            }

            // Create mesh
            Mesh mesh = new Mesh();

            mesh.name         = "CombinedMesh";
            mesh.vertices     = combinedModel.Vertices;
            mesh.normals      = combinedModel.Normals;
            mesh.uv           = combinedModel.UVs;
            mesh.subMeshCount = combinedModel.SubMeshes.Length;

            // Set submesh triangles
            for (int s = 0; s < mesh.subMeshCount; s++)
            {
                var   sub       = combinedModel.SubMeshes[s];
                int[] triangles = new int[sub.PrimitiveCount * 3];
                for (int t = 0; t < sub.PrimitiveCount * 3; t++)
                {
                    triangles[t] = combinedModel.Indices[sub.StartIndex + t];
                }
                mesh.SetTriangles(triangles, s);
            }

            // Finalise mesh
            if (solveTangents)
            {
                TangentSolver(mesh);
            }
            if (lightmapUVs)
            {
                AddLightmapUVs(mesh);
            }
            mesh.RecalculateBounds();

            return(mesh);
        }
Example #11
0
        /// <summary>
        /// Gets Unity Mesh from Daggerfall model.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleon for loading content.</param>
        /// <param name="modelID">Daggerfall model ID to load..</param>
        /// <param name="cachedMaterialsOut">Array of cached materials in order of submesh.</param>
        /// <param name="textureKeysOut">Array of original texture keys in order of submesh.</param>
        /// <param name="hasAnimationsOut">True if one or more materials have animations.</param>
        /// <param name="solveTangents">Solve tangents for this mesh.</param>
        /// <param name="lightmapUVs">Add secondary lightmap UVs to this mesh.</param>
        /// <returns>Mesh object or null.</returns>
        public Mesh GetMesh(
            DaggerfallUnity dfUnity,
            uint modelID,
            out CachedMaterial[] cachedMaterialsOut,
            out int[] textureKeysOut,
            out bool hasAnimationsOut,
            bool solveTangents = false,
            bool lightmapUVs   = false)
        {
            cachedMaterialsOut = null;
            hasAnimationsOut   = false;
            textureKeysOut     = null;

            // Ready check
            if (!IsReady)
            {
                return(null);
            }

            // Get model data
            ModelData model;

            if (!GetModelData(modelID, out model))
            {
                DaggerfallUnity.LogMessage(string.Format("Unknown ModelID {0}.", modelID.ToString()), true);
                return(null);
            }

            // Load materials
            cachedMaterialsOut = new CachedMaterial[model.SubMeshes.Length];
            textureKeysOut     = new int[model.SubMeshes.Length];
            for (int i = 0; i < model.SubMeshes.Length; i++)
            {
                int archive = model.DFMesh.SubMeshes[i].TextureArchive;
                int record  = model.DFMesh.SubMeshes[i].TextureRecord;
                textureKeysOut[i] = MaterialReader.MakeTextureKey((short)archive, (byte)record, (byte)0);

                // Add material to array
                CachedMaterial cachedMaterial;
                dfUnity.MaterialReader.GetCachedMaterial(archive, record, 0, out cachedMaterial);
                cachedMaterialsOut[i] = cachedMaterial;

                // Set animation flag
                if (cachedMaterial.singleFrameCount > 1 && !hasAnimationsOut)
                {
                    hasAnimationsOut = true;
                }
            }

            // Create mesh
            Mesh mesh = new Mesh();

            mesh.name         = modelID.ToString();
            mesh.vertices     = model.Vertices;
            mesh.normals      = model.Normals;
            mesh.uv           = model.UVs;
            mesh.subMeshCount = model.SubMeshes.Length;

            // Set submesh triangles
            for (int s = 0; s < mesh.subMeshCount; s++)
            {
                var   sub       = model.SubMeshes[s];
                int[] triangles = new int[sub.PrimitiveCount * 3];
                for (int t = 0; t < sub.PrimitiveCount * 3; t++)
                {
                    triangles[t] = model.Indices[sub.StartIndex + t];
                }
                mesh.SetTriangles(triangles, s);
            }

            // Finalise mesh
            if (solveTangents)
            {
                TangentSolver(mesh);
            }
            if (lightmapUVs)
            {
                AddLightmapUVs(mesh);
            }
            mesh.RecalculateBounds();
            mesh.Optimize();

            return(mesh);
        }
Example #12
0
        /// <summary>
        /// Sets new Daggerfall material and recreates mesh.
        /// Will use an atlas if specified in DaggerfallUnity singleton.
        /// </summary>
        /// <param name="dfUnity">DaggerfallUnity singleton. Required for content readers and settings.</param>
        /// <param name="archive">Texture archive index.</param>
        /// <param name="record">Texture record index.</param>
        /// <param name="frame">Frame index.</param>
        /// <returns>Material.</returns>
        public Material SetMaterial(int archive, int record, int frame = 0)
        {
            // Get DaggerfallUnity
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            if (!dfUnity.IsReady)
            {
                return(null);
            }

            // Get references
            meshRenderer = GetComponent <MeshRenderer>();

            Vector2  size;
            Mesh     mesh     = null;
            Material material = null;

            if (dfUnity.MaterialReader.AtlasTextures)
            {
                material = dfUnity.MaterialReader.GetMaterialAtlas(
                    archive,
                    0,
                    4,
                    2048,
                    out summary.AtlasRects,
                    out summary.AtlasIndices,
                    4,
                    true,
                    0,
                    false,
                    true);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.AtlasRects[summary.AtlasIndices[record].startIndex],
                    archive,
                    record,
                    out size);
                summary.AtlasedMaterial = true;
                if (summary.AtlasIndices[record].frameCount > 1)
                {
                    summary.AnimatedMaterial = true;
                }
                else
                {
                    summary.AnimatedMaterial = false;
                }
            }
            else
            {
                material = dfUnity.MaterialReader.GetMaterial(
                    archive,
                    record,
                    frame,
                    0,
                    out summary.Rect,
                    4,
                    true,
                    true);
                mesh = dfUnity.MeshReader.GetBillboardMesh(
                    summary.Rect,
                    archive,
                    record,
                    out size);
                summary.AtlasedMaterial  = false;
                summary.AnimatedMaterial = false;
            }

            // Set summary
            summary.FlatType = MaterialReader.GetFlatType(archive);
            summary.Archive  = archive;
            summary.Record   = record;
            summary.Size     = size;

            // Set editor flat types
            if (summary.FlatType == FlatTypes.Editor)
            {
                summary.EditorFlatType = MaterialReader.GetEditorFlatType(summary.Record);
            }

            // Set NPC flat type based on archive
            // This is just a hack for now while performing
            // more research into NPC names
            if (summary.Archive == 334 ||                               // Daggerfall people
                summary.Archive == 346 ||                               // Wayrest people
                summary.Archive == 357 ||                               // Sentinel people
                summary.Archive >= 175 && summary.Archive <= 184)       // Other people
            {
                summary.FlatType = FlatTypes.NPC;
            }

            // Assign mesh and material
            MeshFilter meshFilter = GetComponent <MeshFilter>();
            Mesh       oldMesh    = meshFilter.sharedMesh;

            if (mesh)
            {
                meshFilter.sharedMesh       = mesh;
                meshRenderer.sharedMaterial = material;
            }
            if (oldMesh)
            {
                // The old mesh is no longer required
#if UNITY_EDITOR
                DestroyImmediate(oldMesh);
#else
                Destroy(oldMesh);
#endif
            }

            // Standalone billboards never cast shadows
            meshRenderer.shadowCastingMode = ShadowCastingMode.Off;

            // Add NPC trigger collider
            if (summary.FlatType == FlatTypes.NPC)
            {
                Collider col = gameObject.AddComponent <BoxCollider>();
                col.isTrigger = true;
            }

            return(material);
        }