当前位置: 移动技术网 > IT编程>开发语言>c# > Unity实现图形相交检测

Unity实现图形相交检测

2020年06月23日  | 移动技术网IT编程  | 我要评论
前言图形相交检测常常用在伤害判定,使用自定义的图形相交检测,可以在一定程度上控制性能。比如2d格斗游戏中使用的矩形包围盒(aabb),一些动作游戏中常常出现的扇形攻击。2d的图形相交检测能够满足大部分

前言

图形相交检测常常用在伤害判定,使用自定义的图形相交检测,可以在一定程度上控制性能。

比如2d格斗游戏中使用的矩形包围盒(aabb),一些动作游戏中常常出现的扇形攻击。

2d的图形相交检测能够满足大部分的需求,且可以拓展成为柱状的3d物体,2d比3d的计算复杂度会低很多,3d的图形检测原理与2d相似,本文会实现几个圆形与其他2d图形的相交检测:

    1、圆形与圆形

    2、圆形与胶囊体

    3、圆形与扇形

    4、圆形与凸多边形

    5、圆形与aabb

    6、圆形与obb

通过简单化处理,把被判定物都处理成由圆柱或多个圆柱构成的区域,所以只需要考虑圆形与其他形状的相交。

圆形与圆形

两个圆形的相交检测非常简单直观,只需要判断半径只和与距离的大小。

定义圆形区间:

/// <summary>
/// 圆形区间
/// </summary>
public struct circlearea
 {
 public vector2 o;
 public float r;
 }

o ——圆心坐标

r  ——圆半径

相交判断:

/// <summary>
/// 判断圆形与圆形相交
/// </summary>
/// <param name="circlearea"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool circle(circlearea circlearea, circlearea target)
 {
  return (circlearea.o - target.o).sqrmagnitude < (circlearea.r + target.r) * (circlearea.r + target.r);
 }

分离轴定理

分离轴定理(separating axis theorem, sat)分离轴定理是指,两个不相交的凸集必然存在一个分离轴,使两个凸集在该轴上的投影是分离的。

判断两个形状是否相交,实际上是判断分离轴是否能把两个形状分离。若存在分离轴能使两个图形分离,则这两个图形是分离的。

基于以上理论,寻找分离轴是我们要做的工作,重新考虑两个圆形的相交检测,实际上我们做的是把圆心连线的方向作为分离轴:

上图中两图形的投影在分离轴上是分离的,存在分离线将两者隔开,于是我们可以断定两图形是分离的。

胶囊体的本质

定义一个线段 u,距离 d。胶囊体实际上是与线段 u 的最短距离小于 d 的点的集合。判断一个点 x 处于胶囊体内部,就是判断点与线段的距离。

求点 x 与线段 u 最短距离的过程是:

1、求出点 x 在线段 u 所在直线上的投影点 p;

2、将投影点 p 限制在线段的范围内(如右图中投影点不在线段内,则限定到线段内);

3、x 与 p 的距离即为所求;

/// <summary>
/// 线段与点的最短距离。
/// </summary>
/// <param name="x0">线段起点</param>
/// <param name="u">线段向量</param>
/// <param name="x">求解点</param>
/// <returns></returns>
public static float sqrdistancebetweensegmentandpoint(vector2 x0, vector2 u, vector2 x)
 {
 float t = vector2.dot(x - x0, u) / u.sqrmagnitude;
 return (x - (x0 + mathf.clamp01(t) * u)).sqrmagnitude;
 }

为避免开方计算,结果使用距离的平方。

圆形与胶囊体

分离轴是线段上距离圆心最近的点p与圆心所在方向。

定义胶囊体:

/// <summary>
/// 胶囊体
/// </summary>
 public struct capsulearea
 {
 public vector2 x0;
 public vector2 u;
 public float d;
 }

相交判断:

/// <summary>
/// 判断胶囊体与圆形相交
/// </summary>
/// <param name="capsulearea"></param>
/// <param name="circlearea"></param>
/// <returns></returns>
public static bool capsule(capsulearea capsulearea, circlearea circlearea)
 {
  float sqrd = segmentpointsqrdistance(capsulearea.x0, capsulearea.u, circlearea.o);
  return sqrd < (circlearea.r + capsulearea.d) * (circlearea.r + capsulearea.d);
 }

圆形与扇形

当扇形角度大于180度时,就不再是凸多边形了,不能适用于分离轴理论。我们可以找出相交时圆心的所有可能区域,并把区域划分成可以简单验证的几个区域,逐个试验。

这里共划分了2个区间

1、半径为两者半径和的扇形区间,角度方向同扇形。验证方法是;验证距离与夹角。
2、扇形边为轴,圆形半径为大小组成的胶囊体空间,由于扇形的对称性,我们可以通过把圆心映射到一侧,从而只需要计算1条边。

定义扇形:

/// <summary>
/// 扇形区间。
/// </summary>
 public struct sectorarea
 {
  public vector2 o;
  public float r;
  public vector2 direction;
  public float angle;
 }

相交检测:

