怎样进行2D旋转矩形的碰撞检測。能够使用一种叫OBB的检測算法(Oriented bounding box)方向包围盒。这个算法是基于SAT(Separating Axis Theorem)分离轴定律的。而OBB不不过计算矩形的碰撞检測。而是一种算法模型。

简单解释一下概念,包围盒和分离轴定律。

包围盒:是依据物体的集合形状。来决定盒子的大小和方向,这样能够选择最紧凑的盒子来代表物体。见下图

黑色的就是包围盒,能够是凸多边形,最贴近检測物体就可以。

分离轴定律:两个凸多边形物体,假设我们能找到一个轴,使得两个在物体在该轴上的投影互不重叠。则这两个物体之间没有碰撞发生,该轴为Separating Axis

那么用一般去检測那些轴呢,垂直于多边形每条边的轴。例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG9tXzIyMXg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

所以,分离轴定律变成。两个多边形在全部轴上的投影都发生重叠,则判定为碰撞。否则。没有发生碰撞。

以下。我仅仅考虑矩形的情况。怎样检測分离轴。

非常明显,矩形4条边,有4条检測轴,那么2个矩形就有8个。

可是矩形有2个轴是反复的,所以仅仅须要检測2条轴就能够了,既是矩形的两条互相垂直的边所在的轴。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG9tXzIyMXg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

如上图,推断碰撞,我们须要推断2个矩形在4个轴上的投影是否重叠。这里有2种可能的方式。

第一种,把每一个矩形的4个顶点投影到一个轴上。这样算出4个顶点最长的连线距离,以后相同对待第二个矩形。最后推断2个矩形投影距离是否重叠。

另外一种方式,把2个矩形的半径距离投影到轴上,以后把2个矩形的中心点连线投影到轴上。以后推断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小。本文使用这样的方式。

这里用到一些向量的数学知识。例如以下图:

P点为矩形在X轴上的投影点,矩形在垂直轴上的投影点为原点。这里也能看出来,点P所在的矩形轴, 在X轴上的投影长度为OP,假设矩形逆时针绕远点O旋转。OP在X轴上的投影长度变小,直到为0。OP垂直于X轴。也就是,OP在X轴上的投影长度的最大与最小值。这也解释了。为什么我们选择检測轴为垂直于多边形边的轴,由于在这些轴上我们能取到极值,中间的那些轴就不是必需检測了。

怎样表示轴。我们须要用向量,正确的使用单位向量。可以简化模型,减少思考的难度。例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG9tXzIyMXg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

如果P点的坐标为(px, py), 那么向量P就是(px, py)。点P在X轴上的投影点Q坐标是(qx, qy)。那么向量Q就是(qx, qy)。我们如果X轴上的单位向量是(1, 0)。

那么向量P和X轴上单位向量点乘有:

向量P * X轴单位向量 = |P| * |X轴单位向量| * cosPQ  = px * 1 + py * 0 = px

又由于单位向量的长度等于1所以,px就是向量Q的长度。这是很有意义的,我们就得到一个规律,就是把一个向量点乘一个单位向量。我们得到的是这个向量在这个单位向量上的投影长度。用代码表示为:

/**
* dot-multiply
*/
private float dot(float[] axisA, float[] axisB) {
return Math.abs(axisA[0] * axisB[0] + axisA[1] * axisB[1]);
}

这里float[] 存放的是一个点的x ,y 坐标。axisB 为单位向量,这个结果就是axisA向量在,单位向量axisB上投影的长度。

以下我们看一下,单位向量怎样表示:

单位向量是用单位圆来描写叙述的。如果这个圆的半径为1。那么圆上的不论什么一个坐标到原点构成的向量都能够看作一个单位向量。而且长度为1。这般,明显的点P就是一个单位向量。点P在单位圆上移动,那么这个单位向量就在旋转,向量P就和角A建立了关系。

非常明显的得出。cosA 就是向量P的X坐标。sinA 就是向量P的Y坐标。

