315 lines
12 KiB
C#
Raw Normal View History

2024-08-17 14:27:18 +08:00
using System.Collections.Generic;
using JNGame.Math;
namespace JNGame.PathFinding
{
/// <summary>
/// 二叉空间分割树节点
/// </summary>
public partial class BspTreeNode
{
/// <summary>
/// 划分到三角形数小于此则停止,可以性能测试下,选择一个合适的值
/// </summary>
private const int kMinTriangleCount = 3;
/// <summary>
/// 找到树叶节点,向上取三层,获取包含的三角形,用于查找落位阻挡点的临近三角形
/// </summary>
private const int kNearestDeepSearch = 3;
/// <summary>
/// 当前节点中包含的三角形数据
/// </summary>
public List<TriangleRef> tris;
/// <summary>
/// 当前节点的子节点
/// </summary>
private BspTreeNode[] m_Children;
/// <summary>
/// 当前节点的树深
/// </summary>
public int depth = 0;
/// <summary>
/// 节点的切割线
/// </summary>
public SplitLine splitLine;
/// <summary>
/// 是否是叶子节点
/// </summary>
public bool IsLeaf => m_Children == null;
/// <summary>
/// 左子树
/// </summary>
public BspTreeNode LeftChild => m_Children?[0];
/// <summary>
/// 右子树
/// </summary>
public BspTreeNode RightChild => m_Children?[1];
/// <summary>
/// 获取指定坐标所在的三角形索引
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public int GetTriangle(in LVector2 pos, out int searchDeep, out List<TriangleRef> 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;
}
}
}
/// <summary>
/// 初始化二叉空间树的节点
/// </summary>
/// <param name="tris"></param>
/// <param name="depth"></param>
public void Init(List<TriangleRef> 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<TriangleRef>();
var rTris = new List<TriangleRef>();
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<LVector2> m_RightVerts = new List<LVector2>();
private readonly List<LVector2> m_LeftVerts = new List<LVector2>();
/// <summary>
/// 将三角形按切割线切割为若干个三角形
/// </summary>
/// <param name="lTris"></param>
/// <param name="rTris"></param>
/// <param name="tri"></param>
private void SplitTriangle(List<TriangleRef> lTris, List<TriangleRef> 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<TriangleRef> 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));
}
/// <summary>
/// 选择一条合适的切割线
/// </summary>
/// <param name="tris">待切割的三角形集合</param>
/// <returns></returns>
private static SplitLine PickSplitLine(List<TriangleRef> 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;
}
}
}