diff --git a/Sources/Everglow.Core/DataStructures/UnionFind.cs b/Sources/Everglow.Core/DataStructures/UnionFind.cs
new file mode 100644
index 000000000..3c46dafe1
--- /dev/null
+++ b/Sources/Everglow.Core/DataStructures/UnionFind.cs
@@ -0,0 +1,53 @@
+namespace Everglow.Commons.DataStructures;
+
+public class UnionFind
+{
+ private int[] parent;
+ private int[] rank;
+
+ public UnionFind(int size)
+ {
+ parent = new int[size];
+ rank = new int[size];
+
+ for (int i = 0; i < size; i++)
+ {
+ parent[i] = i;
+ rank[i] = 0;
+ }
+ }
+
+ public int Find(int x)
+ {
+ if (parent[x] != x)
+ {
+ parent[x] = Find(parent[x]);
+ }
+ return parent[x];
+ }
+
+ public void Union(int x, int y)
+ {
+ int rootX = Find(x);
+ int rootY = Find(y);
+
+ if (rootX == rootY)
+ {
+ return;
+ }
+
+ if (rank[rootX] < rank[rootY])
+ {
+ parent[rootX] = rootY;
+ }
+ else if (rank[rootX] > rank[rootY])
+ {
+ parent[rootY] = rootX;
+ }
+ else
+ {
+ parent[rootY] = rootX;
+ rank[rootX]++;
+ }
+ }
+}
diff --git a/Sources/Everglow.Core/Everglow.Core.csproj b/Sources/Everglow.Core/Everglow.Core.csproj
index 145d27e8c..3d6f83865 100644
--- a/Sources/Everglow.Core/Everglow.Core.csproj
+++ b/Sources/Everglow.Core/Everglow.Core.csproj
@@ -6,9 +6,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/Everglow.Core/Utilities/Matrix2x2.cs b/Sources/Everglow.Core/Utilities/Matrix2x2.cs
new file mode 100644
index 000000000..5ed29f298
--- /dev/null
+++ b/Sources/Everglow.Core/Utilities/Matrix2x2.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Utilities
+{
+ ///
+ /// 2x2 矩阵类
+ ///
+ public class Matrix2x2
+ {
+ public static Matrix2x2 Identity = new Matrix2x2(
+ new double[2, 2]
+ {
+ { 1, 0}, { 0, 1 },
+ });
+ public static Matrix2x2 Zero = new Matrix2x2(
+ new double[2, 2]
+ {
+ { 0, 0 }, { 0, 0 },
+ });
+
+ private double[,] _matrix;
+
+ public Matrix2x2()
+ {
+ _matrix = new double[2, 2];
+ }
+
+ public Matrix2x2(double[,] initialMatrix)
+ {
+ if (initialMatrix.GetLength(0) != 2 || initialMatrix.GetLength(1) != 2)
+ throw new ArgumentException("Initial matrix must be 2x2");
+
+ _matrix = initialMatrix;
+ }
+
+ public double this[int row, int col]
+ {
+ get
+ {
+ return _matrix[row, col];
+ }
+ set
+ {
+ _matrix[row, col] = value;
+ }
+ }
+
+ public double Trace()
+ {
+ return _matrix[0, 0] + _matrix[1, 1];
+ }
+
+ public Matrix2x2 Inverse()
+ {
+ double det = Determinant();
+ if (det == 0)
+ throw new InvalidOperationException("Matrix is not invertible.");
+
+ var result = new Matrix2x2
+ {
+ [0, 0] = _matrix[1, 1] / det,
+ [0, 1] = -_matrix[0, 1] / det,
+ [1, 0] = -_matrix[1, 0] / det,
+ [1, 1] = _matrix[0, 0] / det
+ };
+
+ return result;
+ }
+
+ public Matrix2x2 Multiply(Matrix2x2 other)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = this[0, 0] * other[0, 0] + this[0, 1] * other[1, 0],
+ [0, 1] = this[0, 0] * other[0, 1] + this[0, 1] * other[1, 1],
+ [1, 0] = this[1, 0] * other[0, 0] + this[1, 1] * other[1, 0],
+ [1, 1] = this[1, 0] * other[0, 1] + this[1, 1] * other[1, 1]
+ };
+
+ return result;
+ }
+
+ public Vector2 Multiply(Vector2 other)
+ {
+ return new Vector2((float)(this[0, 0] * other.X + this[0, 1] * other.Y),
+ (float)(this[1, 0] * other.X + this[1, 1] * other.Y));
+ }
+
+ public double Determinant()
+ {
+ return _matrix[0, 0] * _matrix[1, 1] - _matrix[0, 1] * _matrix[1, 0];
+ }
+
+ public Matrix2x2 Adjoint()
+ {
+ return new Matrix2x2
+ {
+ [0, 0] = _matrix[1, 1],
+ [0, 1] = -_matrix[0, 1],
+ [1, 0] = -_matrix[1, 0],
+ [1, 1] = _matrix[0, 0],
+ };
+ }
+
+ public static Matrix2x2 operator *(Matrix2x2 a, double b)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] * b,
+ [0, 1] = a[0, 1] * b,
+ [1, 0] = a[1, 0] * b,
+ [1, 1] = a[1, 1] * b
+ };
+
+ return result;
+ }
+
+ public static Matrix2x2 operator *(double b, Matrix2x2 a)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] * b,
+ [0, 1] = a[0, 1] * b,
+ [1, 0] = a[1, 0] * b,
+ [1, 1] = a[1, 1] * b
+ };
+
+ return result;
+ }
+
+ public static Matrix2x2 operator *(Matrix2x2 a, Matrix2x2 b)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] * b[0, 0] + a[0, 1] * b[1, 0],
+ [0, 1] = a[0, 0] * b[0, 1] + a[0, 1] * b[1, 1],
+ [1, 0] = a[1, 0] * b[0, 0] + a[1, 1] * b[1, 0],
+ [1, 1] = a[1, 0] * b[0, 1] + a[1, 1] * b[1, 1]
+ };
+
+ return result;
+ }
+
+ public static Matrix2x2 operator +(Matrix2x2 a, Matrix2x2 b)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] + b[0, 0],
+ [0, 1] = a[0, 1] + b[0, 1],
+ [1, 0] = a[1, 0] + b[1, 0],
+ [1, 1] = a[1, 1] + b[1, 1]
+ };
+
+ return result;
+ }
+
+ public static Matrix2x2 operator -(Matrix2x2 a, Matrix2x2 b)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] - b[0, 0],
+ [0, 1] = a[0, 1] - b[0, 1],
+ [1, 0] = a[1, 0] - b[1, 0],
+ [1, 1] = a[1, 1] - b[1, 1]
+ };
+
+ return result;
+ }
+
+ public static Matrix2x2 operator /(Matrix2x2 a, Matrix2x2 b)
+ {
+ var result = new Matrix2x2
+ {
+ [0, 0] = a[0, 0] / b[0, 0],
+ [0, 1] = a[0, 1] / b[0, 1],
+ [1, 0] = a[1, 0] / b[1, 0],
+ [1, 1] = a[1, 1] / b[1, 1]
+ };
+
+ return result;
+ }
+
+ public Matrix2x2 Transpose()
+ {
+ return new Matrix2x2
+ {
+ [0, 0] = _matrix[0, 0],
+ [0, 1] = _matrix[1, 0],
+ [1, 0] = _matrix[0, 1],
+ [1, 1] = _matrix[1, 1],
+ };
+ }
+
+
+ public static Matrix2x2 CreateRotationMatrix(float r)
+ {
+ return new Matrix2x2
+ {
+ [0, 0] = Math.Cos(r),
+ [0, 1] = -Math.Sin(r),
+ [1, 0] = Math.Sin(r),
+ [1, 1] = Math.Cos(r),
+ };
+ }
+ }
+}
diff --git a/Sources/Everglow.Core/Utilities/Matrix3x3.cs b/Sources/Everglow.Core/Utilities/Matrix3x3.cs
new file mode 100644
index 000000000..6180665ee
--- /dev/null
+++ b/Sources/Everglow.Core/Utilities/Matrix3x3.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Utilities
+{
+ public class Matrix3x3
+ {
+ public static Matrix3x3 Identity = new Matrix3x3(new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1));
+ private double[,] _matrix;
+
+ public Matrix3x3()
+ {
+ _matrix = new double[3, 3];
+ }
+ public Matrix3x3(double[,] initialMatrix)
+ {
+ if (initialMatrix.GetLength(0) != 3 || initialMatrix.GetLength(1) != 3)
+ throw new ArgumentException("Initial matrix must be 3x3");
+
+ _matrix = initialMatrix;
+ }
+
+ public double this[int row, int col]
+ {
+ get
+ {
+ return _matrix[row, col];
+ }
+ set
+ {
+ _matrix[row, col] = value;
+ }
+ }
+
+ public Matrix3x3(Vector3 col0, Vector3 col1, Vector3 col2)
+ {
+ _matrix = new double[3, 3];
+ _matrix[0, 0] = col0.X;
+ _matrix[1, 0] = col0.Y;
+ _matrix[2, 0] = col0.Z;
+
+ _matrix[0, 1] = col1.X;
+ _matrix[1, 1] = col1.Y;
+ _matrix[2, 1] = col1.Z;
+
+ _matrix[0, 2] = col2.X;
+ _matrix[1, 2] = col2.Y;
+ _matrix[2, 2] = col2.Z;
+ }
+
+ public static Matrix3x3 operator *(Matrix3x3 a, double x)
+ {
+ double[,] data = new double[3, 3];
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ data[i, j] = a._matrix[i, j] * x;
+ }
+ }
+
+ return new Matrix3x3(data);
+ }
+
+ public static Matrix3x3 operator -(Matrix3x3 a, Matrix3x3 b)
+ {
+ double[,] data = new double[3, 3];
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ data[i, j] = a._matrix[i, j] - b._matrix[i, j];
+ }
+ }
+
+ return new Matrix3x3(data);
+ }
+
+ public static Matrix3x3 operator +(Matrix3x3 a, Matrix3x3 b)
+ {
+ double[,] data = new double[3, 3];
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ data[i, j] = a._matrix[i, j] + b._matrix[i, j];
+ }
+ }
+
+ return new Matrix3x3(data);
+ }
+
+ public static Matrix3x3 operator /(Matrix3x3 a, Matrix3x3 b)
+ {
+ double[,] data = new double[3, 3];
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ data[i, j] = a._matrix[i, j] / b._matrix[i, j];
+ }
+ }
+
+ return new Matrix3x3(data);
+ }
+
+
+
+ public Matrix3x3 Transpose()
+ {
+ double[,] m = new double[3, 3];
+ m[0, 0] = _matrix[0, 0];
+ m[0, 1] = _matrix[1, 0];
+ m[0, 2] = _matrix[2, 0];
+
+ m[1, 0] = _matrix[0, 1];
+ m[1, 1] = _matrix[1, 1];
+ m[1, 2] = _matrix[2, 1];
+
+ m[2, 0] = _matrix[0, 2];
+ m[2, 1] = _matrix[1, 2];
+ m[2, 2] = _matrix[2, 2];
+ return new Matrix3x3(m);
+ }
+
+ public Matrix3x3 Multiply(Matrix3x3 other)
+ {
+ double[,] result = new double[3, 3];
+
+ result[0, 0] = _matrix[0, 0] * other._matrix[0, 0] + _matrix[0, 1] * other._matrix[1, 0] +
+ _matrix[0, 2] * other._matrix[2, 0];
+ result[0, 1] = _matrix[0, 0] * other._matrix[0, 1] + _matrix[0, 1] * other._matrix[1, 1] +
+ _matrix[0, 2] * other._matrix[2, 1];
+ result[0, 2] = _matrix[0, 0] * other._matrix[0, 2] + _matrix[0, 1] * other._matrix[1, 2] +
+ _matrix[0, 2] * other._matrix[2, 2];
+
+ result[1, 0] = _matrix[1, 0] * other._matrix[0, 0] + _matrix[1, 1] * other._matrix[1, 0] +
+ _matrix[1, 2] * other._matrix[2, 0];
+ result[1, 1] = _matrix[1, 0] * other._matrix[0, 1] + _matrix[1, 1] * other._matrix[1, 1] +
+ _matrix[1, 2] * other._matrix[2, 1];
+ result[1, 2] = _matrix[1, 0] * other._matrix[0, 2] + _matrix[1, 1] * other._matrix[1, 2] +
+ _matrix[1, 2] * other._matrix[2, 2];
+
+ result[2, 0] = _matrix[2, 0] * other._matrix[0, 0] + _matrix[2, 1] * other._matrix[1, 0] +
+ _matrix[2, 2] * other._matrix[2, 0];
+ result[2, 1] = _matrix[2, 0] * other._matrix[0, 1] + _matrix[2, 1] * other._matrix[1, 1] +
+ _matrix[2, 2] * other._matrix[2, 1];
+ result[2, 2] = _matrix[2, 0] * other._matrix[0, 2] + _matrix[2, 1] * other._matrix[1, 2] +
+ _matrix[2, 2] * other._matrix[2, 2];
+
+ return new Matrix3x3(result);
+ }
+
+ public Vector3 Multiply(Vector3 v)
+ {
+ return new Vector3((float)(_matrix[0, 0] * v.X + _matrix[0, 1] * v.Y + _matrix[0, 2] * v.Z),
+ (float)(_matrix[1, 0] * v.X + _matrix[1, 1] * v.Y + _matrix[1, 2] * v.Z),
+ (float)(_matrix[2, 0] * v.X + _matrix[2, 1] * v.Y + _matrix[2, 2] * v.Z));
+ }
+
+ public double Determinant()
+ {
+ return _matrix[0, 0] * (_matrix[1, 1] * _matrix[2, 2] - _matrix[1, 2] * _matrix[2, 1]) -
+ _matrix[0, 1] * (_matrix[1, 0] * _matrix[2, 2] - _matrix[1, 2] * _matrix[2, 0]) +
+ _matrix[0, 2] * (_matrix[1, 0] * _matrix[2, 1] - _matrix[1, 1] * _matrix[2, 0]);
+ }
+
+ public Matrix3x3 Inverse()
+ {
+ double det = Determinant();
+ if (Math.Abs(det) < 1e-7)
+ throw new InvalidOperationException("Non-invertible matrix");
+
+ double[,] result = new double[3, 3];
+
+ result[0, 0] = (_matrix[1, 1] * _matrix[2, 2] - _matrix[1, 2] * _matrix[2, 1]) / det;
+ result[0, 1] = (_matrix[0, 2] * _matrix[2, 1] - _matrix[0, 1] * _matrix[2, 2]) / det;
+ result[0, 2] = (_matrix[0, 1] * _matrix[1, 2] - _matrix[0, 2] * _matrix[1, 1]) / det;
+ result[1, 0] = (_matrix[1, 2] * _matrix[2, 0] - _matrix[1, 0] * _matrix[2, 2]) / det;
+ result[1, 1] = (_matrix[0, 0] * _matrix[2, 2] - _matrix[0, 2] * _matrix[2, 0]) / det;
+ result[1, 2] = (_matrix[0, 2] * _matrix[1, 0] - _matrix[0, 0] * _matrix[1, 2]) / det;
+ result[2, 0] = (_matrix[1, 0] * _matrix[2, 1] - _matrix[1, 1] * _matrix[2, 0]) / det;
+ result[2, 1] = (_matrix[0, 1] * _matrix[2, 0] - _matrix[0, 0] * _matrix[2, 1]) / det;
+ result[2, 2] = (_matrix[0, 0] * _matrix[1, 1] - _matrix[0, 1] * _matrix[1, 0]) / det;
+
+ return new Matrix3x3(result);
+ }
+
+ public double Trace()
+ {
+ return _matrix[0, 0] + _matrix[1, 1] + _matrix[2, 2];
+ }
+
+ public Vector3 Column(int i)
+ {
+ return new Vector3((float)_matrix[0, i], (float)_matrix[1, i], (float)_matrix[2, i]);
+ }
+
+ public bool HasNaN()
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ if (double.IsNaN(_matrix[i, j]))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Core/Utilities/README.md b/Sources/Everglow.Core/Utilities/README.md
new file mode 100644
index 000000000..8858e84fc
--- /dev/null
+++ b/Sources/Everglow.Core/Utilities/README.md
@@ -0,0 +1,19 @@
+# Utils简介
+一些与Tr无关的常用函数放在这里,目前我准备大致分为以下几类
+目前firefly分支未完全合并,同时暂未想到很常用的方法,所以内容先搁置了()
+如果以后某个Utils多了会考虑继续细分,比如几何处理,物理模拟等
+
+## MathUtils
+一些常用的数学计算,处理等
+
+## XNAUtils
+对于XNA一些方法的进一步包装,扩展等
+
+## CSUtils
+对于System的一些方法的包装扩展,比如一堆麻烦的反射什么的()
+
+## Matrix2x2
+包含2x2矩阵的实现,支持常用矩阵操作,包括转置、求逆、迹、旋转等等
+
+## Matrix3x3
+包含2x2矩阵的实现,支持常用矩阵操作,包括转置、求逆、迹、旋转等等
diff --git a/Sources/Everglow.Core/Utilities/Utils.md b/Sources/Everglow.Core/Utilities/Utils.md
deleted file mode 100644
index d5b68ab97..000000000
--- a/Sources/Everglow.Core/Utilities/Utils.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Utils���
-һЩ��Tr�صij��ú����������Ŀǰ�������·�Ϊ���¼���
-Ŀǰfirefly��֧δ��ȫ�ϲ���ͬʱ��δ�뵽�ܳ��õķ��������������ȸ����ˣ���
-����Ժ�ij��Utils���˻ῼ�Ǽ���ϸ�֣����缸�δ���������ģ���
-
-## MathUtils
-һЩ���õ���ѧ���㣬������
-
-## XNAUtils
-����XNAһЩ�����Ľ�һ����װ����չ��
-
-## CSUtils
-����System��һЩ�����İ�װ��չ������һ���鷳�ķ���ʲô�ģ���
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BVHDetect.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BVHDetect.cs
new file mode 100644
index 000000000..f846361e8
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BVHDetect.cs
@@ -0,0 +1,173 @@
+using Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure;
+using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Terraria.GameContent;
+using Terraria;
+using Everglow.Commons.Physics.PBEngine.Core;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase
+{
+ ///
+ /// 使用BVH辅助加速碰撞检测
+ ///
+ public class BVHDetect : BroadPhase
+ {
+ private List _colliders;
+ private Dictionary> _groups;
+ private Dictionary _bvhForGroup;
+ public BVHDetect(CollisionGraph graph) : base(graph)
+ {
+ _colliders = new List();
+ _groups = new Dictionary>();
+ _bvhForGroup = new Dictionary();
+ }
+
+ private void GroupInnerDetection(string name,
+ List> collisionResults)
+ {
+ var pairs = _bvhForGroup[name].QueryPairs();
+ foreach (var pair in pairs)
+ {
+ collisionResults.Add(new KeyValuePair(_colliders[pair.Key],
+ _colliders[pair.Value]));
+ }
+ }
+
+ private void GroupOuterDetection(string groupA, string groupB,
+ List> collisionResults)
+ {
+ var groupAEntries = _groups[groupA];
+ var groupBBVH = _bvhForGroup[groupB];
+ for (int i = 0; i < groupAEntries.Count; i++)
+ {
+ foreach (var id in groupBBVH.QueryRange(groupAEntries[i].BoundingBox))
+ {
+ if (groupAEntries[i].IsDynamic || _colliders[id].ParentObject.RigidBody.MovementType == MovementType.Player ||
+ _colliders[id].ParentObject.RigidBody.MovementType == MovementType.Dynamic)
+ {
+ collisionResults.Add(new KeyValuePair(_colliders[groupAEntries[i].ColliderId],
+ _colliders[id]));
+ }
+ }
+ }
+ }
+
+ public override List> GetCollisionPairs(float deltaTime)
+ {
+ List> finalPairs = new List>();
+ foreach (var group in _groups)
+ {
+ var thisgroup = _groups[group.Key];
+ if (_collisionGraph.Graph.ContainsKey(group.Key))
+ {
+ foreach (var dual in _collisionGraph.Graph[group.Key])
+ {
+ if (dual == group.Key)
+ {
+ GroupInnerDetection(group.Key, finalPairs);
+ continue;
+ }
+ if (!_groups.ContainsKey(dual))
+ continue;
+ GroupOuterDetection(group.Key, dual, finalPairs);
+ }
+ }
+ }
+ return finalPairs;
+ }
+
+ public override void Prepare(List objects, float deltaTime)
+ {
+ _colliders.Clear();
+ _colliders.EnsureCapacity(objects.Count);
+ _groups.Clear();
+ _bvhForGroup.Clear();
+ for (int i = 0; i < objects.Count; i++)
+ {
+ var obj = objects[i];
+ _colliders.Add(obj.Collider);
+ var entry = new ColliderEntry()
+ {
+ ColliderId = i,
+ BoundingBox = obj.Collider.GetAABB(deltaTime),
+ IsDynamic = obj.RigidBody.MovementType == MovementType.Dynamic || obj.RigidBody.MovementType == MovementType.Player
+ };
+
+ if (_groups.ContainsKey(obj.Tag))
+ {
+ _groups[obj.Tag].Add(entry);
+ }
+ else
+ {
+ _groups.Add(obj.Tag, new List()
+ {
+ entry
+ });
+ }
+ }
+ foreach (var group in _groups)
+ {
+ _bvhForGroup.Add(group.Key, new BVH(group.Value));
+ }
+ }
+
+ public override List GetSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ List colliders = new List();
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+ var bvhThisGroup = _bvhForGroup[tag];
+
+ foreach (var id in bvhThisGroup.QueryRange(aabb))
+ {
+ colliders.Add(_colliders[id]);
+ }
+ }
+ return colliders;
+ }
+
+ public override void DrawDebugInfo(SpriteBatch sb)
+ {
+ if (!_groups.ContainsKey("Default"))
+ return;
+ //var data = _bvhForGroup["Default"].GetProfilingData();
+ //sb.Begin();
+ //foreach (var entry in data)
+ //{
+ // int x = (int)(entry.GridBox.MinPoint.X - Main.screenPosition.X);
+ // int y = (int)(-entry.GridBox.MaxPoint.Y - Main.screenPosition.Y);
+ // int w = (int)(entry.GridBox.MaxPoint.X - entry.GridBox.MinPoint.X);
+ // int h = (int)(entry.GridBox.MaxPoint.Y - entry.GridBox.MinPoint.Y);
+ // sb.Draw(TextureAssets.MagicPixel.Value, new Rectangle(x, y, w, h), Color.White * MathHelper.Lerp(0.1f, 0.1f, entry.Layer / 32f));
+ //}
+
+ //sb.End();
+ }
+
+ public override bool TestSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+ var bvhThisGroup = _bvhForGroup[tag];
+
+ if (bvhThisGroup.QueryRange(aabb).Count > 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BroadPhase.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BroadPhase.cs
new file mode 100644
index 000000000..b3ffe925d
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BroadPhase.cs
@@ -0,0 +1,33 @@
+using Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure;
+using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+using Everglow.Commons.Physics.PBEngine.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase
+{
+ ///
+ /// 粗粒度碰撞检测的基类
+ ///
+ public abstract class BroadPhase
+ {
+ protected CollisionGraph _collisionGraph;
+ public BroadPhase(CollisionGraph graph)
+ {
+ _collisionGraph = graph;
+ }
+ public abstract void Prepare(List objects, float deltaTime);
+
+ public abstract List> GetCollisionPairs(float deltaTime);
+
+ public abstract List GetSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags);
+
+ public abstract bool TestSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags);
+
+
+ public abstract void DrawDebugInfo(SpriteBatch sb);
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BruteForceDetect.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BruteForceDetect.cs
new file mode 100644
index 000000000..820fb24e4
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/BruteForceDetect.cs
@@ -0,0 +1,162 @@
+using Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure;
+using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+using Everglow.Commons.Physics.PBEngine.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase
+{
+ ///
+ /// 暴力遍历AABB的粗粒度碰撞检测算法
+ ///
+ public class BruteForceDetect : BroadPhase
+ {
+ private List _colliders;
+ private Dictionary> _groups;
+ public BruteForceDetect(CollisionGraph graph) : base(graph)
+ {
+ _colliders = new List();
+ _groups = new Dictionary>();
+ }
+
+ private void GroupInnerDetection(List entries,
+ List> collisionResults)
+ {
+ for (int i = 0; i < entries.Count; i++)
+ {
+ for (int j = i + 1; j < entries.Count; j++)
+ {
+ if ((entries[i].IsDynamic || entries[j].IsDynamic)
+ && entries[i].BoundingBox.Intersects(entries[j].BoundingBox))
+ {
+ collisionResults.Add(new KeyValuePair(_colliders[entries[i].ColliderId],
+ _colliders[entries[j].ColliderId]));
+ }
+ }
+ }
+ }
+
+ private void GroupOuterDetection(List groupA,
+ List groupB,
+ List> collisionResults)
+ {
+ for (int i = 0; i < groupA.Count; i++)
+ {
+ for (int j = 0; j < groupB.Count; j++)
+ {
+ if ((groupA[i].IsDynamic || groupB[j].IsDynamic)
+ && groupA[i].BoundingBox.Intersects(groupB[j].BoundingBox))
+ {
+ collisionResults.Add(new KeyValuePair(_colliders[groupA[i].ColliderId],
+ _colliders[groupB[j].ColliderId]));
+ }
+ }
+ }
+ }
+
+ public override List> GetCollisionPairs(float deltaTime)
+ {
+ List> finalPairs = new List>();
+ foreach (var group in _groups)
+ {
+ var thisgroup = _groups[group.Key];
+
+ if (_collisionGraph.Graph.ContainsKey(group.Key))
+ {
+ foreach (var dual in _collisionGraph.Graph[group.Key])
+ {
+ if (dual == group.Key)
+ {
+ GroupInnerDetection(thisgroup, finalPairs);
+ continue;
+ }
+ if (!_groups.ContainsKey(dual))
+ continue;
+ GroupOuterDetection(thisgroup, _groups[dual], finalPairs);
+ }
+ }
+ }
+ return finalPairs;
+ }
+
+ public override void Prepare(List objects, float deltaTime)
+ {
+ _colliders.Clear();
+ _colliders.EnsureCapacity(objects.Count);
+ _groups.Clear();
+ for (int i = 0; i < objects.Count; i++)
+ {
+ var obj = objects[i];
+ _colliders.Add(obj.Collider);
+ var entry = new ColliderEntry()
+ {
+ ColliderId = i,
+ BoundingBox = obj.Collider.GetAABB(deltaTime),
+ IsDynamic = obj.RigidBody.MovementType == MovementType.Dynamic || obj.RigidBody.MovementType == MovementType.Player
+ };
+
+ if (_groups.ContainsKey(obj.Tag))
+ {
+ _groups[obj.Tag].Add(entry);
+ }
+ else
+ {
+ _groups.Add(obj.Tag, new List()
+ {
+ entry
+ });
+ }
+ }
+ }
+ public override List GetSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ List colliders = new List();
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+ var thisgroup = _groups[tag];
+
+ foreach (var entry in thisgroup)
+ {
+ if (entry.BoundingBox.Intersects(aabb))
+ {
+ colliders.Add(_colliders[entry.ColliderId]);
+ }
+ }
+ }
+ return colliders;
+ }
+
+ public override void DrawDebugInfo(SpriteBatch sb)
+ {
+
+ }
+
+ public override bool TestSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+ var thisgroup = _groups[tag];
+
+ foreach (var entry in thisgroup)
+ {
+ if (entry.BoundingBox.Intersects(aabb))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/HashGridMethod.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/HashGridMethod.cs
new file mode 100644
index 000000000..449f866e2
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/HashGridMethod.cs
@@ -0,0 +1,159 @@
+using Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure;
+using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+using Everglow.Commons.Physics.PBEngine.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Terraria;
+using Terraria.GameContent;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase
+{
+ internal class HashGridMethod : BroadPhase
+ {
+ private List _colliders;
+ private Dictionary _groups;
+ private int _curHashGridSize;
+ private int _prevHashGridSize;
+ private long _prevPairTime;
+ private long _prev2PairTime;
+
+ public HashGridMethod(CollisionGraph graph) : base(graph)
+ {
+ _colliders = new List();
+ _groups = new Dictionary();
+ _prevHashGridSize = 256;
+ _curHashGridSize = 256;
+
+ _prev2PairTime = long.MaxValue;
+ _prevPairTime = long.MinValue;
+ }
+
+ public override void DrawDebugInfo(SpriteBatch sb)
+ {
+ if (!_groups.ContainsKey("Default"))
+ return;
+ //var data = _groups["Default"].GetProfilingData();
+ //sb.Begin();
+ //foreach (var entry in data)
+ //{
+ // int x = (int)(entry.GridBox.MinPoint.X - Main.screenPosition.X);
+ // int y = (int)(-entry.GridBox.MaxPoint.Y - Main.screenPosition.Y);
+ // int w = (int)(entry.GridBox.MaxPoint.X - entry.GridBox.MinPoint.X);
+ // int h = (int)(entry.GridBox.MaxPoint.Y - entry.GridBox.MinPoint.Y);
+ // sb.Draw(TextureAssets.MagicPixel.Value, new Rectangle(x, y, w, h), Color.White * MathHelper.Lerp(0.1f, 0.5f, entry.NumObjects / 64f));
+ //}
+
+ //sb.End();
+ }
+
+ public override List> GetCollisionPairs(float deltaTime)
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ List> finalPairs = new List>();
+ foreach (var group in _groups)
+ {
+ var thisgroup = _groups[group.Key];
+
+ if (_collisionGraph.Graph.ContainsKey(group.Key))
+ {
+ foreach (var dual in _collisionGraph.Graph[group.Key])
+ {
+ if (dual == group.Key)
+ {
+ foreach (var pair in thisgroup.QueryPairs())
+ {
+ finalPairs.Add(new KeyValuePair(_colliders[pair.Key],
+ _colliders[pair.Value]));
+ }
+ continue;
+ }
+ if (!_groups.ContainsKey(dual))
+ continue;
+ foreach (var pair in thisgroup.QueryPairsWith(_groups[dual]))
+ {
+ finalPairs.Add(new KeyValuePair(_colliders[pair.Key],
+ _colliders[pair.Value]));
+ }
+ }
+ }
+ }
+ sw.Stop();
+ _prev2PairTime = _prevPairTime;
+ _prevPairTime = sw.ElapsedTicks;
+ return finalPairs;
+ }
+
+ public override List GetSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ List colliders = new List();
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+
+ foreach (var id in _groups[tag].QueryRange(aabb))
+ {
+ colliders.Add(_colliders[id]);
+ }
+ }
+ return colliders;
+ }
+
+
+ public override void Prepare(List objects, float deltaTime)
+ {
+ _colliders.Clear();
+ _colliders.EnsureCapacity(objects.Count);
+ _groups.Clear();
+
+ for (int i = 0; i < objects.Count; i++)
+ {
+ var obj = objects[i];
+ _colliders.Add(obj.Collider);
+ var entry = new ColliderEntry()
+ {
+ ColliderId = i,
+ BoundingBox = obj.Collider.GetAABB(deltaTime),
+ IsDynamic = obj.RigidBody.MovementType == MovementType.Dynamic || obj.RigidBody.MovementType == MovementType.Player
+ };
+
+ if (!_groups.ContainsKey(obj.Tag))
+ {
+ _groups.Add(obj.Tag, new MultiLevelHashGrid(4, new int[]
+ {
+ 1024,
+ 256,
+ 64,
+ 16
+ }));
+
+ }
+ _groups[obj.Tag].Add(entry);
+ }
+
+ // _prevHashGridSize = _curHashGridSize;
+ }
+
+ public override bool TestSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+ {
+ foreach (var tag in targetTags)
+ {
+ if (!_groups.ContainsKey(tag))
+ {
+ continue;
+ }
+
+ if (_groups[tag].QueryRange(aabb).Count > 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/BVH.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/BVH.cs
new file mode 100644
index 000000000..e982229f0
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/BVH.cs
@@ -0,0 +1,399 @@
+using Everglow.Commons.Physics.PBEngine.Collision;
+using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+using MonoMod.Cil;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure
+{
+ public struct ProfilingDataBVH
+ {
+ public AABB GridBox;
+ public int Layer;
+ }
+ public class BVHNode
+ {
+ public AABB Box
+ {
+ get; set;
+ }
+ public BVHNode Left
+ {
+ get; set;
+ }
+ public BVHNode Right
+ {
+ get; set;
+ }
+ public int SplitIndex
+ {
+ get; set;
+ }
+ public List Colliders
+ {
+ get; set;
+ }
+ public bool Checked
+ {
+ get; set;
+ }
+
+ public BVHNode()
+ {
+ Colliders = new List();
+ Left = null;
+ Right = null;
+ SplitIndex = -1;
+ Checked = false;
+ }
+ }
+
+ public class BVH
+ {
+ public BVHNode Root
+ {
+ get; private set;
+ }
+
+ private List _colliders;
+ private List> _pairQueryCache;
+
+ private const int MAX_COLLIDERS_PER_NODE = 1;
+
+ public BVH(List colliders)
+ {
+ _colliders = colliders;
+ _pairQueryCache = new List>();
+ Root = _build(0, _colliders.Count - 1);
+
+ //foreach (ColliderEntry collider in _colliders)
+ //{
+ // Insert(collider);
+ //}
+ }
+
+
+ public void Insert(in ColliderEntry entry)
+ {
+ if (Root == null)
+ {
+ Root = new BVHNode()
+ {
+ Box = entry.BoundingBox,
+ Colliders = new List { entry },
+ };
+ return;
+ }
+ _insert(Root, entry);
+ }
+
+ private void _insert(BVHNode node, in ColliderEntry entry)
+ {
+ if (node.Colliders.Count == 0)
+ {
+ var newLeftBox = node.Left.Box.Merge(entry.BoundingBox);
+ var newRightBox = node.Right.Box.Merge(entry.BoundingBox);
+ if (newLeftBox.GetArea() - node.Left.Box.GetArea()
+ < newRightBox.GetArea() - node.Right.Box.GetArea())
+ {
+ _insert(node.Left, entry);
+ }
+ else
+ {
+ _insert(node.Right, entry);
+ }
+ node.Box = node.Left.Box.Merge(node.Right.Box);
+ }
+ else
+ {
+ node.Left = new BVHNode()
+ {
+ Box = node.Colliders[0].BoundingBox,
+ Colliders = new List() { node.Colliders[0] }
+ };
+
+ node.Right = new BVHNode()
+ {
+ Box = entry.BoundingBox,
+ Colliders = new List() { entry }
+ };
+
+ node.Colliders.Clear();
+ node.Box = node.Left.Box.Merge(node.Right.Box);
+ }
+ }
+
+ private BVHNode _build(int l, int r)
+ {
+ BVHNode node = new BVHNode();
+ if (r - l + 1 <= MAX_COLLIDERS_PER_NODE)
+ {
+ node.Colliders = _colliders.GetRange(l, r - l + 1);
+ AABB aabb = new AABB();
+ for (int i = l; i <= r; i++)
+ {
+ aabb.MergeWith(_colliders[i].BoundingBox);
+ }
+ node.Box = aabb;
+ return node;
+ }
+
+ AABB fullBox = new AABB();
+ for (int i = l; i <= r; i++)
+ {
+ fullBox.MergeWith(_colliders[i].BoundingBox);
+ }
+
+ var sortByX = Comparer.Create((a, b) =>
+ {
+ return a.BoundingBox.Center.X.CompareTo(b.BoundingBox.Center.X);
+ });
+ var sortByY = Comparer.Create((a, b) =>
+ {
+ return a.BoundingBox.Center.Y.CompareTo(b.BoundingBox.Center.Y);
+ });
+ Comparer compFunc = (fullBox.MaxPoint.Y - fullBox.MinPoint.Y) > (fullBox.MaxPoint.X - fullBox.MinPoint.X)
+ ? sortByY : sortByX;
+ _colliders.Sort(l, r - l + 1, compFunc);
+
+ int mid = l + (r - l) / 2;
+
+ node.Left = _build(l, mid);
+ node.Right = _build(mid + 1, r);
+ node.Box = node.Left.Box.Merge(node.Right.Box);
+ node.SplitIndex = mid;
+ return node;
+ }
+
+ private void _clearFlags(BVHNode node)
+ {
+ if (node == null)
+ return;
+ node.Checked = false;
+ _clearFlags(node.Left);
+ _clearFlags(node.Right);
+ }
+
+ private void _innerPairQuery(BVHNode node)
+ {
+ if (node == null)
+ return;
+ if (node.Colliders.Count != 0)
+ {
+ int sz = node.Colliders.Count;
+ for (int i = 0; i < sz; i++)
+ {
+ for (int j = i + 1; j < sz; j++)
+ {
+ if ((node.Colliders[i].IsDynamic || node.Colliders[j].IsDynamic)
+ && node.Colliders[i].BoundingBox.Intersects(node.Colliders[j].BoundingBox))
+ {
+ _pairQueryCache.Add(new KeyValuePair(node.Colliders[i].ColliderId,
+ node.Colliders[j].ColliderId));
+ }
+ }
+ }
+ return;
+ }
+ _innerPairQuery(node.Left);
+ _innerPairQuery(node.Right);
+ }
+ private void _outerPairQuery(BVHNode node1, BVHNode node2)
+ {
+ int sz1 = node1.Colliders.Count;
+ int sz2 = node2.Colliders.Count;
+ for (int i = 0; i < sz1; i++)
+ {
+ for (int j = 0; j < sz2; j++)
+ {
+ if ((node1.Colliders[i].IsDynamic || node2.Colliders[j].IsDynamic)
+ && node1.Colliders[i].BoundingBox.Intersects(node2.Colliders[j].BoundingBox))
+ {
+ _pairQueryCache.Add(new KeyValuePair(node1.Colliders[i].ColliderId,
+ node2.Colliders[j].ColliderId));
+ }
+ }
+ }
+ }
+
+ private void _queryPairHelper(BVHNode node1, BVHNode node2)
+ {
+
+ if (node1.Colliders.Count > 0 && node2.Colliders.Count > 0)
+ {
+ _outerPairQuery(node1, node2);
+ return;
+ }
+ if (node1.Colliders.Count > 0 && node2.Colliders.Count == 0)
+ {
+ if (node1.Box.Intersects(node2.Left.Box))
+ {
+ _queryPairHelper(node1, node2.Left);
+ }
+ if (node1.Box.Intersects(node2.Right.Box))
+ {
+ _queryPairHelper(node1, node2.Right);
+ }
+ if (!node2.Checked)
+ {
+ node2.Checked = true;
+ _queryPairHelper(node2.Left, node2.Right);
+ }
+ return;
+ }
+ if (node2.Colliders.Count > 0 && node1.Colliders.Count == 0)
+ {
+ if (node2.Box.Intersects(node1.Left.Box))
+ {
+ _queryPairHelper(node2, node1.Left);
+ }
+ if (node2.Box.Intersects(node1.Right.Box))
+ {
+ _queryPairHelper(node2, node1.Right);
+ }
+ if (!node1.Checked)
+ {
+ node1.Checked = true;
+ _queryPairHelper(node1.Left, node1.Right);
+ }
+ return;
+ }
+
+ if (node1.Box.Intersects(node2.Box))
+ {
+ _queryPairHelper(node1.Left, node2.Left);
+ _queryPairHelper(node1.Right, node2.Left);
+ _queryPairHelper(node1.Left, node2.Right);
+ _queryPairHelper(node1.Right, node2.Right);
+ }
+ if (!node1.Checked)
+ {
+ node1.Checked = true;
+ _queryPairHelper(node1.Left, node1.Right);
+ }
+ if (!node2.Checked)
+ {
+ node2.Checked = true;
+ _queryPairHelper(node2.Left, node2.Right);
+ }
+
+ }
+
+ private void _queryPair(BVHNode node, int l, int r)
+ {
+ // If is a leaf node
+ if (node.Colliders.Count > 0)
+ {
+ for (int i = 0; i < node.Colliders.Count; i++)
+ {
+ for (int j = i + 1; j < node.Colliders.Count; j++)
+ {
+ if ((node.Colliders[i].IsDynamic || node.Colliders[j].IsDynamic)
+ && node.Colliders[i].BoundingBox.Intersects(node.Colliders[j].BoundingBox))
+ {
+ _pairQueryCache.Add(new KeyValuePair(node.Colliders[i].ColliderId,
+ node.Colliders[j].ColliderId));
+ }
+ }
+ }
+ return;
+ }
+ _queryPairHelper(node.Left, node.Right);
+ //_queryPair(node.Left, l, node.SplitIndex);
+ //_queryPair(node.Right, node.SplitIndex + 1, r);
+
+ //if (node.Left.Box.Intersects(node.Right.Box))
+ //{
+ // for (int i = l; i <= node.SplitIndex; i++)
+ // {
+ // for (int j = node.SplitIndex + 1; j <= r; j++)
+ // {
+ // if ((_colliders[i].IsDynamic || _colliders[j].IsDynamic) &&
+ // _colliders[i].BoundingBox.Intersects(_colliders[j].BoundingBox))
+ // {
+ // _pairQueryCache.Add(new KeyValuePair(_colliders[i].ColliderId,
+ // _colliders[j].ColliderId));
+ // }
+ // }
+ // }
+ //}
+ }
+
+ public List> QueryPairs()
+ {
+ _pairQueryCache.Clear();
+ if (_colliders.Count == 0)
+ {
+ return _pairQueryCache;
+ }
+ _clearFlags(Root);
+ _innerPairQuery(Root);
+ _queryPair(Root, 0, _colliders.Count - 1);
+ return _pairQueryCache;
+ }
+
+ public List QueryRange(in AABB targetRange)
+ {
+ List result = new List();
+ if (!targetRange.Intersects(Root.Box))
+ return result;
+
+ Stack stack = new Stack();
+ stack.Push(Root);
+
+ while (stack.Count > 0)
+ {
+ var cur = stack.Pop();
+ if (!cur.Box.Intersects(targetRange))
+ {
+ continue;
+ }
+ if (cur.Colliders.Count != 0)
+ {
+ foreach (var collider in cur.Colliders)
+ {
+ if (collider.BoundingBox.Intersects(targetRange))
+ {
+ result.Add(collider.ColliderId);
+ }
+ }
+ }
+ else
+ {
+ if (cur.Left.Box.Intersects(targetRange))
+ {
+ stack.Push(cur.Left);
+ }
+ if (cur.Right.Box.Intersects(targetRange))
+ {
+ stack.Push(cur.Right);
+ }
+ }
+ }
+ return result;
+ }
+
+ private void _getProfilingData(BVHNode node, int layer, List result)
+ {
+ if(node == null) return;
+ result.Add(new ProfilingDataBVH()
+ {
+ GridBox = node.Box,
+ Layer = layer,
+ });
+
+ _getProfilingData(node.Left, layer + 1, result);
+ _getProfilingData(node.Right, layer + 1, result);
+ }
+ public List GetProfilingData()
+ {
+ List result = new List();
+ _getProfilingData(Root, 0, result);
+ return result;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/ColliderEntry.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/ColliderEntry.cs
new file mode 100644
index 000000000..76b4c2a68
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/ColliderEntry.cs
@@ -0,0 +1,9 @@
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure
+{
+ public struct ColliderEntry
+ {
+ public int ColliderId;
+ public AABB BoundingBox;
+ public bool IsDynamic;
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/CollisionGraph.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/CollisionGraph.cs
new file mode 100644
index 000000000..794010346
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/CollisionGraph.cs
@@ -0,0 +1,60 @@
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure
+{
+ ///
+ /// 存储碰撞组的碰撞关系的图结构
+ ///
+ public class CollisionGraph
+ {
+ public static CollisionGraph DefaultGraph
+ = new CollisionGraph(new Dictionary>() { { "Default", new List() { "Default" } } });
+
+ public Dictionary> Graph
+ {
+ get;
+ set;
+ }
+
+ public CollisionGraph()
+ {
+ Graph = new Dictionary>();
+ }
+
+ public CollisionGraph(Dictionary> graph)
+ {
+ Graph = graph;
+ }
+
+ public void AddDoubleEdge(string groupA, string groupB)
+ {
+ if (groupA == groupB)
+ {
+ return;
+ }
+
+ // Ensure only compare once
+ if (string.CompareOrdinal(groupA, groupB) < 0)
+ {
+ AddSingleEdge(groupA, groupB);
+ }
+ else
+ {
+ AddSingleEdge(groupB, groupA);
+ }
+ }
+
+ public void AddSingleEdge(string groupA, string groupB)
+ {
+ if (Graph.ContainsKey(groupA))
+ {
+ Graph[groupA].Add(groupB);
+ }
+ else
+ {
+ Graph.Add(groupA, new List()
+ {
+ groupB,
+ });
+ }
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/MultiLevelHashGrid.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/MultiLevelHashGrid.cs
new file mode 100644
index 000000000..88df0d935
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/Structure/MultiLevelHashGrid.cs
@@ -0,0 +1,431 @@
+using Everglow.Commons.Physics.PBEngine.Collision;
+using log4net.Core;
+using Microsoft.Xna.Framework;
+using Steamworks;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO.Pipes;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase.Structure
+{
+ public struct ProfilingData
+ {
+ public AABB GridBox;
+ public int NumObjects;
+ }
+ public class MultiLevelHashGrid
+ {
+ private class HGrid
+ {
+ public List Entries;
+
+
+ public HGrid()
+ {
+ Entries = new List();
+ }
+
+ public List> GetPairs()
+ {
+ List> pairs = new List>();
+
+ for (int i = 0; i < Entries.Count; i++)
+ {
+ for (int j = i + 1; j < Entries.Count; j++)
+ {
+ if (Entries[i].BoundingBox.Intersects(Entries[j].BoundingBox)
+ && (Entries[i].IsDynamic || Entries[j].IsDynamic))
+ {
+ // 序号小的在前面,这样判重方便一点
+ if (Entries[i].ColliderId < Entries[j].ColliderId)
+ {
+ pairs.Add(new KeyValuePair(Entries[i].ColliderId, Entries[j].ColliderId));
+ }
+ else
+ {
+ pairs.Add(new KeyValuePair(Entries[j].ColliderId, Entries[i].ColliderId));
+ }
+ }
+ }
+ }
+ return pairs;
+ }
+
+ public List> GetPairsWith(HGrid grid)
+ {
+ List> pairs = new List>();
+
+ for (int i = 0; i < Entries.Count; i++)
+ {
+ for (int j = 0; j < grid.Entries.Count; j++)
+ {
+ if (Entries[i].BoundingBox.Intersects(grid.Entries[j].BoundingBox)
+ && (Entries[i].IsDynamic || grid.Entries[j].IsDynamic))
+ {
+ pairs.Add(new KeyValuePair(Entries[i].ColliderId, grid.Entries[j].ColliderId));
+ }
+ }
+ }
+ return pairs;
+ }
+
+ public List QueryRange(in AABB aabb)
+ {
+ List result = new List();
+ for (int i = 0; i < Entries.Count; i++)
+ {
+ if (Entries[i].BoundingBox.Intersects(aabb))
+ {
+ result.Add(Entries[i].ColliderId);
+ }
+ }
+ return result;
+ }
+ }
+
+ private int _maxLevels;
+ private int _maxOccupiedGridsPerLevel;
+ private int[] _sizeThisLevel;
+ private Dictionary[] _multiGrids;
+ public MultiLevelHashGrid(int maxGridsPerObjectPerLevel, int[] sizesEachLevel)
+ {
+ _maxLevels = sizesEachLevel.Length;
+ _maxOccupiedGridsPerLevel = maxGridsPerObjectPerLevel;
+ _sizeThisLevel = new int[_maxLevels + 1];
+ _sizeThisLevel[0] = 0;
+ for(int i = 1; i <= _maxLevels; i++)
+ {
+ _sizeThisLevel[i] = sizesEachLevel[i - 1];
+ }
+ _multiGrids = new Dictionary[_maxLevels + 1];
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ _multiGrids[i] = new Dictionary();
+ }
+ }
+
+ public void AddRange(List entries)
+ {
+ foreach (var entry in entries)
+ {
+ int level = FindMatchingLevel(entry.BoundingBox);
+ foreach (var grid in OccupiedRange(entry.BoundingBox, level))
+ {
+ Insert(level, grid, entry);
+ }
+ }
+ }
+
+ public void Add(ColliderEntry entry)
+ {
+ int level = FindMatchingLevel(entry.BoundingBox);
+ foreach (var grid in OccupiedRange(entry.BoundingBox, level))
+ {
+ Insert(level, grid, entry);
+ }
+ }
+
+ public List QueryRange(in AABB aabb)
+ {
+ List results = new List(1024);
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ results.AddRange(QueryRange_Level(aabb, i));
+ }
+ return results;
+ }
+
+
+ private List QueryRange_Level(in AABB aabb, int level)
+ {
+ List results = new List();
+ if (level == 0)
+ {
+ if (_multiGrids[0].ContainsKey(new Point(0, 0)))
+ {
+ // 和大物体的碰撞
+ foreach (var largeEntry in _multiGrids[0][new Point(0, 0)].Entries)
+ {
+ if (largeEntry.BoundingBox.Intersects(aabb))
+ {
+ results.Add(largeEntry.ColliderId);
+ }
+ }
+ }
+ return results;
+ }
+
+ if (aabb.GetArea() < (double)_sizeThisLevel[level] * _sizeThisLevel[level] * _multiGrids[level].Count)
+ {
+ foreach (var grid in OccupiedRange(aabb, level))
+ {
+ if (_multiGrids[level].ContainsKey(grid))
+ {
+ var selfAABB = GetGridAABB(grid, level);
+ if (selfAABB.CompletelyInside(aabb))
+ {
+ results.AddRange(_multiGrids[level][grid].Entries.Select(x => x.ColliderId).ToList());
+ }
+ else
+ {
+ results.AddRange(_multiGrids[level][grid].QueryRange(aabb));
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (var gridpair in _multiGrids[level])
+ {
+ var selfAABB = GetGridAABB(gridpair.Key, level);
+ if (selfAABB.Intersects(aabb))
+ {
+ if (selfAABB.CompletelyInside(aabb))
+ {
+ results.AddRange(gridpair.Value.Entries.Select(x => x.ColliderId).ToList());
+ }
+ else
+ {
+ results.AddRange(gridpair.Value.QueryRange(aabb));
+ }
+ }
+ }
+ }
+ return results;
+ }
+
+ public List> QueryPairs()
+ {
+ List> pairs = new List>(1024);
+
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ foreach (var pair in _multiGrids[i])
+ {
+ pairs.AddRange(pair.Value.GetPairs());
+ }
+ }
+
+
+ for (int i = 1; i <= _maxLevels; i++)
+ {
+ // 和上层进行相互检测
+ for (int j = 0; j < i; j++)
+ {
+ if (_multiGrids[j].Count == 0)
+ {
+ continue;
+ }
+ foreach (var pair in _multiGrids[i])
+ {
+ var rp = new Point(0, 0);
+ var pos = new Point(pair.Key.X * _sizeThisLevel[i], pair.Key.Y * _sizeThisLevel[i]);
+ // 获取上层中当前格子对应位置
+ if (j != 0)
+ {
+ int X = (int)Math.Floor((float)pos.X / _sizeThisLevel[j]);
+ int Y = (int)Math.Floor((float)pos.Y / _sizeThisLevel[j]);
+ rp = new Point(X, Y);
+ }
+ if (_multiGrids[j].ContainsKey(rp))
+ {
+ pairs.AddRange(pair.Value.GetPairsWith(_multiGrids[j][rp]));
+ }
+ }
+ }
+ }
+ return pairs.Distinct().ToList();
+ }
+
+
+ public List> QueryPairsWith(MultiLevelHashGrid other)
+ {
+
+ List> pairs = new List>(1024);
+ // 尺寸相等格子
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ if (_multiGrids[i].Count < other._multiGrids[i].Count)
+ {
+ foreach (var pair in _multiGrids[i])
+ {
+ if (other._multiGrids[i].ContainsKey(pair.Key))
+ {
+ pairs.AddRange(pair.Value.GetPairsWith(other._multiGrids[i][pair.Key]));
+ }
+ }
+ }
+ else
+ {
+ foreach (var pair in other._multiGrids[i])
+ {
+ if (_multiGrids[i].ContainsKey(pair.Key))
+ {
+ pairs.AddRange(_multiGrids[i][pair.Key].GetPairsWith(pair.Value));
+ }
+ }
+ }
+ }
+
+ // 自己尺寸比对方尺寸大
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ for (int j = i + 1; j <= _maxLevels; j++)
+ {
+ foreach (var pair_other in other._multiGrids[j])
+ {
+ Point rp = new Point(0, 0);
+ if (i != 0)
+ {
+ var pos = new Point(pair_other.Key.X * _sizeThisLevel[j], pair_other.Key.Y * _sizeThisLevel[j]);
+ // 获取上层中当前格子对应位置
+ int X = (int)Math.Floor((float)pos.X / _sizeThisLevel[i]);
+ int Y = (int)Math.Floor((float)pos.Y / _sizeThisLevel[i]);
+ rp = new Point(X, Y);
+ }
+ if (_multiGrids[i].ContainsKey(rp))
+ {
+ pairs.AddRange(_multiGrids[i][rp].GetPairsWith(pair_other.Value));
+ }
+ }
+ }
+ }
+
+ // 自己尺寸比对方尺寸小
+ for (int i = 0; i <= _maxLevels; i++)
+ {
+ for (int j = i + 1; j <= _maxLevels; j++)
+ {
+ foreach (var pair_self in _multiGrids[j])
+ {
+ Point rp = new Point(0, 0);
+ if (i != 0)
+ {
+ var pos = new Point(pair_self.Key.X * _sizeThisLevel[j], pair_self.Key.Y * _sizeThisLevel[j]);
+ // 获取上层中当前格子对应位置
+ int X = (int)Math.Floor((float)pos.X / _sizeThisLevel[i]);
+ int Y = (int)Math.Floor((float)pos.Y / _sizeThisLevel[i]);
+ rp = new Point(X, Y);
+ }
+ if (other._multiGrids[i].ContainsKey(rp))
+ {
+ pairs.AddRange(pair_self.Value.GetPairsWith(other._multiGrids[i][rp]));
+ }
+ }
+ }
+ }
+ return pairs.Distinct().ToList();
+ }
+
+ private int FindMatchingLevel(in AABB aabb)
+ {
+ int ans = _maxLevels;
+ int l = 0, r = _maxLevels;
+ while (l <= r)
+ {
+ int mid = (l + r) >> 1;
+ if (CanFitInThisLevel(aabb, mid))
+ {
+ l = mid + 1;
+ ans = mid;
+ }
+ else
+ {
+ r = mid - 1;
+ }
+ }
+ return ans;
+ }
+
+ private void Insert(int level, Point pos, ColliderEntry entry)
+ {
+ if (!_multiGrids[level].ContainsKey(pos))
+ {
+ _multiGrids[level].Add(pos, new HGrid());
+ }
+
+ _multiGrids[level][pos].Entries.Add(entry);
+ }
+
+ private List OccupiedRange(in AABB aabb, int level)
+ {
+ if (level == 0)
+ {
+ return new List()
+ {
+ new Point(0, 0),
+ };
+ }
+ int minX = (int)Math.Floor(aabb.MinPoint.X / _sizeThisLevel[level]);
+ int maxX = (int)Math.Floor(aabb.MaxPoint.X / _sizeThisLevel[level]);
+ int minY = (int)Math.Floor(aabb.MinPoint.Y / _sizeThisLevel[level]);
+ int maxY = (int)Math.Floor(aabb.MaxPoint.Y / _sizeThisLevel[level]);
+
+ List points = new List();
+ for (int x = minX; x <= maxX; x++)
+ {
+ for (int y = minY; y <= maxY; y++)
+ {
+ points.Add(new Point(x, y));
+ }
+ }
+ return points;
+ }
+
+ private bool CanFitInThisLevel(in AABB aabb, int level)
+ {
+ if (level == 0)
+ {
+ return true;
+ }
+ int minX = (int)Math.Floor(aabb.MinPoint.X / _sizeThisLevel[level]);
+ int maxX = (int)Math.Floor(aabb.MaxPoint.X / _sizeThisLevel[level]);
+ int minY = (int)Math.Floor(aabb.MinPoint.Y / _sizeThisLevel[level]);
+ int maxY = (int)Math.Floor(aabb.MaxPoint.Y / _sizeThisLevel[level]);
+ return Math.Floor((aabb.MaxPoint.X - aabb.MinPoint.X) / _sizeThisLevel[level]) + 1 <= _maxOccupiedGridsPerLevel
+ && Math.Floor((aabb.MaxPoint.Y - aabb.MinPoint.Y) / _sizeThisLevel[level]) + 1 <= _maxOccupiedGridsPerLevel
+ && (maxX - minX + 1) * (maxY - minY + 1) <= _maxOccupiedGridsPerLevel;
+ }
+
+ private AABB GetGridAABB(Point p, int level)
+ {
+ if (level == 0)
+ {
+ // 0层无限大的AABB
+ return new AABB()
+ {
+ MinPoint = new Vector2(float.NegativeInfinity, float.NegativeInfinity),
+ MaxPoint = new Vector2(float.PositiveInfinity, float.PositiveInfinity)
+ };
+ }
+ int sz = _sizeThisLevel[level];
+ return new AABB()
+ {
+ MinPoint = new Vector2(p.X * sz, p.Y * sz),
+ MaxPoint = new Vector2(p.X * sz + sz, p.Y * sz + sz),
+ };
+ }
+
+ public List GetProfilingData()
+ {
+ List result = new List();
+ for (int i = 1; i <= _maxLevels; i++)
+ {
+ foreach (var pair in _multiGrids[i])
+ {
+ result.Add(new ProfilingData()
+ {
+ GridBox = GetGridAABB(pair.Key, i),
+ NumObjects = pair.Value.Entries.Count
+ });
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/SweepAndPrune.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/SweepAndPrune.cs
new file mode 100644
index 000000000..3eae70789
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/BroadPhase/SweepAndPrune.cs
@@ -0,0 +1,203 @@
+//using Everglow.Commons.Physics.PBEngine.Collision.Colliders;
+//using System;
+//using System.Collections.Generic;
+//using System.Linq;
+//using System.Text;
+//using System.Threading.Tasks;
+
+//namespace Everglow.Commons.Physics.PBEngine.Collision.BroadPhase
+//{
+// internal class SweepAndPrune : IBroadPhase
+// {
+// private struct IntervalPoint
+// {
+// public float V;
+// public bool IsEnd;
+// public int ColliderId;
+// public bool IsDynamic;
+// }
+
+// private class GroupInfo
+// {
+// public List IntervalPointsX;
+// public List IntervalPointsY;
+
+// public GroupInfo()
+// {
+// IntervalPointsX = new List();
+// IntervalPointsY = new List();
+// }
+// }
+
+// private List _colliders;
+// private Dictionary _groups;
+// public SweepAndPrune(CollisionGraph graph) : base(graph)
+// {
+// _colliders = new List();
+// _groups = new Dictionary();
+// }
+// private bool IsDynamic(int id)
+// {
+// return _colliders[id].ParentObject.RigidBody.MovementType == MovementType.Dynamic
+// || _colliders[id].ParentObject.RigidBody.MovementType == MovementType.Player;
+// }
+
+// public override void Prepare(List objects, float deltaTime)
+// {
+// _colliders.Clear();
+// _colliders.EnsureCapacity(objects.Count);
+// _groups.Clear();
+// for (int i = 0; i < objects.Count; i++)
+// {
+// var obj = objects[i];
+// _colliders.Add(obj.Collider);
+// var box = obj.Collider.GetAABB(deltaTime);
+// bool isDynamic = obj.RigidBody.MovementType == MovementType.Dynamic
+// || obj.RigidBody.MovementType == MovementType.Player;
+
+// if (!_groups.ContainsKey(obj.Tag))
+// {
+// _groups.Add(obj.Tag, new GroupInfo());
+// }
+// _groups[obj.Tag].IntervalPointsX.Add(new IntervalPoint() { ColliderId = i, IsEnd = false, V = box.MinPoint.X, IsDynamic = isDynamic });
+// _groups[obj.Tag].IntervalPointsX.Add(new IntervalPoint() { ColliderId = i, IsEnd = true, V = box.MaxPoint.X, IsDynamic = isDynamic });
+// _groups[obj.Tag].IntervalPointsY.Add(new IntervalPoint() { ColliderId = i, IsEnd = false, V = box.MinPoint.Y, IsDynamic = isDynamic });
+// _groups[obj.Tag].IntervalPointsY.Add(new IntervalPoint() { ColliderId = i, IsEnd = true, V = box.MaxPoint.Y, IsDynamic = isDynamic });
+// }
+
+// Comparer compFunc = Comparer.Create((a, b) =>
+// {
+// int cmp1 = a.V.CompareTo(b.V);
+// if (cmp1 == 0)
+// {
+// if (a.IsEnd && !b.IsEnd)
+// {
+// return 1;
+// }
+// else if (!a.IsEnd && b.IsEnd)
+// {
+// return -1;
+// }
+// return 0;
+// }
+// return cmp1;
+// });
+// foreach (var group in _groups)
+// {
+// group.Value.IntervalPointsX.Sort(compFunc);
+// group.Value.IntervalPointsY.Sort(compFunc);
+// }
+// }
+
+// public override List> GetCollisionPairs(float deltaTime)
+// {
+// List> finalPairs = new List>();
+// foreach (var group in _groups)
+// {
+// GroupInnerDetection(group.Key, finalPairs);
+// if (_collisionGraph.Graph.ContainsKey(group.Key))
+// {
+// foreach (var dual in _collisionGraph.Graph[group.Key])
+// {
+// if (!_groups.ContainsKey(dual))
+// continue;
+// GroupOuterDetection(group.Key, dual, finalPairs);
+// }
+// }
+// }
+// return finalPairs;
+// }
+
+// private void GroupInnerDetection(string key, List> pairs)
+// {
+// var xpoints = _groups[key].IntervalPointsX;
+// var ypoints = _groups[key].IntervalPointsY;
+
+// HashSet xSet = new HashSet();
+// HashSet ySet = new HashSet();
+// List> xPairs = new List>();
+// List> yPairs = new List>();
+// foreach (var pt in xpoints)
+// {
+// if (pt.IsEnd)
+// {
+// xSet.Remove(pt.ColliderId);
+// foreach (var c in xSet)
+// {
+// if (pt.IsDynamic || IsDynamic(c))
+// {
+// // 序号小的在前面,这样判重方便一点
+// if (pt.ColliderId < c)
+// {
+// xPairs.Add(new KeyValuePair(pt.ColliderId, c));
+// }
+// else
+// {
+// xPairs.Add(new KeyValuePair(c, pt.ColliderId));
+// }
+// }
+// }
+// }
+// else
+// {
+// xSet.Add(pt.ColliderId);
+// }
+// }
+
+// foreach (var pt in ypoints)
+// {
+// if (pt.IsEnd)
+// {
+// ySet.Remove(pt.ColliderId);
+// foreach (var c in ySet)
+// {
+// if (pt.IsDynamic || IsDynamic(c))
+// {
+// // 序号小的在前面,这样判重方便一点
+// if (pt.ColliderId < c)
+// {
+// yPairs.Add(new KeyValuePair(pt.ColliderId, c));
+// }
+// else
+// {
+// yPairs.Add(new KeyValuePair(c, pt.ColliderId));
+// }
+// }
+// }
+// }
+// else
+// {
+// ySet.Add(pt.ColliderId);
+// }
+// }
+
+// xPairs.AddRange(yPairs);
+
+// xPairs.Sort((a, b) =>
+// {
+// int cmp1 = a.Key.CompareTo(b.Key);
+// if (cmp1 == 0)
+// {
+// return a.Value.CompareTo(b.Value);
+// }
+// return cmp1;
+// });
+
+// for(int i = 0; i < xPairs.Count; i++)
+// {
+// var pt = xPairs[i];
+// if (i == xPairs.Count - 1 || xPairs[i + 1].Key != pt.Key || xPairs[i + 1].Value != pt.Value)
+// {
+// continue;
+// }
+// pairs.Add(new KeyValuePair(_colliders[pt.Key], _colliders[pt.Value]));
+// i++;
+// }
+// }
+
+// public override List GetSingleCollision(AABB aabb, Vector2 velocity, float deltaTime, List targetTags)
+// {
+// throw new NotImplementedException();
+// }
+// }
+//}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/BoxCollider.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/BoxCollider.cs
new file mode 100644
index 000000000..2d6cff39e
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/BoxCollider.cs
@@ -0,0 +1,507 @@
+using Everglow.Commons.Physics.PBEngine.Collision.Shapes;
+using Everglow.Commons.Physics.PBEngine.Core;
+using Everglow.Commons.Physics.PBEngine.GameInteraction;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.Colliders
+{
+ ///
+ /// 四边形、盒子碰撞体
+ ///
+ public class BoxCollider : Collider2D
+ {
+ public Vector2 Size
+ {
+ get => new Vector2(_width, _height);
+ }
+
+ private float _width;
+ private float _height;
+
+ public BoxCollider(float width, float height)
+ {
+ _width = width;
+ _height = height;
+ }
+
+ public override double InertiaTensor(float mass)
+ {
+ return mass / 12.0 * (_width * _width + _height * _height);
+ }
+
+ public override List GetWireFrameWires()
+ {
+ List wires = new List
+ {
+ new Vector2(-_width / 2, -_height / 2),
+ new Vector2(_width / 2, -_height / 2),
+ new Vector2(_width / 2, -_height / 2),
+ new Vector2(_width / 2, _height / 2),
+ new Vector2(_width / 2, _height / 2),
+ new Vector2(-_width / 2, _height / 2),
+ new Vector2(-_width / 2, _height / 2),
+ new Vector2(-_width / 2, -_height / 2),
+ };
+ return wires;
+ }
+
+ public List GetEdges(float dt)
+ {
+ List cornerWorldSpace = GetCornerPoints(dt);
+ List edges = new List();
+ for (int i = 0; i < 4; i++)
+ {
+ edges.Add(new Edge2D(cornerWorldSpace[i], cornerWorldSpace[(i + 1) % cornerWorldSpace.Count]));
+ }
+ return edges;
+ }
+
+ // public override List GetCollisionEvents(Collider2D other, float deltaTime)
+ // {
+ // List events = new List();
+
+ // Vector2[] localPoints = new Vector2[4]
+ // {
+ // new Vector2(-_width / 2, -_height / 2),
+ // new Vector2(_width / 2, -_height / 2),
+ // new Vector2(_width / 2, _height / 2),
+ // new Vector2(-_width / 2, _height / 2)
+ // };
+ // if (other is AABBCollider)
+ // {
+ // AABBCollider aabb_other = (AABBCollider)other;
+ // for (int i = 0; i < 4; i++)
+ // {
+ // var curPos = _bindObject.Position + Matrix2x2.CreateRotationMatrix(_bindObject.Rotation).Multiply(localPoints[i]);
+ // var oldPos = _bindObject.OldPosition + Matrix2x2.CreateRotationMatrix(_bindObject.OldRotation).Multiply(localPoints[i]);
+
+ // foreach (var edge in aabb_other.GetEdges())
+ // {
+ // CollisionEvent2D collision;
+ // if (edge.Segment_Segment_Collision(oldPos, curPos, out collision))
+ // {
+ // collision.Source = this.ParentObject;
+ // collision.Target = other.ParentObject;
+ // collision.LocalOffsetSrc = Matrix2x2.CreateRotationMatrix(_bindObject.Rotation).Multiply(localPoints[i]);
+
+ // events.Add(collision);
+ // }
+ // }
+ // }
+ // }
+ // if (events.Count > 1)
+ // {
+ // Vector2 averagedLocalPoint = new Vector2(0, 0);
+ // Vector2 averagedNormal = new Vector2(0, 0);
+ // float minTime = 1.0f;
+ // for (int i = 0; i < events.Count; i++)
+ // {
+ // averagedLocalPoint += events[i].LocalOffsetSrc;
+ // averagedNormal += events[i].Normal;
+ // minTime = Math.Min(minTime, events[i].Time);
+ // }
+ // averagedLocalPoint /= events.Count;
+ // averagedNormal /= events.Count;
+ // CollisionEvent2D collision = new CollisionEvent2D();
+ // collision.Source = this.ParentObject;
+ // collision.Target = other.ParentObject;
+ // collision.LocalOffsetSrc = averagedLocalPoint;
+ // collision.Normal = Vector2.Normalize(averagedNormal);
+ // collision.Time = minTime;
+
+ // events.Clear();
+ // events.Add(collision);
+ // }
+ // return events;
+ // }
+ public List GetCornerPoints(float dt)
+ {
+ List cornerPoints = new List();
+ Vector2[] localPoints = new Vector2[4]
+ {
+ new Vector2(-_width / 2, -_height / 2),
+ new Vector2(_width / 2, -_height / 2),
+ new Vector2(_width / 2, _height / 2),
+ new Vector2(-_width / 2, _height / 2),
+ };
+
+ var M = ParentObject.CachedRotationalMatrix;
+ for (int i = 0; i < 4; i++)
+ {
+ var curPos = ParentObject.Position + M.Multiply(localPoints[i]);
+ cornerPoints.Add(curPos);
+ }
+ return cornerPoints;
+ }
+
+ public override bool TestCollisionCondition(Collider2D other, float deltaTime, out CollisionInfo info)
+ {
+ info = default(CollisionInfo);
+ if (other is BoxCollider)
+ {
+ BoxCollider b = (BoxCollider)other;
+ List edgesA = GetEdges(deltaTime);
+ List edgesB = b.GetEdges(deltaTime);
+ List cornersA = GetCornerPoints(deltaTime);
+ List cornersB = b.GetCornerPoints(deltaTime);
+ float depth;
+ Vector2 normal;
+ if (GeometryUtils.ConvexPolygonPolygonCollisionInfo(edgesA, cornersA, edgesB, cornersB, out depth, out normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ return true;
+ }
+ }
+ else if (other is SphereCollider)
+ {
+ SphereCollider b = (SphereCollider)other;
+ float depth;
+ Vector2 normal;
+ bool collided = GeometryUtils.SphereConvexPolygonCollisionInfo(b.ParentObject.Position, b.Radius, GetEdges(deltaTime),
+ GetCornerPoints(deltaTime),
+ out depth, out normal);
+ if (collided)
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ }
+ return collided;
+ }
+ else if (other is TileCollider)
+ {
+ float depth;
+ Vector2 normal;
+ if (TileCollisionUtils.GetPolygonTileCollisionInfo(GetAABB(deltaTime), ParentObject.RigidBody.CentroidWorldSpace,
+ GetEdges(deltaTime), GetCornerPoints(deltaTime), out depth, out normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = other.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = normal;
+ return true;
+ }
+ }
+ else if (other is CapsuleCollider)
+ {
+ CapsuleCollider b = (CapsuleCollider)other;
+ b.GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ if (GeometryUtils.CapsuleConvexPolygonCollisionInfo(A, B, b.Radius, GetCornerPoints(deltaTime),
+ GetEdges(deltaTime),
+ out float depth, out Vector2 normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public override void GetContactInfo(in CollisionInfo info, float deltaTime,
+ out List collisionEvents)
+ {
+ collisionEvents = new List();
+ if (info.Target.Collider is BoxCollider)
+ {
+ BoxCollider b = (BoxCollider)info.Target.Collider;
+ List edgesA = GetEdges(deltaTime);
+ List edgesB = b.GetEdges(deltaTime);
+ List cornersA = GetCornerPoints(deltaTime);
+ List cornersB = b.GetCornerPoints(deltaTime);
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = info.Source,
+ Target = info.Target,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ List> contacts = new List>();
+ GeometryUtils.ConvexPolygonPolygonContactInfo(edgesA, cornersA, edgesB, cornersB,
+ ParentObject.RigidBody.CentroidWorldSpace, b.ParentObject.RigidBody.CentroidWorldSpace,
+ contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Key,
+ LocalOffsetTarget = c.Value,
+ });
+ }
+ }
+ else if (info.Target.Collider is SphereCollider)
+ {
+ SphereCollider b = (SphereCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = 0,
+ Source = info.Source,
+ Target = info.Target,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+
+ List> contacts = new List>();
+ GeometryUtils.SphereConvexPolygonContactInfo(b.ParentObject.RigidBody.CentroidWorldSpace, b.Radius, GetEdges(deltaTime),
+ GetCornerPoints(deltaTime), ParentObject.RigidBody.CentroidWorldSpace, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Value,
+ LocalOffsetTarget = c.Key,
+ });
+ }
+ }
+ else if (info.Target.Collider is CapsuleCollider)
+ {
+ CapsuleCollider b = (CapsuleCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = this.ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ b.GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ List> contacts = new List>();
+ GeometryUtils.CapsuleConvexPolygonContactInfo(A, B, b.Radius,
+ GetCornerPoints(deltaTime), GetEdges(deltaTime), ParentObject.RigidBody.CentroidWorldSpace, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Value,
+ LocalOffsetTarget = c.Key,
+ });
+ }
+ }
+ else if (info.Target.Collider is TileCollider)
+ {
+ TileCollisionUtils.GetPolygonTileContactInfo(GetAABB(deltaTime), ParentObject.RigidBody.CentroidWorldSpace,
+ GetEdges(deltaTime), GetCornerPoints(deltaTime), ParentObject, info.Target, deltaTime, collisionEvents);
+ }
+ }
+
+ public override AABB GetAABB(float deltaTime)
+ {
+ var points = GetCornerPoints(deltaTime);
+ Vector2 minPoint = points[0];
+ Vector2 maxPoint = points[0];
+ for (int i = 1; i < points.Count; i++)
+ {
+ minPoint.X = Math.Min(points[i].X, minPoint.X);
+ minPoint.Y = Math.Min(points[i].Y, minPoint.Y);
+ maxPoint.X = Math.Max(points[i].X, maxPoint.X);
+ maxPoint.Y = Math.Max(points[i].Y, maxPoint.Y);
+ }
+ return new AABB() { MaxPoint = maxPoint, MinPoint = minPoint };
+ }
+ }
+}
+
+
+// float maxDepth = -1;
+// if (edgeBelongsToA)
+// {
+// for (int i = 0; i < cornersB.Count; i++)
+// {
+// if (IsInsideEdge(cornersB[i], keyEdge))
+// {
+// float d = Utils.PointDistance2ToSegment(cornersB[i], keyEdge._pA, keyEdge._pB);
+// if (d > maxDepth)
+// {
+// maxDepth = d;
+// }
+// }
+// }
+// Debug.Assert(maxDepth >= 0);
+// for (int i = 0; i < cornersB.Count; i++)
+// {
+// if (IsInsideEdge(cornersB[i], keyEdge))
+// {
+// float d = Utils.PointDistance2ToSegment(cornersB[i], keyEdge._pA, keyEdge._pB);
+// if (d == maxDepth)
+// {
+// collisionEvents.Add(new CollisionEvent2D()
+// {
+// Time = 0,
+// Source = this.ParentObject,
+// Target = b.ParentObject,
+// LocalOffsetSrc = Utils.ProjectPointOnSegment(cornersB[i], keyEdge._pA, keyEdge._pB)
+// - ParentObject.RigidBody.CentroidWorldSpace,
+// LocalOffsetTarget = cornersB[i] - b.ParentObject.RigidBody.CentroidWorldSpace,
+// Normal = normal,
+// Position = Vector2.Zero,
+// Depth = depth
+// });
+// }
+// }
+// }
+
+// }
+// else
+// {
+// for (int i = 0; i < cornersA.Count; i++)
+// {
+// if (IsInsideEdge(cornersA[i], keyEdge))
+// {
+// float d = Utils.PointDistance2ToSegment(cornersA[i], keyEdge._pA, keyEdge._pB);
+// if (d > maxDepth)
+// {
+// maxDepth = d;
+// }
+// }
+// }
+// Debug.Assert(maxDepth >= 0);
+// for (int i = 0; i < cornersA.Count; i++)
+// {
+// if (IsInsideEdge(cornersA[i], keyEdge))
+// {
+// float d = Utils.PointDistance2ToSegment(cornersA[i], keyEdge._pA, keyEdge._pB);
+// if (d == maxDepth)
+// {
+// collisionEvents.Add(new CollisionEvent2D()
+// {
+// Time = 0,
+// Source = b.ParentObject,
+// Target = this.ParentObject,
+// LocalOffsetSrc = Utils.ProjectPointOnSegment(cornersA[i], keyEdge._pA, keyEdge._pB)
+// - b.ParentObject.RigidBody.CentroidWorldSpace,
+// LocalOffsetTarget = cornersA[i] - ParentObject.RigidBody.CentroidWorldSpace,
+// Normal = -normal,
+// Position = Vector2.Zero,
+// Depth = depth
+// });
+// }
+// }
+// }
+// }
+// collisionEvents.Add(new CollisionEvent2D()
+// {
+// Time = 0,
+// Source = this.ParentObject,
+// Target = b.ParentObject,
+// LocalOffsetSrc = curPoint - (b.ParentObject.RigidBody.CentroidWorldSpace + b.ParentObject.RigidBody.LinearVelocity * (float)ans),
+// LocalOffsetTarget = curPoint - (ParentObject.RigidBody.CentroidWorldSpace + ParentObject.RigidBody.LinearVelocity * (float)ans),
+// Normal = -Vector2.Normalize(Utils.Rotate90(closestEdge._pB - closestEdge._pA)),
+// Position = curPoint,
+// });
+
+// AABBCollider b = (AABBCollider)other;
+// double l = 0, r = deltaTime;
+// double ans = r;
+// while (r - l > deltaTime * 1e-2)
+// {
+// double midTime = (r + l) / 2.0;
+// if (IsCollideWith(b, (float)midTime))
+// {
+// ans = Math.Min(ans, midTime);
+// r = midTime;
+// }
+// else
+// {
+// l = midTime;
+// }
+// }
+
+// double t = Math.Max(0, ans);
+// var curPointsA = GetCornerPoints((float)ans);
+// var curEdgesA = GetEdges((float)ans);
+// var curPointsB = b.GetCornerPoints((float)ans);
+// var curEdgesB = b.GetEdges((float)ans);
+
+//// Vertex/Edge collision, A to B
+// foreach (var curPoint in curPointsA)
+// {
+// bool isInside = true;
+// Edge2D closestEdge = null;
+// float closestDistance = float.PositiveInfinity;
+// foreach(var curEdge in curEdgesB)
+// {
+// if (!IsInsideEdge(curPoint, curEdge))
+// {
+// isInside = false;
+// break;
+// }
+// float dist = Utils.PointDistance2ToSegment(curPoint, curEdge._pA, curEdge._pB);
+// if (dist < closestDistance)
+// {
+// closestDistance = dist;
+// closestEdge = curEdge;
+// }
+// }
+
+// if(isInside)
+// {
+// collisionEvents.Add(new CollisionEvent2D()
+// {
+// Time = (float)(t),
+// Source = this.ParentObject,
+// Target = b.ParentObject,
+// LocalOffsetSrc = curPoint - (ParentObject.RigidBody.CentroidWorldSpace + ParentObject.RigidBody.LinearVelocity * (float)ans),
+// LocalOffsetTarget = curPoint - (b.ParentObject.RigidBody.CentroidWorldSpace + b.ParentObject.RigidBody.LinearVelocity * (float)ans),
+// Normal = -Vector2.Normalize(Utils.Rotate90(closestEdge._pB - closestEdge._pA)),
+// Position = curPoint,
+
+// });
+// }
+
+// }
+
+//// Vertex/Edge collision, B to A
+// foreach (var curPoint in curPointsB)
+// {
+// bool isInside = true;
+// Edge2D closestEdge = null;
+// float closestDistance = float.PositiveInfinity;
+// foreach (var curEdge in curEdgesA)
+// {
+// if (!IsInsideEdge(curPoint, curEdge))
+// {
+// isInside = false;
+// break;
+// }
+// float dist = Utils.PointDistance2ToSegment(curPoint, curEdge._pA, curEdge._pB);
+// if (dist < closestDistance)
+// {
+// closestDistance = dist;
+// closestEdge = curEdge;
+// }
+// }
+
+// if (isInside)
+// {
+// collisionEvents.Add(new CollisionEvent2D()
+// {
+// Time = (float)(t),
+// Source = b.ParentObject,
+// Target = this.ParentObject,
+// LocalOffsetSrc = curPoint - (b.ParentObject.RigidBody.CentroidWorldSpace + b.ParentObject.RigidBody.LinearVelocity * (float)ans),
+// LocalOffsetTarget = curPoint - (ParentObject.RigidBody.CentroidWorldSpace + ParentObject.RigidBody.LinearVelocity * (float)ans),
+// Normal = -Vector2.Normalize(Utils.Rotate90(closestEdge._pB - closestEdge._pA)),
+// Position = curPoint,
+// });
+// }
+
+// }
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/CapsuleCollider.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/CapsuleCollider.cs
new file mode 100644
index 000000000..ad52a82c7
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/CapsuleCollider.cs
@@ -0,0 +1,262 @@
+using Everglow.Commons.Physics.PBEngine.Core;
+using Everglow.Commons.Physics.PBEngine.GameInteraction;
+using Everglow.Commons.Utilities;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.Colliders
+{
+ internal class CapsuleCollider : Collider2D
+ {
+ private float _length;
+ private float _radius;
+
+ public float Length
+ {
+ get => _length;
+ set => _length = value;
+ }
+
+ public float Radius
+ {
+ get => _radius;
+ set => _radius = value;
+ }
+
+ public CapsuleCollider(float length, float radius)
+ {
+ _length = length;
+ _radius = radius;
+ }
+
+ public override AABB GetAABB(float deltaTime)
+ {
+ var M = Matrix2x2.CreateRotationMatrix(ParentObject.OldRotation + ParentObject.RigidBody.AngularVelocity * deltaTime);
+ Vector2 P1 = ParentObject.OldPosition + ParentObject.RigidBody.LinearVelocity * deltaTime
+ + M.Multiply(new Vector2(_length / 2, 0));
+ Vector2 P2 = ParentObject.OldPosition + ParentObject.RigidBody.LinearVelocity * deltaTime
+ + M.Multiply(new Vector2(-_length / 2, 0));
+
+ if (P1.X > P2.X)
+ {
+ (P2.X, P1.X) = (P1.X, P2.X);
+ }
+ if (P1.Y > P2.Y)
+ {
+ (P2.Y, P1.Y) = (P1.Y, P2.Y);
+ }
+ return new AABB()
+ {
+ MinPoint = P1 - new Vector2(_radius, _radius),
+ MaxPoint = P2 + new Vector2(_radius, _radius),
+ };
+ }
+
+ public override void GetContactInfo(in CollisionInfo info, float deltaTime, out List collisionEvents)
+ {
+ collisionEvents = new List();
+ if (info.Target.Collider is SphereCollider)
+ {
+ var b = (SphereCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ var contacts = new List>();
+ GeometryUtils.SphereCapsuleContactInfo(
+ b.ParentObject.RigidBody.CentroidWorldSpace,
+ b.Radius, A, B, _radius, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Value,
+ LocalOffsetTarget = c.Key,
+ });
+ }
+ }
+ else if (info.Target.Collider is CapsuleCollider)
+ {
+ var b = (CapsuleCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ b.GetSegment(deltaTime, out Vector2 A2, out Vector2 B2);
+ var contacts = new List>();
+ GeometryUtils.CapsuleCapsuleContactInfo(A, B, _radius, A2, B2, b._radius, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Key,
+ LocalOffsetTarget = c.Value,
+ });
+ }
+ }
+ else if (info.Target.Collider is BoxCollider)
+ {
+ var b = (BoxCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ var contacts = new List>();
+ GeometryUtils.CapsuleConvexPolygonContactInfo(A, B, _radius,
+ b.GetCornerPoints(deltaTime), b.GetEdges(deltaTime), b.ParentObject.RigidBody.CentroidWorldSpace, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Key,
+ LocalOffsetTarget = c.Value,
+ });
+ }
+ }
+ else if (info.Target.Collider is TileCollider)
+ {
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ GameInteraction.TileCollisionUtils.GetCapsuleTileContactInfo(GetAABB(deltaTime), A, B, _radius, ParentObject, info.Target, deltaTime, collisionEvents);
+ }
+ }
+
+ public override List GetWireFrameWires()
+ {
+ var lines = new List();
+ lines.Add(new Vector2(_length / 2, _radius));
+ lines.Add(new Vector2(-_length / 2, _radius));
+
+ float da = MathHelper.Pi / 32;
+ var leftCenter = new Vector2(-_length / 2, 0);
+ for (int i = 0; i < 32; i++)
+ {
+ lines.Add(leftCenter + (i * da + MathHelper.PiOver2).ToRotationVector2() * _radius);
+ lines.Add(leftCenter + ((i + 1) * da + MathHelper.PiOver2).ToRotationVector2() * _radius);
+ }
+ lines.Add(new Vector2(-_length / 2, -_radius));
+ lines.Add(new Vector2(_length / 2, -_radius));
+ var rightCenter = new Vector2(_length / 2, 0);
+ for (int i = 0; i < 32; i++)
+ {
+ lines.Add(rightCenter + (i * da - MathHelper.PiOver2).ToRotationVector2() * _radius);
+ lines.Add(rightCenter + ((i + 1) * da - MathHelper.PiOver2).ToRotationVector2() * _radius);
+ }
+ lines.Add(Vector2.Zero);
+ lines.Add(new Vector2(_length / 2 + _radius, 0));
+ return lines;
+ }
+
+ public override double InertiaTensor(float mass)
+ {
+ float massOfBox = mass * 2 * _radius * _length / (2 * _radius * _length + MathHelper.Pi * _radius * _radius);
+ float massOfSphere = mass - massOfBox;
+ float moi_box = massOfBox / 12.0f * (_length * _length + 4 * _radius * _radius);
+ float moi_sphere = 0.5f * massOfSphere * _radius * _radius;
+ float d1 = 3.0f / 8.0f * _radius;
+ float moi_hsphere = 0.5f * moi_sphere - 0.5f * massOfSphere * d1 * d1;
+ float d2 = _length / 2 + d1;
+ moi_hsphere += 0.5f * massOfSphere * d2 * d2;
+ return moi_box + 2 * moi_hsphere;
+ }
+
+ public void GetSegment(float dt, out Vector2 segA, out Vector2 segB)
+ {
+ var M = ParentObject.CachedRotationalMatrix;
+ var pos = ParentObject.Position;
+ segA = pos + M.Multiply(new Vector2(_length / 2, 0));
+ segB = pos + M.Multiply(new Vector2(-_length / 2, 0));
+ }
+
+ public override bool TestCollisionCondition(Collider2D other, float deltaTime, out CollisionInfo info)
+ {
+ info = default;
+ if (other is SphereCollider)
+ {
+ var b = (SphereCollider)other;
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ if (GeometryUtils.SphereCapsuleCollisionInfo(b.ParentObject.RigidBody.CentroidWorldSpace, b.Radius,
+ A, B, _radius, out float depth, out Vector2 normal))
+ {
+ info.Source = ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = -normal;
+ return true;
+ }
+ }
+ else if (other is CapsuleCollider)
+ {
+ var b = (CapsuleCollider)other;
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ b.GetSegment(deltaTime, out Vector2 A2, out Vector2 B2);
+ if (GeometryUtils.CapsuleCapsuleCollisionInfo(A, B, _radius, A2, B2, b._radius,
+ out float depth, out Vector2 normal))
+ {
+ info.Source = ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ return true;
+ }
+ }
+ else if (other is BoxCollider)
+ {
+ var b = (BoxCollider)other;
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ if (GeometryUtils.CapsuleConvexPolygonCollisionInfo(A, B, _radius, b.GetCornerPoints(deltaTime),
+ b.GetEdges(deltaTime),
+ out float depth, out Vector2 normal))
+ {
+ info.Source = ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ return true;
+ }
+ }
+ else if (other is TileCollider)
+ {
+ float depth;
+ Vector2 normal;
+ GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ if (GameInteraction.TileCollisionUtils.GetCapsuleTileCollisionInfo(GetAABB(deltaTime), A, B, _radius, out depth, out normal))
+ {
+ info.Source = ParentObject;
+ info.Target = other.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = normal;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/Collider2D.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/Collider2D.cs
new file mode 100644
index 000000000..91a8b3333
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/Collider2D.cs
@@ -0,0 +1,35 @@
+using Everglow.Commons.Physics.PBEngine.Core;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.Colliders
+{
+ ///
+ /// 碰撞组件的基类
+ ///
+ public abstract class Collider2D
+ {
+ public PhysicsObject ParentObject
+ {
+ get => _bindObject;
+ set => _bindObject = value;
+ }
+
+ protected PhysicsObject _bindObject;
+
+ public abstract double InertiaTensor(float mass);
+
+ public abstract List GetWireFrameWires();
+
+ // public abstract List GetCollisionEvents(Collider2D other, float deltaTime);
+ public abstract bool TestCollisionCondition(Collider2D other, float deltaTime, out CollisionInfo info);
+
+ public abstract void GetContactInfo(in CollisionInfo info, float deltaTime,
+ out List collisionEvents);
+
+ // public abstract List GetContactInfo(CollisionEvent2D e, float deltaTime);
+ public abstract AABB GetAABB(float deltaTime);
+
+ public Collider2D()
+ {
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/SphereCollider.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/SphereCollider.cs
new file mode 100644
index 000000000..d2c668025
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Colliders/SphereCollider.cs
@@ -0,0 +1,242 @@
+using Everglow.Commons.Physics.PBEngine.Core;
+using Everglow.Commons.Physics.PBEngine.GameInteraction;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.Colliders
+{
+ ///
+ /// 圆形碰撞体
+ ///
+ public class SphereCollider : Collider2D
+ {
+ public float Radius
+ {
+ get => _radius; set => _radius = value;
+ }
+
+ private float _radius;
+
+ public SphereCollider(float radius)
+ {
+ _radius = radius;
+ }
+
+ public override AABB GetAABB(float deltaTime)
+ {
+ var center = ParentObject.OldPosition + ParentObject.RigidBody.LinearVelocity * deltaTime;
+ return new AABB()
+ {
+ MinPoint = center - new Vector2(_radius, _radius),
+ MaxPoint = center + new Vector2(_radius, _radius),
+ };
+ }
+
+ public override void GetContactInfo(in CollisionInfo info, float deltaTime, out List collisionEvents)
+ {
+ collisionEvents = new List();
+ if (info.Target.Collider is SphereCollider)
+ {
+ SphereCollider b = (SphereCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = this.ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+
+ var unit = (ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace).SafeNormalize(Vector2.Zero);
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = -unit * _radius,
+ LocalOffsetTarget = unit * b.Radius,
+ });
+ }
+ else if (info.Target.Collider is BoxCollider)
+ {
+ BoxCollider b = (BoxCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = info.Source,
+ Target = info.Target,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = Vector2.Dot(info.Normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -info.Normal : info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+
+ List> contacts = new List>();
+ GeometryUtils.SphereConvexPolygonContactInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius, b.GetEdges(deltaTime),
+ b.GetCornerPoints(deltaTime), b.ParentObject.RigidBody.CentroidWorldSpace, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Key,
+ LocalOffsetTarget = c.Value,
+ });
+ }
+ }
+ else if (info.Target.Collider is TileCollider)
+ {
+ TileCollisionUtils.GetSphereTileContactInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius,
+ ParentObject, info.Target, deltaTime, collisionEvents);
+ }
+ else if (info.Target.Collider is CapsuleCollider)
+ {
+ CapsuleCollider b = (CapsuleCollider)info.Target.Collider;
+ var e = new CollisionEvent2D()
+ {
+ Time = info.Time,
+ Source = this.ParentObject,
+ Target = b.ParentObject,
+ LocalOffsetSrc = Vector2.Zero,
+ LocalOffsetTarget = Vector2.Zero,
+ Normal = info.Normal,
+ Position = Vector2.Zero,
+ Depth = info.Depth,
+ };
+ b.GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ List> contacts = new List>();
+ GeometryUtils.SphereCapsuleContactInfo(
+ ParentObject.RigidBody.CentroidWorldSpace,
+ _radius, A, B, b.Radius, contacts);
+ foreach (var c in contacts)
+ {
+ collisionEvents.Add(new CollisionEvent2D(e)
+ {
+ LocalOffsetSrc = c.Key,
+ LocalOffsetTarget = c.Value,
+ });
+ }
+ }
+ }
+
+ // public override List GetContactInfo(CollisionEvent2D e, float deltaTime)
+ // {
+ // List events = new List();
+ // if (e.Target.Collider is SphereCollider)
+ // {
+ // SphereCollider b = (SphereCollider)e.Target.Collider;
+ // var unit = (ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace).SafeNormalize(Vector2.Zero);
+ // events.Add(new CollisionEvent2D(e)
+ // {
+ // LocalOffsetSrc = -unit * _radius,
+ // LocalOffsetTarget = unit * b.Radius,
+ // });
+ // }
+ // else if (e.Target.Collider is BoxCollider)
+ // {
+ // BoxCollider b = (BoxCollider)e.Target.Collider;
+ // List edgesB = b.GetEdges(deltaTime);
+ // List cornersB = b.GetCornerPoints(deltaTime);
+ // List> contacts = new List>();
+ // Utils.CirclePolygonContactInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius, edgesB,
+ // cornersB, b.ParentObject.RigidBody.CentroidWorldSpace, contacts);
+ // foreach (var c in contacts)
+ // {
+ // events.Add(new CollisionEvent2D(e)
+ // {
+ // LocalOffsetSrc = c.Key,
+ // LocalOffsetTarget = c.Value,
+ // });
+ // }
+ // }
+ // else if (e.Target.Collider is TileCollider)
+ // {
+ // TileCollisionUtils.GetSphereTileContactInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius, e, events);
+ // }
+ // return events;
+ // }
+ public override List GetWireFrameWires()
+ {
+ List lines = new List();
+ float da = MathHelper.TwoPi / 64;
+ for (int i = 0; i < 64; i++)
+ {
+ lines.Add((i * da).ToRotationVector2() * _radius);
+ lines.Add(((i + 1) * da).ToRotationVector2() * _radius);
+ }
+ lines.Add(Vector2.Zero);
+ lines.Add(ParentObject.CachedRotationalMatrix.Multiply(new Vector2(_radius, 0)));
+ return lines;
+ }
+
+ public override double InertiaTensor(float mass)
+ {
+ return 0.5 * mass * _radius * _radius;
+ }
+
+ public override bool TestCollisionCondition(Collider2D other, float deltaTime, out CollisionInfo info)
+ {
+ info = default(CollisionInfo);
+ if (other is SphereCollider)
+ {
+ SphereCollider b = (SphereCollider)other;
+ float d = Vector2.DistanceSquared(ParentObject.RigidBody.CentroidWorldSpace, b.ParentObject.RigidBody.CentroidWorldSpace);
+ if (d < (_radius + b._radius) * (_radius + b._radius))
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = _radius + b._radius - (float)Math.Sqrt(d);
+ info.Normal = (ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace).SafeNormalize(Vector2.One);
+ return true;
+ }
+ }
+ else if (other is BoxCollider)
+ {
+ BoxCollider b = (BoxCollider)other;
+ float depth;
+ Vector2 normal;
+ if (GeometryUtils.SphereConvexPolygonCollisionInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius,
+ b.GetEdges(deltaTime), b.GetCornerPoints(deltaTime), out depth, out normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = Vector2.Dot(normal, ParentObject.RigidBody.CentroidWorldSpace - b.ParentObject.RigidBody.CentroidWorldSpace) < 0 ? -normal : normal;
+ return true;
+ }
+ }
+ else if (other is TileCollider)
+ {
+ float depth;
+ Vector2 normal;
+ if (TileCollisionUtils.GetSphereTileCollisionInfo(ParentObject.RigidBody.CentroidWorldSpace, _radius,
+ ParentObject, info.Target, out depth, out normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = other.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = normal;
+ return true;
+ }
+ }
+ else if (other is CapsuleCollider)
+ {
+ CapsuleCollider b = (CapsuleCollider)other;
+ b.GetSegment(deltaTime, out Vector2 A, out Vector2 B);
+ if (GeometryUtils.SphereCapsuleCollisionInfo(ParentObject.RigidBody.CentroidWorldSpace, Radius,
+ A, B, b.Radius, out float depth, out Vector2 normal))
+ {
+ info.Source = this.ParentObject;
+ info.Target = b.ParentObject;
+ info.Time = deltaTime;
+ info.Depth = depth;
+ info.Normal = normal;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionEvent2D.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionEvent2D.cs
new file mode 100644
index 000000000..735daf518
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionEvent2D.cs
@@ -0,0 +1,68 @@
+using Everglow.Commons.Physics.PBEngine.Core;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision
+{
+ ///
+ /// 储存碰撞事件的对象,包括接触点信息
+ ///
+ public class CollisionEvent2D
+ {
+ public PhysicsObject Source;
+ public PhysicsObject Target;
+ public float Time;
+ public float Depth;
+ public Vector2 Position;
+ public Vector2 Normal;
+ public Vector2 Offset;
+
+ ///
+ /// From centroid of source object to hit point
+ ///
+ public Vector2 LocalOffsetSrc;
+
+ ///
+ /// From centroid of target object to hit point
+ ///
+ public Vector2 LocalOffsetTarget;
+
+ public float NormalVelOld;
+ public Vector2 TangentDir;
+
+ public CollisionEvent2D(CollisionEvent2D e)
+ {
+ this.Source = e.Source;
+ this.Target = e.Target;
+ this.Time = e.Time;
+ this.Depth = e.Depth;
+ this.Position = e.Position;
+ this.Normal = e.Normal;
+ this.Offset = e.Offset;
+ this.NormalVelOld = e.NormalVelOld;
+ }
+
+ public CollisionEvent2D()
+ {
+ }
+
+ public CollisionEvent2D CreateMirrorEvent()
+ {
+ return new CollisionEvent2D()
+ {
+ Source = this.Target,
+ Target = this.Source,
+ Time = this.Time,
+ Position = this.Position,
+ Normal = -this.Normal,
+ Offset = -this.Offset,
+ LocalOffsetSrc = this.LocalOffsetTarget,
+ LocalOffsetTarget = this.LocalOffsetSrc
+ };
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionInfo.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionInfo.cs
new file mode 100644
index 000000000..aa1db2327
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/CollisionInfo.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Everglow.Commons.Physics.PBEngine.Core;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision
+{
+ ///
+ /// 储存碰撞事件的对象,包括穿透深度和挤出方向,不包括接触点
+ ///
+ public struct CollisionInfo
+ {
+ public PhysicsObject Source;
+ public PhysicsObject Target;
+ public float Time;
+ public float Depth;
+ public Vector2 Normal;
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/AABB.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/AABB.cs
new file mode 100644
index 000000000..e3813cd2c
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/AABB.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision
+{
+ public struct AABB
+ {
+ public Vector2 MinPoint;
+ public Vector2 MaxPoint;
+
+ public AABB()
+ {
+ MinPoint = new Vector2(float.PositiveInfinity);
+ MaxPoint = new Vector2(float.NegativeInfinity);
+ }
+
+ public AABB(Vector2 minP, Vector2 maxP)
+ {
+ MinPoint = minP;
+ MaxPoint = maxP;
+ }
+
+ public Vector2 Center
+ {
+ get => (MaxPoint + MinPoint) / 2;
+ }
+
+ public bool Intersects(in AABB other)
+ {
+ return Math.Max(MinPoint.X, other.MinPoint.X) <= Math.Min(MaxPoint.X, other.MaxPoint.X)
+ && Math.Max(MinPoint.Y, other.MinPoint.Y) <= Math.Min(MaxPoint.Y, other.MaxPoint.Y);
+ }
+
+ public AABB Move(Vector2 v)
+ {
+ return new AABB() { MinPoint = MinPoint + v, MaxPoint = MaxPoint + v };
+ }
+
+ public AABB Merge(in AABB other)
+ {
+ return new AABB() { MinPoint = Vector2.Min(MinPoint, other.MinPoint), MaxPoint = Vector2.Max(MaxPoint, other.MaxPoint) };
+ }
+
+ public void MergeWith(in AABB other)
+ {
+ MinPoint = Vector2.Min(MinPoint, other.MinPoint);
+ MaxPoint = Vector2.Max(MaxPoint, other.MaxPoint);
+ }
+
+ public double GetArea()
+ {
+ double w = MaxPoint.X - MinPoint.X;
+ double h = MaxPoint.Y - MinPoint.Y;
+ return w * h;
+ }
+
+ public bool CompletelyInside(in AABB other)
+ {
+ return other.MinPoint.X <= MinPoint.X && other.MaxPoint.X >= MaxPoint.X
+ && other.MinPoint.Y <= MinPoint.Y && other.MaxPoint.Y >= MaxPoint.Y;
+ }
+
+ public AABB EnLarge(float size)
+ {
+ return new AABB() { MinPoint = MinPoint - new Vector2(size), MaxPoint = MaxPoint + new Vector2(size) };
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/Edge2D.cs b/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/Edge2D.cs
new file mode 100644
index 000000000..b09096849
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Collision/Shapes/Edge2D.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Everglow.Commons.Physics.PBEngine.Core;
+using Everglow.Commons.Utilities;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Collision.Shapes
+{
+ public class Edge2D
+ {
+ public Vector2 _pA, _pB;
+ public Edge2D()
+ {
+
+ }
+
+ public Edge2D(Vector2 A, Vector2 B)
+ {
+ _pA = A;
+ _pB = B;
+ }
+
+ public Vector2 GetNormal()
+ {
+ return -GeometryUtils.Rotate90((_pB - _pA).SafeNormalize(Vector2.Zero));
+ }
+
+ public bool Segment_Segment_Collision(Vector2 a, Vector2 b, out CollisionEvent2D collisionEvent)
+ {
+ var M = new Matrix2x2
+ {
+ [0, 0] = _pB.X - _pA.X,
+ [0, 1] = -(b.X - a.X),
+ [1, 0] = _pB.Y - _pA.Y,
+ [1, 1] = -(b.Y - a.Y),
+ };
+
+ double det = M.Determinant();
+ if (Math.Abs(det) < 1e-7)
+ {
+ collisionEvent = new CollisionEvent2D();
+ return false; // parallel lines
+ }
+ else
+ {
+
+ Vector2 st = M.Adjoint().Multiply(a - _pA) / (float)det;
+ if (st.X >= 0 && st.X <= 1.0f && st.Y >= 0 && st.Y <= 1.0f)
+ {
+ collisionEvent = new CollisionEvent2D
+ {
+ Time = st.Y,
+ Position = _pA + (_pB - _pA) * st.X,
+ Normal = -GeometryUtils.Rotate90((_pB - _pA).SafeNormalize(Vector2.Zero)),
+ Offset = Vector2.Zero
+ };
+ return true;
+ }
+ collisionEvent = new CollisionEvent2D();
+ return false; // intersection is outside of the line segments
+ }
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Constraints/Constraint.cs b/Sources/Everglow.Function/Physics/PBEngine/Constraints/Constraint.cs
new file mode 100644
index 000000000..16722cc05
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Constraints/Constraint.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Everglow.Commons.Physics.PBEngine.Constraints
+{
+ public abstract class Constraint
+ {
+ public abstract void Apply(float deltaTime);
+ public abstract void ApplyForce(float deltaTime);
+
+ public abstract List<(Vector2, Color)> GetDrawMesh();
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Constraints/JointConstraint.cs b/Sources/Everglow.Function/Physics/PBEngine/Constraints/JointConstraint.cs
new file mode 100644
index 000000000..ae2ae980e
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Constraints/JointConstraint.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Everglow.Commons.Physics.PBEngine.Core;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Constraints
+{
+ ///
+ /// TODO: 待定约束
+ ///
+ public class JointConstraint : Constraint
+ {
+ private PhysicsObject _objA;
+ private PhysicsObject _objB;
+ private Vector2 _localPosA;
+ private Vector2 _localPosB;
+ public JointConstraint(PhysicsObject A, PhysicsObject B, Vector2 localPos1, Vector2 localPos2)
+ {
+ _objA = A;
+ _objB = B;
+ _localPosA = localPos1;
+ _localPosB = localPos2;
+ }
+
+ public override void Apply(float deltaTime)
+ {
+ var pA = _objA.LocalToWorldPos(_localPosA);
+ // var pB = _objB.LocalToWorldPos(_localPosB);
+ float w = _objA.RigidBody.InvMass + _objB.RigidBody.InvMass;
+ float weightA = _objA.RigidBody.InvMass / w;
+ var newCenter = pA;
+ var dirA = (newCenter - _objA.Position).SafeNormalize(Vector2.One);
+ var dirB = (newCenter - _objB.Position).SafeNormalize(Vector2.One);
+
+ float oldRotA = MathHelper.WrapAngle(_objA.Rotation);
+ float oldRotB = MathHelper.WrapAngle(_objB.Rotation);
+ //_objA.Rotation = (float)(Math.Atan2(dirA.Y, dirA.X) - Math.Atan2(_localPosA.Y, _localPosA.X));
+ //_objB.Rotation = (float)(Math.Atan2(dirB.Y, dirB.X) - Math.Atan2(_localPosB.Y, _localPosB.X));
+
+ Vector2 addA = newCenter - _objA.LocalToWorldPos(_localPosA);
+ Vector2 addB = newCenter - _objB.LocalToWorldPos(_localPosB);
+ Vector2 oldPosB = _objB.Position;
+ _objA.Position += addA;
+ _objB.Position = newCenter - dirB * 200;
+
+ _objA.RigidBody.LinearVelocity = (_objA.Position - _objA.OldPosition) / deltaTime;
+ _objB.RigidBody.LinearVelocity = (_objB.Position - _objB.OldPosition) / deltaTime;
+ //_objA.RigidBody.AngularVelocity = MathHelper.WrapAngle(_objA.Rotation - _objA.OldRotation) / deltaTime;
+ //_objB.RigidBody.AngularVelocity = MathHelper.WrapAngle(_objB.Rotation - _objB.OldRotation) / deltaTime;
+ }
+
+ public override void ApplyForce(float deltaTime)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override List<(Vector2, Color)> GetDrawMesh()
+ {
+ var drawMesh = new List<(Vector2, Color)>();
+ drawMesh.Add((_objA.LocalToWorldPos(_localPosA), Color.Green));
+ drawMesh.Add((_objB.RigidBody.CentroidWorldSpace, Color.Green));
+
+ return drawMesh;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Constraints/SpringConstraint.cs b/Sources/Everglow.Function/Physics/PBEngine/Constraints/SpringConstraint.cs
new file mode 100644
index 000000000..afcb48fb1
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Constraints/SpringConstraint.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Everglow.Commons.Physics.PBEngine.Core;
+using Everglow.Commons.Utilities;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Constraints
+{
+ ///
+ /// 弹簧约束
+ ///
+ public class SpringConstraint : Constraint
+ {
+ private PhysicsObject _objA;
+ private PhysicsObject _objB;
+ private float _compliance;
+ private float _restLength;
+ private double _lambda;
+ private Vector2 _localPosA;
+ private Vector2 _localPosB;
+
+ public SpringConstraint(PhysicsObject A, PhysicsObject B, float elasticity, float restLength)
+ {
+ _objA = A;
+ _objB = B;
+ _compliance = 1 / elasticity;
+ _restLength = restLength;
+ _lambda = 0;
+ _localPosA = Vector2.Zero;
+ _localPosB = Vector2.Zero;
+ }
+
+ public SpringConstraint(PhysicsObject A, PhysicsObject B, float elasticity, float restLength,
+ Vector2 localPosA,
+ Vector2 localPosB) : this(A, B, elasticity, restLength)
+ {
+ _localPosA = localPosA;
+ _localPosB = localPosB;
+ }
+
+ public static double CalculateShortestAngleDifference(double angle1, double angle2)
+ {
+ double difference = angle2 - angle1;
+ difference = (difference + Math.PI) % (2 * Math.PI) - Math.PI;
+
+ // Adjust the difference to the shortest rotating radians
+ if (difference < -Math.PI)
+ {
+ difference += 2 * Math.PI;
+ }
+ else if (difference >= Math.PI)
+ {
+ difference -= 2 * Math.PI;
+ }
+
+ return difference;
+ }
+
+ public override void Apply(float deltaTime)
+ {
+ //var pA = _objA.LocalToWorldPos(_localPosA);
+ //var pB = _objB.LocalToWorldPos(_localPosB);
+ //var ra = Matrix2x2.CreateRotationMatrix(_objA.Rotation).Multiply(_localPosA);
+ //var rb = Matrix2x2.CreateRotationMatrix(_objB.Rotation).Multiply(_localPosB);
+ //var L = (pB - pA).Length();
+ //var unit = (pB - pA).SafeNormalize(Vector2.Zero);
+ //float C = L - _restLength;
+ //if (C == 0)
+ //{
+ // return;
+ //}
+
+ //double rAdotN = Vector2.Dot(GeometryUtils.Rotate90(ra), -(pB - pA));
+ //double rBdotN = Vector2.Dot(GeometryUtils.Rotate90(rb), (pB - pA));
+ //double R1 = rAdotN * rAdotN * _objA.RigidBody.GlobalInverseInertiaTensor;
+ //double R2 = rBdotN * rBdotN * _objB.RigidBody.GlobalInverseInertiaTensor;
+ //float effMassA = (float)(_objA.RigidBody.InvMass + R1);
+ //float effMassB = (float)(_objB.RigidBody.InvMass + R2);
+ //float alpha = _compliance / deltaTime / deltaTime;
+ //double d_lambda = (-C - alpha * _lambda) / (effMassA + effMassB + alpha);
+ //_lambda = _lambda + d_lambda;
+
+ ////var posA = pA + (float)(_lambda * effMassA ) * -unit ;
+ ////var posB = pB + (float)(_lambda * effMassB) * unit;
+ ////float rotA_p = (pA - _objA.RigidBody.CentroidWorldSpace).ToRotation() - _localPosA.ToRotation();
+ ////float rotA = (float)CalculateShortestAngleDifference((pA - _objA.RigidBody.CentroidWorldSpace).ToRotation(),
+ //// (pB - _objA.RigidBody.CentroidWorldSpace).ToRotation());
+ ////float rotB_p = (pB - _objB.RigidBody.CentroidWorldSpace).ToRotation() - _localPosB.ToRotation();
+ ////float rotB = (float)CalculateShortestAngleDifference((pB - _objB.RigidBody.CentroidWorldSpace).ToRotation(),
+ //// (posB - _objB.RigidBody.CentroidWorldSpace).ToRotation());
+
+ //if (_objB.RigidBody.GlobalInverseInertiaTensor != 0)
+ //{
+ // _objB.RigidBody.AddImpluseImmediate((float)(d_lambda ) * (pB - pA), rb);
+ // //_objB.Rotation += _objB.RigidBody.AngularVelocity * deltaTime;
+ // //_objB.RigidBody.CentroidWorldSpace += _objB.RigidBody.LinearVelocity * deltaTime;
+ // //_objB.RigidBody.LinearVelocity = (_objB.RigidBody.CentroidWorldSpace - _objB.OldPosition) / deltaTime;
+ // //_objB.RigidBody.AngularVelocity = (float)CalculateShortestAngleDifference(_objB.OldRotation, _objB.Rotation) / deltaTime;
+ //}
+ //if (_objA.RigidBody.GlobalInverseInertiaTensor != 0)
+ //{
+ // _objA.RigidBody.AddImpluseImmediate((float)(d_lambda) * -(pB - pA), ra);
+ // //_objA.Rotation += _objA.RigidBody.AngularVelocity * deltaTime;
+ // //_objA.RigidBody.CentroidWorldSpace += _objA.RigidBody.LinearVelocity * deltaTime;
+ // //_objA.Rotation += rotA;
+ // //_objA.RigidBody.CentroidWorldSpace += (posA - _objA.LocalToWorldPos(_localPosA));
+ // //_objA.RigidBody.LinearVelocity = (_objA.RigidBody.CentroidWorldSpace - _objA.OldPosition) / deltaTime;
+ // //_objA.RigidBody.AngularVelocity = (float)CalculateShortestAngleDifference(_objA.OldRotation, _objA.Rotation) / deltaTime;
+ //}
+
+
+
+
+
+ //var ra = Matrix2x2.CreateRotationMatrix(_objA.Rotation).Multiply(_localPosA);
+ //var rb = Matrix2x2.CreateRotationMatrix(_objB.Rotation).Multiply(_localPosB);
+ //var da = (_lambda * _objA.RigidBody.InvMass * -unit).SafeNormalize(Vector2.Zero);
+ //var db = (_lambda * _objB.RigidBody.InvMass * unit).SafeNormalize(Vector2.Zero);
+ //var oldVelA = Vector2.Dot(_objA.RigidBody.LinearVelocity + GeometryUtils.AnuglarVelocityToLinearVelocity(ra, _objA.RigidBody.AngularVelocity), da);
+ //var oldVelB = Vector2.Dot(_objB.RigidBody.LinearVelocity + GeometryUtils.AnuglarVelocityToLinearVelocity(rb, _objB.RigidBody.AngularVelocity), db);
+
+ //double rAdotN = Vector2.Dot(GeometryUtils.Rotate90(ra), da);
+ //double rBdotN = Vector2.Dot(GeometryUtils.Rotate90(rb), db);
+ //double R1 = rAdotN * rAdotN * _objA.RigidBody.GlobalInverseInertiaTensor;
+ //double R2 = rBdotN * rBdotN * _objB.RigidBody.GlobalInverseInertiaTensor;
+ //var impluseA = ((curPosA - pA).Length() / deltaTime - oldVelA) / (_objA.RigidBody.InvMass + (float)R1);
+ //var impluseB = ((curPosB - pB).Length() / deltaTime - oldVelB) / (_objB.RigidBody.InvMass + (float)R2);
+
+ //if (_objA.RigidBody.InvMass != 0)
+ // {
+ // // _objA.RigidBody.AddImpluseImmediate(impluseA * da, ra);
+ // _objA.Position += _objA.RigidBody.LinearVelocity * deltaTime;
+ // _objA.Rotation += _objA.RigidBody.AngularVelocity * deltaTime;
+
+ // _objA.RigidBody.LinearVelocity = (_objA.Position - _objA.OldPosition) / deltaTime;
+ //}
+ // if (_objB.RigidBody.InvMass != 0)
+ // {
+ // _objB.RigidBody.AddImpluseImmediate(impluseB * db, rb);
+ //_objB.Position += _objB.RigidBody.LinearVelocity * deltaTime;
+ //_objB.Rotation += _objB.RigidBody.AngularVelocity * deltaTime;
+ //var test = _objB.LocalToWorldPos(_localPosB);
+ //if (true)
+ // ;
+
+ //_objB.RigidBody.LinearVelocity = (_objB.Position - _objB.OldPosition) / deltaTime;
+ //_objB.RigidBody.AngularVelocity = (_objB.Rotation - _objB.OldRotation) / deltaTime;
+ // }
+ //_objA.RigidBody.AngularVelocity = Utils.Cross(_localPosA, _objA.RigidBody.LinearVelocity);
+ //_objB.RigidBody.AngularVelocity = Utils.Cross(_localPosB, _objB.RigidBody.LinearVelocity);
+ }
+
+ public override void ApplyForce(float deltaTime)
+ {
+ var pA = _objA.LocalToWorldPos(_localPosA);
+ var pB = _objB.LocalToWorldPos(_localPosB);
+ var L = (pB - pA).Length();
+ var unit = (pB - pA).SafeNormalize(Vector2.Zero);
+ float C = L - _restLength;
+ var ra = _objA.CachedRotationalMatrix.Multiply(_localPosA);
+ var rb = _objB.CachedRotationalMatrix.Multiply(_localPosB);
+ _objB.RigidBody.AddForce(C * -unit / _compliance, rb);
+ _objA.RigidBody.AddForce(C * unit / _compliance, ra);
+ }
+
+ public override List<(Vector2, Color)> GetDrawMesh()
+ {
+ var drawMesh = new List<(Vector2, Color)>();
+ drawMesh.Add((_objA.LocalToWorldPos(_localPosA), Color.Green));
+ drawMesh.Add((_objB.LocalToWorldPos(_localPosB), Color.Green));
+
+ return drawMesh;
+ }
+ }
+}
diff --git a/Sources/Everglow.Function/Physics/PBEngine/Core/GeometryUtils.cs b/Sources/Everglow.Function/Physics/PBEngine/Core/GeometryUtils.cs
new file mode 100644
index 000000000..b317f418b
--- /dev/null
+++ b/Sources/Everglow.Function/Physics/PBEngine/Core/GeometryUtils.cs
@@ -0,0 +1,735 @@
+using Everglow.Commons.Physics.PBEngine.Collision;
+using Everglow.Commons.Physics.PBEngine.Collision.Shapes;
+using Everglow.Commons.Utilities;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Terraria;
+
+namespace Everglow.Commons.Physics.PBEngine.Core
+{
+ public static class GeometryUtils
+ {
+
+ public static AABB ToAABBPhysSpace(this Rectangle rect)
+ {
+ return new AABB()
+ {
+ MinPoint = new Vector2(rect.X, -(rect.Y + rect.Height)),
+ MaxPoint = new Vector2(rect.X + rect.Width, -rect.Y)
+ };
+ }
+
+ public static bool ApproxEqual(float a, float b)
+ {
+ return Math.Abs(a - b) < 1e-6;
+ }
+ ///
+ /// Compute ab^T
+ ///
+ ///
+ ///
+ ///
+ public static Matrix3x3 OuterProduct(Vector3 a, Vector3 b)
+ {
+ return new Matrix3x3
+ {
+ [0, 0] = a.X * b.X,
+ [0, 1] = a.X * b.Y,
+ [0, 2] = a.X * b.Z,
+ [1, 0] = a.Y * b.X,
+ [1, 1] = a.Y * b.Y,
+ [1, 2] = a.Y * b.Z,
+ [2, 0] = a.Z * b.X,
+ [2, 1] = a.Z * b.Y,
+ [2, 2] = a.Z * b.Z
+ };
+ }
+
+ ///
+ /// Compute ab^T
+ ///
+ ///
+ ///
+ ///
+ public static Matrix2x2 OuterProduct(Vector2 a, Vector2 b)
+ {
+ return new Matrix2x2
+ {
+ [0, 0] = a.X * b.X,
+ [0, 1] = a.X * b.Y,
+ [1, 0] = a.Y * b.X,
+ [1, 1] = a.Y * b.Y,
+ };
+ }
+
+
+ public static float Cross(this Vector2 a, Vector2 b)
+ {
+ return a.X * b.Y - a.Y * b.X;
+ }
+
+
+ public static void Blit(Texture2D source, RenderTarget2D dest, Effect effect, string pass)
+ {
+ var sb = Main.spriteBatch;
+ var gd = Main.graphics.GraphicsDevice;
+ gd.SetRenderTarget(dest);
+ gd.Clear(Color.Transparent);
+ if (effect != null)
+ {
+ sb.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
+ effect.CurrentTechnique.Passes[pass].Apply();
+ sb.Draw(source, dest.Bounds, Color.White);
+ sb.End();
+ }
+ else
+ {
+ sb.Begin();
+ sb.Draw(source, dest.Bounds, Color.White);
+ sb.End();
+ }
+
+ }
+
+ ///
+ /// The linear velocity corresponding to given angular velocity and arm
+ ///
+ ///
+ ///
+ public static Vector2 AngularVelocityToLinearVelocity(Vector2 a, float w)
+ {
+ return new Vector2(-a.Y, a.X) * w;
+ }
+
+ public static Vector2 Rotate90(Vector2 a)
+ {
+ return new Vector2(-a.Y, a.X);
+ }
+
+ public static Vector2 ProjectPointOnSegment(Vector2 p, Vector2 start, Vector2 end)
+ {
+ var u = (end - start).SafeNormalize(Vector2.Zero);
+ return start + Vector2.Dot(u, p - start) * u;
+ }
+
+ public static float PointSignedDistanceToLine(Vector2 p, Vector2 start, Vector2 end)
+ {
+ Vector2 ab = end - start;
+ Vector2 ax = p - start;
+
+ float crossProductMagnitude = ab.Cross(ax);
+ float ABLength = ab.Length();
+
+ return -crossProductMagnitude / ABLength;
+ }
+
+ public static float PointSignedDistanceToSegmentGetNearest(Vector2 p, Vector2 start, Vector2 end, out Vector2 pointOnSeg)
+ {
+ var u = (end - start).SafeNormalize(Vector2.Zero);
+ float d = Vector2.Dot(u, p - start);
+ float sign = u.Cross(p - start) > 0 ? -1 : 1;
+ if (d <= 0)
+ {
+ pointOnSeg = start;
+ return Vector2.Distance(p, start) * sign;
+ }
+ else if (d >= Vector2.Distance(start, end))
+ {
+ pointOnSeg = end;
+ return Vector2.Distance(p, end) * sign;
+ }
+ pointOnSeg = start + d * u;
+ return (p - (start + d * u)).Length() * sign;
+ }
+
+ public static float PointDistance2ToSegment(Vector2 p, Vector2 start, Vector2 end)
+ {
+ var u = (end - start).SafeNormalize(Vector2.Zero);
+ float d = Vector2.Dot(u, p - start);
+ if (d <= 0)
+ {
+ return Vector2.DistanceSquared(p, start);
+ }
+ else if (d * d >= Vector2.DistanceSquared(start, end))
+ {
+ return Vector2.DistanceSquared(p, end);
+ }
+ return (p - (start + d * u)).LengthSquared();
+ }
+
+ public static float PointDistance2ToSegmentGetNearest(Vector2 p, Vector2 start, Vector2 end, out Vector2 pointOnSeg)
+ {
+ var u = (end - start).SafeNormalize(Vector2.Zero);
+ float d = Vector2.Dot(u, p - start);
+ if (d <= 0)
+ {
+ pointOnSeg = start;
+ return Vector2.DistanceSquared(p, start);
+ }
+ else if (d * d >= Vector2.DistanceSquared(start, end))
+ {
+ pointOnSeg = end;
+ return Vector2.DistanceSquared(p, end);
+ }
+ pointOnSeg = start + d * u;
+ return (p - (start + d * u)).LengthSquared();
+ }
+
+ public static float PointDistance2ToSegmentWithClip(Vector2 p, Vector2 start, Vector2 end)
+ {
+ var u = (end - start).SafeNormalize(Vector2.Zero);
+ float d = Vector2.Dot(u, p - start);
+ if (d <= 0)
+ {
+ return float.PositiveInfinity;
+ }
+ else if (d * d >= Vector2.DistanceSquared(start, end))
+ {
+ return float.PositiveInfinity;
+ }
+ return (p - (start + d * u)).LengthSquared();
+ }
+
+ public static void SphereConvexPolygonContactInfo(Vector2 cSphere, float radius, List edges,
+ List corners, Vector2 centerPoly,
+ List> localPositions)
+ {
+ float minDepth = float.PositiveInfinity;
+ foreach (var edge in edges)
+ {
+ float d = PointDistance2ToSegmentWithClip(cSphere, edge._pA, edge._pB);
+ if (d < minDepth)
+ {
+ minDepth = d;
+ }
+ }
+ foreach (var v in corners)
+ {
+ float d = Vector2.DistanceSquared(v, cSphere);
+ if (d < minDepth)
+ {
+ minDepth = d;
+ }
+ }
+
+ foreach (var edge in edges)
+ {
+ float d = PointDistance2ToSegmentWithClip(cSphere, edge._pA, edge._pB);
+ if (d == minDepth)
+ {
+ var p = ProjectPointOnSegment(cSphere, edge._pA, edge._pB);
+ localPositions.Add(new KeyValuePair(
+ (p - cSphere).SafeNormalize(Vector2.Zero) * radius,
+ p - centerPoly
+ ));
+ }
+ }
+ foreach (var v in corners)
+ {
+ float d = Vector2.DistanceSquared(v, cSphere);
+ if (d == minDepth)
+ {
+ localPositions.Add(new KeyValuePair(
+ (v - cSphere).SafeNormalize(Vector2.Zero) * radius,
+ v - centerPoly
+ ));
+ }
+ }
+ }
+
+ public static bool SphereConvexPolygonCollisionInfo(Vector2 cSphere, float radius, List edges, List corners,
+ out float depth, out Vector2 normal)
+ {
+ float maxDepth = float.NegativeInfinity;
+ normal = Vector2.Zero;
+ bool anyOutside = false;
+ foreach (var edge in edges)
+ {
+ float d = PointDistance2ToSegmentGetNearest(cSphere, edge._pA, edge._pB, out Vector2 pointOnSeg);
+ // 如果点在某一个线段的外面,那么就说明点在多边形的外面
+ if (d > maxDepth && (edge._pB - edge._pA).Cross(cSphere - edge._pA) <= 0)
+ {
+ maxDepth = d;
+ normal = (cSphere - pointOnSeg).SafeNormalize(Vector2.Zero);
+ }
+
+ //if (GeometryUtils.Cross(cSphere - edge._pA, edge._pB - edge._pA) <= 0)
+ //{
+ // anyOutside = true;
+ //}
+ //if (d > maxDepth)
+ //{
+ // maxDepth = d;
+ // keyEdge = edge;
+ //}
+ }
+ if (maxDepth < 0)
+ {
+ depth = 0;
+ return false;
+ }
+ maxDepth = (float)Math.Sqrt(maxDepth);
+ depth = radius - maxDepth;
+ if (maxDepth >= radius)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ //public static bool SphereConvexPolygonIsCollide(Vector2 cSphere, float radius, List edges, Vector2 centerPoly)
+ //{
+ // float maxDepth = float.NegativeInfinity;
+ // foreach (var edge in edges)
+ // {
+ // float d = GeometryUtils.PointSignedDistanceToSegment(cSphere, edge._pA, edge._pB);
+ // if (d > maxDepth)
+ // {
+ // maxDepth = d;
+ // }
+ // }
+ // return maxDepth < radius;
+ //}
+
+ private static (float, float) GetProjectedInterval(List vertices, Vector2 start, Vector2 dir)
+ {
+ float v = Vector2.Dot(dir, vertices[0] - start);
+ float minA = v, maxA = v;
+ for (int i = 1; i < vertices.Count; i++)
+ {
+ float x = Vector2.Dot(dir, vertices[i] - start);
+ minA = Math.Min(minA, x);
+ maxA = Math.Max(maxA, x);
+ }
+ return (minA, maxA);
+ }
+
+ public static bool ConvexPolygonPolygonCollisionInfo(List edgesA, List cornersA,
+ List edgesB, List cornersB, out float depth, out Vector2 normal)
+ {
+ depth = float.PositiveInfinity;
+ normal = Vector2.Zero;
+ for (int i = 0; i < edgesA.Count; i++)
+ {
+ Edge2D edge = edgesA[i];
+ Vector2 axis = edge.GetNormal();
+
+ var (amin, amax) = GetProjectedInterval(cornersA, edge._pA, axis);
+ var (bmin, bmax) = GetProjectedInterval(cornersB, edge._pA, axis);
+
+ if (Math.Max(amin, bmin) > Math.Min(amax, bmax))
+ {
+ return false;
+ }
+
+ float d = MinimumSeparatingDistance(amin, amax, bmin, bmax);
+ if (d < depth)
+ {
+ depth = d;
+ normal = axis;
+ }
+ }
+ for (int i = 0; i < edgesB.Count; i++)
+ {
+ Edge2D edge = edgesB[i];
+ Vector2 axis = edge.GetNormal();
+
+ var (amin, amax) = GetProjectedInterval(cornersA, edge._pA, axis);
+ var (bmin, bmax) = GetProjectedInterval(cornersB, edge._pA, axis);
+
+ if (Math.Max(amin, bmin) > Math.Min(amax, bmax))
+ {
+ return false;
+ }
+
+ float d = MinimumSeparatingDistance(amin, amax, bmin, bmax);
+ if (d < depth)
+ {
+ depth = d;
+ normal = axis;
+ }
+ }
+ return true;
+ }
+
+ public static void ConvexPolygonPolygonContactInfo(List edgesA, List cornersA,
+ List edgesB, List cornersB,
+ Vector2 polyCenterA, Vector2 polyCenterB, List> localPositions)
+ {
+ float closestDistance = float.PositiveInfinity;
+ foreach (var curPoint in cornersA)
+ {
+ foreach (var curEdge in edgesB)
+ {
+ float dist = PointDistance2ToSegment(curPoint, curEdge._pA, curEdge._pB);
+ if (dist < closestDistance)
+ {
+ closestDistance = dist;
+ localPositions.Clear();
+ localPositions.Add(new KeyValuePair(
+ curPoint - polyCenterA,
+ ProjectPointOnSegment(curPoint, curEdge._pA, curEdge._pB)
+ - polyCenterB
+ ));
+ }
+ else if (Math.Abs(dist - closestDistance) < 1e-3)
+ {
+ localPositions.Add(new KeyValuePair(
+ curPoint - polyCenterA,
+ ProjectPointOnSegment(curPoint, curEdge._pA, curEdge._pB)
+ - polyCenterB
+ ));
+ }
+ }
+ }
+ foreach (var curPoint in cornersB)
+ {
+ foreach (var curEdge in edgesA)
+ {
+ float dist = PointDistance2ToSegment(curPoint, curEdge._pA, curEdge._pB);
+ if (dist < closestDistance)
+ {
+ closestDistance = dist;
+ localPositions.Clear();
+ localPositions.Add(new KeyValuePair(
+ ProjectPointOnSegment(curPoint, curEdge._pA, curEdge._pB) - polyCenterA,
+ curPoint - polyCenterB
+ ));
+ }
+ else if (Math.Abs(dist - closestDistance) < 1e-6)
+ {
+ localPositions.Add(new KeyValuePair(
+ ProjectPointOnSegment(curPoint, curEdge._pA, curEdge._pB) - polyCenterA,
+ curPoint - polyCenterB
+ ));
+ }
+ }
+ }
+ }
+
+ public static bool SphereCapsuleCollisionInfo(Vector2 center, float radius, Vector2 segA, Vector2 segB,
+ float radius2, out float depth, out Vector2 normal)
+ {
+ float d = PointDistance2ToSegmentGetNearest(center, segA, segB, out Vector2 p);
+ depth = radius + radius2 - (float)Math.Sqrt(d);
+ normal = Vector2.Zero;
+ if (d >= (radius + radius2) * (radius + radius2))
+ {
+ return false;
+ }
+ normal = (p - center).SafeNormalize(Vector2.Zero);
+ return true;
+ }
+
+ public static void SphereCapsuleContactInfo(Vector2 center, float radius, Vector2 segA, Vector2 segB,
+ float radius2, List> localPositions)
+ {
+ var u = (segB - segA).SafeNormalize(Vector2.Zero);
+ float d_plane = Vector2.Dot(u, center - segA);
+ float length = (segA - segB).Length();
+ Vector2 capsuleCenter = (segA + segB) / 2;
+ d_plane = MathHelper.Clamp(d_plane, 0, length);
+
+ Vector2 vcenter = segA + u * d_plane;
+ Vector2 unit = (center - vcenter).SafeNormalize(Vector2.Zero);
+ localPositions.Add(new KeyValuePair(
+ -unit * radius,
+ vcenter + unit * radius2 - capsuleCenter
+ ));
+ }
+
+ public static bool CapsuleCapsuleCollisionInfo(Vector2 segA1, Vector2 segB1,
+ float radius1, Vector2 segA2, Vector2 segB2,
+ float radius2, out float depth, out Vector2 normal)
+ {
+ Vector2 pointA = segA2;
+ bool flipSign = false;
+ float d = PointDistance2ToSegmentGetNearest(segA2, segA1, segB1, out Vector2 pointB);
+ float d2;
+ if ((d2 = PointDistance2ToSegmentGetNearest(segB2, segA1, segB1, out Vector2 p)) < d)
+ {
+ d = d2;
+ pointA = segB2;
+ pointB = p;
+ }
+
+ if ((d2 = PointDistance2ToSegmentGetNearest(segA1, segA2, segB2, out p)) < d)
+ {
+ d = d2;
+ pointA = segA1;
+ pointB = p;
+ flipSign = true;
+ }
+
+ if ((d2 = PointDistance2ToSegmentGetNearest(segB1, segA2, segB2, out p)) < d)
+ {
+ d = d2;
+ pointA = segB1;
+ pointB = p;
+ flipSign = true;
+ }
+
+
+ depth = radius1 + radius2 - (float)Math.Sqrt(d);
+ normal = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ // 法线方向必须朝向Sphere碰撞体
+ if (flipSign)
+ {
+ normal = -normal;
+ }
+ if (depth <= 0)
+ return false;
+ return true;
+ }
+
+ public static void CapsuleCapsuleContactInfo(Vector2 segA1, Vector2 segB1,
+ float radius1, Vector2 segA2, Vector2 segB2,
+ float radius2, List> localPositions)
+ {
+ Vector2 pointA = segA2;
+ Vector2 capsuleCenter1 = (segA1 + segB1) / 2;
+ Vector2 capsuleCenter2 = (segA2 + segB2) / 2;
+ float d = PointDistance2ToSegmentGetNearest(segA2, segA1, segB1, out Vector2 pointB);
+ Vector2 N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Add(new KeyValuePair(
+ pointB + N * radius1 - capsuleCenter1,
+ pointA - N * radius2 - capsuleCenter2
+ ));
+ float d2;
+ if ((d2 = PointDistance2ToSegmentGetNearest(segB2, segA1, segB1, out Vector2 p)) < d)
+ {
+ d = d2;
+ pointA = segB2;
+ pointB = p;
+ localPositions.Clear();
+ N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Add(new KeyValuePair(
+ pointB + N * radius1 - capsuleCenter1,
+ pointA - N * radius2 - capsuleCenter2
+ ));
+ }
+ else if (d2 == d)
+ {
+ pointA = segB2;
+ pointB = p;
+ N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Add(new KeyValuePair(
+ pointB + N * radius1 - capsuleCenter1,
+ pointA - N * radius2 - capsuleCenter2
+ ));
+ }
+
+ // 防止四个点全都被取,最多只能保留两个接触点
+ if ((d2 = PointDistance2ToSegmentGetNearest(segA1, segA2, segB2, out p)) <= d)
+ {
+ d = d2;
+ pointA = segA1;
+ pointB = p;
+ N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Clear();
+ localPositions.Add(new KeyValuePair(
+ pointA - N * radius1 - capsuleCenter1,
+ pointB + N * radius2 - capsuleCenter2
+ ));
+ }
+
+ if ((d2 = PointDistance2ToSegmentGetNearest(segB1, segA2, segB2, out p)) < d)
+ {
+ d = d2;
+ pointA = segB1;
+ pointB = p;
+ N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Clear();
+ localPositions.Add(new KeyValuePair(
+ pointA - N * radius1 - capsuleCenter1,
+ pointB + N * radius2 - capsuleCenter2
+ ));
+ }
+ else if (d2 == d)
+ {
+ pointA = segB1;
+ pointB = p;
+ N = (pointA - pointB).SafeNormalize(Vector2.UnitX);
+ localPositions.Add(new KeyValuePair(
+ pointA - N * radius1 - capsuleCenter1,
+ pointB + N * radius2 - capsuleCenter2
+ ));
+ }
+
+ if (localPositions.Count > 2)
+ {
+ // Remove all elements after the first two
+ localPositions.RemoveRange(2, localPositions.Count - 2);
+ }
+ }
+
+
+ public static bool CapsuleConvexPolygonCollisionInfo(Vector2 segA1, Vector2 segB1,
+ float radius1, List corners, List edges, out float depth, out Vector2 normal)
+ {
+ depth = float.PositiveInfinity;
+ normal = Vector2.Zero;
+
+ {
+ // 中轴线段的法线和中轴线段本身都是关键分离轴
+ var edge = new Edge2D(segA1, segB1);
+ Vector2 axis = edge.GetNormal();
+ var (amin, amax) = GetProjectedInterval(new List() { segA1, segB1 }, edge._pA, axis);
+ var (bmin, bmax) = GetProjectedInterval(corners, edge._pA, axis);
+
+ amin -= radius1;
+ amax += radius1;
+
+ if (Math.Max(amin, bmin) > Math.Min(amax, bmax))
+ {
+ return false;
+ }
+
+ float d = MinimumSeparatingDistance(amin, amax, bmin, bmax);
+ if (d < depth)
+ {
+ depth = d;
+ normal = axis;
+ }
+
+ axis = (segB1 - segA1).SafeNormalize(Vector2.Zero);
+ (amin, amax) = GetProjectedInterval(new List() { segA1, segB1 }, edge._pA, axis);
+ (bmin, bmax) = GetProjectedInterval(corners, edge._pA, axis);
+
+ amin -= radius1;
+ amax += radius1;
+
+ if (Math.Max(amin, bmin) > Math.Min(amax, bmax))
+ {
+ return false;
+ }
+
+ d = MinimumSeparatingDistance(amin, amax, bmin, bmax);
+ if (d < depth)
+ {
+ depth = d;
+ normal = axis;
+ }
+ }
+
+ var capsuleCorners = new List() { segA1, segB1 };
+ for (int i = 0; i < edges.Count; i++)
+ {
+ Edge2D edge = edges[i];
+ Vector2 axis = edge.GetNormal();
+
+ var (amin, amax) = GetProjectedInterval(capsuleCorners, edge._pA, axis);
+ var (bmin, bmax) = GetProjectedInterval(corners, edge._pA, axis);
+
+ amin -= radius1;
+ amax += radius1;
+
+ if (Math.Max(amin, bmin) > Math.Min(amax, bmax))
+ {
+ return false;
+ }
+
+ float d = MinimumSeparatingDistance(amin, amax, bmin, bmax);
+ if (d < depth)
+ {
+ depth = d;
+ normal = axis;
+ }
+ }
+ return true;
+ }
+
+ public static void CapsuleConvexPolygonContactInfo(Vector2 segA1, Vector2 segB1,
+ float radius1, List corners, List