碰撞检测之 AABB 包围盒

1.包围盒描述

包围盒算法是一种求解离散点集最优包围空间的方法。
基本思想是用体积稍大且特性简单的几何体(称为包围盒)来近似地代替复杂的几何对象。
最常见的包围盒算法有AABB包围盒(Axis-aligned bounding box),包围球(Sphere),方向包围盒OBB(Oriented bounding box) 以及固定方向凸包FDH(Fixed directions hulls或k-DOP)。

2、AABB包围盒

AABB 包围盒就是采用一个长方体将物体包裹起来,进行两个物体的相交性检测时仅检测物体对应包围盒(包裹物体的长方体)的相交性。

另外,AABB 包围盒有一个重要特性,那就是包围盒对应的长方体每一个面都是与某个坐标轴平面平行的,因此,AABB 包围盒又称了 轴对齐包围盒 。

有了上述约定后,确定 AABB 包围盒就简单多了,仅需要记录 6 个值即可,这 6 个值分别代表包围盒在每个坐标轴上的最小值与最大值,即 xmin、xmax、ymin、ymax、zmin、zmax。

也就是说,实际物体上所有的点都必须满足以下条件:
H2O is是液体。
另外,可以将表示 AABB 包围盒的 6 个参数分为以下两组:
在这里插入图片描述
其中,Pmin 是 3 个轴坐标最小值的集合,Pmax 是 3 个轴坐标最大值的集合。

知道了表示 AABB 包围盒的 6 个参数后就可以非常方便地求得 AABB 包围盒的几何中心,公式如下:
在这里插入图片描述

3、AABB 包围盒计算

了解了 AABB 包围盒的原理之后,求 AABB 包围盒 6 个参数的方法就简单多了,只需要对物体中的所有顶点坐标进行扫描,求出各个轴分量的最大值与最小值即可。

首先引入一个辅助类 Vector3f,其对象可以记录 3 个浮点数组成的三元组,用来表示物体的位置、速度等。

public class Vector3f {
    float x;//三维变量中的x值
    float y;//三维变量中的y值
    float z;//三维变量中的z值
    public Vector3f(float x,float y,float z)
    {
        this.x=x;
        this.y=y;
        this.z=z;
    }
    public void add(Vector3f temp)
    {
        this.x+=temp.x;
        this.y+=temp.y;
        this.z+=temp.z;
    }
}

有了此辅助类之后,物体的位置、速度等就可以直接用此类的对象表示了。

接下来就是计算物体的 AABB 包围盒,定义一个对象 AABBBox 来表示包围盒。

public class AABBBox {
    float minX;//x轴最小位置
    float maxX;//x轴最大位置
    float minY;//y轴最小位置
    float maxY;//y轴最大位置
    float minZ;//z轴最小位置
    float maxZ;//z轴最大位置

    public AABBBox(float[] vertices)
    {
        init();
        findMinAndMax(vertices);
    }

    public AABBBox(float minX,float maxX,float minY,float maxY,float minZ,float maxZ)
    {
        this.minX=minX;
        this.maxX=maxX;
        this.minY=minY;
        this.maxY=maxY;
        this.minZ=minZ;
        this.maxZ=maxZ;
    }
    //初始化包围盒的最小以及最大顶点坐标
    public void init()
    {
        minX=Float.POSITIVE_INFINITY; // 正无穷
        maxX=Float.NEGATIVE_INFINITY; // 负无穷
        minY=Float.POSITIVE_INFINITY;
        maxY=Float.NEGATIVE_INFINITY;
        minZ=Float.POSITIVE_INFINITY;
        maxZ=Float.NEGATIVE_INFINITY;
    }
    //获取包围盒的实际最小以及最大顶点坐标
    public void findMinAndMax(float[] vertices)
    {
        for(int i=0;i<vertices.length/3;i++)
        {
            //判断X轴的最小和最大位置
            if(vertices[i*3]<minX)
            {
                minX=vertices[i*3];
            }
            if(vertices[i*3]>maxX)
            {
                maxX=vertices[i*3];
            }
            //判断Y轴的最小和最大位置
            if(vertices[i*3+1]<minY)
            {
                minY=vertices[i*3+1];
            }
            if(vertices[i*3+1]>maxY)
            {
                maxY=vertices[i*3+1];
            }
            //判断Z轴的最小和最大位置
            if(vertices[i*3+2]<minZ)
            {
                minZ=vertices[i*3+2];
            }
            if(vertices[i*3+2]>maxZ)
            {
                maxZ=vertices[i*3+2];
            }
        }
    }
    //获得物体平移后的AABB包围盒
    public AABBBox getCurrAABBBox(Vector3f currPosition)
    {
        AABBBox result=new AABBBox
                (
                        this.minX+currPosition.x,
                        this.maxX+currPosition.x,
                        this.minY+currPosition.y,
                        this.maxY+currPosition.y,
                        this.minZ+currPosition.z,
                        this.maxZ+currPosition.z
                );
        return result;
    }
}

