前言

上一篇博文说道,射线与场景中模型上的所有三角形求交时,会大幅度影响效率且花费比较多的时间,因此会采取使用包围盒的形式,进行一个加速求交。在此文中介绍OBB碰撞模型的碰撞算法

OBB的碰撞模型

有没有想过为什么需要用到OBB模型呢,假设一个场景内两个人物相撞了,你怎么判断它们是否相撞呢,大概就是它们的碰撞体接触在了一起就相撞了。那怎么算碰撞在一起呢(此处只讨论2D规则的包围盒模型)?

方向包围盒OBB"("Oriented Bounding Box)是目前比较流行的一种包围盒,OBB最大的特点是其方向的任意性,这使得可以根据被包围的对象的形状特点尽可能紧密地包围对象,Unity中的BoxCollider的其实就是OBB模型,它不是轴对称模型,而是有方向的

OBB模型

与之相反的是AABB模型,是轴对称模型,即它的边一定与坐标轴平行,算法简单,但使用的局限性比较大,更多还是使用OBB模型

碰撞算法分析

想要判断两个OBB模型碰撞,也就是两个矩形相交,我们分为几个步骤,首先先转换问题,什么时候两个矩形不相交,两个矩形相离可看成有多个直线可将它们之间分开。

当逐渐移动某个矩形,使得某个时刻,两个矩形只有一个交点,交点属于矩形的某条边上,此时为临界状态,当且仅当只有一条直线将他们两个分开,此时这条直线必定与某条边平行。



所以我们只需找两个矩形的四条边分别作为轴,两个矩形的xVt、yVt分别进行投影,看投影后的两个线段是否相离,如果相离则在这个轴上可以将这两个进行分开,故此时两个矩形不相交,反之若相交,则继续接着其他轴进行判断,若所有轴都不能分开,则这两个矩形相交

我们观察 AB proj 与 boxA、boxB 的 xVt proj 、yVt proj 之间的关系,可以得出结论:

AB proj > sum(Vt proj) ,则矩形相离
AB proj = sum(Vt proj) ,则矩形相切
AB proj < sum(Vt proj) ,则矩形相交

参考网上某大佬的代码

参考博文:https://www.cnblogs.com/hont/p/9501169.html



分别取两个矩形的两个边,总共进行四次投影对称,作为对称轴

        axis1 = (P1 - P0).normalized;
axis2 = (P3 - P0).normalized; axis3 = (other.P1 - other.P0).normalized;
axis4 = (other.P3 - other.P0).normalized; mDebugInternalAxisIndex = 0; bool isNotIntersect = false;
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4);

这里是取带符号的长度,用来比较投影后的线段是否相交

        float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis));

相交判断:

        if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;