这样我们就能够得出,单位向量P为(cosA,sinA)。

这个模型的意义就是把单位向量P能够看成矩形的条边。

例如以下图:

那么矩形的还有一个边相应的单位向量S怎样表示呢,向量S和向量P是垂直的,我们能够得出, S(-sinA, cosA), 向量S 点乘 向量P  = 0

至此。我们就能够通过一个旋转角度,得到一个矩形的2个检測轴的单位向量。

代码例如以下:

// unit vector of x axis
private float[] axisX;
// unit vector of y axis
private float[] axisY; // 0 -360
private float rotation; /**
* Set axis x and y by rotation
*
* @param rotation float 0 - 360
*/
public OBB setRotation(float rotation) {
this.rotation = rotation; this.axisX[0] = MathUtils.cos(rotation);
this.axisX[1] = MathUtils.sin(rotation); this.axisY[0] = -MathUtils.sin(rotation);
this.axisY[1] = MathUtils.cos(rotation); return this;
}

下一步怎样计算矩形的半径投影呢,什么又是半径投影呢,看下图:

橙色线段,是矩形的2条检測轴。3张图是矩形旋转的3个特殊位置截图。

蓝色线段就是矩形半径投影。事实上就是,矩形在X轴上最远处的交点,数学上意义就是2条检測轴的投影之和。

2条检測轴的向量和就是中心点到矩形一个顶点的向量,所以投影半径也是中心点到矩形顶点的向量投影长度。注意向量的方向会影响投影长度。

依照中间那幅图,2条检測轴向量和的投影是,2条检測轴投影的差值。假设把当中一个轴,旋转180度,那么2个检測轴和的投影就是,2条轴投影的和值。

至此。假设我们把矩形在随意角度的2条轴向量投影到单位向量上,依据前面的单位向量规律。我们就得到了轴向量在单位向量上投影的长度,而单位向量的长度为1,那么我们得到的就是轴向量与单位向量的比例。在用这个比例乘以轴向量的长度,就得到了轴的投影长度。就能求出轴半径的长度了。

如图

2个矩形检測过程中,每次以一个矩形的检測轴为坐标系,投影还有一个矩形的检測轴。

图中。蓝色线段为左边矩形的半径投影,黄色线段为右边矩形检測轴。

我们须要把右边2条检測轴投影到蓝色线段所在X轴的单位向量,得到投影比例。以后在乘以2条检測轴的长度,就能够得到右边矩形的半径投影。

红色线段为2个矩形的中心点连心,计算其在X轴的投影长度。比較中心点连线的投影长度与2矩形的半径投影长度之和。假设连线投影大。那么在这条轴上没有碰撞,否则碰撞。半径投影代码例如以下:

private float halfWidth;

private float halfHeight;

/**
* Get axisX and axisY projection radius distance on axis
*/
public float getProjectionRadius(float[] axis) { // axis, axisX and axisY are unit vector // projected axisX to axis
float projectionAxisX = this.dot(axis, this.axisX);
// projected axisY to axis
float projectionAxisY = this.dot(axis, this.axisY); return this.halfWidth * projectionAxisX + this.halfHeight * projectionAxisY;
}

推断2矩形终于是否碰撞,须要依次检測4个分离轴,假设在一个轴上没有碰撞,则2个矩形就没有碰撞。代码例如以下:

/**
* OBB is collision with other OBB
*/
public boolean isCollision(OBB obb) {
// two OBB center distance vector
float[] centerDistanceVertor = {
this.centerPoint[0] - obb.centerPoint[0],
this.centerPoint[1] - obb.centerPoint[1]
}; float[][] axes = {
this.axisX,
this.axisY,
obb.axisX,
obb.axisY,
}; for(int i = 0; i < axes.length; i++) {
// compare OBB1 radius projection add OBB2 radius projection to centerDistance projection
if(this.getProjectionRadius(axes[i]) + obb.getProjectionRadius(axes[i])
<= this.dot(centerDistanceVertor, axes[i])) {
return false;
}
} return true;
}

