// scans the geospace and collects points where it finds a floor. public CompiledNavMeshSet ScanFloor(GeoSpace geoSpace) { var timer = DateTime.Now; // collect the heights at each cell. Parallel.For(0, m_blockHeight, new ParallelOptions { MaxDegreeOfParallelism = OPTION_PARALLEL_THREADS }, (blockY) => { float y = m_y1 + blockY * m_step; int blockX = 0; for (float x = m_x1; x < m_x2; x += m_step, blockX++) { float curTop = m_z2; while (curTop > m_z1) { var v0 = new Vector3(x, y, curTop); var ray = new RayX(v0, new Vector3(0, 0, -1), (curTop - m_z1)); float best = (curTop - m_z1); // descend from top to find vertical hits. geoSpace.DoActionOnIntersectingMeshes(ray.GetBoundingBox(), (Vector3[] points) => { for (int i = 0; i < points.Length; i += 3) { float d = ray.IntersectsTriangle(points[i], points[i + 1], points[i + 2]); if (d < best) { best = d; } } return(true); // process all }); // no more hits? if (best >= (curTop - m_z1)) { break; } float z = curTop - best; // round-trip the height value //z = NavMeshUtil.DecodeHeight(m_z1, m_z2, NavMeshUtil.EncodeHeight(m_z1, m_z2, z)); // add the floor if there is enough room above it. if (!geoSpace.HasCollision( new RayX(new Vector3(x, y, z + 0.01f), // raise z to avoid colliding with start point new Vector3(0, 0, 1), // look up m_requiredSpaceAboveFloor))) { AddFloorPoint(x, y, z); } curTop -= best + 1; // +1 to move down to skip some solid space } } }); Debug.WriteLine("ScanFloor time: " + (DateTime.Now - timer)); // for each floor in each cell, determine neighbor connections. timer = DateTime.Now; ComputeDirectionFlags(geoSpace); Debug.WriteLine("ComputeDirectionFlags time: " + (DateTime.Now - timer)); // make a temporary map of each vertex to its neighboring vertices. // this is used for subgraph calculation. timer = DateTime.Now; Dictionary <EdgeVertex, List <EdgeVertex> > edges = GatherEdgeVertices(); Debug.WriteLine("GATHER EDGES time: " + (DateTime.Now - timer)); /* // PRINT ALL EDGES * foreach (var kvp in edges) * { * Debug.WriteLine(kvp.Key); * foreach (var v in kvp.Value) * { * Debug.WriteLine(" " + v); * } * }*/ // TODO - why the need to remove steep edges? they should have been excluded when // computing directions already... // // remove steep edges... modifies edges (and floordata), do this before // // computing subgraphs. // //timer = DateTime.Now; // //RemoveSteepEdges(edges); // //Debug.WriteLine("RemoveSteepEdges time: " + (DateTime.Now - timer)); // separate the subgraphs timer = DateTime.Now; List <HashSet <EdgeVertex> > subgraphs = ComputeSubgraphs(edges); Debug.WriteLine("ComputeSubgraphs time: " + (DateTime.Now - timer)); // FILTERING // filter subgraphs by size if (OPTION_REMOVE_SMALL_GRAPHS) { timer = DateTime.Now; subgraphs = subgraphs.Where(vertices => vertices.Count >= 100).ToList(); Debug.WriteLine("RemoveSmallSubgraphs time: " + (DateTime.Now - timer)); } else { Debug.WriteLine("NOTE: not removing small graphs"); } // POST FILTERING // by now, the gathered subgraph edges may not match the original direction flags, // due to slope checks, height checks, etc... // so, update the direction flags so they reflect the current gathered subgraphs. // it is possible this step could hide bugs, but overall is necessary... timer = DateTime.Now; FixDirectionFlagsToMatchSubgraphs(edges, subgraphs); Debug.WriteLine("FIX DIRECTION FLAGS time: " + (DateTime.Now - timer)); // VALIDATION ValidateAllSubgraphVerticesExistInFloorData(subgraphs); ValidateEdgesAtBoundsAreBlocked(subgraphs); timer = DateTime.Now; var compiledMeshes = new List <CompiledNavMesh>(); foreach (var sg in subgraphs) { compiledMeshes.Add(Build(sg)); } var compiledNavMeshSet = new CompiledNavMeshSet(compiledMeshes); Debug.WriteLine("Compile time: " + (DateTime.Now - timer)); return(compiledNavMeshSet); }
private bool IsDirectionBlocked(GeoSpace geoSpace, Vector3 me, int blockX, int blockY, int dx, int dy) { if (blockX + dx < 0 || blockX + dx >= m_blockWidth || blockY + dy < 0 || blockY + dy >= m_blockHeight) { return(true); // leaving bounding box not allowed } var heights = GetFloorDescListChecked(blockX + dx, blockY + dy); if (heights == null) { return(true); } bool result = true; foreach (var p in heights) { float dz = me.Z - (p.Z100i / 100.0f); if (Math.Abs(dz) > m_maxZStep) { continue; } // check for floor below diagonal midpoint if (dx != 0 && dy != 0) { var mid = new Vector3(me.X + dx * m_step / 2, me.Y + dy * m_step / 2, me.Z); bool diagonalHasVerticalCollision = geoSpace.HasCollision( new RayX(mid + new Vector3(0, 0, m_maxZStep), new Vector3(0, 0, -1), 2 * m_maxZStep)); if (!diagonalHasVerticalCollision) { continue; } } float length = new Vector3(dx * m_step, dy * m_step, dz).Length(); // offset z from the floor to avoid small bumps. const float Z_OFFSET = 0.3f; bool canSeeNeighbor = !geoSpace.HasCollision(new RayX(me + new Vector3(0, 0, Z_OFFSET), Vector3.Normalize(new Vector3(dx, dy, -dz)), length)); if (!canSeeNeighbor) { continue; // view blocked } bool neighborSeesMe = !geoSpace.HasCollision(new RayX(me + new Vector3(dx * m_step, dy * m_step, -dz + Z_OFFSET), Vector3.Normalize(new Vector3(-dx, -dy, dz)), length)); if (!neighborSeesMe) { continue; // view blocked } // check for a sharp drop. look for a large z diff with midpoint z close to one side. if (Math.Abs(dz) >= 0.6f) { int foundSum = 0; for (int i = 1; i < 4; i++) { float highestZ = dz < 0 ? me.Z - dz : me.Z; var mid = new Vector3(me.X + dx * m_step * i / 4, me.Y + dy * m_step * i / 4, highestZ); bool foundStep = geoSpace.HasCollision( new RayX(mid /*+ new Vector3(0, 0, 2)*/, new Vector3(0, 0, -1), 0.8f * Math.Abs(dz) /* * m_maxZStep*/)); if (foundStep) { foundSum++; } } // found sudden dropoff/wall if (foundSum == 0) { continue; } } // dupes can happen (and fail here) if the XY step size is greater than the required height. // can optimize by removing this check and just returning the result. if (!result) { throw new InvalidOperationException("multiple candidate steps"); } result = false; } return(result); }