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