/// <summary>
        /// Loads model data.
        /// </summary>
        /// <param name="id">Key of source mesh.</param>
        /// <param name="modelData">ModelData out.</param>
        /// <param name="scale">Scale of model.</param>
        /// <returns>True if successful.</returns>
        public bool GetModelData(uint id, out ModelData modelData)
        {
            // New model object
            modelData = new ModelData();

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

            // Return from cache if present
            if (modelDict.ContainsKey((int)id))
            {
                modelData = modelDict[(int)id];
                return(true);
            }

            // Find mesh index
            int index = arch3dFile.GetRecordIndex(id);

            if (index == -1)
            {
                return(false);
            }

            // Get DFMesh
            DFMesh dfMesh = arch3dFile.GetMesh(index);

            if (dfMesh.TotalVertices == 0)
            {
                return(false);
            }

            // Load mesh data
            modelData.DFMesh = dfMesh;
            LoadVertices(ref modelData, GlobalScale);
            LoadIndices(ref modelData);

            // Add to cache
            modelDict.Add((int)id, modelData);

            return(true);
        }
        private void LoadVertices(ref ModelData model, float scale)
        {
            const int BuildingDoors         = 74;
            const int DungeonEnterDoors     = 56;
            const int DungeonRuinEnterDoors = 331;
            const int DungeonExitDoors      = 95;

            //const int dungeonFloorRecord = 2;

            // Allocate arrays
            model.Vertices = new Vector3[model.DFMesh.TotalVertices];
            model.Normals  = new Vector3[model.DFMesh.TotalVertices];
            model.UVs      = new Vector2[model.DFMesh.TotalVertices];

            // Static door and dungeon floor lists
            List <ModelDoor> modelDoors = new List <ModelDoor>();
            //List<DFMesh.DFPlane> dungeonFloors = new List<DFMesh.DFPlane>();

            // Loop through all submeshes
            int vertexCount = 0;

            foreach (DFMesh.DFSubMesh dfSubMesh in model.DFMesh.SubMeshes)
            {
                // Get cached material data
                CachedMaterial cm;
                dfUnity.MaterialReader.GetCachedMaterial(dfSubMesh.TextureArchive, dfSubMesh.TextureRecord, 0, out cm);
                Vector2 sz = cm.recordSizes[0];

                // Get texture archive for this submesh as base climate
                int submeshTextureArchive = dfSubMesh.TextureArchive;
                int baseTextureArchive    = (submeshTextureArchive - (submeshTextureArchive / 100) * 100);

                // Get base climate archive for door check
                // All base door textures are > 100, except dungeon ruins doors
                int doorArchive = submeshTextureArchive;
                if (doorArchive > 100 && doorArchive != DungeonRuinEnterDoors)
                {
                    doorArchive = baseTextureArchive;
                }

                // Check if this is a door archive
                bool      doorFound = false;
                DoorTypes doorType  = DoorTypes.None;
                switch (doorArchive)
                {
                case BuildingDoors:
                    doorFound = true;
                    doorType  = DoorTypes.Building;
                    break;

                case DungeonEnterDoors:
                    doorFound = true;
                    doorType  = DoorTypes.DungeonEntrance;
                    break;

                case DungeonRuinEnterDoors:
                    if (dfSubMesh.TextureRecord > 0)        // Dungeon ruins index 0 is just a stone texture
                    {
                        doorFound = true;
                        doorType  = DoorTypes.DungeonEntrance;
                    }
                    break;

                case DungeonExitDoors:
                    doorFound = true;
                    doorType  = DoorTypes.DungeonExit;
                    break;
                }

                //// Check if this is a dungeon floor
                //bool dungeonFloorFound = false;
                //if (baseTextureArchive >= 19 && baseTextureArchive <= 24 && dfSubMesh.TextureRecord == dungeonFloorRecord)
                //    dungeonFloorFound = true;

                // Loop through all planes in this submesh
                int doorCount = 0;
                foreach (DFMesh.DFPlane dfPlane in dfSubMesh.Planes)
                {
                    // If this is a door then each plane is a single door
                    if (doorFound)
                    {
                        // Set door verts
                        DFMesh.DFPoint p0        = dfPlane.Points[0];
                        DFMesh.DFPoint p1        = dfPlane.Points[1];
                        DFMesh.DFPoint p2        = dfPlane.Points[2];
                        ModelDoor      modelDoor = new ModelDoor()
                        {
                            Index = doorCount++,
                            Type  = doorType,
                            Vert0 = new Vector3(p0.X, -p0.Y, p0.Z) * scale,
                            Vert1 = new Vector3(p1.X, -p1.Y, p1.Z) * scale,
                            Vert2 = new Vector3(p2.X, -p2.Y, p2.Z) * scale,
                        };

                        // Set door normal
                        Vector3 u = modelDoor.Vert0 - modelDoor.Vert2;
                        Vector3 v = modelDoor.Vert0 - modelDoor.Vert1;
                        modelDoor.Normal = Vector3.Normalize(Vector3.Cross(u, v));

                        // Add door to list
                        modelDoors.Add(modelDoor);
                    }

                    //// If this a floor then store the polygon
                    //if (dungeonFloorFound)
                    //    dungeonFloors.Add(dfPlane);

                    // Copy each point in this plane to vertex buffer
                    foreach (DFMesh.DFPoint dfPoint in dfPlane.Points)
                    {
                        // Position and normal
                        Vector3 position = new Vector3(dfPoint.X, -dfPoint.Y, dfPoint.Z) * scale;
                        Vector3 normal   = new Vector3(dfPoint.NX, -dfPoint.NY, dfPoint.NZ);

                        // Store vertex data
                        model.Vertices[vertexCount] = position;
                        model.Normals[vertexCount]  = Vector3.Normalize(normal);
                        model.UVs[vertexCount]      = new Vector2((dfPoint.U / sz.x), -(dfPoint.V / sz.y));

                        // Inrement count
                        vertexCount++;
                    }
                }
            }

            // Assign found doors
            model.Doors = modelDoors.ToArray();
            //model.DungeonFloors = dungeonFloors.ToArray();
        }
        private void LoadIndices(ref ModelData model)
        {
            // Allocate model data submesh buffer
            model.SubMeshes = new ModelData.SubMeshData[model.DFMesh.SubMeshes.Length];

            // Allocate index buffer
            model.Indices = new int[model.DFMesh.TotalTriangles * 3];

            // Iterate through all submeshes
            short indexCount = 0;
            short subMeshCount = 0, vertexCount = 0;
            foreach (DFMesh.DFSubMesh dfSubMesh in model.DFMesh.SubMeshes)
            {
                // Set start index and primitive count for this submesh
                model.SubMeshes[subMeshCount].StartIndex = indexCount;
                model.SubMeshes[subMeshCount].PrimitiveCount = dfSubMesh.TotalTriangles;
                model.SubMeshes[subMeshCount].TextureArchive = dfSubMesh.TextureArchive;
                model.SubMeshes[subMeshCount].TextureRecord = dfSubMesh.TextureRecord;

                // Iterate through all planes in this submesh
                foreach (DFMesh.DFPlane dfPlane in dfSubMesh.Planes)
                {
                    // Every DFPlane is a triangle fan radiating from point 0
                    int sharedPoint = vertexCount++;

                    // Index remaining points. There are (plane.Points.Length - 2) triangles in every plane
                    for (int tri = 0; tri < dfPlane.Points.Length - 2; tri++)
                    {
                        // Store 3 points of current triangle
                        model.Indices[indexCount++] = sharedPoint;
                        model.Indices[indexCount++] = vertexCount + 1;
                        model.Indices[indexCount++] = vertexCount;

                        // Increment vertexCount to next point in fan
                        vertexCount++;
                    }

                    // Increment vertexCount to start of next fan in vertex buffer
                    vertexCount++;
                }

                // Increment submesh count
                subMeshCount++;
            }
        }
        private void LoadVertices(ref ModelData model, float scale)
        {
            const int BuildingDoors = 74;
            const int DungeonEnterDoors = 56;
            const int DungeonRuinEnterDoors = 331;
            const int DungeonExitDoors = 95;

            // Allocate arrays
            model.Vertices = new Vector3[model.DFMesh.TotalVertices];
            model.Normals = new Vector3[model.DFMesh.TotalVertices];
            model.UVs = new Vector2[model.DFMesh.TotalVertices];

            // Door list
            List<ModelDoor> modelDoors = new List<ModelDoor>();

            // Loop through all submeshes
            int vertexCount = 0;
            foreach (DFMesh.DFSubMesh dfSubMesh in model.DFMesh.SubMeshes)
            {
                // Get cached material data
                CachedMaterial cm;
                dfUnity.MaterialReader.GetCachedMaterial(dfSubMesh.TextureArchive, dfSubMesh.TextureRecord, 0, out cm);
                Vector2 sz = cm.recordSizes[0];

                // Get base climate archive for door check
                // All base door textures are < 100, except dungeon ruins doors
                int doorArchive = dfSubMesh.TextureArchive;
                if (doorArchive > 100 && doorArchive != DungeonRuinEnterDoors)
                {
                    // Reduce to base texture set
                    // This shifts all building climate doors to the same index
                    ClimateTextureInfo ci = ClimateSwaps.GetClimateTextureInfo(dfSubMesh.TextureArchive);
                    doorArchive = (int)ci.textureSet;
                }

                // Check if this is a door archive
                bool doorFound = false;
                DoorTypes doorType = DoorTypes.None;
                switch (doorArchive)
                {
                    case BuildingDoors:
                        doorFound = true;
                        doorType = DoorTypes.Building;
                        break;
                    case DungeonEnterDoors:
                        doorFound = true;
                        doorType = DoorTypes.DungeonEntrance;
                        break;
                    case DungeonRuinEnterDoors:
                        if (dfSubMesh.TextureRecord > 0)    // Dungeon ruins index 0 is just a stone texture
                        {
                            doorFound = true;
                            doorType = DoorTypes.DungeonEntrance;
                        }
                        break;
                    case DungeonExitDoors:
                        doorFound = true;
                        doorType = DoorTypes.DungeonExit;
                        break;
                }

                // Loop through all planes in this submesh
                int doorCount = 0;
                foreach (DFMesh.DFPlane dfPlane in dfSubMesh.Planes)
                {
                    // If this is a door then each plane is a single door
                    if (doorFound)
                    {
                        // Set door verts
                        DFMesh.DFPoint p0 = dfPlane.Points[0];
                        DFMesh.DFPoint p1 = dfPlane.Points[1];
                        DFMesh.DFPoint p2 = dfPlane.Points[2];
                        ModelDoor modelDoor = new ModelDoor()
                        {
                            Index = doorCount++,
                            Type = doorType,
                            Vert0 = new Vector3(p0.X, -p0.Y, p0.Z) * scale,
                            Vert1 = new Vector3(p1.X, -p1.Y, p1.Z) * scale,
                            Vert2 = new Vector3(p2.X, -p2.Y, p2.Z) * scale,
                        };

                        // Set door normal
                        Vector3 u = modelDoor.Vert0 - modelDoor.Vert2;
                        Vector3 v = modelDoor.Vert0 - modelDoor.Vert1;
                        modelDoor.Normal = Vector3.Normalize(Vector3.Cross(u, v));

                        // Add door to list
                        modelDoors.Add(modelDoor);
                    }

                    // Copy each point in this plane to vertex buffer
                    foreach (DFMesh.DFPoint dfPoint in dfPlane.Points)
                    {
                        // Position and normal
                        Vector3 position = new Vector3(dfPoint.X, -dfPoint.Y, dfPoint.Z) * scale;
                        Vector3 normal = new Vector3(dfPoint.NX, -dfPoint.NY, dfPoint.NZ);

                        // Store vertex data
                        model.Vertices[vertexCount] = position;
                        model.Normals[vertexCount] = Vector3.Normalize(normal);
                        model.UVs[vertexCount] = new Vector2((dfPoint.U / sz.x), -(dfPoint.V / sz.y));

                        // Inrement count
                        vertexCount++;
                    }
                }
            }

            // Assign found doors
            model.Doors = modelDoors.ToArray();
        }
        /// <summary>
        /// Loads model data.
        /// </summary>
        /// <param name="id">Key of source mesh.</param>
        /// <param name="modelData">ModelData out.</param>
        /// <param name="scale">Scale of model.</param>
        /// <returns>True if successful.</returns>
        public bool GetModelData(uint id, out ModelData modelData)
        {
            // New model object
            modelData = new ModelData();

            // Ready check
            if (!IsReady)
                return false;

            // Return from cache if present
            if (modelDict.ContainsKey((int)id))
            {
                modelData = modelDict[(int)id];
                return true;
            }

            // Find mesh index
            int index = arch3dFile.GetRecordIndex(id);
            if (index == -1)
                return false;

            // Get DFMesh
            DFMesh dfMesh = arch3dFile.GetMesh(index);
            if (dfMesh.TotalVertices == 0)
                return false;

            // Load mesh data
            modelData.DFMesh = dfMesh;
            LoadVertices(ref modelData, GlobalScale);
            LoadIndices(ref modelData);

            // Add to cache
            modelDict.Add((int)id, modelData);

            return true;
        }