最后,给出OBB完整的代码封装

java代码:

/**
* @author scott.cgi
* @since 2012-11-19
*
* Oriented bounding box
*/
public class OBB { private float[] centerPoint; private float halfWidth; private float halfHeight; // unit vector of x axis
private float[] axisX;
// unit vector of y axis
private float[] axisY; // 0 -360
private float rotation; private float scaleX;
private float scaleY; private float offsetAxisPointDistance; /**
* Create default OBB
*
* @param x bornCenterX x
* @param y bornCenterY Y
* @param halfWidth
* @param halfHeight
*/
public OBB(float bornCenterX, float bornCenterY, float halfWidth, float halfHeight) { this.halfWidth = halfWidth;
this.halfHeight = halfHeight; this.scaleX = 1.0f;
this.scaleY = 1.0f; this.centerPoint = new float[] {
bornCenterX,
bornCenterY
}; this.axisX = new float[2];
this.axisY = new float[2]; float[] offsetAxisPoint = new float[] {
bornCenterX - Director.getHalfScreenWidth(),
bornCenterY - Director.getHalfScreenHeight()
}; this.offsetAxisPointDistance = (float) Math.sqrt(this.dot(offsetAxisPoint, offsetAxisPoint)); this.setRotation(0.0f);
} /**
* Create default OBB with born in center screen
*
* @param halfWidth
* @param halfHeight
*/
public OBB(float halfWidth, float halfHeight) {
this(Director.getHalfScreenWidth(), Director.getHalfScreenHeight(), halfWidth, halfHeight);
} /**
* Get axisX and axisY projection radius distance on axis
*/
public float getProjectionRadius(float[] axis) { // axis, axisX and axisY are unit vector // projected axisX to axis
float projectionAxisX = this.dot(axis, this.axisX);
// projected axisY to axis
float projectionAxisY = this.dot(axis, this.axisY); return this.halfWidth * this.scaleX * projectionAxisX + this.halfHeight * this.scaleY * projectionAxisY;
} /**
* OBB is collision with other OBB
*/
public boolean isCollision(OBB obb) {
// two OBB center distance vector
float[] centerDistanceVertor = {
this.centerPoint[0] - obb.centerPoint[0],
this.centerPoint[1] - obb.centerPoint[1]
}; float[][] axes = {
this.axisX,
this.axisY,
obb.axisX,
obb.axisY,
}; for(int i = 0; i < axes.length; i++) {
// compare OBB1 radius projection add OBB2 radius projection to centerDistance projection
if(this.getProjectionRadius(axes[i]) + obb.getProjectionRadius(axes[i])
<= this.dot(centerDistanceVertor, axes[i])) {
return false;
}
} return true;
} /**
* dot-multiply
*/
private float dot(float[] axisA, float[] axisB) {
return Math.abs(axisA[0] * axisB[0] + axisA[1] * axisB[1]);
} /**
* Set axis x and y by rotation
*
* @param rotation float 0 - 360
*/
public OBB setRotation(float rotation) {
this.rotation = rotation; this.axisX[0] = MathUtils.cos(rotation);
this.axisX[1] = MathUtils.sin(rotation); this.axisY[0] = -MathUtils.sin(rotation);
this.axisY[1] = MathUtils.cos(rotation); this.setCenter(this.centerPoint[0], this.centerPoint[1]); return this;
} /**
* Set OBB center point and will add offsetAxis value
*/
public OBB setCenter(float x, float y) {
float offsetX = this.offsetAxisPointDistance * MathUtils.cos(this.rotation);
float offsetY = this.offsetAxisPointDistance * MathUtils.sin(this.rotation); this.centerPoint[0] = x + offsetX;
this.centerPoint[1] = y + offsetY; return this;
} /**
* Set OBB scale x, y
*/
public OBB setScale(float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY; return this;
} public float getRotation() {
return this.rotation;
} public float getCenterX() {
return this.centerPoint[0];
} public float getCenterY() {
return this.centerPoint[1];
} public float getHalfWidth() {
return this.halfWidth * this.scaleX;
} public float getHalfHeight() {
return this.halfHeight * this.scaleY;
} }

