} //buildRawContour /* * 以Span为s , direction 为 0 举例,这个函数要寻找的就是s,ds,ns,里面,floor最大值 * ds * ns s * * 如果direction为1的话 * ns ds * s * */ private static int getCornerHeight(OpenHeightSpan span, int direction) { //OpenHeightSpan的floor 就真的是floor了,而不是 //像 SolidHeightSpan那样对应的其实是顶部 int maxFloor = span.floor(); OpenHeightSpan dSpan = null; //顺时针 int directionOffset = NMGenUtility.ClockwiseRotateDir(direction); OpenHeightSpan nSpan = span.getNeighbor(direction); if (nSpan != null) { maxFloor = Math.Max(maxFloor, nSpan.floor()); dSpan = nSpan.getNeighbor(directionOffset); } nSpan = span.getNeighbor(directionOffset); if (nSpan != null) { maxFloor = Math.Max(maxFloor, nSpan.floor()); if (null == dSpan) { dSpan = nSpan.getNeighbor(direction); } } if (dSpan != null) { maxFloor = Math.Max(maxFloor, dSpan.floor()); } return(maxFloor); }
public void getDetailedRegionMap(ref int[] outData, int insertIndex) { for (int i = 0; i < 8; ++i) { outData[insertIndex + i] = NULL_REGION; } OpenHeightSpan nSpan = null; OpenHeightSpan nnSpan = null; for (int dir = 0; dir < 4; dir++) { nSpan = getNeighbor(dir); if (nSpan != null) { outData[insertIndex + dir] = nSpan.regionID(); nnSpan = nSpan.getNeighbor((dir + 1) & 0x3); if (nnSpan != null) { outData[insertIndex + dir + 4] = nnSpan.regionID(); } nnSpan = nSpan.getNeighbor((dir + 3) & 0x3); if (nnSpan != null) { outData[insertIndex + ((dir + 3) & 0x3) + 4] = nnSpan.regionID(); } } } }
public void printDistanceField() { Logger.Log("[OpenHeightfield][printDistanceField]Log Start"); Logger.Log("[OpenHeightfield][printDistanceField]Distance Field Spans|{0}", mSpanCount); int depth = -1; Logger.Log("\t"); for (int width = 0; width < this.width(); ++width) { Logger.Log("{0}\t", width); } OpenHeightFieldIterator iter = GetEnumerator(); while (iter.MoveNext()) { OpenHeightSpan span = iter.Current; if (iter.depthIndex() != depth) { Logger.LogWarning("\n{0}\t", ++depth); } Logger.Log("{0}\t", span.distanceToBorder()); } Logger.Log("[OpenHeightfield][printDistanceField]Log End"); }
} // MoveNext public void Reset() { mNextWidth = 0; mNextDepth = 0; mNext = null; mIsReverseIter = false; }
public bool addData(int widthIndex, int depthIndex, OpenHeightSpan span) { if (widthIndex < 0 || widthIndex >= width() || depthIndex < 0 || depthIndex >= depth()) { Logger.LogWarning("[OpenHeightfield][addData]width|depth|{0}|{1}|{2}|{3}", widthIndex, depthIndex, width(), depth()); return(false); } if (mSpans == null || span == null) { Logger.LogWarning("[OpenHeightfield][addData]mSpan or Span null|{0}|{1}|{2}|{3}", widthIndex, depthIndex, width(), depth()); return(false); } int gridIndex = GetGridIndex(widthIndex, depthIndex); OpenHeightSpan currentSpan; if (!mSpans.TryGetValue(gridIndex, out currentSpan)) { mSpans.Add(gridIndex, span); return(true); } else { Logger.LogWarning("[OpenHeightfield][addData]Span already in|{0}|{1}|{2}|{3}", widthIndex, depthIndex, width(), depth()); return(false); } }
private static bool isSameRegion(OpenHeightSpan span, int dir) { if (span == null) { return(false); } return((span.flags & (1 << dir)) == 0); }
public OpenHeightSpan getData(int widthIndex, int depthIndex) { OpenHeightSpan retSpan = null; if (mSpans != null) { mSpans.TryGetValue(GetGridIndex(widthIndex, depthIndex), out retSpan); } return(retSpan); }
/// <summary> /// </summary> /// <param name="startSpan"></param> /// <param name="borderDirection"></param> /// <param name="newRegionID"></param> private void partialFloodRegion(OpenHeightSpan startSpan, int borderDirection, int newRegionID) { int antiBorderDirection = NMGenUtility.AntiDir(borderDirection); int regionID = startSpan.regionID(); startSpan.setRegionID(newRegionID); startSpan.setDistanceToRegionCore(0); //???所以这个值 没啥卵用啊,一直都是0的 mwOpenSpans.Push(startSpan); mwBorderDistance.Push(0); while (mwOpenSpans.Count > 0) { OpenHeightSpan span = mwOpenSpans.Pop(); int distance = mwBorderDistance.Pop(); for (int i = 0; i < 4; ++i) { OpenHeightSpan nSpan = span.getNeighbor(i); if (null == nSpan || nSpan.regionID() != regionID) { continue; } int nDistance = distance; if (i == borderDirection) //null region所在的方向 { //这里是不是应该小于等于0呢? // 以这个距离Border为0作为边界,将大于0那一侧的Span全部变成 // 新的Region。然后小于等于0那一侧的作为旧的Region保留下来。 if (0 == distance) { continue; } nDistance--; } else if (i == antiBorderDirection) { nDistance++; } //注意上面的if-else,如果都不是这两个方向的Span //会直接被重新设置为新的Region nSpan.setRegionID(newRegionID); nSpan.setDistanceToRegionCore(0); mwOpenSpans.Push(nSpan); mwBorderDistance.Push(nDistance); } } }
public void ReverseReset() { if (mOpenHeightfield != null) { mNextWidth = mOpenHeightfield.width(); mNextDepth = mOpenHeightfield.depth(); } mNext = null; mIsReverseIter = true; }
public void setNeighbor(int direction, OpenHeightSpan neighbor) { switch (direction) { case 0: mNeighborConnection0 = neighbor; break; case 1: mNeighborConnection0 = neighbor; break; case 2: mNeighborConnection0 = neighbor; break; case 3: mNeighborConnection0 = neighbor; break; } }
private static int getNonNullBorderDrection(OpenHeightSpan span) { for (int dir = 0; dir < 4; ++dir) { OpenHeightSpan nSpan = span.getNeighbor(dir); if (nSpan != null && nSpan.regionID() != NULL_REGION) { return(dir); } } return(-1); }
private static int getRegionEdgeDirection(OpenHeightSpan span) { for (int dir = 0; dir < 4; ++dir) { OpenHeightSpan nSpan = span.getNeighbor(dir); if (null == nSpan || nSpan.regionID() != span.regionID()) { return(dir); } } return(-1); }
/// <summary> /// 必须nSpan是存在的,如果不存在的话,span2Border的值自然就0了 /// </summary> /// <param name="span2Border"></param> /// <param name="nSpan"></param> /// <param name="isAxisNeighbor"></param> /// <returns></returns> private int calcMiniDistanceToBorder( int span2Border, OpenHeightSpan nSpan , bool isAxisNeighbor) { if( nSpan != null ) { int nDist = nSpan.distanceToBorder(); if( nDist == NEEDS_INIT ) { nDist = isAxisNeighbor ? 1 : 2; } else { nDist = isAxisNeighbor ? nDist + 2 : nDist + 3; } span2Border = Math.Min(span2Border, nDist); } else { Logger.LogWarning("[OpenHeightfieldBuilder][calcMiniDistanceToBorder]nSpan null"); } return span2Border; }
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; } } } } }
private void calcBorderDistanceBounds() { if (0 == mSpanCount) { return; } mMinBorderDistance = int.MaxValue; mMaxBorderDistance = UNKNOWN; OpenHeightFieldIterator iter = GetEnumerator(); while (iter.MoveNext()) { OpenHeightSpan span = iter.Current; mMinBorderDistance = Math.Min(mMinBorderDistance, span.distanceToBorder()); mMaxBorderDistance = Math.Max(mMaxBorderDistance, span.distanceToBorder()); } if (mMinBorderDistance == int.MaxValue) { mMinBorderDistance = UNKNOWN; } }
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; } }
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); } }
/// <summary> /// 重新设置对应的Region,根据拐点的情况 /// </summary> /// <param name="referenceSpan"></param> /// <param name="borderDirection"></param> /// <param name="cornerDirection"></param> /// <returns></returns> private int selectedRegionID(OpenHeightSpan referenceSpan, int borderDirection, int cornerDirection) { referenceSpan.getDetailedRegionMap(ref mwNeighborRegions, 0); /* * Initial example state: * * a - Known region. * x - Null region. * u - Unknown, not checked yet. * * u u u * u a x * u a a */ int antiBorderDirection = NMGenUtility.AntiDir(borderDirection); int antiCornerDirection = NMGenUtility.AntiDir(cornerDirection); int regionID = mwNeighborRegions[antiBorderDirection]; if (regionID == referenceSpan.regionID() || NULL_REGION == regionID) { /* * The region away from the border is either a null region * or the same region. So we keep the current region. * * u u u u u u * a a x or x a x <-- Potentially bad, but stuck with it. * u a a u a a */ return(referenceSpan.regionID()); } int potentialRegion = regionID; regionID = mwNeighborRegions[antiCornerDirection]; if (regionID == referenceSpan.regionID() || NULL_REGION == regionID) { /* * The region opposite from the corner direction is * either a null region or the same region. So we * keep the current region. * * u a u u x u * b a x or b a x * u a a u a a */ return(referenceSpan.regionID()); } int potentialCount = 0; int currentCount = 0; for (int i = 0; i < 8; ++i) { if (mwNeighborRegions[i] == referenceSpan.regionID()) { currentCount++; } else if (mwNeighborRegions[i] == potentialRegion) { potentialCount++; } } return(potentialCount < currentCount ? referenceSpan.regionID() : potentialRegion); }
/// <summary> /// 就是Reference所属的Region分成两部分来看。分别是backTwo和Reference。 /// 然后再分别和backOne所属的Region来检测连接。 /// </summary> /// <param name="referenceSpan"></param> /// <param name="borderDirection"></param> /// <returns></returns> private bool processOuterCorner(OpenHeightSpan referenceSpan, int borderDirection) { bool hasMultiRegions = false; //比如 borderDirection 是 方向1的话 ,那么 backOne 就是 0 /* * r 是 referenceSpan , x 是 border span,1、2就是对应的backOne和backTwo * 2 x * 1 r */ //backOne 和 backTwo 沿着borderDir方向的两个Span int backOneDirection = (borderDirection + 3) & 0x3; int antiBorderDirection = (borderDirection + 2) & 0x3; OpenHeightSpan backOne = referenceSpan.getNeighbor(backOneDirection); OpenHeightSpan backTwo = backOne.getNeighbor(borderDirection); OpenHeightSpan testSpan; if (backOne.regionID() != referenceSpan.regionID() && backTwo.regionID() == referenceSpan.regionID()) { /* * Example: * * a a x x x a * a a x x a a * b b a a a a * b b a a a a * */ /* * 有问题的布局 * a x * b a * * 需要转换成下面两种 * b x a x * b a b b * */ hasMultiRegions = true; /* * 2 x 2 x * 1 r => t 1 r * */ testSpan = backOne.getNeighbor(backOneDirection); //检查 backTwo 和 backOne 有多少个连接 int backTwoConnections = 0; if (testSpan != null && testSpan.regionID() == backOne.regionID()) { // b 是 backOne , a 是 backTwo /* * a x * t b a * * || * * a x * b b a * */ backTwoConnections++; testSpan = testSpan.getNeighbor(borderDirection); if (testSpan != null && testSpan.regionID() == backOne.regionID()) { /* * * t a x * b b a * * || * * b a x * b b a * */ backTwoConnections++; } } // backTwo - backOne //检查 reference span 和 backOne 有多少连接 int referenceConnections = 0; testSpan = backOne.getNeighbor(antiBorderDirection); if (testSpan != null && testSpan.regionID() == backOne.regionID()) { /* * a x * b a * t * * || * * a x * b a * b */ referenceConnections++; //TODO 这个方向我修改过,应该是转向才对,感觉原作者是笔误 testSpan = testSpan.getNeighbor((borderDirection + 1) & 0x3); if (testSpan != null && testSpan.regionID() == backOne.regionID()) { /* * a x * b a * b t * * * || * * a x * b a * b b * */ if (testSpan != null && testSpan.regionID() == backOne.regionID()) { referenceConnections++; } } } // Reference-backOne //变成这个拐点 if (referenceConnections > backTwoConnections) { referenceSpan.setRegionID(backOne.regionID()); } else { backTwo.setRegionID(backOne.regionID()); } } else if (backOne.regionID() == referenceSpan.regionID() && backTwo.regionID() == referenceSpan.regionID()) { //源码本来的注释 /* * Potential dangerous short wrap. * * a x * a a * * Example of actual problem configuration: * * b b x x * b a x x <- Short wrap. * b a a a * * In the above case, the short wrap around the corner of the * null region has been demonstrated to cause self-intersecting * polygons during polygon formation. * * This algorithm detects whether or not one (and only one) * of the axis neighbors of the corner should be re-assigned to * a more appropriate region. * * In the above example, the following configuration is more * appropriate: * * b b x x * b b x x <- Change to this row. * b a a a */ /* * 2 x * 1 r * * || * * a x * a a * * */ //以1为borderDirection的话,那么相对于backTwo来说 //borderDirection 为 2 ,所以+1 ,CornerDirection 为3,所以+2, int selectedRegion = selectedRegionID(backTwo, (borderDirection + 1) & 0x3, (borderDirection + 2) & 0x3 ); if (backTwo.regionID() == selectedRegion) { //backTwo不用改变,尝试改变一下reference selectedRegion = selectedRegionID(referenceSpan, borderDirection, (borderDirection + 3) & 0x3); // if (selectedRegion != referenceSpan.regionID()) { referenceSpan.setRegionID(selectedRegion); hasMultiRegions = true; } } else { backTwo.setRegionID(selectedRegion); hasMultiRegions = true; } } else { hasMultiRegions = true; } return(hasMultiRegions); }
/* 参考 :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 setNext(OpenHeightSpan value) { mNext = value; }
public OpenHeightfield build(SolidHeightfield sourceField , bool performFullGeneration ) { if( sourceField == null ) { Logger.LogError("[OpenHeightfieldBuilder][build] sourceField null "); return null; } OpenHeightfield result = new OpenHeightfield( sourceField.boundsMin(), sourceField.boundsMax(), sourceField.cellSize(), sourceField.cellHeight() ); for(int depthIndex = 0; depthIndex < sourceField.depth(); depthIndex++) { for(int widthIndex = 0; widthIndex < sourceField.width(); widthIndex++) { OpenHeightSpan baseSpan = null; OpenHeightSpan previousSpan = null; for (HeightSpan span = sourceField.getData(widthIndex, depthIndex); span != null; span = span.next() ) { if ( span.flags() != mFilterFlags ) { continue; } //当前Solid Span的max对应的是对应OpenSpan的floor int floor = span.max(); //下一个Next Solid Span的min对应当前OpenSpan的Ceil。 int ceiling = (span.next() != null ? span.next().min() : int.MaxValue) ; //对应的Open Span OpenHeightSpan oSpan = new OpenHeightSpan(floor, (ceiling - floor ) ); if( baseSpan == null ) { baseSpan = oSpan; } if( previousSpan != null ) { previousSpan.setNext(oSpan); } previousSpan = oSpan; result.incrementSpanCount(); } //for if( baseSpan != null ) { result.addData(widthIndex, depthIndex, baseSpan); } }//for } //for if( performFullGeneration ) { generateNeighborLinks(result); generateDistanceField(result); blurDistanceField(result); generateRegions(result); } return result; }
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); } }
private static bool floodNewRegion( OpenHeightSpan rootSpan, int fillToDist , int regionID, Stack<OpenHeightSpan> workingStack ) { workingStack.Clear(); workingStack.Push(rootSpan); rootSpan.setRegionID(regionID); rootSpan.setDistanceToRegionCore(0); int regionSize = 0; //广度优先搜索 while( workingStack.Count > 0 ) { OpenHeightSpan span = workingStack.Pop(); //表示当前Region是否已经在所属Region附近 bool isOnRegionBorder = false; for( int dir = 0; dir < 4; ++dir ) { OpenHeightSpan nSpan = span.getNeighbor(dir); if( null == nSpan ) { continue; } if( nSpan.regionID() != NULL_REGION && nSpan.regionID() != regionID ) { isOnRegionBorder = true; break; } //对角线邻居 nSpan = nSpan.getNeighbor((dir + 1) & 0x3); if( nSpan != null && nSpan.regionID() != NULL_REGION && nSpan.regionID() != regionID ) { isOnRegionBorder = true; break; } } //for //那么,它就不能成为新的Region if( isOnRegionBorder ) { span.setRegionID(NULL_REGION); continue; } //到这里,表明要新增加一个Region了 regionSize++; for( int dir = 0; dir < 4;++dir ) { OpenHeightSpan nSpan = span.getNeighbor(dir); if( nSpan != null && nSpan.distanceToBorder() >= fillToDist // && nSpan.regionID() == NULL_REGION ) { nSpan.setRegionID(regionID); nSpan.setDistanceToRegionCore(0); //如果是同一区域的话 ?离中心是0的? workingStack.Push(nSpan); } } } // while return regionSize > 0; }
/// <summary> /// 为了防止 Null Region 内含在有效Region内,这阻碍了凸包的生成 /// </summary> /// <param name="startSpan"></param> /// <param name="startDirection"></param> /// <returns></returns> private bool processNullRegion(OpenHeightSpan startSpan, int startDirection) { /* * 这段直接翻译源码的: * 这个算法遍历这个轮廓。正如它所做的,这个算法检测并且修复一些危险的 * Span Configurations。 * * 遍历轮廓:一个很好的可视化方向就是,想像一个机器人面向一堵墙,并坐 * 在地板上。它会采取以下措施来绕过这堵墙: * 1. 如果有一堵墙位于它的前面,顺时针转90度,直到他前面不再是墙。 * 2. 向前一步。 * 3. 逆时针转向90度 * 4. 重复从1开始的步骤 ,直到它发现自己位于原来的起点,还有原来的朝向。 * */ /* * 算法在遍历的同时,检测锐角(90度) 和 钝角(270)拐点。如果 * 一个完整的轮廓被检测完整,并且 钝角拐点比锐角拐点多。那 * 么 null Region 就在这个轮廓里面。否则就在轮廓外面。 * */ //环绕null region 走一圈,最后走回自己的起点 int borderRegionID = startSpan.regionID(); OpenHeightSpan span = startSpan; OpenHeightSpan nSpan = null; int dir = startDirection; int loopCount = 0; int acuteCornerCount = 0; int obtuseCornerCount = 0; int stepsWithoutBorder = 0; bool borderSeenLastLoop = false; bool isBorder = true; bool hasSingleConnection = true; while (++loopCount < int.MaxValue) { //初始方向就是面向的Null Region,所以一开始是肯定是isBorder的 nSpan = span.getNeighbor(dir); if (null == nSpan) { isBorder = true; } else { nSpan.flags = 1; if (NULL_REGION == nSpan.regionID()) { isBorder = true; } else { isBorder = false; if (nSpan.regionID() != borderRegionID) { hasSingleConnection = false; } } } // else if (isBorder) { if (borderSeenLastLoop) { /* * a x * x x */ //其实这个应用用inner来描述更加准确 ,表明a被x包围了 acuteCornerCount++; } else if (stepsWithoutBorder > 1) { /* * a a * a x * */ obtuseCornerCount++; //相对地,我觉得这个应该用outer来描述更加准确,表明a正在包围x stepsWithoutBorder = 0; //处理钝角的各种异常情况 if (processOuterCorner(span, dir)) { hasSingleConnection = false; } } dir = NMGenUtility.ClockwiseRotateDir(dir); borderSeenLastLoop = true; stepsWithoutBorder = 0; } else //注意,不是边界,才会进行移动,如果是边界的话,只会进行转向 { span = nSpan; dir = NMGenUtility.CClockwiseRotateDir(dir); //逆时针转向一下 borderSeenLastLoop = false; stepsWithoutBorder++; } // else //回到原方位了 if (startSpan == span && startDirection == dir) { return(hasSingleConnection && obtuseCornerCount > acuteCornerCount); } } // while return(false); }
//遍历轮廓,找邻接的Region private static void findRegionConnections(OpenHeightSpan startSpan, int startDirection, ref List <int> outConnections) { OpenHeightSpan span = startSpan; int dir = startDirection; int lastEdgeRegionID = NULL_REGION; OpenHeightSpan nSpan = span.getNeighbor(dir); if (nSpan != null) { lastEdgeRegionID = nSpan.regionID(); } //连接的Region,至少存在一个NULL_REGION outConnections.Add(lastEdgeRegionID); int loopCount = 0; while (++loopCount < ushort.MaxValue) { nSpan = span.getNeighbor(dir); int currEdgeRegionID = NULL_REGION; //默认nSpan是null的 if (null == nSpan || nSpan.regionID() != span.regionID()) { if (nSpan != null) { currEdgeRegionID = nSpan.regionID(); } if (currEdgeRegionID != lastEdgeRegionID) { outConnections.Add(currEdgeRegionID); lastEdgeRegionID = currEdgeRegionID; } //顺时针转向下一个 dir = NMGenUtility.ClockwiseRotateDir(dir); } else { //这个分支代表 Region是相同的 span = nSpan; //逆时针转一下 dir = NMGenUtility.CClockwiseRotateDir(dir); } if (startSpan == span && startDirection == dir) { break; } } //while //TODO 为啥会存在首尾相同呢?因为退出条件是原来的那个点么 int connectionsCnt = outConnections.Count; if (connectionsCnt > 1 && outConnections[0] == outConnections[connectionsCnt - 1]) { outConnections.RemoveAt(connectionsCnt - 1); } }
private void buildRawContours(OpenHeightSpan startSpan, int startWidthIndex, int startDepthIndex, int startDirection, ref List <int> outContourVerts ) { OpenHeightSpan span = startSpan; int dir = startDirection; int spanX = startWidthIndex; int spanZ = startDepthIndex; int loopCount = 0; while (++loopCount < ushort.MaxValue) { //这个Edge的一个Span if (!isSameRegion(span, dir)) { //span int px = spanX; int py = getCornerHeight(span, dir); int pz = spanZ; /* * 这里取的是点,所以需要做这些偏移来记录点,而不是记录对应的Span * 这里需要结合 : 方向 + 某个点 ,来理解 为什么要加这个偏移 * * * * --- * * | S | * ----—-* * * dir = 0 的时候,需要取左上角的点 * dir = 1 的时候,需要取右上角的点 * dir = 2 的时候,需要取右下角的点 * dir = 3 的时候,取的就是参考点 * * 以左下角的点为参考点,就是 dir方向对应的顺时针的点 * */ /* * Update the px and pz values based on current direction. * The update is such that the corner being represented is * clockwise from the edge the direction is currently pointing * toward. */ switch (dir) { case 0: pz++; break; case 1: px++; pz++; break; case 2: px++; break; } int regionThisDirection = NULL_REGION; OpenHeightSpan nSpan = span.getNeighbor(dir); if (nSpan != null) { regionThisDirection = nSpan.regionID(); } //这是轮廓的点 outContourVerts.Add(px); outContourVerts.Add(py); outContourVerts.Add(pz); outContourVerts.Add(regionThisDirection); span.flags &= ~(1 << dir); //清除dir对应的位 dir = NMGenUtility.ClockwiseRotateDir(dir); } //isSameRegion else { //这段就是步进到下一个同Region的Span,很好理解。 span = span.getNeighbor(dir); switch (dir) { case 0: spanX--; break; case 1: spanZ++; break; case 2: spanX++; break; case 3: spanZ--; break; } dir = NMGenUtility.CClockwiseRotateDir(dir); } // no the SameRegion if (span == startSpan && dir == startDirection) { break; } } //while } //buildRawContour
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
public bool MoveNext() { if (mOpenHeightfield == null) { Logger.LogError("[OpenHeightfield][OpenHeightFieldIterator][MoveNext]Null"); return(false); } //公有逻辑 if (mNext != null) { if (mNext.next() != null) { mNext = mNext.next(); return(true); } else { if (mIsReverseIter) { mNextWidth--; } else { mNextWidth++; } } } if (mIsReverseIter) { #region 反向遍历 for (int depthIndex = mNextDepth; depthIndex >= 0; --depthIndex) { for (int widthIndex = mNextWidth; widthIndex >= 0; --widthIndex) { OpenHeightSpan span = mOpenHeightfield.getData(widthIndex, depthIndex); if (span != null) { mNext = span; mNextWidth = widthIndex; mNextDepth = depthIndex; return(true); } } mNextWidth = 0; } mNext = null; mNextDepth = -1; mNextWidth = -1; return(false); #endregion } else { #region 正向遍历 for (int depthIndex = mNextDepth; depthIndex < mOpenHeightfield.depth(); ++depthIndex) { for (int widthIndex = mNextWidth; widthIndex < mOpenHeightfield.width(); widthIndex++) { OpenHeightSpan span = mOpenHeightfield.getData(widthIndex, depthIndex); if (span != null) { mNext = span; mNextWidth = widthIndex; mNextDepth = depthIndex; return(true); } } mNextWidth = 0; } mNext = null; mNextDepth = -1; mNextWidth = -1; return(false); #endregion } // if-else } // MoveNext
/// <summary> /// TODO 解决那两个地方为啥是加2 /// </summary> /// <param name="inoutSpans"></param> /// <param name="maxIterations"></param> private void expandRegions(List<OpenHeightSpan> inoutSpans ,int maxIterations) { if( inoutSpans.Count == 0 ) { return; } int iterCount = 0; while(true) { int skipped = 0; for(int iSpan = 0; iSpan < inoutSpans.Count; ++iSpan) { OpenHeightSpan span = inoutSpans[iSpan]; if( null == span ) { skipped++; continue; } int spanRegion = NULL_REGION; int regionCenterDist = int.MaxValue; //看看邻居的RegionID情况 for(int dir = 0; dir < 4; ++dir) { OpenHeightSpan nSpan = span.getNeighbor(dir); if( null == nSpan ) { continue; } if( nSpan.regionID() > NULL_REGION ) { //TODO 为啥是+2 //初始的话,这个值都是0的。 if( nSpan.distanceToRegionCore() + 2 < regionCenterDist ) { int sameRegionCount = 0; if( mUseConservativeExpansion ) { for( int ndir = 0; ndir < 4; ++ndir ) { OpenHeightSpan nnSpan = nSpan.getNeighbor(ndir); if( null == nnSpan ) { continue; } if( nnSpan.regionID() == nSpan.regionID() ) { sameRegionCount++; } } } //如果轴对齐邻居的邻居的Region相同,也就是多于1个体素就可以了 if( !mUseConservativeExpansion || sameRegionCount > 1) { //TODO 为啥要加2 spanRegion = nSpan.regionID(); regionCenterDist = nSpan.distanceToRegionCore() + 2; } } } } // for if( spanRegion != NULL_REGION ) { inoutSpans[iSpan] = null; span.setRegionID(spanRegion); } else { skipped++; } } // for if( skipped == inoutSpans.Count ) { break; } if( maxIterations != -1) { iterCount++; if( iterCount > maxIterations ) { break; } } } }