mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交Unity 联机Pro
This commit is contained in:
131
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.UnityExt.cs
Normal file
131
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.UnityExt.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
// 定义BspTree的Unity可视化扩展
|
||||
|
||||
//#define SHOW_BSP_TREE_GIZOMS
|
||||
#if UNITY_EDITOR // && SHOW_BSP_TREE_GIZOMS
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JNGame.Math;
|
||||
using UnityEngine;
|
||||
|
||||
namespace JNGame.PathFinding
|
||||
{
|
||||
// BspTreeNode的调试信息
|
||||
public partial class BspTreeNode
|
||||
{
|
||||
public static int s_AutoIncretID = 0;
|
||||
|
||||
public int debugNodeId;
|
||||
public BspTreeNode()
|
||||
{
|
||||
debugNodeId = ++s_AutoIncretID;
|
||||
}
|
||||
}
|
||||
|
||||
// BspTree的可视化绘制
|
||||
public partial class BspTree
|
||||
{
|
||||
#region Debug Gizoms
|
||||
public static Transform s_DebugTrans;
|
||||
public static Material s_DebugMat;
|
||||
|
||||
public void DebugDraw()
|
||||
{
|
||||
var tran = DrawNode(m_RootNode, 0, s_DebugTrans);
|
||||
tran.transform.position += Vector3.up * 0.05f;
|
||||
}
|
||||
|
||||
private Vector3 Hash13(int id)
|
||||
{
|
||||
var val = (Mathf.Sin(15741.254f * id) + 1) / 2;
|
||||
var val2 = (Mathf.Sin(7331.5147f * id) + 1) / 2;
|
||||
var val3 = (Mathf.Sin(24317.433f * id) + 1) / 2;
|
||||
return new Vector3(val, val2, val3);
|
||||
}
|
||||
|
||||
private Transform DrawNode(BspTreeNode node, int depth, Transform parent)
|
||||
{
|
||||
var val = Hash13(node.debugNodeId);
|
||||
Color color = new Color(val.x, val.y, val.z, 1);
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
var tran = CreateGo(node.debugNodeId.ToString(), node.tris, color).transform;
|
||||
tran.SetParent(parent, true);
|
||||
return tran;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tran = CreateGo(node.debugNodeId.ToString(), node.splitLine, color).transform;
|
||||
tran.SetParent(parent, true);
|
||||
|
||||
DrawNode(node.LeftChild, depth + 1, tran);
|
||||
DrawNode(node.RightChild, depth + 1, tran);
|
||||
return tran;
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject CreateGo(string name, in SplitLine plane, in Color color)
|
||||
{
|
||||
return new GameObject(name);
|
||||
#if false
|
||||
var dir = plane.Direction.Normalized;
|
||||
var or = plane.vertexA;
|
||||
var s = or - dir * worldSize;
|
||||
var e = or + dir * worldSize;
|
||||
var perp = new LVector2(-dir.y, dir.x);
|
||||
const float lineSize = 0.3f;
|
||||
var s1 = perp * lineSize.ToLFloat() + s;
|
||||
var e1 = perp * lineSize.ToLFloat() + e;
|
||||
List<Vector3> vertices = new List<Vector3>();
|
||||
List<int> tirs = new List<int>();
|
||||
vertices.Add(s.ToVector3XZ());
|
||||
vertices.Add(e.ToVector3XZ());
|
||||
vertices.Add(e1.ToVector3XZ());
|
||||
vertices.Add(s1.ToVector3XZ());
|
||||
int triIdx = 0;
|
||||
tirs.Add(0);
|
||||
tirs.Add(1);
|
||||
tirs.Add(2);
|
||||
tirs.Add(0);
|
||||
tirs.Add(2);
|
||||
tirs.Add(3);
|
||||
return CreateGo(name, vertices, tirs, color);
|
||||
#endif
|
||||
}
|
||||
|
||||
private GameObject CreateGo(string name, List<TriangleRef> allRawTriangle, in Color color)
|
||||
{
|
||||
List<Vector3> vertices = new List<Vector3>();
|
||||
List<int> tirs = new List<int>();
|
||||
int triIdx = 0;
|
||||
foreach (var tri in allRawTriangle)
|
||||
{
|
||||
vertices.Add(tri.vertexA.ToVector3XZ());
|
||||
vertices.Add(tri.vertexB.ToVector3XZ());
|
||||
vertices.Add(tri.vertexC.ToVector3XZ());
|
||||
tirs.Add(triIdx++);
|
||||
tirs.Add(triIdx++);
|
||||
tirs.Add(triIdx++);
|
||||
}
|
||||
|
||||
return CreateGo(name, vertices, tirs, color);
|
||||
}
|
||||
|
||||
private GameObject CreateGo(string name, List<Vector3> vertices, List<int> tirs, in Color color)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var render = go.AddComponent<MeshRenderer>();
|
||||
var mat = new Material(s_DebugMat) { color = color };
|
||||
render.material = mat;
|
||||
|
||||
var tempMesh = new Mesh();
|
||||
tempMesh.vertices = vertices.ToArray();
|
||||
tempMesh.triangles = tirs.ToArray();
|
||||
go.AddComponent<MeshFilter>().mesh = tempMesh;
|
||||
return go;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba3cb62d3cc64ae4a94be3cbd1bf6ef8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
63
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.cs
Normal file
63
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using JNGame.Math;
|
||||
|
||||
namespace JNGame.PathFinding
|
||||
{
|
||||
/// <summary>
|
||||
/// 二叉空间分割树
|
||||
/// </summary>
|
||||
public partial class BspTree
|
||||
{
|
||||
private readonly List<TriangleRef> m_AllTriangle = new List<TriangleRef>();
|
||||
|
||||
/// <summary>
|
||||
/// 根节点
|
||||
/// </summary>
|
||||
private BspTreeNode m_RootNode;
|
||||
|
||||
/// <summary>
|
||||
/// 查找点邻接三角形的缓存,每次查找都会清理,外部不可缓存此列表,只可一次性使用
|
||||
/// </summary>
|
||||
private readonly List<int> m_TempNeareastTrianges = new List<int>(16);
|
||||
|
||||
/// <summary>
|
||||
/// 构造二叉空间分割树
|
||||
/// </summary>
|
||||
/// <param name="rawTriangles">原始的三角形数据</param>
|
||||
public void Init(List<NavTriangle> rawTriangles)
|
||||
{
|
||||
foreach (var tri in rawTriangles)
|
||||
{
|
||||
m_AllTriangle.Add(new TriangleRef(tri));
|
||||
}
|
||||
|
||||
m_RootNode = new BspTreeNode();
|
||||
m_RootNode.Init(m_AllTriangle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据位置检索BSP树,快速定位点所在三角形区域
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
/// <param name="nearTriangleIndices"></param>
|
||||
/// <returns></returns>
|
||||
public int GetTriangle(in LVector3 pos, out List<int> nearTriangleIndices)
|
||||
{
|
||||
m_TempNeareastTrianges.Clear();
|
||||
var pos2d = pos.ToLVector2XZ();
|
||||
int triangleIndex = m_RootNode.GetTriangle(pos2d, out _, out List<TriangleRef> nearTriangles);
|
||||
if (triangleIndex == -1)
|
||||
{ // 未找到落位的三角形,说明在阻挡内,获取阻挡周边的一些三角形
|
||||
for (int i = 0; i < nearTriangles.Count; ++i)
|
||||
{
|
||||
if (!m_TempNeareastTrianges.Contains(nearTriangles[i].index))
|
||||
{
|
||||
m_TempNeareastTrianges.Add(nearTriangles[i].index);
|
||||
}
|
||||
}
|
||||
}
|
||||
nearTriangleIndices = m_TempNeareastTrianges;
|
||||
return triangleIndex;
|
||||
}
|
||||
}
|
||||
}
|
11
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.cs.meta
Normal file
11
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTree.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 477985406605c4a0c9d9718247233d1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
315
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTreeNode.cs
Normal file
315
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/BspTreeNode.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 133bf2d5724b64ac0945954588c88155
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
10
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/ESplitType.cs
Normal file
10
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/ESplitType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace JNGame.PathFinding
|
||||
{
|
||||
public enum EnumSplitType
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
OnPlane,
|
||||
EnumCount
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9b5e74642ed142cd83c3453ce2d5894
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
115
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/SplitLine.cs
Normal file
115
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/SplitLine.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using JNGame.Math;
|
||||
|
||||
namespace JNGame.PathFinding
|
||||
{
|
||||
/// <summary>
|
||||
/// 二叉空间树的切割线
|
||||
/// </summary>
|
||||
public struct SplitLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 切割线起点
|
||||
/// </summary>
|
||||
public LVector2 vertexA;
|
||||
/// <summary>
|
||||
/// 切割线终点
|
||||
/// </summary>
|
||||
public LVector2 vertexB;
|
||||
/// <summary>
|
||||
/// 切割线方向
|
||||
/// </summary>
|
||||
public readonly LVector2 Direction => vertexB - vertexA;
|
||||
|
||||
/// <summary>
|
||||
/// 构造切割线
|
||||
/// </summary>
|
||||
/// <param name="vertexA"></param>
|
||||
/// <param name="vertexB"></param>
|
||||
public SplitLine(in LVector2 vertexA, in LVector2 vertexB)
|
||||
{
|
||||
this.vertexA = vertexA;
|
||||
this.vertexB = vertexB;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取三角形的切割结果
|
||||
/// </summary>
|
||||
/// <param name="line">切割线</param>
|
||||
/// <param name="tri">切割三角形</param>
|
||||
/// <returns>切割线左侧、右侧或经过切割线</returns>
|
||||
public static EnumSplitType GetSplitResult(in SplitLine line, TriangleRef tri)
|
||||
{
|
||||
var lineDir = line.Direction;
|
||||
// 三角形三个顶点与切割线的位置关系
|
||||
var valA = LVector2.Cross(lineDir, tri.vertexA - line.vertexA);
|
||||
var valB = LVector2.Cross(lineDir, tri.vertexB - line.vertexA);
|
||||
var valC = LVector2.Cross(lineDir, tri.vertexC - line.vertexA);
|
||||
|
||||
var isRight = false;
|
||||
if (valA != 0) isRight = valA < 0;
|
||||
if (valB != 0) isRight = valB < 0;
|
||||
if (valC != 0) isRight = valC < 0;
|
||||
|
||||
var isA = valA <= 0;
|
||||
var isB = valB <= 0;
|
||||
var isC = valC <= 0;
|
||||
if (isA == isB && isB == isC)
|
||||
{
|
||||
return isRight ? EnumSplitType.Right : EnumSplitType.Left;
|
||||
}
|
||||
|
||||
isA = valA >= 0;
|
||||
isB = valB >= 0;
|
||||
isC = valC >= 0;
|
||||
if (isA == isB && isB == isC)
|
||||
{
|
||||
return isRight ? EnumSplitType.Right : EnumSplitType.Left;
|
||||
}
|
||||
|
||||
return EnumSplitType.OnPlane;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶点和切割线的位置关系
|
||||
/// </summary>
|
||||
/// <param name="line">切割线</param>
|
||||
/// <param name="vertex">顶点</param>
|
||||
/// <returns></returns>
|
||||
public static EnumSplitType ClassifyPointToPlane(in SplitLine line, in LVector2 vertex)
|
||||
{
|
||||
var val = LVector2.Cross(line.Direction, vertex - line.vertexA);
|
||||
if (val == 0)
|
||||
{
|
||||
return EnumSplitType.OnPlane;
|
||||
}
|
||||
else
|
||||
{
|
||||
return val < 0 ? EnumSplitType.Right : EnumSplitType.Left;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算p0p1,p2p3的交点
|
||||
/// </summary>
|
||||
/// <param name="p0"></param>
|
||||
/// <param name="p1"></param>
|
||||
/// <param name="p2"></param>
|
||||
/// <param name="p3"></param>
|
||||
/// <returns></returns>
|
||||
public static LVector2 GetIntersectPoint(in LVector2 p0, in LVector2 p1, in LVector2 p2, in LVector2 p3)
|
||||
{
|
||||
var diff = p2 - p0;
|
||||
var d1 = p1 - p0;
|
||||
var d2 = p3 - p2;
|
||||
var demo = LMath.Cross(d1, d2); //det
|
||||
if (LMath.Abs(demo) < LFloat.EPSILON)
|
||||
{
|
||||
// 叉积结果为0,说明两个向量平行
|
||||
return p0;
|
||||
}
|
||||
|
||||
var t1 = LMath.Cross(diff, d2) / demo; // Cross2D(diff,-d2)
|
||||
return p0 + (p1 - p0) * t1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a160827a7e254845beb3b10a0644f26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
137
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/TriangleRef.cs
Normal file
137
JNFrame2/Assets/JNGame/Map/2DPathFinding/BSP/TriangleRef.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
// using JNGame.Collision2D;
|
||||
using JNGame.Math;
|
||||
// using Debug = JNGame.Logging.Debug;
|
||||
|
||||
namespace JNGame.PathFinding
|
||||
{
|
||||
/// <summary>
|
||||
/// 导航三角形在二叉空间分割树中的引用
|
||||
/// <para>一个导航三角形可能会被树分割为多个三角形引用</para>
|
||||
/// </summary>
|
||||
public class TriangleRef
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前三角形引用关联的导航三角形索引
|
||||
/// </summary>
|
||||
public int index;
|
||||
/// <summary>
|
||||
/// 三角形顶点A
|
||||
/// </summary>
|
||||
public LVector2 vertexA;
|
||||
/// <summary>
|
||||
/// 三角形顶点B
|
||||
/// </summary>
|
||||
public LVector2 vertexB;
|
||||
/// <summary>
|
||||
/// 三角形顶点C
|
||||
/// </summary>
|
||||
public LVector2 vertexC;
|
||||
|
||||
/// <summary>
|
||||
/// 是否是分割的三角形
|
||||
/// </summary>
|
||||
public bool IsSplit { get; private set; } = false;
|
||||
/// <summary>
|
||||
/// 三角形的边框
|
||||
/// </summary>
|
||||
public SplitLine[] borders;
|
||||
|
||||
/// <summary>
|
||||
/// 根据下标获取三角形的顶点,合法下标0/1/2
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="IndexOutOfRangeException"></exception>
|
||||
public LVector2 this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return index switch
|
||||
{
|
||||
0 => vertexA,
|
||||
1 => vertexB,
|
||||
2 => vertexC,
|
||||
_ => throw new IndexOutOfRangeException("vector idx invalid" + index),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据原始导航三角形创建引用
|
||||
/// <para>由此构造的三角形引用认为是未切割过的三角形</para>
|
||||
/// </summary>
|
||||
/// <param name="srcTriangle">原始的导航三角形</param>
|
||||
public TriangleRef(NavTriangle srcTriangle)
|
||||
: this(srcTriangle.vertexA.ToLVector2XZ(), srcTriangle.vertexB.ToLVector2XZ(), srcTriangle.vertexC.ToLVector2XZ(), srcTriangle.index)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// 根据分割的三个顶点和原始导航三角形创建引用
|
||||
/// <para>由此构造的三角形引用认为是切割过的三角形</para>
|
||||
/// </summary>
|
||||
/// <param name="vertexA">三角形顶点A</param>
|
||||
/// <param name="vertexB">三角形顶点B</param>
|
||||
/// <param name="vertexC">三角形顶点C</param>
|
||||
/// <param name="srcTriangle">原始的导航三角形</param>
|
||||
public TriangleRef(in LVector2 vertexA, in LVector2 vertexB, in LVector2 vertexC, TriangleRef srcTriangle)
|
||||
: this(vertexA, vertexB, vertexC, srcTriangle.index)
|
||||
{
|
||||
IsSplit = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 私有构造
|
||||
/// </summary>
|
||||
/// <param name="vertexA">三角形顶点A</param>
|
||||
/// <param name="vertexB">三角形顶点B</param>
|
||||
/// <param name="vertexC">三角形顶点C</param>
|
||||
/// <param name="idx">引用的导航三角形索引</param>
|
||||
private TriangleRef(in LVector2 vertexA, in LVector2 vertexB, in LVector2 vertexC, int idx)
|
||||
{
|
||||
this.vertexA = vertexA;
|
||||
this.vertexB = vertexB;
|
||||
this.vertexC = vertexC;
|
||||
index = idx;
|
||||
borders = new SplitLine[]
|
||||
{
|
||||
new SplitLine(vertexA, vertexB),
|
||||
new SplitLine(vertexB, vertexC),
|
||||
new SplitLine(vertexC, vertexA)
|
||||
};
|
||||
//check valid
|
||||
CheckValid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三角形三点不可重合
|
||||
/// </summary>
|
||||
private void CheckValid()
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (borders[i].Direction == LVector2.Zero)
|
||||
{
|
||||
// Debug.Assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断点是否在此三角形内
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contain(in LVector2 pos)
|
||||
{
|
||||
// ABC三个顶点绕序为顺时针,三个叉积都需要为负,则点在三角形内
|
||||
var isRightA = LVector2.Cross(vertexB - vertexA, pos - vertexA) > 0;
|
||||
if (isRightA) return false;
|
||||
var isRightB = LVector2.Cross(vertexC - vertexB, pos - vertexB) > 0;
|
||||
if (isRightB) return false;
|
||||
var isRightC = LVector2.Cross(vertexA - vertexC, pos - vertexC) > 0;
|
||||
if (isRightC) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63961ae30f8cb4ca7a519fa7a7cdc394
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user