public void generateNeighborLinks(OpenHeightfield field)
        {
            if( field == null )
            {
                Logger.LogError("[OpenHeightfieldBuilder][generateNeighborLinks]field Empty"); 
                return; 
            }

            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current; 
                for(int dir = 0; dir < 4; ++dir)
                {
                    //邻居的GirdIndex
                    int nWidthIndex = (iter.widthIndex() + BoundeField.getDirOffsetWidth(dir));
                    int nDepthIndex = (iter.depthIndex() + BoundeField.getDirOffsetDepth(dir)); 
                    
                    for(OpenHeightSpan nSpan = field.getData(nWidthIndex,nDepthIndex);
                        nSpan != null;
                        nSpan = nSpan.next())
                    {
                        int maxFloor = Math.Max(span.floor(), nSpan.floor());
                        int minCeling = Math.Min(span.ceiling(), nSpan.ceiling()); 

                        
                        if( (minCeling - maxFloor) >= mMinTraversableHeight   //邻居之间的通道足够高,可以通过 
                            && Math.Abs(nSpan.floor() - span.floor()) <= mMaxTraversableStep )  //两邻居之间的落差足够小
                        {
                            span.setNeighbor(dir, nSpan);
                            break; 
                        }
                    }

                }
            }
        }
        /* 参考 :http://www.cnblogs.com/wantnon/p/4947067.html 
        *       http://fab.cba.mit.edu/classes/S62.12/docs/Meijster_distance.pdf
        * 具体算法名:Saito算法
        * 因为初始化值都是未知的,通过一个大约估计的初始化,来重复来回算一个大致准确的距离
        * 值 ?
        */
        public void generateDistanceField(OpenHeightfield field)
        {
            if (field == null)
            {
                Logger.LogError("[OpenHeightfieldBuilder][generateNeighborLinks]field Empty");
                return;
            }

          

            /*
             * 先将OpenHeightField的Span数据转换成0和1的二值图。如果是边缘Span
             * 那么就是0,否则就是NEEDS_INIT。
             */


            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current;
                bool isBorder = false; 

                for(int dir = 0; dir < 4; ++dir)
                {
                    OpenHeightSpan nSpan = span.getNeighbor(dir); 
                    if( null == nSpan
                        || nSpan.getNeighbor(dir == 3 ? 0 : dir + 1) == null) 
                    {
                        //如果8个邻居有任何一个缺失的话,那么就是边界Border
                        isBorder = true;
                        break;
                    }
                }

                if( isBorder )
                {
                    //自己就是边界Border
                    span.setDistanceToBorder(BORDER); 
                }
                else
                {
                    //需要再次计算
                    span.setDistanceToBorder(NEEDS_INIT);
                }
            } //while


            /*
             * 逆时针访问?
             *     
             *    (-1,1)  (0,1)  (1,1)
             *    (-1,0)    x    (1,0)
             *    (-1,-1) (0,-1) (1,-1)
             */

            //Pass 1
            //顺序访问 (-1,0) (-1,-1) (0,-1) (1,-1)
            iter.Reset(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current;
                int selfDist = span.distanceToBorder(); 

                if( selfDist == BORDER)
                {
                    continue; 
                }

                //(-1,0)
                OpenHeightSpan nSpan = span.getNeighbor(0);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, true);

                //(-1,-1),左下角的领域
                nSpan = nSpan.getNeighbor(3); //领域0的领域3,也就是原Span的左下角
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, false);

                //(0,-1)
                nSpan = span.getNeighbor(3);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, true);

                //(1,-1) 
                nSpan = nSpan.getNeighbor(2);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, true);
                
                span.setDistanceToBorder(selfDist); 

            } // while


            //Pass 2
            //顺序访问 (1,0) (1,1) (0,1) (-1,1)
            //注意这个是反向遍历
            iter.ReverseReset(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current;
                int selfDist = span.distanceToBorder();
                if( selfDist == BORDER )
                {
                    continue; 
                }

                /* 
                 * 因为经过Pass1之后 ,所有的Span要么就是Border
                 * 要么就是有个大概值的,不会等于NEED_INIT的。
                 * 所以直接按照按照上面的流程跑
                 */

                //(1,0)
                OpenHeightSpan nSpan = span.getNeighbor(2);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, true);

                //(1,1)
                nSpan = nSpan.getNeighbor(1);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, false);

                //(0,1)
                nSpan = span.getNeighbor(1);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, true);

                //(-1,1)
                nSpan = nSpan.getNeighbor(0);
                selfDist = calcMiniDistanceToBorder(selfDist, nSpan, false);

                span.setDistanceToBorder(selfDist); 
            }

            field.clearBorderDistanceBounds(); 
        }
        public void blurDistanceField(OpenHeightfield field)
        {
            if( null == field )
            {
                return; 
            }

            if( mSmoothingThreshold <= 0 )
            {
                return; 
            }

            //Span => Distance
            Dictionary<OpenHeightSpan, int> blurResults = new Dictionary<OpenHeightSpan, int>();

            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current;
                int origDist = span.distanceToBorder(); 
                if( origDist <= mSmoothingThreshold )  //这里也会Border引进去
                {
                    blurResults.Add(span, mSmoothingThreshold);
                    continue; 
                }

                int workingDist = origDist; 
                //将9个格子的距离加起来
                for(int dir = 0; dir < 4; ++dir)
                {
                    OpenHeightSpan nSpan = span.getNeighbor(dir); 
                    if( null == nSpan )
                    {
                        //这是不知道能不能看作自己占的比较增加了?但是为啥是 * 2 呢?
                        workingDist += origDist * 2; 
                    }
                    else
                    {
                        workingDist += nSpan.distanceToBorder();
                        nSpan = nSpan.getNeighbor( (dir+1) & 0x3 );   //对角线的
                        if( null == nSpan )
                        {
                            workingDist += origDist; 
                        }
                        else
                        {
                            workingDist += nSpan.distanceToBorder();  
                        }
                    }
                }  //for 

                if( blurResults.ContainsKey(span) )
                {
                    //除以9是平均呢,但是加上五就真的是不知道为什么了
                    blurResults[span] = (workingDist + 5) / 9; 
                }

            }  //while

            //更新一下距离值 
            foreach( var blurIter in blurResults )
            {
                blurIter.Key.setDistanceToBorder(blurIter.Value);   
            }
        }
        public void generateRegions( OpenHeightfield field )
        {
            if (null == field)
            {
                return;
            }

            //这个距离,控制生成的网络有多贴近实际的模型
            int minDist = mTraversableAreaBorderSize + field.minBorderDistance();
            int expandIterations = 4 + (mTraversableAreaBorderSize * 2); //TODO emmm

            //排除奇数
            int dist = (field.maxBorderDistance() - 1) & ~1 ;

            List<OpenHeightSpan> floodedSpans = new List<OpenHeightSpan>(1024);
            Stack<OpenHeightSpan> workingStack = new Stack<OpenHeightSpan>(1024);

            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator();

            int nextRegionID = 1;
            //高于这个距离的体素都得生成Regions,那剩下的体素怎么办呢?
            while ( dist > minDist )  
            {
                iter.Reset();
                floodedSpans.Clear(); 

                while( iter.MoveNext() )
                {
                    OpenHeightSpan span = iter.Current; 
                    if( span.regionID() == NULL_REGION
                        && span.distanceToBorder() >= dist )
                    {
                        floodedSpans.Add(span); 
                    }
                }

                if( nextRegionID > 1 )
                {
                    //大于1表示已经至少存在1个region,先去尝试一下合并
                    if( dist > 0 )
                    {
                        expandRegions(floodedSpans, expandIterations);     
                    }
                    else  //这里不太可能会走到吧?除非minDist == 0
                    {
                        expandRegions(floodedSpans, -1); 
                    }
                }

                //剩下的可能要生成新的Region
                foreach( OpenHeightSpan span in floodedSpans )
                {
                    if( null == span 
                        || span.regionID() != NULL_REGION )
                    {
                        continue; 
                    }

                    //TODO ????
                    int fillTo = Math.Max(dist - 2, minDist); 
                    if( floodNewRegion(span,fillTo,nextRegionID,workingStack ))
                    {
                        nextRegionID++; 
                    }
                }

                //更新深度
                dist = Math.Max(dist - 2, 0); 
            }  //while dist > minDist

            //最后一篇循环
            iter.Reset();
            floodedSpans.Clear(); 
            while( iter.MoveNext() )
            {
                OpenHeightSpan span = iter.Current; 
                if( span.distanceToBorder() >= minDist
                    && span.regionID() == NULL_REGION )
                {
                    floodedSpans.Add(span); 
                }
            }

            if( minDist > 0 )
            {
                expandRegions(floodedSpans, expandIterations * 8); 
            }
            else
            {
                expandRegions(floodedSpans, -1); 
            }
            field.setRegionCount(nextRegionID);

            //后处理
            foreach( IOpenHeightFieldAlgorithm algorithm in mRegionAlgorithms )
            {
                algorithm.apply(field);  
            }
        }
