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); } }
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 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 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