在 AABBBox 的构造函数中需要传入物体的顶点序列,在这些序列中分别找出 x 、y、z 坐标的最小值和最大值,也可以直接传入包围盒的 6 个顶点参数。

如果在程序运行过程中,物体发生了移动,那就需要根据移动后的位置计算产生新包围盒对象,getCurrAABBBox方法需要传递物体移动后位置的 3 个坐标分量,用于与原始包围盒的 6 个 参数进行运算,产生移动后包围盒的 6 个参数。

4、AABB 包围盒的碰撞检测

在上面求得了物体的包围盒,求包围盒的目的就是为了简化物体运动过程中的碰撞检测,接下来就是介绍 AABB 包围盒碰撞检测的策略。

由于任何一个 AABB 包围盒的各个面都平行于坐标平面,因此判断两个 AABB 包围盒是否发生碰撞仅需要分别判断 3 个轴方向的交叠部分大小是否大于设定的阈值,若大于则发生了碰撞,否则没有发生碰撞。

具体碰撞检测代码如下:

 public boolean check(RigidBody ra, RigidBody rb)//true为撞上
    {
        float[] over = calOverTotal
                (
	                // 两个物体的 AABB 包围盒
                        ra.collObject.getCurrAABBBox(ra.currLocation),
                        rb.collObject.getCurrAABBBox(rb.currLocation)
                );
        // 三个方向的交叠值与设定的阈值进行比较
        return over[0] > V_UNIT && over[1] > V_UNIT && over[2] > V_UNIT;
    }
	
	// 传入两个物体的 AABB 包围盒
    public float[] calOverTotal(AABBBox a, AABBBox b) {
        float xOver = calOverOne(a.maxX, a.minX, b.maxX, b.minX);
        float yOver = calOverOne(a.maxY, a.minY, b.maxY, b.minY);
        float zOver = calOverOne(a.maxZ, a.minZ, b.maxZ, b.minZ);
        return new float[]{xOver, yOver, zOver};
    }
	// 计算每个轴方向的交叠值
    public float calOverOne(float amax, float amin, float bmax, float bmin) {
        float minMax = 0;
        float maxMin = 0;
        if (amax < bmax)//a物体在b物体左侧
        {
            minMax = amax;
            maxMin = bmin;
        } else //a物体在b物体右侧
        {
            minMax = bmax;
            maxMin = amin;
        }

        if (minMax > maxMin) {
            return minMax - maxMin;
        } else {
            return 0;
        }
    }

在 calOverTotal 方法里面要传入两个物体的 AABB 包围盒。

然后在 calOverOne 方法里面分别计算两个包围盒 3 个轴的交叠值,思路就是先比较两个AABB 包围盒在对应轴的最大分量,此比较是为了得出两个物体的方位,哪个物体在此轴的正方向那一侧。然后用在轴正方向的那一侧物体的坐标最小值减去在轴负方向那一侧的物体的最大值,如果为负数,则说明没有发生交叠,如果其值为正数,则说明两个物体发生了交叠,交叠值和设定的阈值进行比较。

5、光线与包围盒(AABB)的相交检测算法

