public static Terrain LoadADT(string filename, CacheStorage cache, int shaderProgram, bool loadModels = false)
        {
            var adt = new WoWFormatLib.Structs.ADT.ADT();

            var result = new Terrain();

            //Load ADT from file
            if (WoWFormatLib.Utils.CASC.FileExists(filename))
            {
                var adtreader = new ADTReader();
                adtreader.LoadADT(filename);
                adt = adtreader.adtfile;
            }
            else
            {
                throw new Exception("ADT " + filename + " does not exist!");
            }

            var TileSize    = 1600.0f / 3.0f;   //533.333
            var ChunkSize   = TileSize / 16.0f; //33.333
            var UnitSize    = ChunkSize / 8.0f; //4.166666
            var MapMidPoint = 32.0f / ChunkSize;

            var verticelist = new List <Vertex>();
            var indicelist  = new List <int>();

            result.vao = GL.GenVertexArray();
            GL.BindVertexArray(result.vao);

            result.vertexBuffer = GL.GenBuffer();
            result.indiceBuffer = GL.GenBuffer();

            var materials = new List <Material>();

            if (adt.textures.filenames == null)
            {
                for (var ti = 0; ti < adt.diffuseTextureFileDataIDs.Count(); ti++)
                {
                    var material = new Material();
                    material.filename  = adt.diffuseTextureFileDataIDs[ti].ToString();
                    material.textureID = BLPLoader.LoadTexture(adt.diffuseTextureFileDataIDs[ti], cache);

                    if (adt.texParams != null && adt.texParams.Count() >= ti)
                    {
                        material.scale = (float)Math.Pow(2, (adt.texParams[ti].flags & 0xF0) >> 4);
                        if (adt.texParams[ti].height != 0.0 || adt.texParams[ti].offset != 1.0)
                        {
                            material.heightScale  = adt.texParams[ti].height;
                            material.heightOffset = adt.texParams[ti].offset;

                            if (!WoWFormatLib.Utils.CASC.FileExists(adt.heightTextureFileDataIDs[ti]))
                            {
                                Console.WriteLine("Height texture: " + adt.heightTextureFileDataIDs[ti] + " does not exist! Falling back to original texture (hack)..");
                                material.heightTexture = BLPLoader.LoadTexture(adt.diffuseTextureFileDataIDs[ti], cache);
                            }
                            else
                            {
                                material.heightTexture = BLPLoader.LoadTexture(adt.heightTextureFileDataIDs[ti], cache);
                            }
                        }
                        else
                        {
                            material.heightScale  = 0.0f;
                            material.heightOffset = 1.0f;
                        }
                    }
                    else
                    {
                        material.heightScale  = 0.0f;
                        material.heightOffset = 1.0f;
                        material.scale        = 1.0f;
                    }
                    materials.Add(material);
                }
            }
            else
            {
                for (var ti = 0; ti < adt.textures.filenames.Count(); ti++)
                {
                    var material = new Material();
                    material.filename  = adt.textures.filenames[ti];
                    material.textureID = BLPLoader.LoadTexture(adt.textures.filenames[ti], cache);

                    if (adt.texParams != null && adt.texParams.Count() >= ti)
                    {
                        material.scale = (float)Math.Pow(2, (adt.texParams[ti].flags & 0xF0) >> 4);
                        if (adt.texParams[ti].height != 0.0 || adt.texParams[ti].offset != 1.0)
                        {
                            material.heightScale  = adt.texParams[ti].height;
                            material.heightOffset = adt.texParams[ti].offset;

                            var heightName = adt.textures.filenames[ti].Replace(".blp", "_h.blp");
                            if (!WoWFormatLib.Utils.CASC.FileExists(heightName))
                            {
                                Console.WriteLine("Height texture: " + heightName + " does not exist! Falling back to original texture (hack)..");
                                material.heightTexture = BLPLoader.LoadTexture(adt.textures.filenames[ti], cache);
                            }
                            else
                            {
                                material.heightTexture = BLPLoader.LoadTexture(heightName, cache);
                            }
                        }
                        else
                        {
                            material.heightScale  = 0.0f;
                            material.heightOffset = 1.0f;
                        }
                    }
                    else
                    {
                        material.heightScale  = 0.0f;
                        material.heightOffset = 1.0f;
                        material.scale        = 1.0f;
                    }
                    materials.Add(material);
                }
            }


            var initialChunkY = adt.chunks[0].header.position.Y;
            var initialChunkX = adt.chunks[0].header.position.X;

            var renderBatches = new List <RenderBatch>();

            for (uint c = 0; c < adt.chunks.Count(); c++)
            {
                var chunk = adt.chunks[c];

                var off = verticelist.Count();

                var batch = new RenderBatch();

                batch.groupID = c;

                for (int i = 0, idx = 0; i < 17; i++)
                {
                    for (var j = 0; j < (((i % 2) != 0) ? 8 : 9); j++)
                    {
                        var v = new Vertex();
                        v.Normal = new Vector3(chunk.normals.normal_0[idx], chunk.normals.normal_1[idx], chunk.normals.normal_2[idx]);
                        if (chunk.vertexShading.red != null)
                        {
                            v.Color = new Vector4(chunk.vertexShading.blue[idx] / 255.0f, chunk.vertexShading.green[idx] / 255.0f, chunk.vertexShading.red[idx] / 255.0f, chunk.vertexShading.alpha[idx] / 255.0f);
                        }
                        else
                        {
                            v.Color = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
                        }

                        v.TexCoord = new Vector2((j + (((i % 2) != 0) ? 0.5f : 0f)) / 8f, (i * 0.5f) / 8f);

                        v.Position = new Vector3(chunk.header.position.X - (i * UnitSize * 0.5f), chunk.header.position.Y - (j * UnitSize), chunk.vertices.vertices[idx++] + chunk.header.position.Z);

                        if ((i % 2) != 0)
                        {
                            v.Position.Y -= 0.5f * UnitSize;
                        }

                        verticelist.Add(v);
                    }
                }

                result.startPos = verticelist[0];

                batch.firstFace = (uint)indicelist.Count();
                for (var j = 9; j < 145; j++)
                {
                    indicelist.AddRange(new int[] { off + j + 8, off + j - 9, off + j });
                    indicelist.AddRange(new int[] { off + j - 9, off + j - 8, off + j });
                    indicelist.AddRange(new int[] { off + j - 8, off + j + 9, off + j });
                    indicelist.AddRange(new int[] { off + j + 9, off + j + 8, off + j });
                    if ((j + 1) % (9 + 8) == 0)
                    {
                        j += 9;
                    }
                }
                batch.numFaces = (uint)(indicelist.Count()) - batch.firstFace;

                var layermats      = new List <uint>();
                var alphalayermats = new List <int>();
                var layerscales    = new List <float>();
                var layerheights   = new List <int>();

                batch.heightScales  = new Vector4();
                batch.heightOffsets = new Vector4();

                for (var li = 0; li < adt.texChunks[c].layers.Count(); li++)
                {
                    if (adt.texChunks[c].alphaLayer != null)
                    {
                        alphalayermats.Add(BLPLoader.GenerateAlphaTexture(adt.texChunks[c].alphaLayer[li].layer));
                    }

                    layermats.Add((uint)cache.materials[adt.diffuseTextureFileDataIDs[adt.texChunks[c].layers[li].textureId]]);

                    var curMat = materials.Where(material => material.filename == adt.diffuseTextureFileDataIDs[adt.texChunks[c].layers[li].textureId].ToString()).Single();

                    layerscales.Add(curMat.scale);
                    layerheights.Add(curMat.heightTexture);

                    batch.heightScales[li]  = curMat.heightScale;
                    batch.heightOffsets[li] = curMat.heightOffset;
                }

                batch.materialID        = layermats.ToArray();
                batch.alphaMaterialID   = alphalayermats.ToArray();
                batch.scales            = layerscales.ToArray();
                batch.heightMaterialIDs = layerheights.ToArray();

                var indices  = indicelist.ToArray();
                var vertices = verticelist.ToArray();

                GL.BindBuffer(BufferTarget.ArrayBuffer, result.vertexBuffer);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertices.Count() * 12 * sizeof(float)), vertices, BufferUsageHint.StaticDraw);

                //var normalAttrib = GL.GetAttribLocation(shaderProgram, "normal");
                //GL.EnableVertexAttribArray(normalAttrib);
                //GL.VertexAttribPointer(normalAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 11, sizeof(float) * 0);

                var colorAttrib = GL.GetAttribLocation(shaderProgram, "color");
                GL.EnableVertexAttribArray(colorAttrib);
                GL.VertexAttribPointer(colorAttrib, 4, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 3);

                var texCoordAttrib = GL.GetAttribLocation(shaderProgram, "texCoord");
                GL.EnableVertexAttribArray(texCoordAttrib);
                GL.VertexAttribPointer(texCoordAttrib, 2, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 7);

                var posAttrib = GL.GetAttribLocation(shaderProgram, "position");
                GL.EnableVertexAttribArray(posAttrib);
                GL.VertexAttribPointer(posAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 9);

                GL.BindBuffer(BufferTarget.ElementArrayBuffer, result.indiceBuffer);
                GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(indices.Length * sizeof(int)), indices, BufferUsageHint.StaticDraw);

                renderBatches.Add(batch);
            }

            var doodads           = new List <Doodad>();
            var worldModelBatches = new List <WorldModelBatch>();

            if (loadModels)
            {
                for (var mi = 0; mi < adt.objects.models.entries.Count(); mi++)
                {
                    Console.WriteLine("Loading model #" + mi);

                    var modelentry = adt.objects.models.entries[mi];
                    var mmid       = adt.objects.m2NameOffsets.offsets[modelentry.mmidEntry];

                    var modelfilename = "";
                    for (var mmi = 0; mmi < adt.objects.m2Names.offsets.Count(); mmi++)
                    {
                        if (adt.objects.m2Names.offsets[mmi] == mmid)
                        {
                            modelfilename = adt.objects.m2Names.filenames[mmi].ToLower();
                            break;
                        }
                    }

                    doodads.Add(new Doodad
                    {
                        filename = modelfilename,
                        position = new Vector3(-(modelentry.position.X - 17066), modelentry.position.Y, -(modelentry.position.Z - 17066)),
                        rotation = new Vector3(modelentry.rotation.X, modelentry.rotation.Y, modelentry.rotation.Z),
                        scale    = modelentry.scale
                    });

                    if (!cache.doodadBatches.ContainsKey(modelfilename))
                    {
                        M2Loader.LoadM2(modelfilename, cache, shaderProgram);
                    }
                }

                for (var wmi = 0; wmi < adt.objects.worldModels.entries.Count(); wmi++)
                {
                    var wmofilename = "";

                    var wmodelentry = adt.objects.worldModels.entries[wmi];
                    var mwid        = adt.objects.wmoNameOffsets.offsets[wmodelentry.mwidEntry];

                    for (var wmfi = 0; wmfi < adt.objects.wmoNames.offsets.Count(); wmfi++)
                    {
                        if (adt.objects.wmoNames.offsets[wmfi] == mwid)
                        {
                            wmofilename = adt.objects.wmoNames.filenames[wmfi].ToLower();
                            break;
                        }
                    }

                    if (wmofilename.Length == 0)
                    {
                        throw new Exception("Unable to find filename for WMO!");
                    }

                    if (!cache.worldModelBatches.ContainsKey(wmofilename))
                    {
                        WMOLoader.LoadWMO(wmofilename, cache, shaderProgram);
                    }

                    worldModelBatches.Add(new WorldModelBatch
                    {
                        position   = new Vector3(-(wmodelentry.position.X - 17066.666f), wmodelentry.position.Y, -(wmodelentry.position.Z - 17066.666f)),
                        rotation   = new Vector3(wmodelentry.rotation.X, wmodelentry.rotation.Y, wmodelentry.rotation.Z),
                        worldModel = cache.worldModelBatches[wmofilename]
                    });
                }
            }

            result.renderBatches     = renderBatches.ToArray();
            result.doodads           = doodads.ToArray();
            result.worldModelBatches = worldModelBatches.ToArray();

            cache.terrain.Add(filename, result);
            return(result);
        }
        public static Terrain LoadADT(string filename, CacheStorage cache, int shaderProgram)
        {
            WoWFormatLib.Structs.ADT.ADT adt = new WoWFormatLib.Structs.ADT.ADT();

            Terrain result = new Terrain();

            //Load ADT from file
            if (WoWFormatLib.Utils.CASC.cascHandler.FileExists(filename))
            {
                var adtreader = new ADTReader();
                adtreader.LoadADT(filename);
                adt = adtreader.adtfile;
            }
            else
            {
                throw new Exception("ADT " + filename + " does not exist!");
            }

            float TileSize    = 1600.0f / 3.0f;   //533.333
            float ChunkSize   = TileSize / 16.0f; //33.333
            float UnitSize    = ChunkSize / 8.0f; //4.166666
            float MapMidPoint = 32.0f / ChunkSize;

            List <Vertex> verticelist = new List <Vertex>();
            List <Int32>  indicelist  = new List <Int32>();

            result.vao = GL.GenVertexArray();
            GL.BindVertexArray(result.vao);

            result.vertexBuffer = GL.GenBuffer();
            result.indiceBuffer = GL.GenBuffer();

            List <Material> materials = new List <Material>();

            for (int ti = 0; ti < adt.textures.filenames.Count(); ti++)
            {
                Material material = new Material();
                material.filename  = adt.textures.filenames[ti];
                material.textureID = BLPLoader.LoadTexture(adt.textures.filenames[ti], cache);

                if (adt.texParams != null && adt.texParams.Count() >= ti)
                {
                    material.scale = (float)Math.Pow(2, (adt.texParams[ti].flags & 0xF0) >> 4);
                    if (adt.texParams[ti].height != 0.0 || adt.texParams[ti].offset != 1.0)
                    {
                        material.heightScale  = adt.texParams[ti].height;
                        material.heightOffset = adt.texParams[ti].offset;

                        var heightName = adt.textures.filenames[ti].Replace(".blp", "_h.blp");
                        if (!WoWFormatLib.Utils.CASC.cascHandler.FileExists(heightName))
                        {
                            Console.WriteLine("Height texture: " + heightName + " does not exist! Falling back to original texture (hack)..");
                            material.heightTexture = BLPLoader.LoadTexture(adt.textures.filenames[ti], cache);
                        }
                        else
                        {
                            material.heightTexture = BLPLoader.LoadTexture(heightName, cache);
                        }
                    }
                    else
                    {
                        material.heightScale  = 0.0f;
                        material.heightOffset = 1.0f;
                    }
                }
                else
                {
                    material.heightScale  = 0.0f;
                    material.heightOffset = 1.0f;
                    material.scale        = 1.0f;
                }
                materials.Add(material);
            }

            var initialChunkY = adt.chunks[0].header.position.Y;
            var initialChunkX = adt.chunks[0].header.position.X;

            List <RenderBatch> renderBatches = new List <RenderBatch>();

            for (uint c = 0; c < adt.chunks.Count(); c++)
            {
                var chunk = adt.chunks[c];

                int off = verticelist.Count();

                RenderBatch batch = new RenderBatch();

                batch.groupID = c;

                for (int i = 0, idx = 0; i < 17; i++)
                {
                    for (int j = 0; j < (((i % 2) != 0) ? 8 : 9); j++)
                    {
                        Vertex v = new Vertex();
                        v.Normal = new Vector3(chunk.normals.normal_0[idx], chunk.normals.normal_1[idx], chunk.normals.normal_2[idx]);
                        if (chunk.vertexShading.red != null)
                        {
                            v.Color = new Vector4(chunk.vertexShading.blue[idx] / 255.0f, chunk.vertexShading.green[idx] / 255.0f, chunk.vertexShading.red[idx] / 255.0f, chunk.vertexShading.alpha[idx] / 255.0f);
                        }
                        else
                        {
                            v.Color = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
                        }

                        v.TexCoord = new Vector2((j + (((i % 2) != 0) ? 0.5f : 0f)) / 8f, (i * 0.5f) / 8f);

                        v.Position = new Vector3(chunk.header.position.X - (i * UnitSize * 0.5f), chunk.header.position.Y - (j * UnitSize), chunk.vertices.vertices[idx++] + chunk.header.position.Z);

                        if ((i % 2) != 0)
                        {
                            v.Position.Y -= 0.5f * UnitSize;
                        }

                        verticelist.Add(v);
                    }
                }

                result.startPos = verticelist[0];

                batch.firstFace = (uint)indicelist.Count();
                for (int j = 9; j < 145; j++)
                {
                    indicelist.AddRange(new Int32[] { off + j + 8, off + j - 9, off + j });
                    indicelist.AddRange(new Int32[] { off + j - 9, off + j - 8, off + j });
                    indicelist.AddRange(new Int32[] { off + j - 8, off + j + 9, off + j });
                    indicelist.AddRange(new Int32[] { off + j + 9, off + j + 8, off + j });
                    if ((j + 1) % (9 + 8) == 0)
                    {
                        j += 9;
                    }
                }
                batch.numFaces = (uint)(indicelist.Count()) - batch.firstFace;

                var layermats      = new List <uint>();
                var alphalayermats = new List <int>();
                var layerscales    = new List <float>();
                var layerheights   = new List <int>();

                batch.heightScales  = new Vector4();
                batch.heightOffsets = new Vector4();

                for (int li = 0; li < adt.texChunks[c].layers.Count(); li++)
                {
                    if (adt.texChunks[c].alphaLayer != null)
                    {
                        alphalayermats.Add(BLPLoader.GenerateAlphaTexture(adt.texChunks[c].alphaLayer[li].layer));
                    }
                    layermats.Add((uint)cache.materials[adt.textures.filenames[adt.texChunks[c].layers[li].textureId].ToLower()]);

                    var curMat = materials.Where(material => material.filename == adt.textures.filenames[adt.texChunks[c].layers[li].textureId]).Single();

                    layerscales.Add(curMat.scale);
                    layerheights.Add(curMat.heightTexture);

                    batch.heightScales[li]  = curMat.heightScale;
                    batch.heightOffsets[li] = curMat.heightOffset;
                }

                batch.materialID        = layermats.ToArray();
                batch.alphaMaterialID   = alphalayermats.ToArray();
                batch.scales            = layerscales.ToArray();
                batch.heightMaterialIDs = layerheights.ToArray();

                int[]    indices  = indicelist.ToArray();
                Vertex[] vertices = verticelist.ToArray();

                GL.BindBuffer(BufferTarget.ArrayBuffer, result.vertexBuffer);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertices.Count() * 12 * sizeof(float)), vertices, BufferUsageHint.StaticDraw);

                //var normalAttrib = GL.GetAttribLocation(shaderProgram, "normal");
                //GL.EnableVertexAttribArray(normalAttrib);
                //GL.VertexAttribPointer(normalAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 11, sizeof(float) * 0);

                var colorAttrib = GL.GetAttribLocation(shaderProgram, "color");
                GL.EnableVertexAttribArray(colorAttrib);
                GL.VertexAttribPointer(colorAttrib, 4, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 3);

                var texCoordAttrib = GL.GetAttribLocation(shaderProgram, "texCoord");
                GL.EnableVertexAttribArray(texCoordAttrib);
                GL.VertexAttribPointer(texCoordAttrib, 2, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 7);

                var posAttrib = GL.GetAttribLocation(shaderProgram, "position");
                GL.EnableVertexAttribArray(posAttrib);
                GL.VertexAttribPointer(posAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 12, sizeof(float) * 9);

                GL.BindBuffer(BufferTarget.ElementArrayBuffer, result.indiceBuffer);
                GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(indices.Length * sizeof(int)), indices, BufferUsageHint.StaticDraw);

                renderBatches.Add(batch);
            }

            result.renderBatches = renderBatches.ToArray();
            //result.doodads = doodads.ToArray();
            //result.worldModelBatches = worldModelBatches.ToArray();

            cache.terrain.Add(filename, result);
            return(result);
        }