PC-20230316NUNE\Administrator 894100ae37 提交Unity 联机Pro
2024-08-17 14:27:18 +08:00

315 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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