Cocos2d-x教程(35)-三维拾取Ray-AABB碰撞检测算法

欢迎加入Cocos2d-x 交流群:193411763

转载时请注明原文出处 :http://blog.csdn.net/u012945598/article/details/39927911

-----------------------------------------------------------------------------------------------------------------------------------------------------------

1.三维拾取技术

在3D游戏中通常会有这样的需求,用户可以选取3D世界中的某些物体进行如拖拽等操作,这时便需要程序通过将二维屏幕上的点坐标转换为三维世界中的坐标,并进行比对,这个过程就需要用到三维拾取。

三维拾取的基本原理并不复杂,我们仍然以Cocos2d-x 3.3beta0版本来分析。拾取思想可以简单的理解为:首先得到在屏幕上的触摸点的坐标,然后根据摄像机投影矩阵与屏幕上的触摸点计算出一条射线ray,注意,正常情况下之后应该去找与射线相交并且交点距离射线起点最近的点所在的包围盒,这个包围盒才是应该被触摸到的包围盒,但是实际上Cocos2d-x 3.3beta0中并没有做此操作,这个问题在后文讨论。

2.原理图

三维拾取原理图如图1-1所示:

图1-1

如上图的这种情况,射线实际上会与物体A和物体B都相交,但是实际上物体A才应该是被触摸到的物体。但是Cocos2d-x 3.3beta0中目前还没有做此处理,仅判断出了射线是否与某一当前存在的包围盒存在交点。下面看一下Cocos2d-x 3.3beta0中OBB包围盒Demo中的一段的码:

void Sprite3DWithOBBPerfromanceTest::onTouchesBegan(const std::vector<Touch*>& touches, Event* event)
{
    for (auto touch: touches)
    {
        auto location = touch->getLocationInView(); //获取在屏幕坐标系中触摸点的坐标

        if(_obb.size() > 0) //判断屏幕上是否存在OBB包围盒
        {
            _intersetList.clear();
            Ray ray;  //射线
            //根据屏幕坐标系触摸点坐标计算射线在世界坐标系中的起始点和方向矢量
            calculateRayByLocationInView(&ray,location);

            for(int i = 0; i < _obb.size(); i++)
            {
                if(ray.intersects(_obb[i])) //判断射线与包围盒是否相交
                {
                    _intersetList.insert(i);
                    return;
                }
            }
        }
    }
}

这个算法在对包围盒进行遍历时,一旦得出的射线和某一个包围盒碰撞了,循环便终止了,然后取到了这个物体的包围盒。但是如果两个包围盒重叠在一起的时候,应该判断是哪个包围盒距离射线起点的距离更近,更近的才是应该被摸到的盒子。而此种做法相当于,两个重叠的盒子哪个排在容器前面先被遍历到了就相当于摸到了哪个。

下面抛开上述问题,回到图1-1。按照图1-1所示,最终需要做的就是,根据屏幕上的触摸点求出射线与近平面和远平面的交点,这样便能得到我们所需要的射线了。在Cocos2d-x 3.3beta0中,Ray表示的便是射线类,里面包含了射线的起点以及方向矢量,同时提供了与AABB包围盒、OBB包围盒碰撞检测的算法。同时在上述代码中,调用了一个方法:calculateRayByLocationInView(Ray* ray, const Vec2& location)。这个方法便是根据屏幕坐标系上一点坐标求射线的方法,下面来看一下实现:

//将屏幕上一点坐标转化为世界坐标系中的坐标
void Sprite3DWithOBBPerfromanceTest::unproject(const Mat4& viewProjection, const Size* viewport, Vec3* src, Vec3* dst)
{
    assert(dst);

    assert(viewport->width != 0.0f && viewport->height != 0.0f);

    //计算点在摄像机坐标系中的坐标,利用触摸点的坐标与摄像机近平面坐标的线性相关性
    Vec4 screen(src->x / viewport->width, ((viewport->height - src->y)) / viewport->height, src->z, 1.0f);

    screen.x = screen.x * 2.0f - 1.0f;
    screen.y = screen.y * 2.0f - 1.0f;
    screen.z = screen.z * 2.0f - 1.0f;

    //将得到的摄像机坐标系中的坐标经摄像机矩阵的逆矩阵变换得到其世界坐标
    viewProjection.getInversed().transformVector(screen, &screen);

    //齐次坐标规范化
    if (screen.w != 0.0f)
    {
        screen.x /= screen.w;
        screen.y /= screen.w;
        screen.z /= screen.w;
    }
    //保存该点的世界坐标
    dst->set(screen.x, screen.y, screen.z);
}
//计算射线
void Sprite3DWithOBBPerfromanceTest::calculateRayByLocationInView(Ray* ray, const Vec2& location)
{
    auto dir = Director::getInstance();
    auto view = dir->getWinSize(); //获取窗口大小 用于计算触摸点在摄像机坐标系中位置
    Mat4 mat = dir->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    //获取投影矩阵栈栈顶元素(即原栈顶元素的拷贝,携带父节点的变换信息)
    mat = dir->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

    Vec3 src = Vec3(location.x, location.y, -1);
    Vec3 nearPoint; //近平面点
    unproject(mat, &view, &src, &nearPoint);//计算近平面点在世界坐标系中的坐标

    src = Vec3(location.x, location.y, 1);
    Vec3 farPoint; //远平面点
    unproject(mat, &view, &src, &farPoint);//计算远平面点在世界坐标系中的坐标

    Vec3 direction; //方向矢量
    Vec3::subtract(farPoint, nearPoint, &direction); //远平面点减去近平面点求方向矢量
    direction.normalize(); //归一化

    ray->_origin = nearPoint;  //射线起点位置
    ray->_direction = direction; //射线方向矢量
}

