void CreateFilter() { NavTerrain includeFlags = 0; NavTerrain excludeFlags = 0; if (_sourceUnit.IsTypeId(TypeId.Unit)) { Creature creature = _sourceUnit.ToCreature(); if (creature.CanWalk()) { includeFlags |= NavTerrain.Ground; } if (creature.CanSwim()) { includeFlags |= (NavTerrain.Water | NavTerrain.Magma | NavTerrain.Slime); } } else { includeFlags = (NavTerrain.Ground | NavTerrain.Water | NavTerrain.Magma | NavTerrain.Slime); } _filter.setIncludeFlags((ushort)includeFlags); _filter.setExcludeFlags((ushort)excludeFlags); UpdateFilter(); }
void UpdateFilter() { // allow creatures to cheat and use different movement types if they are moved // forcefully into terrain they can't normally move in if (_sourceUnit.IsInWater() || _sourceUnit.IsUnderWater()) { NavTerrain includedFlags = (NavTerrain)_filter.getIncludeFlags(); includedFlags |= GetNavTerrain(_sourceUnit.GetPositionX(), _sourceUnit.GetPositionY(), _sourceUnit.GetPositionZ()); _filter.setIncludeFlags((ushort)includedFlags); } }
/**************************************************************************/ bool loadMap(uint mapID, uint tileX, uint tileY, MeshData meshData, Spot portion) { string mapFileName = $"maps/{mapID:D4}_{tileY:D2}_{tileX:D2}.map"; if (!File.Exists(mapFileName)) { return(false); } using (BinaryReader reader = new BinaryReader(File.Open(mapFileName, FileMode.Open, FileAccess.Read))) { map_fileheader fheader = reader.ReadStruct <map_fileheader>(); if (fheader.versionMagic != SharedConst.MAP_VERSION_MAGIC) { Console.WriteLine($"{mapFileName} is the wrong version, please extract new .map files"); return(false); } bool haveTerrain = false; bool haveLiquid = false; reader.BaseStream.Seek(fheader.heightMapOffset, SeekOrigin.Begin); map_heightHeader hheader = reader.ReadStruct <map_heightHeader>(); if (hheader.fourcc == 1413957709) { haveTerrain = !Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.NoHeight); haveLiquid = fheader.liquidMapOffset != 0;// && !m_skipLiquid; } // no data in this map file if (!haveTerrain && !haveLiquid) { return(false); } // data used later byte[][][] holes = new byte[16][][]; for (var i = 0; i < 16; ++i) { holes[i] = new byte[16][]; for (var x = 0; x < 16; ++x) { holes[i][x] = new byte[8]; } } byte[][] liquid_type = new byte[16][]; for (var i = 0; i < 16; ++i) { liquid_type[i] = new byte[16]; } List <int> ltriangles = new List <int>(); List <int> ttriangles = new List <int>(); // terrain data if (haveTerrain) { float heightMultiplier; float[] V9 = new float[SharedConst.V9_SIZE_SQ]; float[] V8 = new float[SharedConst.V8_SIZE_SQ]; int expected = SharedConst.V9_SIZE_SQ + SharedConst.V8_SIZE_SQ; if (Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.AsInt8)) { byte[] v9 = new byte[SharedConst.V9_SIZE_SQ]; byte[] v8 = new byte[SharedConst.V8_SIZE_SQ]; int count = 0; count += reader.Read(v9, 0, SharedConst.V9_SIZE_SQ); count += reader.Read(v8, 0, SharedConst.V8_SIZE_SQ); if (count != expected) { Console.WriteLine($"TerrainBuilder.loadMap: Failed to read some data expected {expected}, read {count}"); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else if (Convert.ToBoolean(hheader.flags & (uint)MapHeightFlags.AsInt16)) { ushort[] v9 = new ushort[SharedConst.V9_SIZE_SQ]; ushort[] v8 = new ushort[SharedConst.V8_SIZE_SQ]; for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { v9[i] = reader.ReadUInt16(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { v8[i] = reader.ReadUInt16(); } heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight; } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight; } } else { for (var i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { V9[i] = reader.ReadSingle(); } for (var i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { V8[i] = reader.ReadSingle(); } } // hole data if (fheader.holesSize != 0) { reader.BaseStream.Seek(fheader.holesOffset, SeekOrigin.Begin); int readCount = 0; for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { for (var c = 0; c < 8; ++c) { if (readCount == fheader.holesSize) { break; } holes[i][x][c] = reader.ReadByte(); } } } } int count1 = meshData.solidVerts.Count / 3; float xoffset = ((float)tileX - 32) * SharedConst.GRID_SIZE; float yoffset = ((float)tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V9, xoffset, yoffset, ref coord, ref V9); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } for (int i = 0; i < SharedConst.V8_SIZE_SQ; ++i) { getHeightCoord(i, Grid.V8, xoffset, yoffset, ref coord, ref V8); meshData.solidVerts.Add(coord[0]); meshData.solidVerts.Add(coord[2]); meshData.solidVerts.Add(coord[1]); } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += 1) { getHeightTriangle(i, j, indices); ttriangles.Add(indices[2] + count1); ttriangles.Add(indices[1] + count1); ttriangles.Add(indices[0] + count1); } } } // liquid data if (haveLiquid) { reader.BaseStream.Seek(fheader.liquidMapOffset, SeekOrigin.Begin); map_liquidHeader lheader = reader.ReadStruct <map_liquidHeader>(); float[] liquid_map = null; if (!Convert.ToBoolean(lheader.flags & 0x0001)) { for (var i = 0; i < 16; ++i) { for (var x = 0; x < 16; ++x) { liquid_type[i][x] = reader.ReadByte(); } } } if (!Convert.ToBoolean(lheader.flags & 0x0002)) { int toRead = lheader.width * lheader.height; liquid_map = new float[toRead]; for (var i = 0; i < toRead; ++i) { liquid_map[i] = reader.ReadSingle(); } } if (liquid_map != null) { int count = meshData.liquidVerts.Count / 3; float xoffset = (tileX - 32) * SharedConst.GRID_SIZE; float yoffset = (tileY - 32) * SharedConst.GRID_SIZE; float[] coord = new float[3]; int row, col; // generate coordinates if (!Convert.ToBoolean(lheader.flags & 0x0002)) { int j = 0; for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height || col < lheader.offsetX || col >= lheader.offsetX + lheader.width) { // dummy vert using invalid height meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(SharedConst.INVALID_MAP_LIQ_HEIGHT); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); continue; } getLiquidCoord(i, j, xoffset, yoffset, ref coord, ref liquid_map); meshData.liquidVerts.Add(coord[0]); meshData.liquidVerts.Add(coord[2]); meshData.liquidVerts.Add(coord[1]); j++; } } else { for (int i = 0; i < SharedConst.V9_SIZE_SQ; ++i) { row = i / SharedConst.V9_SIZE; col = i % SharedConst.V9_SIZE; meshData.liquidVerts.Add((xoffset + col * SharedConst.GRID_PART_SIZE) * -1); meshData.liquidVerts.Add(lheader.liquidLevel); meshData.liquidVerts.Add((yoffset + row * SharedConst.GRID_PART_SIZE) * -1); } } int[] indices = { 0, 0, 0 }; int loopStart = 0, loopEnd = 0, loopInc = 0, triInc = Spot.Bottom - Spot.Top; getLoopVars(portion, ref loopStart, ref loopEnd, ref loopInc); // generate triangles for (int i = loopStart; i < loopEnd; i += loopInc) { for (Spot j = Spot.Top; j <= Spot.Bottom; j += triInc) { getHeightTriangle(i, j, indices, true); ltriangles.Add(indices[2] + count); ltriangles.Add(indices[1] + count); ltriangles.Add(indices[0] + count); } } } } // now that we have gathered the data, we can figure out which parts to keep: // liquid above ground, ground above liquid int loopStart1 = 0, loopEnd1 = 0, loopInc1 = 0, tTriCount = 4; bool useTerrain, useLiquid; float[] lverts = meshData.liquidVerts.ToArray(); int[] ltris = ltriangles.ToArray(); float[] tverts = meshData.solidVerts.ToArray(); int[] ttris = ttriangles.ToArray(); if ((ltriangles.Count + ttriangles.Count) == 0) { return(false); } // make a copy of liquid vertices // used to pad right-bottom frame due to lost vertex data at extraction float[] lverts_copy = null; if (meshData.liquidVerts.Count != 0) { lverts_copy = new float[meshData.liquidVerts.Count]; Array.Copy(lverts, lverts_copy, meshData.liquidVerts.Count); } getLoopVars(portion, ref loopStart1, ref loopEnd1, ref loopInc1); for (int i = loopStart1; i < loopEnd1; i += loopInc1) { for (int j = 0; j < 2; ++j) { // default is true, will change to false if needed useTerrain = true; useLiquid = true; NavTerrain navTerrain = 0; // FIXME: "warning: the address of ‘liquid_type’ will always evaluate as ‘true’" // if there is no liquid, don't use liquid if (meshData.liquidVerts.Count == 0 || ltriangles.Count == 0) { useLiquid = false; } else { LiquidTypeMask liquidType = (LiquidTypeMask)getLiquidType(i, liquid_type); switch (liquidType) { default: useLiquid = false; break; case LiquidTypeMask.Water: case LiquidTypeMask.Ocean: // merge different types of water navTerrain = NavTerrain.Water; break; case LiquidTypeMask.Magma: navTerrain = NavTerrain.Magma; break; case LiquidTypeMask.Slime: navTerrain = NavTerrain.Slime; break; case LiquidTypeMask.DarkWater: // players should not be here, so logically neither should creatures useTerrain = false; useLiquid = false; break; } } // if there is no terrain, don't use terrain if (ttriangles.Count == 0) { useTerrain = false; } // while extracting ADT data we are losing right-bottom vertices // this code adds fair approximation of lost data if (useLiquid) { float quadHeight = 0; uint validCount = 0; for (uint idx = 0; idx < 3; idx++) { float h = lverts_copy[ltris[idx] * 3 + 1]; if (h != SharedConst.INVALID_MAP_LIQ_HEIGHT && h < SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { quadHeight += h; validCount++; } } // update vertex height data if (validCount > 0 && validCount < 3) { quadHeight /= validCount; for (uint idx = 0; idx < 3; idx++) { float h = lverts[ltris[idx] * 3 + 1]; if (h == SharedConst.INVALID_MAP_LIQ_HEIGHT || h > SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX) { lverts[ltris[idx] * 3 + 1] = quadHeight; } } } // no valid vertexes - don't use this poly at all if (validCount == 0) { useLiquid = false; } } // if there is a hole here, don't use the terrain if (useTerrain && fheader.holesSize != 0) { useTerrain = !isHole(i, holes); } // we use only one terrain kind per quad - pick higher one if (useTerrain && useLiquid) { float minLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; float maxLLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; for (uint x = 0; x < 3; x++) { float h = lverts[ltris[x] * 3 + 1]; if (minLLevel > h) { minLLevel = h; } if (maxLLevel < h) { maxLLevel = h; } } float maxTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT; float minTLevel = SharedConst.INVALID_MAP_LIQ_HEIGHT_MAX; for (uint x = 0; x < 6; x++) { float h = tverts[ttris[x] * 3 + 1]; if (maxTLevel < h) { maxTLevel = h; } if (minTLevel > h) { minTLevel = h; } } // terrain under the liquid? if (minLLevel > maxTLevel) { useTerrain = false; } //liquid under the terrain? if (minTLevel > maxLLevel) { useLiquid = false; } } // store the result if (useLiquid) { meshData.liquidType.Add((byte)navTerrain); for (int k = 0; k < 3; ++k) { meshData.liquidTris.Add(ltris[k]); } } if (useTerrain) { for (int k = 0; k < 3 * tTriCount / 2; ++k) { meshData.solidTris.Add(ttris[k]); } } ltris = ltris.Skip(3).ToArray(); ttris = ttris.Skip(3 * tTriCount / 2).ToArray(); } } } return(meshData.solidTris.Count != 0 || meshData.liquidTris.Count != 0); }
/**************************************************************************/ public bool loadVMap(uint mapID, uint tileX, uint tileY, MeshData meshData) { var vmapManager = new VMapManager2(); VMAPLoadResult result = vmapManager.loadMap("vmaps", mapID, tileX, tileY); bool retval = false; do { if (result == 0) { break; } Dictionary <uint, StaticMapTree> instanceTrees; vmapManager.getInstanceMapTree(out instanceTrees); if (instanceTrees[mapID] == null) { break; } ModelInstance[] models = null; uint count; instanceTrees[mapID].getModelInstances(out models, out count); if (models == null) { break; } for (uint i = 0; i < count; ++i) { ModelInstance instance = models[i]; if (instance == null) { continue; } // model instances exist in tree even though there are instances of that model in this tile WorldModel worldModel = instance.getWorldModel(); if (worldModel == null) { continue; } // now we have a model to add to the meshdata retval = true; List <GroupModel> groupModels; worldModel.getGroupModels(out groupModels); // all M2s need to have triangle indices reversed bool isM2 = instance.name.Contains(".m2") || instance.name.Contains(".M2"); // transform data float scale = instance.iScale; Matrix3 rotation = Matrix3.fromEulerAnglesXYZ(MathF.PI * instance.iRot.Z / -180.0f, MathF.PI * instance.iRot.X / -180.0f, MathF.PI * instance.iRot.Y / -180.0f); Vector3 position = instance.iPos; position.X -= 32 * SharedConst.GRID_SIZE; position.Y -= 32 * SharedConst.GRID_SIZE; foreach (var it in groupModels) { List <Vector3> tempVertices; List <Vector3> transformedVertices = new List <Vector3>(); List <MeshTriangle> tempTriangles; WmoLiquid liquid = null; it.getMeshData(out tempVertices, out tempTriangles, out liquid); // first handle collision mesh transform(tempVertices.ToArray(), transformedVertices, scale, rotation, position); int offset = meshData.solidVerts.Count / 3; copyVertices(transformedVertices.ToArray(), meshData.solidVerts); copyIndices(tempTriangles, meshData.solidTris, offset, isM2); // now handle liquid data if (liquid != null) { List <Vector3> liqVerts = new List <Vector3>(); List <uint> liqTris = new List <uint>(); uint tilesX, tilesY, vertsX, vertsY; Vector3 corner; liquid.getPosInfo(out tilesX, out tilesY, out corner); vertsX = tilesX + 1; vertsY = tilesY + 1; byte[] flags = liquid.iFlags; float[] data = liquid.iHeight; NavTerrain type = NavTerrain.Empty; // convert liquid type to NavTerrain switch (liquid.iType & 3) { case 0: case 1: type = NavTerrain.Water; break; case 2: type = NavTerrain.Magma; break; case 3: type = NavTerrain.Slime; break; } // indexing is weird... // after a lot of trial and error, this is what works: // vertex = y*vertsX+x // tile = x*tilesY+y // flag = y*tilesY+x Vector3 vert; for (uint x = 0; x < vertsX; ++x) { for (uint y = 0; y < vertsY; ++y) { vert = new Vector3(corner.X + x * SharedConst.GRID_PART_SIZE, corner.Y + y * SharedConst.GRID_PART_SIZE, data[y * vertsX + x]); vert = vert * rotation * scale + position; vert.X *= -1.0f; vert.Y *= -1.0f; liqVerts.Add(vert); } } uint idx1, idx2, idx3, idx4; uint square; for (uint x = 0; x < tilesX; ++x) { for (uint y = 0; y < tilesY; ++y) { if ((flags[x + y * tilesX] & 0x0f) != 0x0f) { square = x * tilesY + y; idx1 = square + x; idx2 = square + 1 + x; idx3 = square + tilesY + 1 + 1 + x; idx4 = square + tilesY + 1 + x; // top triangle liqTris.Add(idx3); liqTris.Add(idx2); liqTris.Add(idx1); // bottom triangle liqTris.Add(idx4); liqTris.Add(idx3); liqTris.Add(idx1); } } } int liqOffset = meshData.liquidVerts.Count / 3; for (int x = 0; x < liqVerts.Count; ++x) { meshData.liquidVerts.Add(liqVerts[x].Y); meshData.liquidVerts.Add(liqVerts[x].Z); meshData.liquidVerts.Add(liqVerts[x].X); } for (int x = 0; x < liqTris.Count / 3; ++x) { meshData.liquidTris.Add((int)(liqTris[x * 3 + 1] + liqOffset)); meshData.liquidTris.Add((int)(liqTris[x * 3 + 2] + liqOffset)); meshData.liquidTris.Add((int)(liqTris[x * 3] + liqOffset)); meshData.liquidType.Add((byte)type); } } } } }while (false); vmapManager.unloadMap(mapID, tileX, tileY); return(retval); }