c
代码:

/*
* OBBRect.h
*
* Created on: 2013-2-11
* Author: scott.cgi
*/ #ifndef OBBRect_Rect_H_
#define OBBRect_Rect_H_ #include <stdbool.h> #include "Mojoc/Graphics/Draw/Rect.h"
#include "Mojoc/Toolkit/Def/CodeStyle.h"
#include "Mojoc/Toolkit/Utils/AMathUtils.h"
#include "Mojoc/Toolkit/Def/StructMember.h" #ifdef __cplusplus
extern "C" {
#endif typedef struct OBBRect OBBRect; /**
* Oriented bounding box in Rect shape
* Use openGL world coordinate system
*/
struct OBBRect { float centerX;
float centerY; /** Set origin(0,0) when obbRect create */
float originX;
float originY; /** Clockwise [0 - 360] */
float rotation;
float scaleX;
float scaleY; Get(
/** Unit vector of x axis */
StructMember(Vector2, xAxis); /** Unit vector of y axis */
StructMember(Vector2, yAxis); Rect* rect; /** Distance of center point to origin(0, 0 */
float offsetDistance;
/** Degree of vector which center point to origin(0, 0 */
float offsetDegree; float halfWidth;
float halfHeight;
); }; typedef struct {
OBBRect* (*create) (Rect* rect);
void (*init) (Rect* rect, Out(OBBRect* obbRect)); /**
* Set obbRect rotation or origin x,y called updateCenter for
* update obbRect center x,y
*/
void (*updateCenter)(OBBRect* obbRect); /**
* Set obbRect rotation
*/
void (*setRotation) (OBBRect* obbRect, float rotation); /**
* OBBRect is collision with other OBBRect
*/
bool (*isCollision) (OBBRect* obbRect, OBBRect* otherObb); /**
* Get real width with scaleX
*/
float (*getWidth) (OBBRect* obbRect); /**
* Get real height with scaleY
*/
float (*getHeight) (OBBRect* obbRect); } _AOBBRect; extern _AOBBRect AOBBRect; #ifdef __cplusplus
}
#endif #endif /* OBBRect_H_ */
/*
* OBBRect.c
*
* Created on: 2013-2-11
* Author: scott.cgi
*/ #include <malloc.h>
#include <math.h> #include "Mojoc/Physics/OBBRect.h"
#include "Mojoc/Toolkit/Utils/AMathUtils.h"
#include "Mojoc/Toolkit/Platform/Log.h" static void updateCenter(OBBRect* obbRect) {
obbRect->centerX = obbRect->originX +
obbRect->offsetDistance * obbRect->scaleX *
AMathUtils_Cos(obbRect->rotation + obbRect->offsetDegree); obbRect->centerY = obbRect->originY +
obbRect->offsetDistance * obbRect->scaleY *
AMathUtils_Sin(obbRect->rotation + obbRect->offsetDegree);
} static void setRotation(OBBRect* obbRect, float rotation) {
obbRect->xAxis->x = AMathUtils_Cos(rotation);
obbRect->xAxis->y = AMathUtils_Sin(rotation); obbRect->yAxis->x = -AMathUtils_Sin(rotation);
obbRect->yAxis->y = AMathUtils_Cos(rotation); obbRect->rotation = rotation;
} /**
* Get axisX and axisY projection radius distance on unit axis
*/
static inline float getProjectionRadius(OBBRect* obbRect, Vector2* axis) { // axis, axisX and axisY are unit vector // projected axisX to axis
float projectionAxisX = AMathUtils_DotMultiply2(axis->x, axis->y, obbRect->xAxis->x, obbRect->xAxis->y);
// projected axisY to axis
float projectionAxisY = AMathUtils_DotMultiply2(axis->x, axis->y, obbRect->yAxis->x, obbRect->yAxis->y); return obbRect->halfWidth * obbRect->scaleX * projectionAxisX +
obbRect->halfHeight * obbRect->scaleY * projectionAxisY; } static bool isCollision(OBBRect* obbRect, OBBRect* otherObb) {
// two OBBRect center distance vector
Vector2 distanceVec = {
obbRect->centerX - otherObb->centerX,
obbRect->centerY - otherObb->centerY,
}; Vector2* axes[] = {
obbRect->xAxis,
obbRect->yAxis,
otherObb->xAxis,
otherObb->yAxis,
}; for (int i = 0; i < 4; i++) {
// compare OBBRect1 radius projection add OBBRect2 radius projection to centerDistance projection
if (getProjectionRadius(obbRect, axes[i]) + getProjectionRadius(otherObb, axes[i]) <=
AMathUtils_DotMultiply2(distanceVec.x, distanceVec.y, axes[i]->x, axes[i]->y)) { return false;
}
} return true;
} static float getWidth(OBBRect* obbRect) {
return (obbRect->halfWidth * 2) * obbRect->scaleX;
} static float getHeight(OBBRect* obbRect) {
return (obbRect->halfHeight * 2) * obbRect->scaleY;
} static inline void initOBBRect(OBBRect* obbRect, Rect* rect) {
obbRect->rect = rect; obbRect->halfWidth = (rect->right - rect->left) / 2;
obbRect->halfHeight = (rect->bottom - rect->top) / 2; obbRect->scaleX = 1.0f;
obbRect->scaleY = 1.0f; obbRect->originX = 0.0f;
obbRect->originY = 0.0f; obbRect->centerX = rect->left + obbRect->halfWidth;
obbRect->centerY = rect->top - obbRect->halfHeight; obbRect->offsetDistance = sqrtf(AMathUtils_DotMultiply2(obbRect->centerX, obbRect->centerY, obbRect->centerX, obbRect->centerY));
obbRect->offsetDegree = AMathUtils_Atan2(obbRect->centerX, obbRect->centerY); LogD("centerX = %f, centerY = %f", obbRect->centerX, obbRect->centerY);
LogD("offsetDistance = %f, offsetDegree = %f",
obbRect->offsetDistance, obbRect->offsetDegree); setRotation(obbRect, 0.0f);
} static OBBRect* create(Rect* rect) {
OBBRect* obbRect = (OBBRect*) malloc(sizeof(OBBRect));
initOBBRect(obbRect, rect); return obbRect;
} static void init(Rect* rect, Out(OBBRect* obbRect)) {
initOBBRect(obbRect, rect);
} _AOBBRect AOBBRect = {
create,
init, updateCenter,
setRotation,
isCollision,
getWidth,
getHeight,
};