Example #5
0
        public void apply(OpenHeightfield field)
        {
            if (null == field)
            {
                Logger.LogError("[CLeanNullRegionBorders][apply]field null");
                return;
            }

            int nextRegionID = field.regionCount();

            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator();
            while (iter.MoveNext())
            {
                OpenHeightSpan span = iter.Current;
                if (span.flags != 0)
                {
                    continue;
                }

                span.flags = 1;

                OpenHeightSpan workingSpan   = null;
                int            edgeDirection = -1;

                if (NULL_REGION == span.regionID())
                {
                    //找到Border Span第一个非空的邻居
                    edgeDirection = getNonNullBorderDrection(span);
                    if (-1 == edgeDirection)
                    {
                        continue;
                    }

                    //起点Span为有所属Region的Span
                    //而行走方向为 null Region所在的方向
                    workingSpan = span.getNeighbor(edgeDirection);
                    //转180度
                    edgeDirection = NMGenUtility.AntiDir(edgeDirection);
                }
                else if (!mUseOnlyNullSpans)
                {
                    //起点Span为 null Region的Span
                    //而行走方向即为有所属 Region 的 Span

                    edgeDirection = getNullBorderDrection(span);
                    if (-1 == edgeDirection)
                    {
                        continue;
                    }
                    workingSpan = span;
                }
                else
                {
                    continue;
                }

                //上面两个分支都会保证workingSpan是有所属Region的
                //而Dir 即是 Region 为 Null Region的Dir
                bool isEncompassedNullRegion = processNullRegion(workingSpan, edgeDirection);
                if (isEncompassedNullRegion)   //确定以这个Span为起点的Region,是一个单一Region,并且内含了一个Null Region
                {
                    //如果是完全包含了不可走的NullRegion ,就将这个Region分割成两个Region
                    partialFloodRegion(workingSpan, edgeDirection, nextRegionID);
                    nextRegionID++;
                }
            }

            field.setRegionCount(nextRegionID);

            iter.Reset();
            while (iter.MoveNext())
            {
                iter.Current.flags = 0;
            }
        }