5.1 Cocos2dx中实现Ray-AABB相交(碰撞)检测的算法
bool Ray::intersects(const AABB& aabb) const
{
  Vec3 ptOnPlane; //射线与包围盒某面的交点
  Vec3 min = aabb._min; //aabb包围盒最小点坐标
  Vec3 max = aabb._max; //aabb包围盒最大点坐标
  
  const Vec3& origin = _origin; //射线起始点
  const Vec3& dir = _direction; //方向矢量
  
  float t;
  
  //分别判断射线与各面的相交情况
  
  //判断射线与包围盒x轴方向的面是否有交点
  if (dir.x != 0.f) //射线x轴方向分量不为0 若射线方向矢量的x轴分量为0,射线不可能经过包围盒朝x轴方向的两个面
  {
    /*
      使用射线与平面相交的公式求交点
     */
    if (dir.x > 0)//若射线沿x轴正方向偏移
      t = (min.x - origin.x) / dir.x;
    else  //射线沿x轴负方向偏移
      t = (max.x - origin.x) / dir.x;
    
    if (t > 0.f) //t>0时则射线与平面相交
    {
      ptOnPlane = origin + t * dir; //计算交点坐标
      //判断交点是否在当前面内
      if (min.y < ptOnPlane.y && ptOnPlane.y < max.y && min.z < ptOnPlane.z && ptOnPlane.z < max.z)
      {
        return true; //射线与包围盒有交点
      }
    }
  }
  
  //若射线沿y轴方向有分量 判断是否与包围盒y轴方向有交点
  if (dir.y != 0.f)
  {
    if (dir.y > 0)
      t = (min.y - origin.y) / dir.y;
    else
      t = (max.y - origin.y) / dir.y;
    
    if (t > 0.f)
    {
      ptOnPlane = origin + t * dir;
 
      if (min.z < ptOnPlane.z && ptOnPlane.z < max.z && min.x < ptOnPlane.x && ptOnPlane.x < max.x)
      {
        return true;
      }
    }
  }
  
  //若射线沿z轴方向有分量 判断是否与包围盒y轴方向有交点
  if (dir.z != 0.f)
  {
    if (dir.z > 0)
      t = (min.z - origin.z) / dir.z;
    else
      t = (max.z - origin.z) / dir.z;
    
    if (t > 0.f)
    {
      ptOnPlane = origin + t * dir;
      
      if (min.x < ptOnPlane.x && ptOnPlane.x < max.x && min.y < ptOnPlane.y && ptOnPlane.y < max.y)
      {
        return true;
      }
    }
  }
  
  return false;
}
5.2 Slabs method (最常用的方法)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
观察上述三幅图可以得出,只要发生区间交叠,光线与平面就能相交,

那么区间交叠出现的条件便是:光线进入平面处的最大t值小于光线离开平面处的最小t值

那么问题就变成了如何求 光线进入平面处的最大t值 以及 光线离开平面处的最小t值

这个问题很简单,通过光线与平面相交的参数方程求解就可以了,

光线的参数方程为R(t) = O + t * Dir

一般平面方程为aX+bY+cZ+d=0,因为AABB的六个面分别平行于XY、XZ、YZ平面,所以平面的方程为X=d,Y=d,Z=d

光线与垂直于x轴的两个面相交时,t = (d - O.x) / Dir.x
光线与垂直于y轴的两个面相交时,t = (d - O.y) / Dir.y
光线与垂直于z轴的两个面相交时,t = (d - O.z) / Dir.z

注意到t<0时,交点位于光线的起点之后,则光线(射线)并未与盒体发生相交

bool BBox::hit(const Ray& ray) const
{
	double ox = ray.o.x;double oy = ray.o.y;double oz = ray.o.z;
	double dx = ray.d.x;double dy = ray.d.y;double dz = ray.d.z;
	double tx_min,ty_min,tz_min;
	double tx_max,ty_max,tz_max;
 
	//x0,y0,z0为包围体的最小顶点
	//x1,y1,z1为包围体的最大顶点
	if(abs(dx) < 0.000001f) 
	{
		//若射线方向矢量的x轴分量为0且原点不在盒体内
		if(ox < x1 || ox > x0)
			return false ;
	}
	else
	{
		if(dx>=0)
		{
			tx_min = (x0-ox)/dx;
			tx_max = (x1-ox)/dx;
		}
		else
		{
			tx_min = (x1-ox)/dx;
			tx_max = (x0-ox)/dx;
		}
 
	}
 
	
	if(abs(dy) < 0.000001f) 
	{
		//若射线方向矢量的x轴分量为0且原点不在盒体内
		if(oy < y1 || oy > y0)
			return false ;
	}
	else
	{
		if(dy>=0)
		{
			ty_min = (y0-oy)/dy;
			ty_max = (y1-oy)/dy;
		}
		else
		{
			ty_min = (y1-oy)/dy;
			ty_max = (y0-oy)/dy;
		}
	}
 
	if(abs(dz) < 0.000001f) 
	{
		//若射线方向矢量的x轴分量为0且原点不在盒体内
		if(oz < z1 || oz > z0)
			return false ;
	}
	else
	{
		if(dz>=0)
		{
			tz_min = (z0-oz)/dz;
			tz_max = (z1-oz)/dz;
		}
		else
		{
			tz_min = (z1-oz)/dz;
			tz_max = (z0-oz)/dz;
		}
	}
 
	double t0,t1;
	//光线进入平面处(最靠近的平面)的最大t值 
	t0=max(tz_min,max(tx_min,ty_min));
	
	//光线离开平面处(最远离的平面)的最小t值
	t1=min(tz_max,min(tx_max,ty_max));
	
	return t0<t1;
}

6、参考

《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
光线与包围盒(AABB)的相交检测算法
射线与包围盒的相交测试

  • 15
    点赞
  • 131
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值