关于射线检测与碰撞检测
关于射线检测与碰撞检测
基础知识 射线与平面的交点
我们假设射线
起点为点
R
0
R_0
R0
方向为单位向量
R
d
R_d
Rd
到射线起点距离为
t
t
t的点
P
n
P_n
Pn可表示为
P
n
=
R
0
+
R
d
×
t
P_n=R_0+R_d\times t
Pn=R0+Rd×t
我们设平面上一点为点
P
0
P_0
P0
平面的法线为单位向量
D
D
D
则平面的一点
P
n
P_n
Pn满足
P
n
⋅
D
=
P
0
⋅
D
P_n \cdot D = P_0 \cdot D
Pn⋅D=P0⋅D
上式代入这个式子得式子
(
R
0
+
R
d
×
t
)
⋅
D
=
P
0
⋅
D
(R_0+R_d\times t) \cdot D = P_0 \cdot D
(R0+Rd×t)⋅D=P0⋅D
我们目标是要求出
t
t
t
t
×
R
d
⋅
D
=
P
0
⋅
D
−
R
0
⋅
D
t\times R_d\cdot D = P_0 \cdot D - R_0\cdot D
t×Rd⋅D=P0⋅D−R0⋅D
解出 t = P 0 ⋅ D − R 0 ⋅ D R d ⋅ D t = \frac { P_0 \cdot D - R_0\cdot D}{R_d\cdot D} t=Rd⋅DP0⋅D−R0⋅D
即 t = ( P 0 − R 0 ) ⋅ D R d ⋅ D t = \frac { (P_0 - R_0)\cdot D}{R_d\cdot D} t=Rd⋅D(P0−R0)⋅D
这时我们利用公式 P n = R 0 + R d × t P_n=R_0+R_d\times t Pn=R0+Rd×t 就可求出相交点了。
这个方法很基础,是一切平面射线检测的前提。
bool RayPlane(Vector3 p, Vector3 a, Ray ray, float dis, out Vector3 intP, out float f)//平面与射线
{
intP = Vector3.zero; f = 0;//输出值
float dn = Vector3.Dot(ray.direction, a);//先计算dn, 公式 t=(p-pD)·n/pD·n
if (dn > -1e-6f) return false;//大于0表示同向,等于0表示射线与平面平等,两种情况都返回假
float t = Vector3.Dot(p - ray.origin, a) / dn;//根据公式求 t=(p-pD)·n/pD·n
if (t > dis || t < 0) return false;//距离太大,或在射线的反向延长线上,都返回假
f = t;//保存到原点的距离
intP = ray.origin + t * ray.direction; //求出最终交点
return Contains(intP);//返回这点是否出界
}
1. 射线与球体的交点检测
我们假设射线
起点为点
R
0
R_0
R0
方向为单位向量
R
d
R_d
Rd
到射线起点距离为
t
t
t的点
P
n
P_n
Pn可表示为
P
n
=
R
0
+
R
d
×
t
P_n=R_0+R_d\times t
Pn=R0+Rd×t
我们设圆
圆心为点
C
C
C
圆的半径是
r
r
r
设
D
=
R
0
−
C
D = R_0-C
D=R0−C 从射线起点到圆心
设
a
=
D
⋅
R
d
a = D\cdot R_d
a=D⋅Rd 表示
D
D
D投影到射线的长度
设
e
2
=
D
⋅
D
e^2 = D\cdot D
e2=D⋅D 表示从射线起点到圆心模长平方
设
f
=
a
−
t
f = a-t
f=a−t 表示投影长度减去t的长度
设圆心到射线的垂线长为
g
g
g 则有
g
2
+
a
2
=
e
2
g^2+a^2 = e^2
g2+a2=e2
另有 g 2 + f 2 = r 2 g^2+f^2 = r^2 g2+f2=r2
合并两式得
r
2
−
f
2
+
a
2
=
e
2
r^2- f^2 + a^2 = e^2
r2−f2+a2=e2 我们目标要先求出
f
f
f
f
=
a
2
+
r
2
−
e
2
f=\sqrt{a^2+r^2-e^2}
f=a2+r2−e2
∵
f
=
a
−
t
\because f = a-t
∵f=a−t
∴
t
=
a
−
a
2
+
r
2
−
e
2
\therefore t = a-\sqrt{a^2+r^2-e^2}
∴t=a−a2+r2−e2
如果
a
2
+
r
2
−
e
2
<
0
a^2+r^2-e^2<0
a2+r2−e2<0 则表示射线与球体没有交点。
好了,既然
t
t
t 求出来了,就好办了。
我们利用公式
P
n
=
R
0
+
R
d
×
t
P_n=R_0+R_d\times t
Pn=R0+Rd×t 就可求出相交点了
public bool RayCast(Ray ray, float d2, out Hitinfo hitinfo)//求射线与球体相交:射线,距离平方,输出
{
hitinfo = new Hitinfo();d
Vector3 e = Pos - ray.origin, d = ray.direction;
float
e2 = e.sqrMagnitude,//模平方
r2 = rad * rad,//圆半径平方
a = Vector3.Dot(e, d),//e投影到d的模长
a2 = a * a,//a平方
f2 = r2 + a2 - e2;//f平方
if (e2 < r2 || e2 > d2 + r2 || a < 0 || f2 < 0) return false;//内部,距离太短,相反,无交点
hitinfo.dis = a - Mathf.Sqrt(f2);//到射线原点的距离
hitinfo.point = ray.GetPoint(hitinfo.dis);//射线与球体的交点
hitinfo.obj = this;//球体
return true;
}
2. AABB框重叠检测,可以快速判断两个AABB是否产生碰撞,以用来决定是否需要更进一步的复杂检测
如果两个AABB框重叠,则在三个轴上均会有交集,
使用它们的最小角点和最大角点即可判断出来
Corners[0]代表最小角点,Corners[1]代表最大角点,
public bool Overlap(Aabb b)//检查两个AABB是否重叠
{
Vector3[] cor = Corners, bcor = b.Corners;
if (cor[0].x > bcor[1].x || bcor[0].x > cor[1].x) return false;//区间无相交检测
if (cor[0].y > bcor[1].y || bcor[0].y > cor[1].y) return false;//区间无相交检测
if (cor[0].z > bcor[1].z || bcor[0].z > cor[1].z) return false;//区间无相交检测
return true;//三个方向都有交集,才相交
}
3. 判断AABB包含点算法,可以用来判断球和正方体是否产生碰撞
AABB框包含一个点,则这个点比最小角点大,比最大角点小
如果我们把AABB框进行扩展,即可求出球体和AABB是否有碰撞呢?
为什么这么说呢,因为我们AABB扩展尺寸刚好是球的半径,那么我们只要判断AABB是否包含圆心了,就知道两者是否产生了碰撞。
public bool Contains(Vector3 p)//是否包含一点
{
var cor = Corners;//获取两个极值端点
return Vector3.Min(cor[0], p) == cor[0] && Vector3.Max(cor[1], p) == cor[1];
}
public bool ContainsExpand(Vector3 p, float e)//尺寸扩展后 是否包含一点
{
extents += Vector3.one * e;//扩展
bool res = Contains(p);//判断包含
extents -= Vector3.one * e;//恢复
return res;
}
4. AABB框的射线检测算法,更容易实现长方体射线检测。
如果射线起点在框内,可以直接返回假。
要射线与六个面的交点会有点多,但如果面与射线同向或平行,我们就排除。
剩下最多三个面,再来用射线与平面相交法来检测交点。
如果交点在射线的反向延长线上,我们就丢弃。
如果交点在AABB框内,表示这个点是击中点, 表示相交成功!
public bool RayCast(Ray ray, float dis, out Hitinfo hit)//AABB射线相交检测,射线分别与六个面检测
{
hit = new Hitinfo();
Vector3[] corns = Corners;//最小最大角点数组
if (Contains(ray.origin)) return false;//如果包含原点,返回假
for (int i = 0; i < 6; i++)//遍历6个面, min点管左下后,max点管右上前
{
if (RayPlane(corns[i / 3], sixP[i], ray, dis, out Vector3 intP, out float f))//如果有交点
{
hit.dis = f;//保存到射线原点的距离
hit.point = intP;//射线与球体的交点
hit.obj = this;//球体
return true;
}
}
return false;
}
5. 长方体的射线检测算法,调用了AABB射线检测
1。获取这个长方体的原始AABB框,并将射线转换到局部空间。问题转换成对AABB求射线交点,和上一个问题是一样的。
2。获取射线与AABB框的交点,调用上面的方法!
3。转换这个交点到世界空间,即是碰撞点,完成!
public bool RayCast(Ray ray, float dis, out Hitinfo hit)//Box的射线相交检测, 转换到局部,再应用AABB检测
{
hit = new Hitinfo();
ray.origin = TranformPointInv(ray.origin);//转换射线起点到局部坐标
ray.direction = TranformDirInv(ray.direction);//转换射线方向到局部坐标
bool hited = Ab0.RayCast(ray, dis, out hit);
hit.point = TranformPoint(hit.point);//交点 转换回到 世界坐标
return hited;
}
6. 长方体的碰撞检测,思路简单高效。
思路如下(有两个长方体一个叫a, 一个叫b):
顶点包含测试:
b有任何一个顶点包含于a中,返回真
a有任何一个顶点包含于b中,返回真
AABB包围框测试, a,b的包围框没有交集,则不可能发生碰撞,直接返回假
这样可以筛选出大量的有相交和无相交的情况
如果以上两种情况都不满足,那作如下运算:
将b的每一条边当作一条射线, 射向a求交点, 调用上面的求长方体与射线的交点即可得到答案, 长方体的边共有12条,那需要12次运算,
但只要有其中任何一条边和a相交,不需要再继续遍历了,到这可立即返回真
另需要说明一点,因为之前做了顶点包含测试,后是因为顶点没有出现过包含的现象才会执行到这里,所以射线只需要单向测试,并不需要反向再测试一遍。这个地方,仔细想想就应该明白了。
public bool Overlap(Box b)//检测盒子和盒子是否相交, 把b的每条边当成射线进行检测。
{
Vector3[]
averts = Array.ConvertAll(Ab0.Verts, g => TranformPoint(g)),//顶点转换到世界坐标
bverts = Array.ConvertAll(b.Ab0.Verts, g => b.TranformPoint(g));//顶点转换到世界坐标 可用于构建射线
if (ContainsOne(bverts)) return true;//顶点包含测试, 以返回已相交的几何体
if (b.ContainsOne(averts)) return true;//顶点包含测试, 以返回已相交的几何体
if (!AbVerts.Overlap(b.AbVerts)) return false;//最新AABB没相交,返回假, 以排除不可能相交的几何体
var size = b.Ab0.Size;//size是x,y,z三个方向的长度尺寸
foreach (var item in Aabb.edges)//12条边做成12根射线,分别做一次射线检测
{
Hitinfo hit;
Ray ray = new Ray(bverts[item[0]], bverts[item[1]] - bverts[item[0]]);//生成射线
if (RayCast(ray, size[item[2]], out hit)) return true;//测试过顶点包含,不需要反向测试
}
return false;
}
以上内容介绍并不是很详细,比如怎么从局部坐标转到世界坐标,怎么从世界坐标转换到局部坐标,这个问题的讨论又需要花费一个较大的篇幅,好吧,本文主要是为了记录一下编程的思想,也希望能为您提供启发。先这样吧!
上一篇: 2D游戏中检测是否在地面的一种方法
下一篇: leetcode 767.重构字符串