using System.Collections.Generic; using JNGame.Math; namespace JNGame.PathFinding { /// /// 二叉空间分割树节点 /// public partial class BspTreeNode { /// /// 划分到三角形数小于此则停止,可以性能测试下,选择一个合适的值 /// private const int kMinTriangleCount = 3; /// /// 找到树叶节点,向上取三层,获取包含的三角形,用于查找落位阻挡点的临近三角形 /// private const int kNearestDeepSearch = 3; /// /// 当前节点中包含的三角形数据 /// public List tris; /// /// 当前节点的子节点 /// private BspTreeNode[] m_Children; /// /// 当前节点的树深 /// public int depth = 0; /// /// 节点的切割线 /// public SplitLine splitLine; /// /// 是否是叶子节点 /// public bool IsLeaf => m_Children == null; /// /// 左子树 /// public BspTreeNode LeftChild => m_Children?[0]; /// /// 右子树 /// public BspTreeNode RightChild => m_Children?[1]; /// /// 获取指定坐标所在的三角形索引 /// /// /// public int GetTriangle(in LVector2 pos, out int searchDeep, out List triangeGroup) { if (IsLeaf) { searchDeep = -1; triangeGroup = null; foreach (var tri in tris) { if (tri.Contain(pos)) { return tri.index; } } searchDeep = kNearestDeepSearch; return -1; } else { var isLeft = LVector2.Cross(splitLine.vertexB - splitLine.vertexA, pos - splitLine.vertexA) >= 0; if (isLeft) { int targetIndex = LeftChild.GetTriangle(pos, out searchDeep, out triangeGroup); if (targetIndex == -1) { --searchDeep; if (searchDeep == 0) { triangeGroup = tris; } } return targetIndex; } else { int targetIndex = RightChild.GetTriangle(pos, out searchDeep, out triangeGroup); if (targetIndex == -1) { --searchDeep; if (searchDeep == 0) { triangeGroup = tris; } } return targetIndex; } } } /// /// 初始化二叉空间树的节点 /// /// /// public void Init(List tris, int depth = 0) { this.depth = depth; this.tris = tris; if (tris.Count <= kMinTriangleCount) { // 三角形数少于指标,停止 return; } if (depth > 20) { // 树深超过20层,停止 return; } // 选择分割线 splitLine = PickSplitLine(tris); m_Children = new BspTreeNode[] { new BspTreeNode(), new BspTreeNode() }; var lTris = new List(); var rTris = new List(); foreach (var tri in tris) { var val = SplitLine.GetSplitResult(in splitLine, tri); if (val == EnumSplitType.Left) { // 三角形位于切割线左侧,加入左子树三角形列表 lTris.Add(tri); } else if (val == EnumSplitType.Right) { // 三角形位于切割线右侧,加入右子树三角形列表 rTris.Add(tri); } else { // 切割线经过三角形,将三角形分为几个多边形 SplitTriangle(lTris, rTris, tri); } } LeftChild.Init(lTris, depth + 1); RightChild.Init(rTris, depth + 1); } private readonly List m_RightVerts = new List(); private readonly List m_LeftVerts = new List(); /// /// 将三角形按切割线切割为若干个三角形 /// /// /// /// private void SplitTriangle(List lTris, List rTris, TriangleRef tri) { int numVerts = 3; m_RightVerts.Clear(); m_LeftVerts.Clear(); var a = tri[2]; var aSide = SplitLine.ClassifyPointToPlane(splitLine, a); for (int i = 0; i < numVerts; i++) { var b = tri[i]; var bSide = SplitLine.ClassifyPointToPlane(splitLine, b); if (bSide == EnumSplitType.Right) { if (aSide == EnumSplitType.Left) { var p = SplitLine.GetIntersectPoint(a, b, splitLine.vertexA, splitLine.vertexB); m_RightVerts.Add(p); m_LeftVerts.Add(p); } else if (aSide == EnumSplitType.OnPlane) { if (m_RightVerts.Count == 0 || a != m_RightVerts[m_RightVerts.Count - 1]) { m_RightVerts.Add(a); } } m_RightVerts.Add(b); } else if (bSide == EnumSplitType.Left) { if (aSide == EnumSplitType.Right) { var p = SplitLine.GetIntersectPoint(a, b, splitLine.vertexA, splitLine.vertexB); m_RightVerts.Add(p); m_LeftVerts.Add(p); } else if (aSide == EnumSplitType.OnPlane) { if (m_LeftVerts.Count == 0 || a != m_LeftVerts[m_LeftVerts.Count - 1]) { m_LeftVerts.Add(a); } } m_LeftVerts.Add(b); } else { if (aSide == EnumSplitType.Right) { if (!(m_RightVerts.Count == 3 && b == m_RightVerts[0])) { m_RightVerts.Add(b); } } else if (aSide == EnumSplitType.Left) { if (!(m_LeftVerts.Count == 3 && b == m_LeftVerts[0])) { m_LeftVerts.Add(b); } } } a = b; aSide = bSide; } //push into tri list if (m_LeftVerts.Count >= 3) { if (m_LeftVerts.Count == 3) { AddTriangle(lTris, m_LeftVerts[0], m_LeftVerts[1], m_LeftVerts[2], tri); } else { // Debug.Assert(m_LeftVerts.Count == 4); AddTriangle(lTris, m_LeftVerts[0], m_LeftVerts[1], m_LeftVerts[2], tri); AddTriangle(lTris, m_LeftVerts[0], m_LeftVerts[2], m_LeftVerts[3], tri); } } if (m_RightVerts.Count >= 3) { if (m_RightVerts.Count == 3) { AddTriangle(rTris, m_RightVerts[0], m_RightVerts[1], m_RightVerts[2], tri); } else { // Debug.Assert(m_RightVerts.Count == 4); AddTriangle(rTris, m_RightVerts[0], m_RightVerts[1], m_RightVerts[2], tri); AddTriangle(rTris, m_RightVerts[0], m_RightVerts[2], m_RightVerts[3], tri); } } } private void AddTriangle(List triangles, in LVector2 vertexA, in LVector2 vertexB, in LVector2 vertexC, TriangleRef tri) { if (vertexA == vertexB || vertexB == vertexC || vertexC == vertexA) { return; } triangles.Add(new TriangleRef(vertexA, vertexB, vertexC, tri)); } /// /// 选择一条合适的切割线 /// /// 待切割的三角形集合 /// private static SplitLine PickSplitLine(List tris) { int minScore = int.MinValue; int minLCount = 0; int minRCount = 0; SplitLine bestLine = new SplitLine(); int[] splitCounter = new int[(int)EnumSplitType.EnumCount]; // 每次切割,三角形位于切割线左右和线上的次数统计 var tirCount = tris.Count; foreach (var tri in tris) { // 遍历所有三角形的边框线,作为切割线来切割空间,计算每次切割的分数,从而选取最优的分割线 foreach (var borderLine in tri.borders) { for (int i = 0; i < (int)EnumSplitType.EnumCount; i++) { splitCounter[i] = 0; } foreach (var otherTri in tris) { var val = (int)SplitLine.GetSplitResult(borderLine, otherTri); splitCounter[val]++; } // Clock wise =>Left ++ CCW=>Right++// self tri is on the left var leftCount = splitCounter[(int)EnumSplitType.Left]; var rightCount = splitCounter[(int)EnumSplitType.Right]; var splitCount = splitCounter[(int)EnumSplitType.OnPlane]; if ((leftCount == 0 || rightCount == 0) && (leftCount + rightCount) == tirCount) { // 所有的三角形都在同一侧,忽略 continue; } // 根据切割线左右的三角形数,以及切割的三角形数,计算本次分割的得分 var balanceVal = LMath.Abs(leftCount - rightCount); var sameVal = LMath.Min(leftCount, rightCount); int score = sameVal * 3 - balanceVal - splitCount * 2; if (score > minScore) { // 选取得分最高的作为切割线 minLCount = leftCount; minRCount = rightCount; minScore = score; bestLine = borderLine; } } } return bestLine; } } }