3.Ray-AABB碰撞检测

进行求出射线后,需要做的便是与包围盒的碰撞检测了,如之前的代码所示,在做碰撞检测时,Cocos2d-x 3.3beta0中的Ray类里面为我们提供了intersects()方法,该方法的参数有OBB对象和AABB对象两种,实际上最终都是转换成了对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;
}
时间: 2024-10-21 20:35:31

Cocos2d-x教程(35)-三维拾取Ray-AABB碰撞检测算法的相关文章

Cocos2d-x教程(33)-三维物体AABB碰撞检测算法

欢迎加入Cocos2d-x 交流群:193411763 转载时请注明原文出处 :http://blog.csdn.net/u012945598/article/details/38870705 ---------------------------------------------------------------------------------------------------------------------------------------------------------

Cocos2d-三维拾取Ray-AABB碰撞检测算法【转】

1.三维拾取技术 在3D游戏中通常会有这样的需求,用户可以选取3D世界中的某些物体进行如拖拽等操作,这时便需要程序通过将二维屏幕上的点坐标转换为三维世界中的坐标,并进行比对,这个过程就需要用到三维拾取. 三维拾取的基本原理并不复杂,我们仍然以Cocos2d-x 3.3beta0版本来分析.拾取思想可以简单的理解为:首先得到在屏幕上的触摸点的坐标,然后根据摄像机投影矩阵与屏幕上的触摸点计算出一条射线ray,注意,正常情况下之后应该去找与射线相交并且交点距离射线起点最近的点所在的包围盒,这个包围盒才

PCL系列——三维重构之移动立方体算法

PCL系列 PCL系列--读入PCD格式文件操作 PCL系列--将点云数据写入PCD格式文件 PCL系列--拼接两个点云 PCL系列--从深度图像(RangeImage)中提取NARF关键点 PCL系列--如何可视化深度图像 PCL系列--如何使用迭代最近点法(ICP)配准 PCL系列--如何逐渐地配准一对点云 PCL系列--三维重构之泊松重构 PCL系列--三维重构之贪婪三角投影算法 PCL系列--三维重构之移动立方体算法 说明 通过本教程,我们将会学会: 如果通过移动立方体算法进行三维点云重

Cocos2d-x教程(34)-三维物体OBB碰撞检测算法

欢迎加入Cocos2d-x 交流群:193411763 转载时请注明原文出处 :http://blog.csdn.net/u012945598/article/details/39665911 ---------------------------------------------------------------------------------------------------------------------------------------- 在上一篇文章中讲解了AABB包围

玖游论坛VIP教程之游戏内存数据分析和基本算法

课程目录:[第二部分 游戏内存数据分析和基本算法] 第1集 介绍存储器与设置CheatEngine第2集 CheatEngine基本应用.通关训练教程第3集 分析<传世si服>角色基址及遍历角色属性第4集 用内存插件读取<传世si服>角色属性第5集 分析<狼烟Online>角色基址第6集 分析<传奇si服>角色基址及遍历角色属性第7集 分析<传奇si服>寻路CALL第8集 分析<传奇si服>停止寻路CALL第9集 按键精灵调用<

【Cocos2d入门教程六】Cocos2d-x事件篇之触摸

Cocos游戏当中产生一个事件时,可以有多个对象在监听该事件,所以有优先级(Priority).优先级越高(Priority值越小),事件响应越靠前. 关系图: 新 事件分发机制:在2.x 版本事件处理时,将要触发的事件交给代理(delegate)处理,再通过实现代理里面的onTouchBegan等方法接收事件,最后完成事件的响应. 而在新的事件分发机制(3.x)中,只需通过创建一个事件监听器-用来实现各种触发后的逻辑,然后添加到事件分发器_eventDispatcher,所 有事件监听器由这个

【Cocos2d入门教程八】浅析Cocoss2d下的音频引擎及封装音频类

Cocos2d-x提供了一个音频CocosDenshion引擎,CocosDenshion引擎可以独立于Cocos2d-x单独使用,CocosDenshion引擎本质上封装了OpenAL音频处理库.具体使用的API是SimpleAudioEngine.在大多数平台上,cocos2d-x可以调用不同的SDK API来播放背景音乐和音效.CocosDenshion音频引擎只能在同一时间只能播放一首背景音乐,但是能同时播放许多音效.下面列举CocosDenshion引擎下的SimpleAudioEng

【Cocos2d入门教程四】Cocos2d-x菜单篇

游戏世界多姿多彩,无论多靓丽的游戏,多耐玩的游戏,在与游戏用户交互上的往往是菜单. 上一章我们已经大概了解了导演.节点.层.精灵.这一章以菜单为主题. 菜单(Menu)包含以下内容: 1.精灵菜单项(MenuItemSprite) 2.图片菜单项(MenuItemImage) 3.文字菜单项(MenuItemFont) 4.标签菜单项(MenuItemLabel) 4.开关菜单项(MenuItemToggle) 菜单的继承关系: 这里图片菜单项也是同样继承于精灵菜单下.图片菜单项也可称为精灵菜单

【Cocos2d入门教程三】HelloWorld之一目了然

什么程序都是从HelloWorld先开始.同样Cocos2d-x我们先从HelloWorld进行下手.下面是HelloWorld的运行完成图: 建立好的Cocos游戏项目中会有两个比较常用接触的文件夹.分别为Classes与resource.Classes存取代码文件,resource存取资源文件,下面为完整的项目架构: 我们先来看下最基本的AppDelegate.cpp类 1 #include "AppDelegate.h" 2 #include "HelloWorldSc