矩形旋转碰撞,OBB方向包围盒算法实现的更多相关文章

  1. 旋转矩形碰撞检测 OBB方向包围盒算法

    在cocos2dx中进行矩形的碰撞检测时需要对旋转过的矩形做碰撞检查,由于游戏没有使用Box2D等物理引擎,所以采用了OBB(Oriented bounding box)方向包围盒算法,这个算法是基于 ...

  2. 游戏碰撞OBB算法(java代码)

    业务需求      游戏2D型号有圆形和矩形,推断说白了就是碰撞检测 :      1.圆形跟圆形是否有相交      2.圆形跟矩形是否相交       3.矩形和矩形是否相交           ...

  3. PDF怎么编辑,如何旋转PDF页面方向

    很多的时候,无论是工作中,还是在学习中都会遇到PDF文件,对于PDF文件,熟悉的小伙伴知道,在编辑PDF文件的时候,是需要使用到PDF编辑软件的,那么,在编辑PDF文件的时候,需要旋转文件的页面,这时 ...

  4. libgdx判断矩形重叠碰撞

    有两种方式. 1. 排除法,排除四种不可能重叠的情况就是了. public static boolean IsOverlap( Rectangle rect1, Rectangle rect2 ){ ...

  5. 2.纯 CSS 创作一个矩形旋转 loader 特效

    原文地址:2.纯 CSS 创作一个矩形旋转 loader 特效 扩展后地址:https://scrimba.com/c/cNJVWUR  扩展地址:https://codepen.io/pen/ HT ...

  6. 2d游戏中的射线与矩形检测碰撞

    cc.exports.LineCollideRect(startLine,endLine,rect)--向量与矩形检测碰撞 --获取矩形的四个顶点位置 local p = {cc.p(rect.x,r ...

  7. unity2D以最小的角度旋转到目标方向(y方向为角色的主方向)

    一.使用向量原理转换到目标方向 为了让角色的自身y转向目标方向,并且以最小角度旋转,要点是获得当前方向与目标方向的叉值,从而判断应该旋转的方向 float rotateSpeed; //相对目标位置运 ...

  8. 17.纯 CSS 创作炫酷的同心矩形旋转动画

    原文地址:https://segmentfault.com/a/1190000014807564 感想: 这个特效不难,但是这想法可能想不到,哈哈,怎么又废了. HTML代码: <div cla ...

  9. 前端每日实战:2# 视频演示如何用纯 CSS 创作一个矩形旋转 loader 特效

    效果预览 按下右侧的"点击预览"按钮在当前页面预览,点击链接全屏预览. https://codepen.io/zhang-ou/pen/vjLQMM 可交互视频教程 此视频是可以交 ...

随机推荐

  1. Android菜鸟的成长笔记(12)——Handler、Loop、MessageQueue

    原文:[置顶] Android菜鸟的成长笔记(12)——Handler.Loop.MessageQueue 当一个程序第一次启动时,Android会启动一条主线程(Main Thread),主线程主要 ...

  2. 友情转发一则Erlang招聘广告

    新锐手游开发公司WalkYY,招聘Erlang游戏服务端开发工程师若干名,要求有半年以上Erlang游戏服务端开发经验,熟悉Erlang OTP和MySQL数据库.公司团队靠谱,发展空间大,有意者请发 ...

  3. 程序启动读取和关闭时保存应用程序设置(QSettings)

    保存应用程序设置(QSettings)1. QSettings 类 QSettings 提供保存应用程序当前设置的接口,可以方便地保存程序的状态,例如窗口大小和位置,选项的选中状态等等.在 Windo ...

  4. Delphi透明组件开发(去掉自己的csOpaque,去掉父控件的WS_CLIPCHILDREN,增加WS_EX_TRANSPARENT,截获WM_ERASEBKGND,然后在WM_DRAWITEM里画) good

    透明的控件, 一般继承自TGraphicControl的(就是那些没有handle属性, 不能有focus的控件, 如image)都有Transparent属性. 对TWinControl类的控件, ...

  5. LeetCode——Populating Next Right Pointers in Each Node II

    Follow up for problem "Populating Next Right Pointers in Each Node". What if the given tre ...

  6. 它们的定义Adapterg在etView( )正在使用View.setTag()与不同的是不使用。

    首先看使用Tag案件. @Override public View getView(int position, View view, ViewGroup group) { ViewHolder hol ...

  7. ThinkPHP中实例化对象M()和D()的区别,select和find的区别

    原文:ThinkPHP中实例化对象M()和D()的区别,select和find的区别 1.ThinkPHP中实例化对象M()和D()的区别 在实例化的过程中,经常使用D方法和M方法,这两个方法的区别在 ...

  8. Java程序猿的JavaScript学习笔记(3——this/call/apply)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  9. 设计模式之——Factory(工厂模式)

    工厂模式用于,通过统一的创建对象接口来创建对象,而子类可以决定对象的创建方式. class CObject { }; class CCar : public CObject { }; class CF ...

  10. phpc.sinaapp.com 加密的解密方法

    原文:phpc.sinaapp.com 加密的解密方法 很简单,用类似phpjm的解密方式,替换掉_inc.php中最后一个return中的eval为print就出来了.