Example #6
0
        public void apply(OpenHeightfield field)
        {
            if (null == field)
            {
                Logger.LogError("[FilterOutSmallRegions][apply]field null");
                return;
            }

            if (field.regionCount() < 2)
            {
                Logger.LogError("[FilterOutSmallRegions][apply]RegionCnt|{0}", field.regionCount());
                return;
            }

            //PS:索引即是对应的ID
            Region[] regions = new Region[field.regionCount()];
            for (int i = 0; i < field.regionCount(); ++i)
            {
                regions[i] = new Region(i);
            }

            #region 收集邻接信息
            OpenHeightfield.OpenHeightFieldIterator iter = field.GetEnumerator();
            while (iter.MoveNext())
            {
                OpenHeightSpan span = iter.Current;
                if (span.regionID() <= NULL_REGION)
                {
                    continue;
                }

                //索引即RegionID
                Region region = regions[span.regionID()];
                region.spanCount++;

                for (OpenHeightSpan nextHigherSpan = span.next();
                     nextHigherSpan != null;
                     nextHigherSpan = nextHigherSpan.next()
                     )
                {
                    int nextHigherSpanRegionID = nextHigherSpan.regionID();
                    if (nextHigherSpanRegionID <= NULL_REGION)
                    {
                        continue;
                    }
                    //因为是同属一个Grid的,所以肯定是重叠的
                    if (!region.overlappingRegions.Contains(nextHigherSpanRegionID))
                    {
                        region.overlappingRegions.Add(nextHigherSpanRegionID);
                    }
                } //for

                if (region.connections.Count > 0)
                {
                    continue;
                }

                int edgeDirection = getRegionEdgeDirection(span);
                if (edgeDirection != -1)
                {
                    findRegionConnections(span, edgeDirection, ref region.connections);
                }
            } // while

            #endregion

            #region 清理孤岛Region
            for (int regionID = 1; regionID < field.regionCount(); ++regionID)
            {
                Region region = regions[regionID];
                if (0 == region.spanCount)
                {
                    continue;
                }

                // 有且仅有一个 Null Region 邻居
                if (region.connections.Count == 1 &&
                    region.connections[0] == NULL_REGION)
                {
                    if (region.spanCount < mMinUnconnectedRegionSize)
                    {
                        region.resetWithID(0);
                    }
                }
            }  //for

            #endregion

            #region 合并小的Region
            int mergeCount;
            do
            {
                mergeCount = 0;

                foreach (Region region in regions)
                {
                    if (region.id <= NULL_REGION ||
                        region.spanCount == 0)
                    {
                        continue;
                    }

                    if (region.spanCount > mMergeRegionSize)
                    {
                        continue;
                    }

                    Region targetMergeRegion = null;
                    int    smallestSizeFound = int.MaxValue;

                    foreach (int nRegionID in region.connections)
                    {
                        if (nRegionID <= 0)
                        {
                            continue;
                        }

                        Region nRegion = regions[nRegionID];
                        if (nRegion.spanCount < smallestSizeFound &&
                            canMerge(region, nRegion))
                        {
                            targetMergeRegion = nRegion;
                            smallestSizeFound = nRegion.spanCount;
                        }
                    } // foreach nRegionID

                    if (targetMergeRegion != null &&
                        mergeRegions(targetMergeRegion, region))     //为啥是反过来Merge。。。
                    {
                        int oldRegionID = region.id;
                        region.resetWithID(targetMergeRegion.id);

                        foreach (Region r in regions)
                        {
                            if (r.id <= NULL_REGION)
                            {
                                continue;
                            }
                            if (r.id == oldRegionID)
                            {
                                r.id = targetMergeRegion.id;
                            }
                            else
                            {
                                replaceNeighborRegionID(r, oldRegionID, targetMergeRegion.id);
                            }
                        } // foreach regions

                        mergeCount++;
                    } // if mergerRegion
                }     // foreach region
            } while (mergeCount > 0);

            #endregion

            #region  re-map 区域ID,保持ID连接
            foreach (Region region in regions)
            {
                if (region.id >= NULL_REGION)
                {
                    region.remap = true;
                }
            }

            int currRegionID = NULL_REGION;
            foreach (Region region in regions)
            {
                if (!region.remap)
                {
                    continue;
                }
                currRegionID++;
                int oldID = region.id;

                foreach (Region r in regions)
                {
                    if (r.id == oldID)
                    {
                        r.id    = currRegionID;
                        r.remap = false;
                    }
                } //foreach
            }     //foreach

            field.setRegionCount(currRegionID + 1);

            iter.Reset();
            while (iter.MoveNext())
            {
                OpenHeightSpan span = iter.Current;
                if (NULL_REGION == span.regionID())
                {
                    continue;
                }
                else
                {
                    //真正re-map一下
                    span.setRegionID(regions[span.regionID()].id);
                }
            }

            #endregion
        }  //apply
