private static void AddCgfToWorld(CgfLoader cgf, ref Matrix xform, Vector3 position, GeoSpace geoSpace) { s_tempVertices.Clear(); // traverse nodes cgf.TraverseNodes((node, transform) => NodeHandler(cgf, node, position, transform), ref xform); // Add collected vertices as a new mesh. geoSpace.AddCollidableMeshToTree(s_tempVertices); }
// generates navmesh data for the selected levels. public static void GenerateAllNav(WorldIdXmlLoader worldIdXmlLoader, DirManager meshesDir, DirManager levelsDir, string levelId, bool skipExistingNav, string outputPath) { int curFolderIndex = -1; foreach (var pairs in worldIdXmlLoader.FolderNamesById) { curFolderIndex++; string clientLevelId = pairs.Key; string levelFolder = pairs.Value; if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId) { // skip excluded continue; } string outputNavFilename = Path.Combine(outputPath, clientLevelId + ".nav"); if (skipExistingNav && File.Exists(outputNavFilename)) { Console.WriteLine($" ** Skipping (already exists): {clientLevelId} - {levelFolder}"); continue; } Console.WriteLine($" Loading meshes for {clientLevelId} - {levelFolder}"); var LEVEL_TIME = DateTime.Now; var TIMER = DateTime.Now; var geoSpace = new GeoSpace(); // brushes var brushlst = LevelLoadHelper.CreateBrushLstLoader(levelsDir, Path.Combine(levelFolder, "brush.lst")); if (brushlst != null) { var cgfMap = LevelLoadHelper.CreateBrushLstCgfLoaderMap(meshesDir, brushlst); foreach (var brush in brushlst.brushEntries) { CgfLoader cgf; if (!cgfMap.TryGetValue(brush.meshIdx, out cgf)) { continue; } Matrix brushMatrix = LevelLoadHelper.GetBrushMatrix(brush); AddCgfToWorld(cgf, ref brushMatrix, brush.position, geoSpace); } } // objects var ctx = LevelLoadHelper.LoadObjectsLst(meshesDir, levelsDir, levelFolder); if (ctx != null) { foreach (var o in ctx.objects) { CgfLoader cgf; if (!ctx.cgfMap.TryGetValue(o.ObjectId, out cgf)) { continue; } var xform = LevelLoadHelper.GetObjectMatrix(o); AddCgfToWorld(cgf, ref xform, o.Position, geoSpace); } } // terrain bool loadedH32 = false; string h32path = Path.Combine(levelFolder, @"terrain\land_map.h32"); if (levelsDir.Exists(h32path)) { using (var landMapStream = levelsDir.OpenFile(h32path)) new H32Loader(landMapStream).LoadIntoGeoSpace(geoSpace); loadedH32 = true; } Console.WriteLine(" Data load time: " + (DateTime.Now - TIMER)); if (brushlst == null && ctx == null && !loadedH32) { Console.WriteLine(" ** Skipping (no level data found)"); continue; } // build geo TIMER = DateTime.Now; geoSpace.BuildTree(); geoSpace.Validate(); Console.WriteLine(" Geo build time: " + (DateTime.Now - TIMER)); // get size of level for scanning var bb = geoSpace.GetBoundingBox(); float startX = Math.Max(0, bb.Min.X); float endX = bb.Max.X; float startY = Math.Max(0, bb.Min.Y); float endY = bb.Max.Y; int top = (int)Math.Ceiling(bb.Max.Z); int bot = Math.Max(0, (int)bb.Min.Z); // TODO - print bounding box // TODO - save log file if (endX <= startX || endY <= startY || top < bot) { throw new InvalidOperationException( $"unexpected level size for {clientLevelId} {levelFolder} bb: {bb}"); } // compile mesh TIMER = DateTime.Now; float step = 1f; var navMeshBuilder = new NavMeshBuilder(startX, startY, bot, endX, endY, top, step); CompiledNavMeshSet compiledMeshSet = navMeshBuilder.ScanFloor(geoSpace); Console.WriteLine(" Raycast time: " + (DateTime.Now - TIMER)); // save to file using (var fs = File.OpenWrite(outputNavFilename)) compiledMeshSet.Save(fs); Console.WriteLine($" Level {clientLevelId} finished in {DateTime.Now - LEVEL_TIME}"); } }
// load terrain vertices for collision testing public void LoadIntoGeoSpace(GeoSpace geoSpace) { if (isEmpty) { return; } // Rules: // - subdivide X & Y to create smaller AABBs for geospace. // - remove steep slopes. const int subdivide = 8; var meshTriangleVertices = new List <Vector3>(); for (int sectorY = 0; sectorY < width - 1; sectorY += subdivide) { for (int sectorX = 0; sectorX < width - 1; sectorX += subdivide) { meshTriangleVertices.Clear(); // can reduce to 2 triangles only if all vertices are the same height and none are removed. // TODO - use a real poly reduction algorithm instead of fixed size. bool optimizationAllowed = true; int endX = Math.Min(width - 1, sectorX + subdivide); int endY = Math.Min(width - 1, sectorY + subdivide); for (int y = sectorY; y < endY; y++) { for (int x = sectorX; x < endX; x++) { var p1 = VertexLookup(x, y); var p2 = VertexLookup(x, y + 1); var p3 = VertexLookup(x + 1, y); var p4 = VertexLookup(x + 1, y + 1); // TODO - replace with a correct slope test. // TODO - test if any maps have unit size other than 2. if (Math.Abs(p1.Z - p2.Z) >= 2 || Math.Abs(p1.Z - p3.Z) >= 2) { optimizationAllowed = false; continue; } meshTriangleVertices.Add(p1); meshTriangleVertices.Add(p3); meshTriangleVertices.Add(p2); meshTriangleVertices.Add(p2); meshTriangleVertices.Add(p4); meshTriangleVertices.Add(p3); } } if (meshTriangleVertices.Count == 0) { continue; } if (optimizationAllowed) { // if all points have the same Z, replace with 2 triangles. if (meshTriangleVertices.All(v => v.Z == meshTriangleVertices[0].Z)) { // don't optimize if there is a mix of 3F and non-3F mats, as this would break cutouts. // a mix would be rare, so for simplicity, just don't optimize when 3F is found. bool anyMat3F = false; for (int y = sectorY; y < endY; y++) { for (int x = sectorX; x < endX; x++) { if (IsCutout(x, y)) { anyMat3F = true; goto exitMatCheck; } } } exitMatCheck: if (!anyMat3F) { var p1 = VertexLookup(sectorX, sectorY); var p2 = VertexLookup(sectorX, endY); var p3 = VertexLookup(endX, sectorY); var p4 = VertexLookup(endX, endY); if (p1.Z != meshTriangleVertices[0].Z || p2.Z != meshTriangleVertices[0].Z || p3.Z != meshTriangleVertices[0].Z || p4.Z != meshTriangleVertices[0].Z) { throw new InvalidOperationException("Z mismatch"); } meshTriangleVertices.Clear(); meshTriangleVertices.Add(p1); meshTriangleVertices.Add(p3); meshTriangleVertices.Add(p2); meshTriangleVertices.Add(p2); meshTriangleVertices.Add(p4); meshTriangleVertices.Add(p3); } } } geoSpace.AddCollidableMeshToTree(meshTriangleVertices); } } }