private float[] ReadHeights(MH2O currentMH2O, uint ofsData2b, int HeightMapLen, long offset) { var heights = new float[HeightMapLen]; if (ofsData2b != 0) { Reader.BaseStream.Position = offset; for (int i = 0; i < HeightMapLen; i++) { var height = Reader.ReadSingle(); if (height == 0) { height = currentMH2O.heightLevel1; } heights[i] = height; } } else { for (int i = 0; i < HeightMapLen; i++) { heights[i] = currentMH2O.heightLevel1; } } return(heights); }
/// <summary> /// Processes an individual MH2O Chunk /// </summary> /// <param name="Header">MH20 Header Chunk</param> /// <param name="ofsMH20">MH20 OFFset</param> /// <returns>MH2O Filled with information</returns> private MH2O processMH20(MH2OHeader Header, long ofsMH20) { if (Header.used == 0) { return(new MH2O { used = false }); } Reader.BaseStream.Position = ofsMH20 + Header.ofsData1 + 2; var currentMH2O = new MH2O(); currentMH2O.used = true; currentMH2O.type = ((MH2O.FluidType)Reader.ReadUInt16()); currentMH2O.heightLevel1 = Reader.ReadSingle(); currentMH2O.heightLevel2 = Reader.ReadSingle(); currentMH2O.xOffset = Reader.ReadByte(); currentMH2O.yOffset = Reader.ReadByte(); currentMH2O.width = Reader.ReadByte(); currentMH2O.height = Reader.ReadByte(); UInt32 ofsData2a = Reader.ReadUInt32(); UInt32 ofsData2b = Reader.ReadUInt32(); currentMH2O.RenderBitMap = ReadRenderBitMap(currentMH2O, ofsData2a, ofsMH20 + ofsData2a, ofsData2b - ofsData2a); currentMH2O.heights = ReadHeights(currentMH2O, ofsData2b, (currentMH2O.width + 1) * (currentMH2O.height + 1), ofsMH20 + ofsData2b); return(currentMH2O); }
private MH2O ReadMH20SubChunk(uint size, BinaryReader bin) { var chunk = new MH2O(); chunk.headers = new MH2OHeader[256]; for (var i = 0; i < 256; i++) { chunk.headers[i].offsetInstances = bin.ReadUInt32(); chunk.headers[i].layerCount = bin.ReadUInt32(); chunk.headers[i].offsetAttributes = bin.ReadUInt32(); } return(chunk); }
public void Read() { if (Chunk == null) { return; } var stream = Chunk.GetStream(); MH2O = new MH2O(); MH2O.Read(Chunk.GetReader()); HeightMaps = new MH2O.MH2OHeightmapData[256]; Information = new MH2O.MH2OInformation[256]; for (int i = 0; i < MH2O.Headers.Length; i++) { var header = MH2O.Headers[i]; if (header == null || header.LayerCount == 0) { continue; } stream.Seek(Chunk.Offset + header.ofsInformation, SeekOrigin.Begin); var information = new MH2O.MH2OInformation(); information.Read(Chunk.GetReader()); Information[i] = information; // Ensure we have heightmap data if (information.ofsHeightmapData == 0) { continue; } MH2O.MH2OHeightmapData heightMap; if (IsOcean(information.LiquidObjectId, information.LiquidTypeId)) { heightMap = GetOceanHeightMap(information.MinHeightLevel); } else { // Read the height map stream.Seek(Chunk.Offset + information.ofsHeightmapData, SeekOrigin.Begin); heightMap = new MH2O.MH2OHeightmapData(); heightMap.Read(Chunk.GetReader()); heightMap.RenderMask = GetRenderMask(header, information); } HeightMaps[i] = heightMap; } }
private byte[] ReadRenderBitMap(MH2O currentMH2O, uint ofsData2a, long offset, uint size) { var map = new byte[currentMH2O.height]; if (ofsData2a != 0) { Reader.BaseStream.Position = offset; for (int i = 0; i < currentMH2O.height; i++) { map[i] = i < size?Reader.ReadByte() : (byte)0x00; } } return(map); }
public ADT(Files.WOTLK.ADT wotlk) { ADTfileInfo = new FileInfo(wotlk.ADTfileInfo.Name.Split('.')[0] + ".adt"); Logger.log(ADTfileInfo.Name, Logger.Type.CONVERT, "<- " + wotlk.ADTfileInfo.Name); MVER = new MVER(wotlk.MVER); MHDR = new MHDR(wotlk.MHDR); if (wotlk.MH2O != null) { MH2O = new MH2O(wotlk.MH2O); } foreach (Files.WOTLK.Chunks.MCNK x in wotlk.MCNKs) { MCNKs.Add(new MCNK_ADT(x)); MCNKLength += MCNKs[MCNKs.Count - 1].GetBytes().Length; } Logger.log("MCNK[]", Logger.Type.LEVEL1); if (wotlk.MFBO != null) { MFBO = new MFBO(wotlk.MFBO); } else if (YetAnotherAdtConverter.Program.config.CreateMFBO) { UInt32 address = 0; MFBO = new MFBO(); address += (UInt32)MHDR.Header.Byte_size; if (MH2O != null) { address += (UInt32)MH2O.GetBytes().Length; } address += (UInt32)MCNKLength; MHDR.UpdateMFBO(address); } }
private static void PrepareWaterInfo(MH2O water) { if (water == null) { return; } if (!water.Header.Used) { return; } var min = float.MaxValue; var max = float.MinValue; foreach (var height in water.Heights) { min = Math.Min(height, min); max = Math.Max(height, max); } water.IsFlat = (Math.Abs(max - min) < 1.0f); }
/// <summary> /// Parse all MH2O element from file stream /// </summary> public override MH2O[,] Parse() { Reader.BaseStream.Position = AbsoluteStart; var result = new MH2O[16, 16]; var mh2oHeader = new MH2OHeader[256]; for (int i = 0; i < 256; i++) { mh2oHeader[i].ofsData1 = Reader.ReadUInt32(); mh2oHeader[i].used = Reader.ReadUInt32(); mh2oHeader[i].ofsData2 = Reader.ReadUInt32(); } for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { result[x, y] = processMH20(mh2oHeader[y * 16 + x], AbsoluteStart); } } return(result); }
public ADT(string filePath) { ADTfileInfo = new FileInfo(filePath); Logger.log(ADTfileInfo.Name, Logger.Type.READ, ADTfileInfo.DirectoryName); //Start reading the .ADT File using (BinaryReader reader = new BinaryReader(ADTfileInfo.Open(FileMode.Open))) { Length = (int)reader.BaseStream.Length; reader.BaseStream.Seek(0, SeekOrigin.Begin); int MCNK_counter = 0; int MCNK_size = 0; while (reader.BaseStream.Position < reader.BaseStream.Length) { byte[] ChunkMagic = reader.ReadBytes(4); byte[] ChunkSize = reader.ReadBytes(4); byte[] ChunkContent = reader.ReadBytes(BitConverter.ToInt32(ChunkSize, 0)); string ChunkMagicString = MagicBytesToString(ChunkMagic); //read the chunks switch (ChunkMagicString) { case "MVER": MVER = new MVER(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MHDR": MHDR = new MHDR(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MCIN": MCIN = new MCIN(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MTEX": MTEX = new MTEX(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MMDX": MMDX = new MMDX(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MMID": MMID = new MMID(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MWMO": MWMO = new MWMO(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MWID": MWID = new MWID(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MDDF": MDDF = new MDDF(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MODF": MODF = new MODF(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MH2O": MH2O = new MH2O(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; case "MCNK": MCNKs.Add(new MCNK(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent)); break; case "MFBO": MFBO = new MFBO(MagicBytesToChars(ChunkMagic), ChunkSize, ChunkContent); break; } if (ChunkMagicString == "MCNK") { MCNK_counter++; MCNK_size += BitConverter.ToInt32(ChunkSize, 0); } else if (ChunkMagicString == "\0\0\0\0") /*Logger.log("0 Byte Chunk", Logger.Direction.WARNING);*/ } { } if (MCNK_counter > 0) { Logger.log("MCNK[]", Logger.Type.LEVEL1, MCNK_size + " byte"); } } }
private static void PrepareWaterInfo(MH2O water) { if (water == null) return; if (!water.Header.Used) return; var min = float.MaxValue; var max = float.MinValue; foreach (var height in water.Heights) { min = Math.Min(height, min); max = Math.Max(height, max); } water.IsFlat = (Math.Abs(max - min) < TerrainConstants.MinAllowedHeightDiff); }
public void Read() { if (Chunk == null) { return; } Vertices = new List <Vector3>(); Indices = new List <Triangle <uint> >(); var stream = Chunk.GetStream(); MH2O = new MH2O(); MH2O.Read(Chunk.GetReader()); HeightMaps = new Chunks.MH2O.MH2OHeightmapData[256]; var tilePos = ADT.TilePosition; for (int i = 0; i < MH2O.Headers.Length; i++) { var header = MH2O.Headers[i]; if (header == null || header.LayerCount == 0) { continue; } stream.Seek(Chunk.Offset + header.ofsInformation, SeekOrigin.Begin); var information = new MH2O.MH2OInformation(); information.Read(Chunk.GetReader()); // Ensure we have heightmap data if (information.ofsHeightmapData == 0) { continue; } // Not an ocean, lets grab the height map and render mask MH2O.MH2OHeightmapData heightMap; if (!IsOcean(information.LiquidObjectId, information.LiquidTypeId)) { // Read the height map stream.Seek(Chunk.Offset + information.ofsHeightmapData, SeekOrigin.Begin); heightMap = new MH2O.MH2OHeightmapData(); heightMap.Read(Chunk.GetReader()); heightMap.RenderMask = GetRenderMask(header, information); } else { heightMap = GetOceanHeightMap(information.MinHeightLevel); } HeightMaps[i] = heightMap; var basePos = new Vector3(tilePos.X - (Constants.ChunkSize * (i % 16)), tilePos.Y - (Constants.ChunkSize * (i / 16)), 0); for (int y = information.YOffset; y < (information.YOffset + information.Height); y++) { for (int x = information.XOffset; x < (information.XOffset + information.Width); x++) { if (!heightMap.RenderMask.ShouldRender(x, y)) { continue; } var v = new Vector3(basePos.X - (x * Constants.UnitSize), basePos.Y - (y * Constants.UnitSize), heightMap.Heightmap[x, y]); var vo = (uint)Vertices.Count; Vertices.Add(v); Vertices.Add(new Vector3(v.X - Constants.UnitSize, v.Y, v.Z)); Vertices.Add(new Vector3(v.X, v.Y - Constants.UnitSize, v.Z)); Vertices.Add(new Vector3(v.X - Constants.UnitSize, v.Y - Constants.UnitSize, v.Z)); Indices.Add(new Triangle <uint>(TriangleType.Water, vo, vo + 2, vo + 1)); Indices.Add(new Triangle <uint>(TriangleType.Water, vo + 2, vo + 3, vo + 1)); } } } }
private static MH2O ProcessMH2O(BinaryReader fileReader, MH2OHeader header, long waterSegmentBase) { var water = new MH2O(); if (header.LayerCount == 0) { water.Used = false; return water; } water.Used = true; fileReader.BaseStream.Position = waterSegmentBase + header.ofsData1; water.Flags = (MH2OFlags)fileReader.ReadUInt16(); water.Type = (FluidType)fileReader.ReadUInt16(); water.HeightLevel1 = fileReader.ReadSingle(); water.HeightLevel2 = fileReader.ReadSingle(); water.XOffset = fileReader.ReadByte(); water.YOffset = fileReader.ReadByte(); water.Width = fileReader.ReadByte(); water.Height = fileReader.ReadByte(); var ofsWaterFlags = fileReader.ReadUInt32(); var ofsWaterHeightMap = fileReader.ReadUInt32(); var heightMapLen = (water.Width + 1) * (water.Height + 1); var heights = new float[heightMapLen]; // If flags is 2, the chunk is for an ocean, and there is no heightmap if (ofsWaterHeightMap != 0 && (water.Flags & MH2OFlags.Ocean) == 0) { fileReader.BaseStream.Position = waterSegmentBase + ofsWaterHeightMap; for (var i = 0; i < heightMapLen; i++) { heights[i] = fileReader.ReadSingle(); if (heights[i] == 0) { heights[i] = water.HeightLevel1; } } } else { for (var i = 0; i < heightMapLen; i++) { heights[i] = water.HeightLevel1; } } water.Heights = new float[water.Height + 1, water.Width + 1]; for (var r = 0; r <= water.Height; r++) { for (var c = 0; c <= water.Width; c++) { water.Heights[r, c] = heights[c + r * c]; } } return water; }
private static MH2O ProcessMH2O(BinaryReader fileReader, MH20Header header, long waterSegmentBase) { var water = new MH2O(); if (header.LayerCount == 0) { water.Header.Used = false; return(water); } water.Header.Used = true; fileReader.BaseStream.Position = waterSegmentBase + header.ofsData1; water.Header.Flags = (MH2OFlags)fileReader.ReadUInt16(); water.Header.Type = (LiquidType)fileReader.ReadUInt16(); water.Header.HeightLevel1 = fileReader.ReadSingle(); water.Header.HeightLevel2 = fileReader.ReadSingle(); water.Header.YOffset = fileReader.ReadByte(); water.Header.XOffset = fileReader.ReadByte(); water.Header.Width = fileReader.ReadByte(); water.Header.Height = fileReader.ReadByte(); var ofsWaterFlags = fileReader.ReadUInt32(); var ofsWaterHeightMap = fileReader.ReadUInt32(); water.RenderBitMap = new byte[water.Header.Height]; if (ofsWaterFlags != 0) { fileReader.BaseStream.Position = waterSegmentBase + ofsWaterFlags; for (var i = 0; i < water.Header.Height; i++) { if (i < (ofsWaterHeightMap - ofsWaterFlags)) { water.RenderBitMap[i] = fileReader.ReadByte(); } else { water.RenderBitMap[i] = 0; } } } //var heightMapLen = (water.Header.Width + 1) * (water.Header.Height + 1); var heightMapLen = (TerrainConstants.UnitsPerChunkSide + 1) * (TerrainConstants.UnitsPerChunkSide + 1); water.HeightsArray = new float[heightMapLen]; // If flags is 2, the chunk is for an ocean, and there is no heightmap if (ofsWaterHeightMap != 0 && (water.Header.Flags & MH2OFlags.Ocean) == 0) { fileReader.BaseStream.Position = waterSegmentBase + ofsWaterHeightMap; for (var i = 0; i < heightMapLen; i++) { water.HeightsArray[i] = fileReader.ReadSingle(); if (water.HeightsArray[i].IsWithinEpsilon(0.0f)) { water.HeightsArray[i] = water.Header.HeightLevel1; } } } else { for (var i = 0; i < heightMapLen; i++) { water.HeightsArray[i] = water.Header.HeightLevel1; } } return(water); }
private TriangleList GenerateVertexAndIndicesH2O() { var vertices = new List <VertexPositionNormalColored>(); var indices = new List <int>(); if (MH2OArray != null) { float offset_x = (533.33333f / 16) / 8; float offset_z = (533.33333f / 16) / 8; int VertexCounter = 0; int _TempVertexCounter = 0; for (int My = 0; My < 16; My++) { for (int Mx = 0; Mx < 16; Mx++) { float[,] MH2OHeightMap; bool[,] MH2ORenderMap; _TempVertexCounter = VertexCounter; float x = MCNKArray[Mx, My].x; float z = MCNKArray[Mx, My].z; MH2O mh2O = MH2OArray[Mx, My]; float y_pos = mh2O.heightLevel1; if (mh2O.used) { Color clr = GetColor(mh2O.type); MH2OHeightMap = mh2O.GetMapHeightsMatrix(); MH2ORenderMap = mh2O.GetRenderBitMapMatrix(); for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) { float x_pos = x - (c * offset_x); float z_pos = z - (r * offset_z); if (((r >= mh2O.yOffset) && ((r - mh2O.yOffset) <= mh2O.height)) && ((c >= mh2O.xOffset) && ((c - mh2O.xOffset) <= mh2O.width))) { y_pos = MH2OHeightMap[r - mh2O.yOffset, c - mh2O.xOffset]; // +_MH2O.heightLevel1; var position = new Vector3(x_pos, y_pos, z_pos); vertices.Add(new VertexPositionNormalColored(position, clr, Vector3.Up)); _TempVertexCounter++; } } } for (int r = mh2O.yOffset; r < mh2O.yOffset + mh2O.height; r++) { for (int c = mh2O.xOffset; c < mh2O.xOffset + mh2O.width; c++) { int row = r - mh2O.yOffset; int col = c - mh2O.xOffset; //if ((MH2ORenderMap[row, c]) || ((_MH2O.height == 8) && (_MH2O.width == 8))) { indices.Add(VertexCounter + ((row + 1) * (mh2O.width + 1) + col)); indices.Add(VertexCounter + (row * (mh2O.width + 1) + col)); indices.Add(VertexCounter + (row * (mh2O.width + 1) + col + 1)); indices.Add(VertexCounter + ((row + 1) * (mh2O.width + 1) + col + 1)); indices.Add(VertexCounter + ((row + 1) * (mh2O.width + 1) + col)); indices.Add(VertexCounter + (row * (mh2O.width + 1) + col + 1)); } } } VertexCounter = _TempVertexCounter; } } } } return(new TriangleList(indices, vertices)); }
public void Write() { using (var writer = new BinaryWriter(File.OpenWrite("test.adt"))) { // MVER writer.Write(MVER.GetChunkHeaderBytes()); writer.Write(MVER.GetChunkBytes()); // Write MHDR later when we got all offsets var positionBeforeMHDR = writer.BaseStream.Position; writer.BaseStream.Position += _HeaderSize + MHDR.ChunkSize; // MTEX MHDR.OffsetMTEX = (uint)writer.BaseStream.Position; writer.Write(MTEX.GetChunkHeaderBytes()); writer.Write(MTEX.GetChunkBytes()); // MMDX MHDR.OffsetMMDX = (uint)writer.BaseStream.Position; writer.Write(MMDX.GetChunkHeaderBytes()); writer.Write(MMDX.GetChunkBytes()); // MMID MHDR.OffsetMMID = (uint)writer.BaseStream.Position; writer.Write(MMID.GetChunkHeaderBytes()); writer.Write(MMID.GetChunkBytes()); // MWMO MHDR.OffsetMWMO = (uint)writer.BaseStream.Position; writer.Write(MWMO.GetChunkHeaderBytes()); writer.Write(MWMO.GetChunkBytes()); // MWID MHDR.OffsetMWID = (uint)writer.BaseStream.Position; writer.Write(MWID.GetChunkHeaderBytes()); writer.Write(MWID.GetChunkBytes()); // MDDF MHDR.OffsetMDDF = (uint)writer.BaseStream.Position; writer.Write(MDDF.GetChunkHeaderBytes()); writer.Write(MDDF.GetChunkBytes()); // MODF MHDR.OffsetMODF = (uint)writer.BaseStream.Position; writer.Write(MODF.GetChunkHeaderBytes()); writer.Write(MODF.GetChunkBytes()); // MH2O MHDR.OffsetMH2O = (uint)writer.BaseStream.Position; writer.Write(MH2O.GetChunkHeaderBytes()); writer.Write(MH2O.GetChunkBytes()); // MCNK for (int i = 0; i < MCIN.Entries.Length; i++) { MCIN.Entries[i].OffsetMCNK = (uint)writer.BaseStream.Position; MCIN.Entries[i].ChunkSize = MCNK[i].ChunkSize; writer.Write(MCNK[i].GetChunkHeaderBytes()); writer.Write(MCNK[i].GetChunkBytes()); } // MCIN MHDR.OffsetMCIN = (uint)writer.BaseStream.Position; writer.Write(MCIN.GetChunkHeaderBytes()); writer.Write(MCIN.GetChunkBytes()); // MFBO if (MHDR.Flags.HasFlag(MHDRFlags.MFBO)) { MHDR.OffsetMFBO = (uint)writer.BaseStream.Position; writer.Write(MFBO.GetChunkHeaderBytes()); writer.Write(MFBO.GetChunkBytes()); } else { MHDR.OffsetMFBO = 0; } // MTXF MHDR.OffsetMTXF = (uint)writer.BaseStream.Position; writer.Write(MTXF.GetChunkHeaderBytes()); writer.Write(MTXF.GetChunkBytes()); // MHDR writer.BaseStream.Position = positionBeforeMHDR; writer.Write(MHDR.GetChunkHeaderBytes()); writer.Write(MHDR.GetChunkBytes()); } }
public static bool ConvertADT(ChunkedFile adt, string mapName, string outputPath, int gx, int gy, uint build, bool ignoreDeepWater) { // Prepare map header MapFileHeader map; map.mapMagic = SharedConst.MAP_MAGIC; map.versionMagic = SharedConst.MAP_VERSION_MAGIC; map.buildMagic = build; // Get area flags data for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { AreaIDs[x][y] = 0; LiquidEntries[x][y] = 0; LiquidFlags[x][y] = 0; } } for (var x = 0; x < SharedConst.ADT_GRID_SIZE; ++x) { for (var y = 0; y < SharedConst.ADT_GRID_SIZE; ++y) { V8[x][y] = 0; LiquidSnow[x][y] = false; } } for (var x = 0; x < SharedConst.ADT_GRID_SIZE + 1; ++x) { for (var y = 0; y < SharedConst.ADT_GRID_SIZE + 1; ++y) { V9[x][y] = 0; } } for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { for (var z = 0; z < 8; ++z) { Holes[x][y][z] = 0; } } } bool hasHoles = false; bool hasFlightBox = false; foreach (var fileChunk in adt.chunks.LookupByKey("MCNK")) { MCNK mcnk = fileChunk.As <MCNK>(); // Area data AreaIDs[mcnk.IndexY][mcnk.IndexX] = (ushort)mcnk.AreaID; // Height // Height values for triangles stored in order: // 1 2 3 4 5 6 7 8 9 // 10 11 12 13 14 15 16 17 // 18 19 20 21 22 23 24 25 26 // 27 28 29 30 31 32 33 34 // . . . . . . . . // For better get height values merge it to V9 and V8 map // V9 height map: // 1 2 3 4 5 6 7 8 9 // 18 19 20 21 22 23 24 25 26 // . . . . . . . . // V8 height map: // 10 11 12 13 14 15 16 17 // 27 28 29 30 31 32 33 34 // . . . . . . . . // Set map height as grid height for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V9[cy][cx] = mcnk.ypos; } } for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V8[cy][cx] = mcnk.ypos; } } // Get custom height FileChunk chunk = fileChunk.GetSubChunk("MCVT"); if (chunk != null) { MCVT mcvt = chunk.As <MCVT>(); // get V9 height map for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V9[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + x]; } } // get V8 height map for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; V8[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + SharedConst.ADT_CELL_SIZE + 1 + x]; } } } // Liquid data if (mcnk.MCLQCount > 8) { FileChunk lquidChunk = fileChunk.GetSubChunk("MCLQ"); if (lquidChunk != null) { MCLQ liquid = lquidChunk.As <MCLQ>(); int count = 0; for (int y = 0; y < SharedConst.ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x < SharedConst.ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; if (liquid.Flags[y][x] != 0x0F) { LiquidSnow[cy][cx] = true; if (!ignoreDeepWater && Convert.ToBoolean(liquid.Flags[y][x] & (1 << 7))) { LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.DarkWater; } ++count; } } } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidRiver)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 1; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Water; // water } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidOcean)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 2; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Ocean; // ocean } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidMagma)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 3; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Magma; // magma } if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidSlime)) { LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 4; LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.Slime; // slime } if (count == 0 && LiquidFlags[mcnk.IndexY][mcnk.IndexX] != 0) { Console.WriteLine("Wrong liquid detect in MCLQ chunk"); } for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; ++y) { int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y; for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; ++x) { int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x; LiquidHeight[cy][cx] = liquid.Liquid[y][x].Height; } } } } // Hole data if (!mcnk.Flags.HasAnyFlag(MCNKFlags.HighResHoles)) { uint hole = mcnk.HolesLowRes; if (hole != 0) { if (TransformToHighRes((ushort)hole, Holes[mcnk.IndexY][mcnk.IndexX])) { hasHoles = true; } } } else { Buffer.BlockCopy(mcnk.HighResHoles, 0, Holes[mcnk.IndexY][mcnk.IndexX], 0, 8); if (BitConverter.ToUInt64(Holes[mcnk.IndexY][mcnk.IndexX], 0) != 0) { hasHoles = true; } } } // Get liquid map for grid (in WOTLK used MH2O chunk) FileChunk chunkMH2O = adt.GetChunk("MH2O"); if (chunkMH2O != null) { MH2O h2o = chunkMH2O.As <MH2O>(); for (int i = 0; i < SharedConst.ADT_CELLS_PER_GRID; i++) { for (int j = 0; j < SharedConst.ADT_CELLS_PER_GRID; j++) { MH2OInstance?h = h2o.GetLiquidInstance(i, j); if (!h.HasValue) { continue; } MH2OInstance adtLiquidHeader = h.Value; MH2OChunkAttribute?attrs = h2o.GetLiquidAttributes(i, j); int count = 0; ulong existsMask = h2o.GetLiquidExistsBitmap(adtLiquidHeader); for (int y = 0; y < adtLiquidHeader.GetHeight(); y++) { int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY(); for (int x = 0; x < adtLiquidHeader.GetWidth(); x++) { int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX(); if (Convert.ToBoolean(existsMask & 1)) { LiquidSnow[cy][cx] = true; ++count; } existsMask >>= 1; } } LiquidEntries[i][j] = h2o.GetLiquidType(adtLiquidHeader); if (LiquidEntries[i][j] != 0) { var liquidTypeRecord = LiquidTypeStorage[LiquidEntries[i][j]]; switch ((LiquidType)liquidTypeRecord.SoundBank) { case LiquidType.Water: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Water; break; case LiquidType.Ocean: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Ocean; if (!ignoreDeepWater && attrs.Value.Deep != 0) { LiquidFlags[i][j] |= LiquidHeaderTypeFlags.DarkWater; } break; case LiquidType.Magma: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Magma; break; case LiquidType.Slime: LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Slime; break; default: Console.WriteLine($"\nCan't find Liquid type {adtLiquidHeader.LiquidType} for map {mapName}\nchunk {i},{j}\n"); break; } if (count == 0 && LiquidFlags[i][j] != 0) { Console.WriteLine("Wrong liquid detect in MH2O chunk"); } } else { Console.WriteLine($"LiquidEntries is 0 for [{i}][{j}] ({i * j})"); } int pos = 0; for (int y = 0; y <= adtLiquidHeader.GetHeight(); y++) { int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY(); for (int x = 0; x <= adtLiquidHeader.GetWidth(); x++) { int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX(); LiquidHeight[cy][cx] = h2o.GetLiquidHeight(adtLiquidHeader, i, j, pos); pos++; } } } } } FileChunk chunkMFBO = adt.GetChunk("MFBO"); if (chunkMFBO != null) { MFBO mfbo = chunkMFBO.As <MFBO>(); for (var i = 0; i < 3; ++i) { FlightBoxMax[i][0] = mfbo.Max.coords[0 + i * 3]; FlightBoxMax[i][1] = mfbo.Max.coords[1 + i * 3]; FlightBoxMax[i][2] = mfbo.Max.coords[2 + i * 3]; FlightBoxMin[i][0] = mfbo.Min.coords[0 + i * 3]; FlightBoxMin[i][1] = mfbo.Min.coords[1 + i * 3]; FlightBoxMin[i][2] = mfbo.Min.coords[2 + i * 3]; } hasFlightBox = true; } //============================================ // Try pack area data //============================================ bool fullAreaData = false; uint areaId = AreaIDs[0][0]; for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y) { for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x) { if (AreaIDs[y][x] != areaId) { fullAreaData = true; break; } } } map.areaMapOffset = 44; map.areaMapSize = 8; MapAreaHeader areaHeader; areaHeader.fourcc = SharedConst.MAP_AREA_MAGIC; areaHeader.flags = 0; if (fullAreaData) { areaHeader.gridArea = 0; map.areaMapSize += 512; } else { areaHeader.flags |= AreaHeaderFlags.NoArea; areaHeader.gridArea = (ushort)areaId; } //============================================ // Try pack height data //============================================ float maxHeight = -20000; float minHeight = 20000; for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { float h = V8[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { float h = V9[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } } // Check for allow limit minimum height (not store height in deep ochean - allow save some memory) if (minHeight < -2000.0f) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { if (V8[y][x] < -2000.0f) { V8[y][x] = -2000.0f; } } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { if (V9[y][x] < -2000.0f) { V9[y][x] = -2000.0f; } } } if (minHeight < -2000.0f) { minHeight = -2000.0f; } if (maxHeight < -2000.0f) { maxHeight = -2000.0f; } } map.heightMapOffset = map.areaMapOffset + map.areaMapSize; map.heightMapSize = (uint)Marshal.SizeOf <MapHeightHeader>(); MapHeightHeader heightHeader; heightHeader.fourcc = SharedConst.MAP_HEIGHT_MAGIC; heightHeader.flags = 0; heightHeader.gridHeight = minHeight; heightHeader.gridMaxHeight = maxHeight; if (maxHeight == minHeight) { heightHeader.flags |= HeightHeaderFlags.NoHeight; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.005f) { heightHeader.flags |= HeightHeaderFlags.NoHeight; } if (hasFlightBox) { heightHeader.flags |= HeightHeaderFlags.HasFlightBounds; map.heightMapSize += 18 + 18; } // Try store as packed in uint16 or uint8 values if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight)) { float step = 0; // Try Store as uint values if (true)//CONF_allow_float_to_int { float diff = maxHeight - minHeight; if (diff < 2.0f) // As uint8 (max accuracy = CONF_float_to_int8_limit/256) { heightHeader.flags |= HeightHeaderFlags.AsInt8; step = 255 / diff; } else if (diff < 2048.0f) // As uint16 (max accuracy = CONF_float_to_int16_limit/65536) { heightHeader.flags |= HeightHeaderFlags.AsInt16; step = 65535 / diff; } } // Pack it to int values if need if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8)) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { UInt8_V8[y][x] = (byte)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { UInt8_V9[y][x] = (byte)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 16641 + 16384; } else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16)) { for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { UInt16_V8[y][x] = (ushort)((V8[y][x] - minHeight) * step + 0.5f); } } for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++) { UInt16_V9[y][x] = (ushort)((V9[y][x] - minHeight) * step + 0.5f); } } map.heightMapSize += 33282 + 32768; } else { map.heightMapSize += 66564 + 65536; } } //============================================ // Pack liquid data //============================================ ushort firstLiquidType = LiquidEntries[0][0]; LiquidHeaderTypeFlags firstLiquidFlag = LiquidFlags[0][0]; bool fullType = false; for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; y++) { for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; x++) { if (LiquidEntries[y][x] != firstLiquidType || LiquidFlags[y][x] != firstLiquidFlag) { fullType = true; y = SharedConst.ADT_CELLS_PER_GRID; break; } } } MapLiquidHeader mapLiquidHeader = new(); // no water data (if all grid have 0 liquid type) if (firstLiquidFlag == 0 && !fullType) { // No liquid data map.liquidMapOffset = 0; map.liquidMapSize = 0; } else { int minX = 255, minY = 255; int maxX = 0, maxY = 0; maxHeight = -20000; minHeight = 20000; for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++) { for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++) { if (LiquidSnow[y][x]) { if (minX > x) { minX = x; } if (maxX < x) { maxX = x; } if (minY > y) { minY = y; } if (maxY < y) { maxY = y; } float h = LiquidHeight[y][x]; if (maxHeight < h) { maxHeight = h; } if (minHeight > h) { minHeight = h; } } else { LiquidHeight[y][x] = -2000.0f; } } } map.liquidMapOffset = map.heightMapOffset + map.heightMapSize; map.liquidMapSize = (uint)Marshal.SizeOf <MapLiquidHeader>(); mapLiquidHeader.fourcc = SharedConst.MAP_LIQUID_MAGIC; mapLiquidHeader.flags = LiquidHeaderFlags.None; mapLiquidHeader.liquidType = 0; mapLiquidHeader.offsetX = (byte)minX; mapLiquidHeader.offsetY = (byte)minY; mapLiquidHeader.width = (byte)(maxX - minX + 1 + 1); mapLiquidHeader.height = (byte)(maxY - minY + 1 + 1); mapLiquidHeader.liquidLevel = minHeight; if (maxHeight == minHeight) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight; } // Not need store if flat surface if ((maxHeight - minHeight) < 0.001f) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight; } if (!fullType) { mapLiquidHeader.flags |= LiquidHeaderFlags.NoType; } if (mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType)) { mapLiquidHeader.liquidFlags = firstLiquidFlag; mapLiquidHeader.liquidType = firstLiquidType; } else { map.liquidMapSize += 512 + 256; } if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { map.liquidMapSize += (uint)(sizeof(float) * mapLiquidHeader.width * mapLiquidHeader.height); } } if (hasHoles) { if (map.liquidMapOffset != 0) { map.holesOffset = map.liquidMapOffset + map.liquidMapSize; } else { map.holesOffset = map.heightMapOffset + map.heightMapSize; } map.holesSize = 2048; } else { map.holesOffset = 0; map.holesSize = 0; } // Ok all data prepared - store it using (BinaryWriter binaryWriter = new(File.Open(outputPath, FileMode.Create, FileAccess.Write))) { binaryWriter.WriteStruct(map); // Store area data binaryWriter.WriteStruct(areaHeader); if (!areaHeader.flags.HasFlag(AreaHeaderFlags.NoArea)) { for (var x = 0; x < AreaIDs.Length; ++x) { for (var y = 0; y < AreaIDs[x].Length; ++y) { binaryWriter.Write(AreaIDs[x][y]); } } } // Store height data binaryWriter.WriteStruct(heightHeader); if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight)) { if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16)) { for (var x = 0; x < UInt16_V9.Length; ++x) { for (var y = 0; y < UInt16_V9[x].Length; ++y) { binaryWriter.Write(UInt16_V9[x][y]); } } for (var x = 0; x < UInt16_V8.Length; ++x) { for (var y = 0; y < UInt16_V8[x].Length; ++y) { binaryWriter.Write(UInt16_V8[x][y]); } } } else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8)) { for (var x = 0; x < UInt8_V9.Length; ++x) { for (var y = 0; y < UInt8_V9[x].Length; ++y) { binaryWriter.Write(UInt8_V9[x][y]); } } for (var x = 0; x < UInt8_V8.Length; ++x) { for (var y = 0; y < UInt8_V8[x].Length; ++y) { binaryWriter.Write(UInt8_V8[x][y]); } } } else { for (var x = 0; x < V9.Length; ++x) { for (var y = 0; y < V9[x].Length; ++y) { binaryWriter.Write(V9[x][y]); } } for (var x = 0; x < V8.Length; ++x) { for (var y = 0; y < V8[x].Length; ++y) { binaryWriter.Write(V8[x][y]); } } } } if (heightHeader.flags.HasFlag(HeightHeaderFlags.HasFlightBounds)) { for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(FlightBoxMax[x][y]); } } for (var x = 0; x < 3; ++x) { for (var y = 0; y < 3; ++y) { binaryWriter.Write(FlightBoxMin[x][y]); } } } // Store liquid data if need if (map.liquidMapOffset != 0) { binaryWriter.WriteStruct(mapLiquidHeader); if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType)) { for (var x = 0; x < LiquidEntries.Length; ++x) { for (var y = 0; y < LiquidEntries[x].Length; ++y) { binaryWriter.Write(LiquidEntries[x][y]); } } for (var x = 0; x < LiquidFlags.Length; ++x) { for (var y = 0; y < LiquidFlags[x].Length; ++y) { binaryWriter.Write((byte)LiquidFlags[x][y]); } } } if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight)) { for (int y = 0; y < mapLiquidHeader.height; y++) { for (int x = 0; x < mapLiquidHeader.width; x++) { binaryWriter.Write(LiquidHeight[y + mapLiquidHeader.offsetY][x + mapLiquidHeader.offsetX]); } } } } // store hole data if (hasHoles) { for (var x = 0; x < Holes.Length; ++x) { for (var y = 0; y < Holes[x].Length; ++y) { for (var z = 0; z < Holes[x][y].Length; ++z) { binaryWriter.Write(Holes[x][y][z]); } } } } } return(true); }