Introduction 游戏开发过程中,特别是战斗中避免不了要判定各种攻击和技能是否击中,而这些判定都得通过碰撞系统来实现正确的判定。本篇文章正是为了学习了解战斗系统中攻击技能判定是如何实现的而编写的。
后续大部分理论知识和小部分文字以及截图来源:
【Unity】图形相交检测
感谢该博主的无私分享
所有的代码后续会传到Github上,想查看可视化绘制的完整代码可在Github上下到。
碰撞检测 What 碰撞检测 — 碰撞检测指判定物体(不论是3D还是2D物体)之间的相交。比如物理系统里碰撞检测指的是物体之间的实体碰撞以及物理效果。战斗系统里碰撞检测指的是技能和攻击的命中判定。
Why 那么为什么需要碰撞检测了?
物理游戏里要模拟真实的物理效果,碰撞检测是必不可少的。
战斗系统里,攻击和技能的命中判定都离不开碰撞检测。
纯数学级别的碰撞检测模拟比Unity自带的Collider准确且高效。
Note:
虽然我们可以通过Unity现成的Collider去做碰撞检测,但Collider的碰撞判定顺序是不可控的,并且Collider组件会带来很大的性能开销,所以无论是出于准确性还是性能考虑,战斗力的技能和攻击判定我们都不能直接用Collider(特别是涉及网络同步的时候)。
How 功能需求
支持3D一些简单形状(点,AABB,OBB,球形)的碰撞检测判定
支持2D一些简单形状(点,矩形,圆形,扇形,胶囊体,OBB)的碰撞检测判定
理论知识 碰撞检测从原理上来说就是数学运算,就是图形相交检测。
所以要实现一套自己的碰撞检测系统,我们要先学习图形相交检测相关的数学知识。
这里我们以2D的碰撞检测为例来学习实现碰撞检测系统。复杂的3D碰撞检测很多时候可以简化到2D来实现相同的效果并且得到更高效的判定结果。后续只会实现一部分简单的3D形状的碰撞判定。
2D形状 点 点在2D里我们通过X,Y坐标来定义,所以点的定义如下:
圆形 圆形是由圆心+半径来定义,圆形的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public struct Circle2D{ public Vector2 Center; public float Radius; public Circle2D (Vector2 center, float radius ) { Center = center; Radius = radius; } }
矩形(轴对齐包围盒-AABB) 矩形(轴对齐包围盒-AABB)是由中心点+长宽来定义,长宽边分别与X/Y轴平行的矩形,矩形(轴对齐包围盒-AABB)定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public struct AABB2D{ public Vector2 Center; public Vector2 Extents; public AABB2D (Vector2 center, Vector2 extents ) { Center = center; Extents = extents; } }
Note:
矩形(轴对齐包围盒-AABB)的长宽平行于X和Y轴
有向包围盒(OBB) 有向包围盒(OBB)可以理解成一个带旋转角度的矩形(轴对齐包围盒-AABB),正因为矩形(轴对齐包围盒-AABB)无法表达长宽不平行与X和Y轴的形状,所以才有了有向包围盒(OBB)。
让我们通过下图来形象的理解圆形,矩形(轴对齐包围盒-AABB)和有向包围盒(OBB)的区别:
向包围盒(OBB)由中心点+长宽+旋转角度来定义,定义如下:
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 public struct OBB2D{ public Vector2 Center; public Vector2 Extents; public float Angle; public OBB2D (Vector2 center, Vector2 extents, float angle ) { Center = center; Extents = extents; Angle = angle; } }
胶囊体 胶囊体是由起点+线段u+胶囊体半径d定义。胶囊体实际上是与线段u的最短距离d的点的集合。
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 public struct Capsule2D{ public Vector2 StartPoint; public Vector2 PointLine; public float Radius; public Capsule2D (Vector2 startpoint, Vector2 pointline, float radius ) { StartPoint = startpoint; PointLine = pointline; Radius = radius; } }
从上面引申出了点与线段的最短距离算法:
1 2 3 4 5 6 7 8 9 10 11 12 public static float SqrDistanceBetweenSegmentAndPoint (Vector2 startpoint, Vector2 line, Vector2 targetpoint ) { float t = Vector2.Dot(targetpoint - startpoint, line) / line.sqrMagnitude; return (targetpoint - (startpoint + Mathf.Clamp01(t) * line)).sqrMagnitude; }
要理解上面的公式,需要知道向量里的一些基本概念,参考:
向量学习
上面的计算公式理解如下:
float t = Vector2.Dot(targetpoint - startpoint, line) / line.sqrMagnitude; // 计算求解点映射到线段line上的比例。
(startpoint + Mathf.Clamp01(t) * line) // 计算的是映射点P点的坐标
(targetpoint - (startpoint + Mathf.Clamp01(t) * line) ) // 表达的是目标点到线段的那条向量d
(targetpoint - (startpoint + Mathf.Clamp01(t) line) ) .sqrMagnitude; // 得到目标点到线段的向量的距离平方(*后续用平方比代替开方比,因为平方比在计算机上比开方比更快 )
后续计算形状相交很多都会转化成点与线的距离来比较实现碰撞检测
Note:
判定一个点处于胶囊体内部,就是判断点与线段的距离
计算机开方计算比平方计算慢,所以我们采用平方比较来代替开方值比较
扇形 扇形是由起点+扇形半径+扇形朝向+扇形角度定义,扇形定义如下:
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 public struct Sector2D{ public Vector2 StartPoint; public float Radius; public Vector2 Direction; public float Angle; public Sector2D (Vector2 startpoint, float radius, Vector2 direction, float angle ) { StartPoint = startpoint; Radius = radius; Direction = direction; Angle = angle; } }
凸多边形 多边形的定义是否多个顶点位置定义的,多边形定义如下:
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 40 41 42 43 * Description: Polygon2D.cs * Author: TONYTANG * Create Date: 2022/03/20 */ using System.Collections;using System.Collections.Generic;using UnityEngine;namespace TH.Module.Collision2D { public struct Polygon2D { public Vector2[] Vertexes; public Polygon2D (Vector2[] vertexes ) { Vertexes = vertexes; } public bool IsValide ( ) { if (Vertexes == null || Vertexes.Length < 3 )) { Debug.LogError("多边形顶点数不应该小于3个!" ); return false ; } return true ; } } }
Note:
上述定义并不能保证一定是凸多边形
分离轴定理 了解了各种形状的定义后,在了解真正的碰撞检测判定前,为了更好的理解后面的碰撞检测计算,我们先来了解一个概念分离轴定理
分离轴定理(separating axis theorem, SAT)分离轴定理是指,两个不相交的凸集必然存在一个分离轴,使两个凸集在该轴上的投影是分离的。
判断两个形状是否相交,实际上是判断分离轴是否能把两个形状分离。若存在分离轴能使两个图形分离,则这两个图形是分离的。
基于以上理论,寻找分离轴是我们要做的工作,重新考虑两个圆形的相交检测,实际上我们做的是把圆心连线的方向作为分离轴:
通过分离轴定理的定义可以看出,通过找到两个凸集形状的分离轴,我们可以实现两个形状相交的判定,这个会成为我们后面判定碰撞检测的重要理论知识
接下来让我们真正进入碰撞检测实战。
形状碰撞检测 点与圆形 点与圆形的相交判定实际上就是判定点与圆心的距离是否大于半径。
这里理论很简单,就不上效果图了,直接看代码:
1 2 3 4 5 6 7 8 9 10 public static bool CircleAndPointIntersection2D (Circle2D circle1, Vector2 point ) { return (circle1.Center - point).sqrMagnitude < circle1.Radius * circle1.Radius; }
点与矩形(AABB) 点与矩形的判定也比较容易,利用AABB的轴和XZ平行,我们可以直接比较点的X和Z是否在AABB的最大最小范围内即可(也可以理解成点与AABB中心的距离是否小于等于AABB的宽/2和长/2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static bool PointInAABB2D (AABB2D aabb, Vector2 point ) { Vector2 offset = aabb.Center - point; offset = Vector2.Max(offset, -offset); return offset.x <= aabb.Extents.x / 2 && offset.y <= aabb.Extents.y / 2 ; }
代码比较简单就不详细解释了。
点与多边形 判定一个点是否在多边形内 ,本文使用的是角度和 和叉积(点线) 判定法,更多的解题思路参考:
详谈判断点在多边形内的七种方法(最全面)
上文中提到射线法 比较优,但考虑到理解难易度,还是角度和 和叉积(点线) 法更易理解,本文主要以这两种解法为例。
角度和法 学过平面几何的同学都知道,如果一个点在凸多边形内,那么这个点和凸多边形各顶点连线构成的夹角和应该为360度 。
角度和法正是利用了这一个定义来实现一个点是否在凸多边形内的。
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 public static bool PointInPolygon2D (Polygon2D polygon, Vector2 point ) { if (!polygon.IsValide()) { return false ; } Vector2[] polygonVertexes = polygon.Vertexes; int polygonVertexsNumber = polygonVertexes.Length; Vector2[] pointLines = new Vector2[polygonVertexsNumber]; for (int i = 0 , length = polygonVertexsNumber; i < length; i++) { pointLines[i] = polygonVertexes[i] - point; } float totalAngle = Vector2.SignedAngle(pointLines[polygonVertexsNumber - 1 ], pointLines[0 ]); for (int i = 0 , length = polygonVertexsNumber - 1 ; i < length; i++) { totalAngle += Vector2.SignedAngle(pointLines[i], pointLines[i + 1 ]); } if (Mathf.Abs(Mathf.Abs(totalAngle) - 360 f) < 0.1 f) { return true ; } return false ; }
从上面可以看到我们通过累加点到多边形所有顶点构成的线段的夹角总和判定除了点是否在多边形内。但上面的方法new了大量的Vector3数组以及用到了Vector2.SignedAngle()等反三角函数,所以速度和效率上并不是不太优,接下来我们会讲到针对点是否在凸多边形内的更优解法(叉乘(点线)法 )。
Note:
此方法适用于凸多边形和凹多边形
叉积(点线)法 叉积(点线)法的核心思想是判定点到多边形所有顶点构成的边是否都在相邻多边形边的一侧(顺时针的话需要都在左侧,逆时针的话需要都在右侧)
这里我们利用叉乘(叉乘结果>0还是<0能区分)来实现两个向量的左右关系判定。
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 public static bool PointInConvexPolygon2D (Polygon2D polygon, Vector2 point ) { if (!polygon.IsValide()) { return false ; } Vector2[] polygonVertexes = polygon.Vertexes; int polygonVertexsNumber = polygonVertexes.Length; Vector3 pointLine; Vector3 polygonEdge; int index; for (int i = 0 , length = polygonVertexsNumber; i < length; i++) { pointLine = new Vector3(point.x - polygonVertexes[i].x, 0 , point.y - polygonVertexes[i].y); index = (i + 1 ) % polygonVertexsNumber; polygonEdge = new Vector3(polygonVertexes[index].x - polygonVertexes[i].x, 0 , polygonVertexes[index].y - polygonVertexes[i].y); if (Vector3.Cross(pointLine, polygonEdge).y >= 0 ) { return false ; } } return true ; }
从上面的效果图可以看出我们通过叉积法也成功判定出了点是否在凸多边形 内。
但上面的方式需要创建大量的Vector3来构建边以及叉乘判定,虽然Vector3是结构体类型不会造成GC问题,但核心只是为了实现两个向量的方向判定,依然有优化的空间。
我们可以把2D的向量方向当做就在XZ平面的3D向量来计算(即把Y轴值当做0),通过叉乘最原始的公式直接计算叉乘后Y轴值的大小来判定两个向量的方向关系 。
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 40 41 42 43 44 public static bool BetterPointInConvexPolygon2D (Polygon2D polygon, Vector2 point ) { if (!polygon.IsValide()) { return false ; } Vector2[] polygonVertexes = polygon.Vertexes; int polygonVertexsNumber = polygonVertexes.Length; int index; float lineX; float lineZ; float edgeX; float edgeZ; for (int i = 0 , length = polygonVertexsNumber; i < length; i++) { lineX = point.x - polygonVertexes[i].x; lineZ = point.y - polygonVertexes[i].y; index = (i + 1 ) % polygonVertexsNumber; edgeX = polygonVertexes[index].x - polygonVertexes[i].x; edgeZ = polygonVertexes[index].y - polygonVertexes[i].y; if ((lineZ * edgeX - lineX * edgeZ) > 0 ) { return false ; } } return true ; }
可以看到不通过构建Vector3,我们直接利用叉乘的公式把2D当3D计算出叉乘的Y值,依然可以有效的判定出点是否在凸多边形内。
在3D维度两个向量不再是在单纯的XZ平面而是在任意平面,这里不再能直接使用叉乘的y值进行判定,除非两个向量在XZ平面。
Note:
此方法适用于凸多边形
注意叉积判定对于多边形顶点的顺序要求
2维向量叉乘AXB = (0, 0, x1 y2 - x2 y1),x1 y2 - x2 y1大于0表示A在B的右侧,小于0表示A在B的左侧,等于0表示A和B平行(大前提是左手坐标系,右手坐标系左右侧是反过来的)
叉乘y大于0表示A在B的左侧还是小于0表示A在B的左侧主要看我们用的左手坐标系还是右手坐标系,因为Unity是左手坐标系,所欲叉乘y大于0表示A在B的左侧
利用叉乘结果y大于0还是y小于0区分方向的同时,我们还能用于推断向量A在向量B的左侧还是右侧(大前提是两个向量在XZ平面)
圆形与圆形 圆形和圆形相交判定起始就是2个圆心的距离是否大于两个圆的半径之和。
1 2 3 4 5 6 7 8 9 10 public static bool CircleAndCircleIntersection2D (Circle2D circle1, Circle2D circle2 ) { return (circle1.Center - circle2.Center).sqrMagnitude < (circle1.Radius + circle2.Radius) * (circle1.Radius + circle2.Radius); }
这个比较简单没什么好说的。
圆形与胶囊体 结合前面分离轴定理,我们要想判定圆形和胶囊体是否相交,首先找到分离轴,而圆形和胶囊体的分离轴是胶囊体线段上距离圆形最近的点P与圆心所在的方向。
所以判定圆心到胶囊体线段的距离是否大于胶囊体半径+圆形半径即可。
1 2 3 4 5 6 7 8 9 10 11 public static bool CircleAndCapsuleIntersection2D (Circle2D circle, Capsule2D capsule ) { float sqrdistance = SqrDistanceBetweenSegmentAndPoint(capsule.StartPoint, capsule.PointLine, circle.Center); return sqrdistance < (circle.Radius + capsule.Radius) * (circle.Radius + capsule.Radius); }
圆形与矩形(AABB) 利用AABB的对称性,我们以AABB中心为原点,两边为坐标轴的坐标系。通过将圆形移动AABB的大小,然后看圆形所在位置与中心点距离是否还大于圆形半径来判定圆形和AABB是否相交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static bool CircleAndAABBIntersection2D (Circle2D circle, AABB2D aabb ) { Vector2 v = Vector2.Max(circle.Center - aabb.Center, -(circle.Center - aabb.Center)); Vector2 u = Vector2.Max(v - aabb.Extents / 2 , Vector2.zero); return u.sqrMagnitude < circle.Radius * circle.Radius; }
圆形与有向包围盒(OBB) 结合前面OBB的定义:
有向包围盒(OBB)可以理解成一个带旋转角度的矩形(轴对齐包围盒-AABB)
可以看出OBB和AABB的主要区别是支持了选择的AABB,那么我们判定OBB和圆形的相交检测也就等价于把圆形旋转到OBB所在坐标系后,然后当做圆形和AABB相交判定即可。
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 private static Vector3 Vector3Temp;public static bool CircleAndOBBIntersection2D (Circle2D circle, OBB2D obb ) { Vector2 point = circle.Center - obb.Center; Vector3Temp.x = point.x; Vector3Temp.y = 0 ; Vector3Temp.z = point.y; Vector3 point2 = Quaternion.AngleAxis(-obb.Angle, Vector3.up) * Vector3Temp; point.x = point2.x; point.y = point2.z; Vector2 v = Vector2.Max(point, -point); Vector2 u = Vector2.Max(v - obb.Extents / 2 , Vector2.zero); return u.sqrMagnitude < circle.Radius * circle.Radius; }
Note:
Quaternion.AngleAxis()如果绕Y轴旋转直接乘以Vector2会得到错误的结果,所以我们需要当做Vector3来计算,最后再转换回Vector2
虽然Vector3是结构体不会造成GC,但不想每次判定都创建Vector3所以定义了一个临时Vector3用于计算重用
圆形与凸多边形 圆形与凸多边形相交判定只需要在点和凸多边形相交判定上增加圆心不在凸多边形内时,判定圆心到多边形每条边的距离是否有小于圆形半径即可。
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 public static bool CircleAndPolygonIntersection2D (Circle2D circle, Polygon2D polygon ) { if (BetterPointInConvexPolygon2D(polygon, circle.Center)) { return true ; } Vector2 circleCenter = circle.Center; float sqrR = circle.Radius * circle.Radius; var vertexes = polygon.Vertexes; int polygonVertexsNumber = polygon.Vertexes.Length; Vector2 edge; int index; for (int i = 0 , length = polygonVertexsNumber; i < length; i++) { index = (i + 1 ) % polygonVertexsNumber; edge.x = vertexes[index].x - vertexes[i].x; edge.y = vertexes[index].y - vertexes[i].y; if (SqrDistanceBetweenSegmentAndPoint(vertexes[i], edge, circleCenter) < sqrR) { return true ; } } return false ; }
注释写的很详细了,就不详细解释说明了。
圆形与扇形 当扇形角度大于180度时,就不再是凸多边形了,不能适用于分离轴理论。
我们把相交判定分成两个部分判定:
圆形在扇形角度内,直接判定扇形原点和圆心距离是否大于扇形半径+圆形半径
圆形在扇形角度外,利用扇形对称性,将原因映射到扇形一侧,通过映射后的原因与扇形一条边的距离是否小于圆形半径判定相交
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 public static bool CircleAndSectorIntersection2D (Circle2D circle, Sector2D sector ) { Vector2 tempDistance = circle.Center - sector.StartPoint; float halfAngle = Mathf.Deg2Rad * sector.Angle / 2 ; if (tempDistance.sqrMagnitude < (sector.Radius + circle.Radius) * (sector.Radius + circle.Radius)) { if (Vector3.Angle(tempDistance, sector.Direction) < sector.Angle / 2 ) { return true ; } else { Vector2 targetInsectorAxis = new Vector2(Vector2.Dot(tempDistance, sector.Direction), Mathf.Abs(Vector2.Dot(tempDistance, new Vector2(-sector.Direction.y, sector.Direction.x)))); Vector2 directionInSectorAxis = sector.Radius * new Vector2(Mathf.Cos(halfAngle), Mathf.Sin(halfAngle)); return SqrDistanceBetweenSegmentAndPoint(Vector2.zero, directionInSectorAxis, targetInsectorAxis) <= circle.Radius * circle.Radius; } } return false ; }
Note:
当扇形角度大于180度时,就不再是凸多边形了,不能适用于分离轴理论。
矩形(AABB)与矩形(AABB) AABB与AABB的相交判定,考虑到AABB的轴都相同且都为矩形的特殊性,相交判定只需要判定两个AABB原点的距离是否有任何一个小于宽/2之和或长/2之和即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static bool AABBAndAABBIntersection2D (AABB2D aabb1, AABB2D aabb2 ) { Vector2 offset = aabb2.Center - aabb1.Center; offset = Vector2.Max(offset, -offset); return offset.x <= (aabb1.Extents.x / 2 + aabb2.Extents.x / 2 ) && offset.y <= (aabb1.Extents.y / 2 + aabb2.Extents.y / 2 ); }
AABB和AABB的判定比较简单,就不详细解释了。
进阶学习 关于OBB等凸多边形相关的交叉判定,我们需要用到分离轴定理,这里暂时不深入实现了(TODO ),详情参考:
UNITY实战进阶-OBB包围盒详解-6
关于3D的相交检测,这里我们用Unity Bounds提供的关于OBB的实现类。
更多的3D相交检测自定义实现,可以参考二维的实现扩展到3维,这里暂时不做进一步的学习记录。(大部分时候我们可以简化相交检测,比如讲3D简化到2D,扇形椭圆简化到圆形或点来实现相交碰撞检测 )
线段相交 线段相交理论知识参考:
Unity3D C#数学系列之判断两条线段是否相交并求交点
相交大前提:
AB和CD必须共面。
如何判定AB和CD共面了?
通过计算ACD平面法线是否与AB垂直即可 (2D向量可以省去这一步 )。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static bool IsCoplanarVector (Vector3 a, Vector3 b, Vector3 c, Vector3 d ) { var ab = b - a; var ca = a - c; var cd = d - c; var normal = Vector3.Cross(ca, cd); if (Mathf.Approximately(Vector3.Dot(normal, ab), Mathf.Epsilon)) { return true ; } return false ; }
如何判定相交:
快速排斥和跨立实现判定是否相交
这里我采用跨立法来判定,跨立法是指两个线段相交必须满足两个线段的两个点分别在另一个线段的两侧 。
这里我们利用叉乘 来判定两个向量的方向从而判定是否线段在另一个线段两侧。
2D向量:
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 40 41 42 43 public static float Cross (Vector2 a, Vector2 b ) { return a.x * b.y - b.x * a.y; } public static bool IsCross (Vector2 a, Vector2 b, Vector2 c, Vector2 d ) { var ab = b - a; var ac = c - a; var ad = d - a; if (Vector2Utilities.Cross(ab, ac) * Vector2Utilities.Cross(ab, ad) >= 0 ) { return false ; } var ca = a - c; var cb = b - c; var cd = d - c; if (Vector2Utilities.Cross(cd, ca) * Vector2Utilities.Cross(cd, cb) >= 0 ) { return false ; } return true ; }
3D向量:
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 public static bool IsCross (Vector3 a, Vector3 b, Vector3 c, Vector3 d ) { var ab = b - a; var ac = c - a; var ad = d - a; if (Vector3.Dot(Vector3.Cross(ab, ac), Vector3.Cross(ab, ad)) >= 0 ) { return false ; } var ca = a - c; var cb = b - c; var cd = d - c; if (Vector3.Dot(Vector3.Cross(cd, ca), Vector3.Cross(cd, cb)) >= 0 ) { return false ; } return true ; }
如何计算交点:
几何法分析出交点
让我们直接看下结论:
这个结论主要是通过2D平面几何的相似三角形以及向量叉乘在的集合解释是平行四边形面积(两个三角形面积之和)推到而来的。
详情推到参考:
Unity3D C#数学系列之判断两条线段是否相交并求交点
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 public static bool GetIntersectPoint (Vector2 a, Vector2 b, Vector2 c, Vector2 d, out Vector2 intersectPos ) { intersectPos = Vector2.zero; if (!IsCross(a, b, c, d)) { return false ; } var ab = b - a; var ca = a - c; var cd = d - c; Vector3 v1 = Vector3.Cross(ca, cd); Vector3 v2 = Vector3.Cross(cd, ab); float ratio = Vector3.Dot(v1, v2) / v2.sqrMagnitude; intersectPos = a + ab * ratio; return true ; }
可以看到我们成功可视化算出了两条线段相交的点。
那么如果我们不考虑两条线段相交,单纯想求得两个线段(包含延长线)上的交点时,应该怎么计算了?
实际上相似三角形的推论即使两个线段不相交也是成立的,所以我们唯一要确保的就是两个线段不平行,只有两个线段不平行才有交点。所以我们不需要判定线段两个点是否在两侧只需判定两个线段是否平行,剩下的步骤和之前一致即可。
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 40 41 42 43 44 45 46 public static bool IsParallel (Vector2 a, Vector2 b, Vector2 c, Vector2 d ) { var ab = b - a; var cd = d - c; return Mathf.Approximately(Vector2Utilities.Cross(ab, cd), Mathf.Epsilon); } public static bool GetLineIntersectPoint (Vector2 a, Vector2 b, Vector2 c, Vector2 d, out Vector2 intersectPos ) { intersectPos = Vector2.zero; if (IsParallel(a, b, c, d)) { return false ; } var ab = b - a; var ca = a - c; var cd = d - c; Vector3 v1 = Vector3.Cross(ca, cd); Vector3 v2 = Vector3.Cross(cd, ab); float ratio = Vector3.Dot(v1, v2) / v2.sqrMagnitude; intersectPos = a + ab * ratio; return true ; }
可以看到我们通过不判定线段点是否在两侧,只判定平时依然计算出了两个线段在延长线上的交点。
实战 待添加……
注意事项
Github CollisionDetectionStudy
学习总结
2D图形相交检测更多的是平面几何知识,通过将图形转换成纯数学定义来求解
向量里的点乘叉乘对于求解向量的角度和旋转方向以及两个向量的相对位置很有用
Reference 【Unity】图形相交检测
3D 碰撞检测
Unity3D-游戏中的技能碰撞检测
[包围盒]球,AABB,OBB
UNITY实战进阶-OBB包围盒详解-6
unity3d:两条线段相交并求交点坐标
Unity3D C#数学系列之判断两条线段是否相交并求交点