简约的示例图
using UnityEngine;
// OBB.cs
public class OBB : MonoBehaviour
{
public bool enableDebug;
public int debug_axisIndex;
int mDebugInternalAxisIndex; public Vector2 size; public Color gizmosColor = Color.white; Vector2 P0 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(-size * 0.5f); } }
Vector2 P1 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(size.x * 0.5f, -size.y * 0.5f, 0)); } }
Vector2 P2 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(size * 0.5f); } }
Vector2 P3 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(-size.x * 0.5f, size.y * 0.5f, 0)); } } Vector2 axis1, axis2, axis3, axis4; // 较参考博文添加以下变量,用来缓存向量减少gc
Vector3 xProject_P0;
Vector3 xProject_P1;
Vector3 xProject_P2;
Vector3 xProject_P3; Vector3 yProject_P0;
Vector3 yProject_P1;
Vector3 yProject_P2;
Vector3 yProject_P3; public bool Intersects(OBB other)
{
axis1 = (P1 - P0).normalized;
axis2 = (P3 - P0).normalized; axis3 = (other.P1 - other.P0).normalized;
axis4 = (other.P3 - other.P0).normalized; mDebugInternalAxisIndex = 0; bool isNotIntersect = false;
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4); return isNotIntersect ? false : true;
} bool ProjectionIsNotIntersect(OBB x, OBB y, Vector2 axis)
{
xProject_P0 = Vector3.Project(x.P0, axis);
xProject_P1 = Vector3.Project(x.P1, axis);
xProject_P2 = Vector3.Project(x.P2, axis);
xProject_P3 = Vector3.Project(x.P3, axis); float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis)); yProject_P0 = Vector3.Project(y.P0, axis);
yProject_P1 = Vector3.Project(y.P1, axis);
yProject_P2 = Vector3.Project(y.P2, axis);
yProject_P3 = Vector3.Project(y.P3, axis); float y_p0 = yProject_P0.magnitude * Mathf.Sign(Vector3.Dot(yProject_P0, axis));
float y_p1 = yProject_P1.magnitude * Mathf.Sign(Vector3.Dot(yProject_P1, axis));
float y_p2 = yProject_P2.magnitude * Mathf.Sign(Vector3.Dot(yProject_P2, axis));
float y_p3 = yProject_P3.magnitude * Mathf.Sign(Vector3.Dot(yProject_P3, axis)); float xMin = Mathf.Min(x_p0, x_p1, x_p2, x_p3);
float xMax = Mathf.Max(x_p0, x_p1, x_p2, x_p3);
float yMin = Mathf.Min(y_p0, y_p1, y_p2, y_p3);
float yMax = Mathf.Max(y_p0, y_p1, y_p2, y_p3); if (enableDebug)
{
if (debug_axisIndex == mDebugInternalAxisIndex)
{
Debug.DrawRay(Vector3.Project(x.P0, axis), Vector3.one * 0.1f);
Debug.DrawRay(Vector3.Project(x.P2, axis), Vector3.one * 0.1f); Debug.DrawRay(Vector3.Project(y.P0, axis), Vector3.one * 0.1f, Color.white * 0.9f);
Debug.DrawRay(Vector3.Project(y.P2, axis), Vector3.one * 0.1f, Color.white * 0.9f); Debug.DrawRay(Vector3.zero, Vector3.one * 0.1f, Color.black);
Debug.DrawRay(Vector3.zero, axis, Color.yellow);
Debug.DrawRay(xMin * Vector3.right, Vector3.one * 0.1f, Color.blue);
Debug.DrawRay(xMax * Vector3.right, Vector3.one * 0.1f, Color.cyan);
Debug.DrawRay(yMin * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f);
Debug.DrawRay(yMax * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f); Debug.Log("(yMin >= xMin && yMin <= xMax): " + (yMin >= xMin && yMin <= xMax) + " frame count: " + Time.frameCount);
Debug.Log("(yMax >= xMin && yMax <= xMax): " + (yMax >= xMin && yMax <= xMax) + " frame count: " + Time.frameCount);
Debug.Log("(xMin >= yMin && xMin <= yMax): " + (xMin >= yMin && xMin <= yMax) + " frame count: " + Time.frameCount);
Debug.Log("(xMax >= yMin && xMax <= yMax): " + (xMax >= yMin && xMax <= yMax) + " frame count: " + Time.frameCount);
}
mDebugInternalAxisIndex++;
} if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;
// 此处只需做两次判断即可,参考博文做了四次判断
// if (xMin >= yMin && xMin <= yMax) return false;
// if (xMax >= yMin && xMax <= yMax) return false; return true;
} void OnDrawGizmos()
{
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.color = gizmosColor;
Gizmos.DrawWireCube(Vector3.zero, new Vector3(size.x, size.y, 1f));
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// OBBTest.cs
public class OBBTest : MonoBehaviour
{
public OBB a;
public OBB b; void Update()
{
var isIntersects = a.Intersects(b);
if (isIntersects)
{
a.gizmosColor = Color.red;
b.gizmosColor = Color.red;
}
else
{
a.gizmosColor = Color.white;
b.gizmosColor = Color.white;
}
}
}

效果



游戏中的2D OBB碰撞模型的碰撞算法介绍和实践的更多相关文章

  1. 游戏中的AOI(Area of Interest)算法

    游戏中的AOI(Area of Interest)算法 游戏的AOI算法应该算作游戏的基础核心了,许多逻辑都是因为AOI进出事件驱动的,许多网络同步数据也是因为AOI进出事件产生的.因此,良好的AOI ...

  2. 游戏中的 2D 可见性

    转自:http://www.gameres.com/469173.html 拖动圆点转一圈,看看玩家都能看到些什么: 这个算法也能计算出给定光源所照亮的区域.对每条光线,我们可以构建出被照亮区域的光线 ...

  3. 如何在Cocos2D游戏中实现A*寻路算法(六)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  4. Unity 2D游戏开发教程之游戏中精灵的跳跃状态

    Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...

  5. 地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了

    地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了 四叉树对于区域查询,效率比较高. 原理图

  6. Unity3D 2D游戏中寻径算法的一些解决思路

    需求 unity3d的3d开发环境中,原生自带了Navigation的组件,可以很便捷快速的实现寻路功能.但是在原生的2d中并没有相同的功能. 现在国内很多手机游戏都有自动寻路的功能,或者游戏中存在一 ...

  7. 《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9374935 作者:七十一雾央 新浪微博:http:// ...

  8. Unity3D系列教程--使用免费工具在Unity3D中开发2D游戏 第一节

    声明:   本博客文章翻译类别的均为个人翻译,版权全部.出处: http://blog.csdn.net/ml3947,个人博客:http://www.wjfxgame.com. 译者说明:这是一个系 ...

  9. 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型&障碍物判定

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...

随机推荐

  1. 一个C#开发编写Java框架的心路历程

    前言 这一篇絮絮叨叨,逻辑不太清晰的编写Java框架的的一个过程,主要描述我作为一个java初学者,在编写Java框架时的一些心得感悟. 因为我是C#的开发者,所以,在编写Java框架时,或多或少会带 ...

  2. Python接口自动化实现

    一.代码结构: 二.接口签名实现: 1. 设所有发送的数据集合为M,将集合M内非空参数值的参数按照[参数名+"="+参数值]的ASCII码从小到大排序(字典序),然后按拼接key1 ...

  3. 仿String()构造器函数 【总结】

    需求 实现以下方法: 控制台结果: 需求分析: 首先确定new调用的this和什么对象绑定,如果跟默认返回的对象绑定肯定做不到 [ ] 这样的访问,所以要在构造器内部返回一个包装过的数组 1.leng ...

  4. Object类中的常用方法

    1.getClass方法 源码: 功能: 返回此Object的运行时类. 什么是运行时类? 如上图所示,类从被加载到虚拟机内存开始,到卸载出内存为止,他的生命周期一共包含7个阶段.其中加载阶段虚拟机需 ...

  5. HarmonyOS三方件开发指南(15)-LoadingView功能介绍

    目录: 1. LoadingView组件功能介绍2. Lottie使用方法3. Lottie开发实现4.<HarmonyOS三方件开发指南>系列文章合集 1. LoadingView组件功 ...

  6. 对接快递100&聚水潭API

    对接快递100&聚水潭API 入我相思门,知我相思苦. 简介:对接第三方平台快递100&聚水潭API的简要总结. 1.感悟 个人感觉快递100的API更友好一些,比如有SDK可以调用: ...

  7. java例题_11 求不重复数

    1 /*11 [程序 11 求不重复数字] 2 题目:有 1.2.3.4 这四个数字,能组成多少个互不相同且无重复数字的三位数?都是多少? 3 程序分析:可填在百位.十位.个位的数字都是 1.2.3. ...

  8. Kotlin编写Processing程序(使用函数式编程思维和面向接口方式)

    写一例Kotlin编写的Processing程序,充分调用函数式编程思维和面向接口的编程思维,供自己和读者参考学习. 初衷 想要实现一行行的文字排版功能,每一行作为一个单位,可制定显示的位置.大小.文 ...

  9. thinkphp各版本常用漏洞总结

    0x01 漏洞分析及复现 1.漏洞分析 漏洞影响范围: Thinkphp 5.1.0 - 5.1.31Thinkphp 5.0.5 - 5.0.23 漏洞产生原因: Thinkphp5.x版本(5.0 ...

  10. Java中注释的形式

    单行注释 单行注释 // #双斜杠 快捷键:Ctrl + / 多行注释 多行注释 /* */ #单斜杠星号 星号单斜杠 快捷键:Ctrl + shift + / 文档注释 多行注释 /** */ #单 ...