Example #7
0
        public ContourSet build(OpenHeightfield sourceField)
        {
            if (null == sourceField ||
                0 == sourceField.regionCount())
            {
                Logger.LogError("[ContourSetBuilder][build]sourceField Invalid");
                return(null);
            }

            ContourSet result = new ContourSet(sourceField.boundsMin(),
                                               sourceField.boundsMax(),
                                               sourceField.cellSize(),
                                               sourceField.cellHeight(),
                                               sourceField.regionCount());

            int discardedContours = 0;

            /*
             *  If a span has no connections to external regions or is
             *  completely surrounded by other regions (a single span island),
             *  its flag will be zero.
             *
             *  If a span is connected to one or more external regions then the
             *  flag will be a 4 bit value where connections are recorded as
             *  follows:
             *      bit1 = neighbor0
             *      bit2 = neighbor1
             *      bit3 = neighbor2
             *      bit4 = neighbor3
             *  With the meaning of the bits as follows:
             *      0 = neighbor in same region.
             *      1 = neighbor not in same region. (Neighbor may be the null
             *      region or a real region.)
             */
            OpenHeightfield.OpenHeightFieldIterator iter = sourceField.GetEnumerator();
            while (iter.MoveNext())
            {
                OpenHeightSpan span = iter.Current;
                span.flags = 0;   //默认没有与任何外部Region相连
                if (NULL_REGION == span.regionID())
                {
                    continue;
                }

                for (int dir = 0; dir < 4; ++dir)
                {
                    int            nRegionID = NULL_REGION;
                    OpenHeightSpan nSpan     = span.getNeighbor(dir);
                    if (nSpan != null)
                    {
                        nRegionID = nSpan.regionID();
                    }
                    //这里是反常操作,先将相同的当作是1,然后统一位反
                    if (nRegionID == span.regionID())
                    {
                        span.flags |= 1 << dir;
                    }
                } // for

                //1111111
                span.flags ^= 0xf;
                if (span.flags == 0xf)   //证明四个邻居都不在同一个Region或者是一个孤岛Span
                {
                    //重置这个位置
                    span.flags = 0;
                    discardedContours++;
                    Logger.LogWarning("[ContourSetBuilder][apply]Island Span|{0}|{1}", span.regionID(), discardedContours);
                }
            }  //while iter

            List <int> workingRawVerts        = new List <int>(256);
            List <int> workingSimplifiedVerts = new List <int>(64);

            iter.Reset();
            while (iter.MoveNext())
            {
                OpenHeightSpan span = iter.Current;
                if (NULL_REGION == span.regionID() ||
                    0 == span.flags)      //flag等于0的话,是前面那些孤岛Span
                {
                    continue;
                }

                workingRawVerts.Clear();
                workingSimplifiedVerts.Clear();

                //找到第一个不是同一个Region的Span
                int startDir = 0;
                while (isSameRegion(span, startDir))
                {
                    startDir++;
                }

                buildRawContours(span,
                                 iter.widthIndex(),
                                 iter.depthIndex(),
                                 startDir,
                                 ref workingRawVerts
                                 );

                generateSimplifiedContour(span.regionID(),
                                          workingRawVerts,
                                          ref workingSimplifiedVerts);

                //TODO 为什么小于12个顶点就不行呢?
                if (workingSimplifiedVerts.Count < 12)
                {
                    Logger.LogWarning("[ContourSetBuilder][build]Discarded Contour|{0}|{1}|", span.regionID(), discardedContours);
                    discardedContours++;
                }
                else
                {
                    result.add(new Contour(span.regionID(),
                                           workingRawVerts,
                                           workingSimplifiedVerts));
                }
            } // while iter

            if (discardedContours > 0)
            {
                Logger.LogWarning("[ContourSetBuilder][build]Contours not generated|{0}|", discardedContours);
            }

            if (result.size() + discardedContours
                != sourceField.regionCount() - 1)
            {
                for (int regionID = 1; regionID < sourceField.regionCount(); ++regionID)
                {
                    int regionMatches = 0;
                    for (int iContour = 0; iContour < result.size(); ++iContour)
                    {
                        if (regionID == result.get(iContour).regionID)
                        {
                            regionMatches++;
                        }
                    }

                    if (regionMatches > 1)
                    {
                        Logger.LogError("[ContourSetBuilder][build]More than |{0}|{1}", regionID, regionMatches);
                    }
                } // for

                for (int iContour = 0; iContour < result.size(); ++iContour)
                {
                    Contour contour = result.get(iContour);
                    if (contour.regionID <= 0)
                    {
                        Logger.LogError("[ContourSetBuilder][build]null region contour");
                    }
                    else if (contour.regionID >= sourceField.regionCount())
                    {
                        Logger.LogError("[ContourSetBuilder][build]contour out of range|{0}", contour.regionID);
                    }
                } // for

                Logger.LogError("[ContourSetBuilder][build]Error{0}|{1}|{2}|{3}",
                                sourceField.regionCount() - 1,
                                result.size() + discardedContours,
                                result.size(),
                                discardedContours
                                );

                return(null);
            }


            return(result);
        } // build