mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-06-26 19:34:47 +00:00
315 lines
12 KiB
C#
315 lines
12 KiB
C#
|
||
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;
|
||
}
|
||
}
|
||
} |