Exemple #1
0
        private void ComputeDirectionFlags(GeoSpace geoSpace)
        {
            // run in 2 passes to avoid "collection was modified".
            for (int interleave = 0; interleave < 2; interleave++)
            {
                Parallel.For(0, m_blockHeight, new ParallelOptions {
                    MaxDegreeOfParallelism = OPTION_PARALLEL_THREADS
                }, (y) =>
                {
                    if (y % 2 == interleave)
                    {
                        return;
                    }

                    int i = y * m_blockWidth; // index to floor cell
                    for (int x = 0; x < m_blockWidth; x++, i++)
                    {
                        List <NavMeshBuilderFloorDesc> heights = m_floorData[i];
                        if (heights == null)
                        {
                            continue;
                        }

                        for (int f = 0; f < heights.Count; f++)
                        {
                            int z100      = heights[f].Z100i;
                            byte newFlags = GetBlockedDirections(geoSpace, x, y, z100);
                            heights[f]    = new NavMeshBuilderFloorDesc(z100, newFlags);
                        }
                    }
                });
            }
        }
Exemple #2
0
        // test if any of the 8 adjacent cells are blocked.
        // used by Build() - only valid after all points are added.
        private byte GetBlockedDirections(GeoSpace geoSpace, int blockX, int blockY, int z100i)
        {
            float x = m_x1 + blockX * m_step;
            float y = m_y1 + blockY * m_step;

            var me = new Vector3(x, y, z100i / 100.0f);

            byte flags = 0;

            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, -1, 0))
            {
                flags |= NavMeshUtil.DIRECTION_LEFT;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, -1, 1))
            {
                flags |= NavMeshUtil.DIRECTION_TL;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, 0, 1))
            {
                flags |= NavMeshUtil.DIRECTION_TOP;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, 1, 1))
            {
                flags |= NavMeshUtil.DIRECTION_TR;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, 1, 0))
            {
                flags |= NavMeshUtil.DIRECTION_RIGHT;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, 1, -1))
            {
                flags |= NavMeshUtil.DIRECTION_BR;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, 0, -1))
            {
                flags |= NavMeshUtil.DIRECTION_BOTTOM;
            }
            if (IsDirectionBlocked(geoSpace, me, blockX, blockY, -1, -1))
            {
                flags |= NavMeshUtil.DIRECTION_BL;
            }

            return(flags);
        }
Exemple #3
0
        // 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);
        }
Exemple #4
0
        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);
        }