/// <summary>
/// 判断圆形与扇形相交。
/// </summary>
/// <param name="sectorarea"></param>
/// <param name="target"></param>
/// <returns></returns>
  public static bool sector(sectorarea sectorarea, circlearea target)
  {
   vector2 tempdistance = target.o - sectorarea.o;
   float halfangle = mathf.deg2rad * sectorarea.angle / 2;
   if (tempdistance.sqrmagnitude < (sectorarea.r + target.r) * (sectorarea.r + target.r))
   {
    if (vector3.angle(tempdistance, sectorarea.direction) < sectorarea.angle / 2)
    {
     return true;
    }
    else
    {
     vector2 targetinsectoraxis = new vector2(vector2.dot(tempdistance,
      sectorarea.direction), mathf.abs(vector2.dot(tempdistance, new vector2(-sectorarea.direction.y, sectorarea.direction.x))));
     vector2 directioninsectoraxis = sectorarea.r * new vector2(mathf.cos(halfangle), mathf.sin(halfangle));
     return segmentpointsqrdistance(vector2.zero, directioninsectoraxis, targetinsectoraxis) <= target.r * target.r;
    }
   }
   return false;
  }

圆形与凸多边形

定义多边形:

/// <summary>
/// 多边形区域。
/// </summary>
public struct polygonarea
 {
  public vector2[] vertexes;
 }

相交检测:

/// <summary>
/// 判断多边形与圆形相交
/// </summary>
/// <param name="polygonarea"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool polygons(polygonarea polygonarea, circlearea target)
  {
   if (polygonarea.vertexes.length < 3)
   {
    debug.log("多边形边数小于3.");
    return false;
   }
   #region 定义临时变量
   //圆心
   vector2 circlecenter = target.o;
   //半径的平方
   float sqrr = target.r * target.r;
   //多边形顶点
   vector2[] polygonvertexes = polygonarea.vertexes;
   //圆心指向顶点的向量数组
   vector2[] directionbetweencenterandvertexes = new vector2[polygonarea.vertexes.length];
   //多边形的边
   vector2[] polygonedges = new vector2[polygonarea.vertexes.length];
   for (int i = 0; i < polygonarea.vertexes.length; i++)
   {
    directionbetweencenterandvertexes[i] = polygonvertexes[i] - circlecenter;
    polygonedges[i] = polygonvertexes[i] - polygonvertexes[(i + 1)% polygonarea.vertexes.length];
   }
   #endregion
 
   #region 以下为圆心处于多边形内的判断。
   //总夹角
   float totalangle = vector2.signedangle(directionbetweencenterandvertexes[polygonvertexes.length - 1], directionbetweencenterandvertexes[0]);
   for (int i = 0; i < polygonvertexes.length - 1; i++)
    totalangle += vector2.signedangle(directionbetweencenterandvertexes[i], directionbetweencenterandvertexes[i + 1]);
   if (mathf.abs(mathf.abs(totalangle) - 360f) < 0.1f)
    return true;
   #endregion
   #region 以下为多边形的边与圆形相交的判断。
   for (int i = 0; i < polygonedges.length; i++)
    if (segmentpointsqrdistance(polygonvertexes[i], polygonedges[i], circlecenter) < sqrr)
     return true;
   #endregion
   return false;
  }

圆形与aabb

定义aabb:

/// <summary>
/// aabb区域
/// </summary>
public struct aabbarea
 {
  public vector2 center;
  public vector2 extents;
 }

aabb是凸多边形的特例,是长宽边分别与x/y轴平行的矩形,这里我们要充分的利用他的对称性。

1 利用对称性将目标圆心映射到,以aabb中心为原点、两边为坐标轴的坐标系,的第一象限

2 将目标圆心映射到,以aabb第一象限角点为原点、两边为坐标轴的坐标系,的第一象限

3 最后只需要判断圆形半径与步骤2中映射点的向量大小

相交检测:

/// <summary>
/// 判断aabb与圆形相交
/// </summary>
/// <param name="aabbarea"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool aabb(aabbarea aabbarea, circlearea target)
  {
   vector2 v = vector2.max(aabbarea.center - target.o, -(aabbarea.center - target.o));
   vector2 u = vector2.max(v - aabbarea.extents,vector2.zero);
   return u.sqrmagnitude < target.r * target.r;
  }

圆形与obb

定义obb:

/// <summary>
/// obb区域
/// </summary>
public struct obbarea
 {
  public vector2 center;
  public vector2 extents;
  public float angle;
 }

obb相对于aabb,矩形边不与坐标轴重合,对于它和圆形的相交检测只需要把圆形旋转到obb边所在坐标系中,剩下的步骤与aabb的相同。

相交检测:

/// <summary>
/// 判断obb与圆形相交
/// </summary>
/// <param name="obbarea"></param>
/// <param name="target"></param>
/// <returns></returns>
public static bool obb(obbarea obbarea, circlearea target)
  {
   vector2 p = obbarea.center - target.o;
   p = quaternion.angleaxis(-obbarea.angle, vector3.forward) * p;
   vector2 v = vector2.max(p, -p);
   vector2 u = vector2.max(v - obbarea.extents, vector2.zero);
   return u.sqrmagnitude < target.r * target.r;
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网