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