} //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(); } } } }
/// <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); }
/* 参考 :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); } }
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> /// 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> /// 就是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); } }
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