public SolidHeightfieldIterator(SolidHeightfield field) { if (field == null) { Logger.LogError("[SolidHeightFieldIterator][ctor]filed Empty"); return; } mSoldHeightfield = field; Reset(); }
public void reset() { voxelizationTime = UNDEFINED; regionGenTime = UNDEFINED; contourGenTime = UNDEFINED; polyGenTime = UNDEFINED; finalMeshGenTime = UNDEFINED; mSolidHeightfield = null; mOpenHeightfield = null; mContours = null; mPolyMesh = null; }
/// <summary> /// 确保垂直方向上两个Span之间的区域不会卡头 /// </summary> /// <param name="field"></param> private void markLowHeightSpans(SolidHeightfield field) { SolidHeightfield.SolidHeightfieldIterator iter = field.GetEnumerator(); while (iter.MoveNext()) { HeightSpan span = iter.Current; if ((span.flags() & SpanFlags.WALKABLE) == 0) { continue; } int spanFloor = span.max(); //SolidSpan的max,其实就是OpenSpan的底部 int spanCeiling = (span.next() != null) ? span.next().min() : int.MaxValue; if (spanCeiling - spanFloor <= mMinTraversableHeight) { span.setFlags(span.flags() & ~SpanFlags.WALKABLE); } } }
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 setSolidHeightfield(SolidHeightfield field) { mSolidHeightfield = field; }
public TriangleMesh build(float[] vertices, int[] indices, IntermediateData outIntermediateData) { if (outIntermediateData != null) { outIntermediateData.reset(); } long timerStart = 0; if (outIntermediateData != null) { timerStart = System.DateTime.Now.Ticks; } SolidHeightfield solidField = mSolidHeightFieldBuilder.build(vertices, indices); if (solidField == null || !solidField.hasSpans()) { return(null); } if (outIntermediateData != null) { outIntermediateData.voxelizationTime = System.DateTime.Now.Ticks - timerStart; } if (outIntermediateData != null) { outIntermediateData.setSolidHeightfield(solidField); } if (outIntermediateData != null) { timerStart = System.DateTime.Now.Ticks; } OpenHeightfield openField = mOpenHeightFieldBuilder.build(solidField, false); if (null == openField) { return(null); } if (outIntermediateData != null) { outIntermediateData.setOpenHeightfield(openField); } mOpenHeightFieldBuilder.generateNeighborLinks(openField); mOpenHeightFieldBuilder.generateDistanceField(openField); mOpenHeightFieldBuilder.blurDistanceField(openField); mOpenHeightFieldBuilder.generateRegions(openField); if (outIntermediateData != null) { outIntermediateData.regionGenTime = System.DateTime.Now.Ticks - timerStart; } if (outIntermediateData != null) { timerStart = System.DateTime.Now.Ticks; } ContourSet contours = mContourSetBuilder.build(openField); if (null == contours) { return(null); } if (outIntermediateData != null) { outIntermediateData.contourGenTime = System.DateTime.Now.Ticks - timerStart; } if (outIntermediateData != null) { outIntermediateData.setContours(contours); } if (outIntermediateData != null) { timerStart = System.DateTime.Now.Ticks; } PolyMeshField polMesh = mPolyMeshBuilder.build(contours); if (null == polMesh) { return(null); } if (null != outIntermediateData) { outIntermediateData.polyGenTime = System.DateTime.Now.Ticks - timerStart; } if (outIntermediateData != null) { outIntermediateData.setPolyMesh(polMesh); } if (outIntermediateData != null) { timerStart = System.DateTime.Now.Ticks; } TriangleMesh mesh = mTriangleMeshBuilder.build(polMesh, openField); if (outIntermediateData != null && mesh != null) { outIntermediateData.finalMeshGenTime = System.DateTime.Now.Ticks - timerStart; } return(mesh); }
//vertices是以(x,y,z)(x,y,z)三组三组为一个顶点 public SolidHeightfield build(float[] vertices, int[] indices) { if (vertices == null || indices == null || vertices.Length % 3 != 0 || indices.Length % 3 != 0) { Logger.LogError("[SolidHeightfieldBuilder][build]invalid"); return(null); } SolidHeightfield result = new SolidHeightfield(mCellSize, mCellHeight); //用作分母,方便后面计算的 float inverseCellSize = 1 / result.cellSize(); float inverseCellHeight = 1 / result.cellHeight(); float xmin = vertices[0]; float ymin = vertices[1]; float zmin = vertices[2]; float xmax = vertices[0]; float ymax = vertices[1]; float zmax = vertices[2]; //遍历所有顶点,找出最大的Bounds for (int i = 3; i < vertices.Length; i += 3) { xmax = Math.Max(vertices[i], xmax); ymax = Math.Max(vertices[i + 1], ymax); zmax = Math.Max(vertices[i + 2], zmax); xmin = Math.Min(vertices[i], xmin); ymin = Math.Min(vertices[i + 1], ymin); zmin = Math.Min(vertices[i + 2], zmin); } result.setBounds(xmin, ymin, zmin, xmax, ymax, zmax); //判断哪些多边形的表面是可以行走的,坡度不能太大 int[] polyFlags = markInputMeshWalkableFlags(vertices, indices); //开始对每个面进行体素化 int polyCount = indices.Length / 3; for (int iPoly = 0; iPoly < polyCount; iPoly++) { voxelizeTriangle(iPoly, vertices, indices, polyFlags[iPoly], inverseCellSize, inverseCellHeight, result); } markLowHeightSpans(result); if (mClipLedges) { markLedgeSpans(result); } return(result); }
//按面来体素化? private static void voxelizeTriangle(int polyIndex, float[] vertices, int[] indices, int polyFlags, float inverseCellSize, float inverseCellHeight, SolidHeightfield inoutField) //本来就是引用的话,传进来就会被修改 { int pPoly = polyIndex * 3; //一个面的三个点 float[] triVerts = new float[] { vertices[indices[pPoly] * 3], //VertA x vertices[indices[pPoly] * 3 + 1], //VertA y vertices[indices[pPoly] * 3 + 2], //VertA z vertices[indices[pPoly + 1] * 3], //VertB x vertices[indices[pPoly + 1] * 3 + 1], //VertB y vertices[indices[pPoly + 1] * 3 + 2], //VertB z vertices[indices[pPoly + 2] * 3], //VertC x vertices[indices[pPoly + 2] * 3 + 1], //VertC y vertices[indices[pPoly + 2] * 3 + 2], //VertC z }; //三角面的xz投影包围盒 float[] triBoundsMin = new float[] { triVerts[0], triVerts[1], triVerts[2] }; float[] triBoundsMax = new float[] { triVerts[0], triVerts[1], triVerts[2] }; // int vertPointer = 3 相当于 int i = 1 ,因为是从第二个顶点开始算起,数组长度总共为9 // Loop through all vertices to determine the actual bounding box. for (int vertPointer = 3; vertPointer < 9; vertPointer += 3) { triBoundsMin[0] = Math.Min(triBoundsMin[0], triVerts[vertPointer]); triBoundsMin[1] = Math.Min(triBoundsMin[1], triVerts[vertPointer + 1]); triBoundsMin[2] = Math.Min(triBoundsMin[2], triVerts[vertPointer + 2]); triBoundsMax[0] = Math.Min(triBoundsMax[1], triVerts[vertPointer]); triBoundsMax[1] = Math.Min(triBoundsMax[2], triVerts[vertPointer + 1]); triBoundsMax[2] = Math.Min(triBoundsMax[3], triVerts[vertPointer + 2]); } if (!inoutField.overlaps(triBoundsMin, triBoundsMax)) { return; } //将三角形的坐标转换成对应的cell坐标系,就是具体对应到哪个width和depth的Column //两个坐标相差得到距离,再除以cell的大小,得到每个坐标对应落在哪个Cell int triWidthMin = (int)((triBoundsMin[0] - inoutField.boundsMin()[0]) * inverseCellSize); int triDepthMin = (int)((triBoundsMin[2] - inoutField.boundsMin()[2]) * inverseCellSize); int triWidthMax = (int)((triBoundsMax[0] - inoutField.boundsMin()[0]) * inverseCellSize); int triDepthMax = (int)((triBoundsMax[2] - inoutField.boundsMin()[2]) * inverseCellSize); triWidthMin = clamp(triWidthMin, 0, inoutField.width() - 1); triDepthMin = clamp(triDepthMin, 0, inoutField.depth() - 1); triWidthMax = clamp(triWidthMax, 0, inoutField.width() - 1); triDepthMax = clamp(triWidthMax, 0, inoutField.depth() - 1); //从论文的图示来看,三角形与矩形的交点组成的凸包最多有7个点。 float[] inVerts = new float[21]; float[] outVerts = new float[21]; float[] inrowVerts = new float[21]; float fieldHeight = inoutField.boundsMax()[1] - inoutField.boundsMin()[1]; //http://www.sunshine2k.de/coding/java/SutherlandHodgman/SutherlandHodgman.html for (int depthIndex = triDepthMin; depthIndex <= triDepthMax; ++depthIndex) { Array.Copy(triVerts, 0, inVerts, 0, triVerts.Length); int intermediateVertCount = 3; //将体素depth坐标为 depthIndex 对应的 Edge 转变为 笛卡尔坐标的 z. //其实就是从体素坐标系转变为 笛卡尔坐标系 float rowWorldZ = inoutField.boundsMin()[2] + (depthIndex * inoutField.cellSize()); //用 z = rowWorldZ的直线裁剪三角形 intermediateVertCount = clipPoly(inVerts, intermediateVertCount, ref outVerts, 0, 1, -rowWorldZ); if (intermediateVertCount < 3) { continue; } //用 z = -(rowWorldZ + inoutField.cellSize) 的直线裁剪三角形 intermediateVertCount = clipPoly(outVerts, intermediateVertCount, ref inrowVerts, 0, -1, rowWorldZ + inoutField.cellSize()); if (intermediateVertCount < 3) { continue; } for (int widthIndex = triWidthMin; widthIndex <= triWidthMax; ++widthIndex) { int vertCount = intermediateVertCount; //将体素width坐标为 widthIndex 转变为 笛卡尔坐标的 x float colWorldX = inoutField.boundsMin()[0] + (widthIndex * inoutField.cellSize()); //用直线 x = colWorldX的直线进行裁剪 vertCount = clipPoly(inrowVerts, vertCount, ref outVerts, 1, 0, -colWorldX); if (vertCount < 3) { continue; } //用直线 x = -(colWorldX + CellSize) 进行裁剪 vertCount = clipPoly(outVerts, vertCount, ref inrowVerts, -1, 0, colWorldX + inoutField.cellSize()); if (vertCount < 3) { continue; } float heightMin = inVerts[1]; float heightMax = inVerts[1]; for (int i = 1; i < vertCount; ++i) { heightMin = Math.Min(heightMin, inVerts[i * 3 + 1]); heightMax = Math.Min(heightMax, inVerts[i * 3 + 1]); } //heigtMin和heighMax都是坐标,所以需要将其转换成相对于 inoutField 地板的高度,也就是一个标量 heightMin -= inoutField.boundsMin()[1]; heightMax -= inoutField.boundsMin()[1]; if (heightMax < 0.0f || heightMin > fieldHeight) { Logger.LogWarning("[SolidHeightfieldBuilder][voxelizeTriangle]Invalid|{0}|{1}|{2}|{3}|{4}", widthIndex, depthIndex, heightMin, heightMax, fieldHeight); continue; } if (heightMin < 0.0f) { Logger.LogWarning("[SolidHeightfieldBuilder][voxelizeTriangle]heigtMin Change|{0}|{1}|{2}|{3}|", widthIndex, depthIndex, heightMin, inoutField.boundsMin()[1]); heightMin = inoutField.boundsMin()[1]; } if (heightMax > fieldHeight) { Logger.LogWarning("[SolidHeightfieldBuilder][voxelizeTriangle]heightMax Change|{0}|{1}|{2}|{3}|", widthIndex, depthIndex, heightMax, fieldHeight, inoutField.boundsMin()[1]); heightMax = inoutField.boundsMax()[1]; } //将坐标重新转换为Grid的坐标,其实也是表示这三角面最低点和最高点,分别属于哪两个体素 int heightIndexMin = clamp( (int)Math.Floor(heightMin * inverseCellHeight), 0, short.MaxValue ); int heightIndexMax = clamp( (int)Math.Ceiling(heightMax * inverseCellHeight), 0, short.MaxValue ); inoutField.addData(widthIndex, depthIndex, heightIndexMin, heightIndexMax, polyFlags ); } } }
/// <summary> /// 边缘裁剪,那种像断崖式的Span也不可走的 /// </summary> /// <param name="field"></param> private void markLedgeSpans(SolidHeightfield field) { SolidHeightfield.SolidHeightfieldIterator iter = field.GetEnumerator(); while (iter.MoveNext()) { HeightSpan span = iter.Current; if ((span.flags() & SpanFlags.WALKABLE) == 0) { continue; } int widthIndex = iter.widthIndex(); int depthIndex = iter.depthIndex(); int currFloor = span.max(); int currCeiling = (span.next() != null) ? span.next().min() : int.MaxValue; int minDistanceToNeighbor = int.MaxValue; for (int dir = 0; dir < 4; dir++) { int nWidthIndex = widthIndex + BoundeField.getDirOffsetWidth(dir); int nDepthIndex = depthIndex + BoundeField.getDirOffsetDepth(dir); HeightSpan nSpan = field.getData(nWidthIndex, nDepthIndex); if (null == nSpan) { //TODO 这里没有搞懂为啥是 -mMaxTraversableStep - currFloor,是为了比-mMaxTraversableStep更小,以便直接判断不能行走吗? // 用大可行的距离,再减去currFloor,得到的肯定是一个更小的值。 // currFloor - mMaxTraversableStep 是一个最大允许的落差地板距离。注意这里只考虑下落的情况 minDistanceToNeighbor = Math.Min(minDistanceToNeighbor, -mMaxTraversableStep - currFloor); continue; } /* * 先考虑一种特殊情况 ,那就是 * 那就是nSpan.min也比currFloor要高,那么对应的 * 的邻居相当于也是没有Floor的,所以默认取-mMaxTraversableStep吧。 */ int nFloor = -mMaxTraversableStep; int nCeiling = nSpan.min(); //当前Span所处列的currCeiling和邻居的nCeiling相比,取最低的 if (Math.Min(currCeiling, nCeiling) - currFloor > mMinTraversableHeight) { minDistanceToNeighbor = Math.Min(minDistanceToNeighbor, (nFloor - currFloor)); } for (nSpan = field.getData(nWidthIndex, nDepthIndex); nSpan != null; nSpan = nSpan.next()) { nFloor = nSpan.max(); //现在才开始用max考虑真正存在的Floor nCeiling = (nSpan.next() != null) ? nSpan.next().min() : int.MaxValue; if (Math.Min(currCeiling, nCeiling) - Math.Max(currFloor, nFloor) > mMinTraversableHeight) { minDistanceToNeighbor = Math.Min(minDistanceToNeighbor, (nFloor - currFloor)); } } } //如果最近的距离比较最大掉落还小的放在,那么就是不可行走的 if (minDistanceToNeighbor < -mMaxTraversableStep) { span.setFlags(span.flags() & ~SpanFlags.WALKABLE); } } }