抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

简介

本文主要阐述胶囊体与其他包围盒的碰撞检测原理和实现方式。由于其基本原理相同,因此放在同一篇讲。

原理

对于胶囊体来说,碰撞检测的原理都是一样的,都是确定两个检测点,然后判断点与点之间的关系。因此我们可以参考胶囊体之间的碰撞检测得出胶囊体与其他包围盒的碰撞检测原理。

胶囊体与圆的碰撞,其实就是简化版的胶囊体与胶囊体的碰撞。因为在胶囊体的碰撞检测中,我们最终得到的就是圆与圆的碰撞。

因此对于胶囊体与圆来说,圆就相当于省去一个胶囊体的计算,而直接得到结果。

胶囊体与 AABB 的碰撞,我们只要先拿到 AABB 中心点在胶囊体线段上的最近点,然后根据这个最近点求 AABB 表面的最近点,然后比较两点的距离是否小于胶囊体半径即可。

我们可以使用往期碰撞检测中获取最近点的方法即可。

判断碰撞的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//求胶囊体半径平方
float totalRadius = Mathf.Pow(data2.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}

胶囊体与 OBB 的碰撞,我们只要先拿到 OBB 中心点在胶囊体线段的最近点,然后根据这个最近点求 OBB 表面上的最近点,然后比较两点的距离是否小于胶囊体半径即可。

我们可以使用往期碰撞检测中获取最近点的方法即可。

判断碰撞的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//求胶囊体半径平方
float totalRadius = Mathf.Pow(data2.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}

胶囊体与射线的碰撞,我们不妨把射线看成另一个胶囊体的线段部分,根据射线的起点和终点我们可以得到线段的中心点,然后就和胶囊体碰撞检测的流程一样进行即可。

不过在最终的碰撞检测部分,逻辑要和 AABB 及 OBB 一样,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//求胶囊体半径平方
float totalRadius = Mathf.Pow(data2.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}

代码

工具函数

求线段上最近点
1
2
3
4
5
6
7
8
private Vector3 GetClosestPointOnLineSegment(Vector3 start, Vector3 end, Vector3 point)
{
Vector3 line = end - start;
//dot line line 求长度平方
float ratio = Vector3.Dot(point - start, line) / Vector3.Dot(line, line);
ratio = Mathf.Min(Mathf.Max(ratio, 0), 1);
return start + ratio * line;
}
求AABB最近点
1
2
3
4
5
6
7
8
9
private Vector3 GetClosestPointAABB(Vector3 pos, CollisionData other)
{
//Vector3 center = data1.center;
Vector3 nearP = Vector3.zero;
nearP.x = Mathf.Clamp(pos.x, other.min.x, other.max.x);
nearP.y = Mathf.Clamp(pos.y, other.min.y, other.max.y);
nearP.z = Mathf.Clamp(pos.z, other.min.z, other.max.z);
return nearP;
}
求OBB最近点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Vector3 GetClosestPointOBB(Vector3 pos,CollisionData other)
{
Vector3 nearP = data2.center;
//求球心与OBB中心的距离向量 从OBB中心指向球心
Vector3 center1 = pos;
Vector3 center2 = data2.center;
Vector3 dist = center1 - center2;

float[] extents = new float[3] { data2.extents.x, data2.extents.y, data2.extents.z };
Vector3[] axes = data2.axes;

for (int i = 0; i < 3; i++)
{
//计算距离向量到OBB坐标轴的投影长度 即距离向量在OBB坐标系中的对应坐标轴的长度
float distance = Vector3.Dot(dist, axes[i]);
distance = Mathf.Clamp(distance, -extents[i], extents[i]);
//还原到世界坐标
nearP.x += distance * axes[i].x;
nearP.y += distance * axes[i].y;
nearP.z += distance * axes[i].z;
}
return nearP;
}

碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void CollisionCapsule2Circle(CollisionData data1,CollisionData data2)
{
//计算头尾点最值
Vector3 point1 = data1.center + data1.direction * data1.extents.y;
Vector3 point2 = data1.center - data1.direction * data1.extents.y;

Vector3 closest = GetClosestPointOnLineSegment(point1, point2, data2.center);

//求两个球半径和
float totalRadius = Mathf.Pow(data1.radius + data2.radius, 2);
//球两个球心之间的距离
float distance = (closest - data2.center).sqrMagnitude;
//距离小于等于半径和则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void CollisionCapsule2AABB(CollisionData data1,CollisionData data2)
{
//计算头尾点最值
Vector3 pointA1 = data1.center + data1.direction * data1.extents.y;
Vector3 pointA2 = data1.center - data1.direction * data1.extents.y;

Vector3 closest1 = GetClosestPointOnLineSegment(pointA1, pointA2, closest2);
Vector3 closest2 = GetClosestPointAABB(closest1, data2);

//求胶囊体半径平方
float totalRadius = Mathf.Pow(data1.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void CollisionCapsule2OBB(CollisionData data1,CollisionData data2)
{
//计算头尾点最值
Vector3 pointA1 = data1.center + data1.direction * data1.extents.y;
Vector3 pointA2 = data1.center - data1.direction * data1.extents.y;

Vector3 closest1 = GetClosestPointOnLineSegment(pointA1, pointA2, closest2);
Vector3 closest2 = GetClosestPointOBB(closest1, data2);

//求胶囊体半径平方
float totalRadius = Mathf.Pow(data1.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void CollisionRay2Capsule(CollisionData data1,CollisionData data2)
{
//计算头尾点最值
Vector3 pointA1 = data1.center;
Vector3 pointA2 = data1.center + data1.direction * data1.radius;

Vector3 pointB1 = data2.center + data2.direction * data2.extents.y;
Vector3 pointB2 = data2.center - data2.direction * data2.extents.y;

Vector3 center = (pointA1 + pointA2) * 0.5f;

Vector3 closest2;

if ((pointB1 - center).magnitude <= (pointB2 - center).magnitude)
{
closest2 = pointB1;
}
else
{
closest2 = pointB2;
}

Vector3 closest1 = GetClosestPointOnLineSegment(pointA1, pointA2, closest2);
closest2 = GetClosestPointOnLineSegment(pointB1, pointB2, closest1);

//求胶囊体半径平方
float totalRadius = Mathf.Pow(data2.radius, 2);
//求两个点之间的距离
float distance = (closest1 - closest2).sqrMagnitude;
//距离小于等于半径平方则碰撞
if (distance <= totalRadius)
{
return true;
}
else
{
return false;
}
}

项目工程

更新日志

2024-05-19

  1. 修复 AABB 与 OBB 最近点检测 bug。

2024-04-18

  1. 修复 AABB 与 OBB 求最近点参数带入错误。

2024-03-05

  1. 更新胶囊体与其他碰撞检测基本内容。

评论