游戏碰撞之OBB算法实现(java代码实现)

公司业务需求

游戏2D模型有圆形和矩形,判断碰撞说白了就是检测 :

1.圆形跟圆形是否有相交

2.圆形跟矩形是否相交

3.矩形和矩形是否相交

先明白要实现的原理,才能有思路写代码

第1个最好判断,判断两个圆中心点的矩形是否小于这两个圆的半径之和

第2个纠结了我一下,不过也不难先看图圆跟矩形关系有4种情况,如下图

只要判断圆心到矩形4条边的距离都小于圆的半径或者圆心在矩形内则它们相交,还要判断圆心在矩形内是防止出现上面第四张图那样的特殊情况

圆心到边的距离有如下两种情况

两个箭头表示点到线段的距离,第二种情况是特殊情况

第3个判断,有以下几种情况

我发现有个规律,两个矩形相交必然有一方的顶点在对方的图形内

判断矩形是否包含某个点这个简单:只要判断点到矩形4条边的距离之和是否等于矩形长与宽的和(高中数学知识)

不过判断上面这些比不是说实现就完结了,还要考虑性能问题,游戏对性能要求高,而且有相应的算法,AABB,OBB算法

我只讲OBB碰撞

方向包围盒(Oriented bounding box),简称OBB。方向包围盒类似于AABB,但是具有方向性、可以旋转,AABB不能旋转。如图3所示。

判断两个图形(凸边形)是否相离:肯定有一条直线能让这两个图形在这条直线上的投影是不相交(换个说法:只要有一条直线能把他们分离出来)如下图

判断公式:BE>(BC+DE)=(1/2AC+1/2DF)=1/2(AC+DF)      ---》 BE>1/2(AC+DF) 可推出两个矩形是分离了;

上图是两个矩形,o1和o2分别为两个矩形的中心,x直线是平行于A1A2的一条直线,你可以把他看做坐标轴x,其中x跟y直线垂直,很明显有一条直线y把两个矩形分开了所以这两个矩形是不相交的

因为A1A2平行于x轴,所以矩形1在直线x轴的投影的长度为线段AC, 矩形2在x轴的投影为DF,只要判断AC跟DF不相交,说明肯定有一条直线能把他们分离出来,如图中的y轴

还有个更加简单的方法,那就是这两个矩形的中心点连线o1o2的投影是否大于 两个矩形在x轴的投影的绝对值的一半的和   o1o2在x轴的投影为BE  只要判断 BE>(BC+DE)=(1/2AC+1/2DF)=1/2(AC+DF)
就说明这两个矩形在x轴(也相当于直线A3A4,A1A2)的投影不相交,x轴不是真正意义上的x轴,我只是让大家看得明了一点。因为矩形是有规则的图形,只要分别给他们检测矩形每一条边的垂直线的投影就可以了,两个矩形总共8条边,因为每个矩形的边都有两两平行,和两两垂直,所以我们只要检测两个矩形到每个矩形相邻两条边的投影是否相交,只要4次检测中有其中一次两个矩形的投影没有相交就可以判断这两个矩形没有相交,反之就是相交

因为要用到高中数学知识向量运算,数学公式:

公式一:

两点 A(x1,y1) ,B(x2,y2)

向量a=AB=(x2-x1,y2-y1) ;

公式二:

向量a=(xa,ya),b=(xb,yb)

cosA=a*b/(|a|*|b|)

向量a到B的投影=     |a|*cosA=|a|*( a*b/(|a|*|b|) )=
 (a.b)/|b|

a*b=xa*xb+ya*yb;

|b|=sqrt(xb*xb+yb*yb);

投影=(a.b)/|b|=(xa*xb+ya*yb)/sqrt(xb*xb+yb*yb)

注意 :我们需要的投影我们只取正数

求矩形在一条直线的投影大小=矩形相邻两条边在直线上的投影的绝对值的和;

比如上图矩形B1B2B3B4在x投影=|B3B1在x轴投影|+|B1B2在x轴投影|=+|B1B2在x轴投影|+|B2B4在x轴投影|

上图我就不加画了,画多了花,你们看一下也应该明白,图上的x轴换成直线A1A2或者A3A4是一样的效果,所以大家千万别被这个迷惑,我只是想让大家看明白点

原理明白了,代码就不是问题了,你学什么语言就用什么语言,因为我学的是java,那我就用java实现

一下是我的源码,大家只要看两个矩形碰撞那里就可以了

我这是以4个顶点确定一个矩形,客户端需要这么做的,其实我觉得一个中心点,长,宽,旋转的角度来确定应该矩形比较好,不然四个顶点,万一传错了其中一个,拼成的不是矩形那不是出问题了。

package game;

/**
 * @author hjn
 *
 */
public abstract class AbstractShape implements IShape {

	protected int x;

	protected int y;

	public AbstractShape(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}

}
package game;

/**
 * @author hjn
 *
 */
public interface IShape {

	int getX();

	int getY();

	boolean collision(IShape other);

	boolean contains(int px, int py);

	IShape copy();

	void moveTo(int px, int py);

}
/**
 *
 */
package game;

/**
 * 圆形
 *
 * @author hjn
 *
 */
public class Circle extends AbstractShape {

	/** 半径 */
	private int radius;

	public Circle(int px, int py, int radius) {
		super(px, py);
		this.radius = radius;
	}
    /**
     * 跟别的图像是否有重叠
     */
	@Override
	public boolean collision(IShape other) {
		if (other instanceof Circle) {
			Circle otherCircle = (Circle) other;
			return (this.radius + otherCircle.radius) * (this.radius + otherCircle.radius) >= ((this.x - otherCircle.x) * (this.x - otherCircle.x) + (this.y - otherCircle.y) * (this.y - otherCircle.y));

		}else if(other instanceof Rectangle){
			return other.collision(this);
		}
		return false;
	}

	/**
	 * 是否包含某个点
	 */
	@Override
	public boolean contains(int px, int py) {
		return this.radius * radius - ((this.x - px) * (this.x - px) + (this.y - py) * (this.y - py)) >= 0;
	}

	public int getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}

	@Override
	public Circle copy() {
		return new Circle(radius, radius, radius);
	}

	@Override
	public void moveTo(int px, int py) {
		this.x = px;
		this.y = py;
	}
}
/**
 *
 */
package game;

/**
 * 矩形
 *
 * @author hjn
 *
 */
public class Rectangle extends AbstractShape {

	/** 矩形的顶点 */
	private int[][] vertex;

	public Rectangle(int px, int py, int[][] vertex) {
		super(px, py);
		this.vertex = vertex;
	}

	/**
	 * 是否有重叠,圆心到矩形6条边的距离小于圆的半径并且圆心不在矩形内那么这个矩形跟这个圆重叠
	 */
	@Override
	public boolean collision(IShape other) {
		try {
			if (other instanceof Circle) {
				Circle circle = (Circle) other;
				boolean fla = this.getLength(circle.x, circle.y, vertex[0][0],
						vertex[0][1], vertex[1][0], vertex[1][1])
						- circle.getRadius() < 0;
				if (fla) {
					return true;
				}
				boolean fla2 = this.getLength(circle.x, circle.y, vertex[0][0],
						vertex[0][1], vertex[2][0], vertex[2][1])
						- circle.getRadius() < 0;
				if (fla2) {
					return true;
				}
				boolean fla3 = this.getLength(circle.x, circle.y, vertex[0][0],
						vertex[0][1], vertex[3][0], vertex[3][1])
						- circle.getRadius() < 0;
				if (fla3) {
					return true;
				}
				boolean fla4 = this.getLength(circle.x, circle.y, vertex[1][0],
						vertex[1][1], vertex[2][0], vertex[2][1])
						- circle.getRadius() < 0;
				if (fla4) {
					return true;
				}
				boolean fla5 = this.getLength(circle.x, circle.y, vertex[1][0],
						vertex[1][1], vertex[3][0], vertex[3][1])
						- circle.getRadius() < 0;
				if (fla5) {
					return true;
				}
				boolean fla6 = this.getLength(circle.x, circle.y, vertex[2][0],
						vertex[2][1], vertex[3][0], vertex[3][1])
						- circle.getRadius() < 0;
				if (fla6) {
					return true;
				}
				boolean fla7 = this.contains(circle.x, circle.y);
				if (fla7) {
					return true;
				}
			} else if (other instanceof Rectangle) {
				Rectangle otherRectangle = (Rectangle) other;
				return this.collisionOBB(otherRectangle);
			}
		} catch (Exception e) {
			// 数组下标越界
			e.printStackTrace();
			return false;
		}
		return false;
	}

	/**
	 * OBB矩形碰撞检测
	 * @param rectangle 矩形2
	 * @return
	 */
	private boolean collisionOBB(Rectangle rectangle){
		int[][] vertex2=rectangle.vertex;
		/*矩形2相邻两条边的向量*/
		int wx1=vertex2[0][0]-vertex2[1][0];
		int wy1=vertex2[0][1]-vertex2[1][1];
		int wx2=vertex2[1][0]-vertex2[2][0];
		int wy2=vertex2[1][1]-vertex2[2][1];
		/*两个矩形中心点连接向量*/
		int centerX=(vertex2[0][0]+vertex2[2][0])/2-(vertex[0][0]+vertex[2][0])/2;
		int centerY=(vertex2[0][0]+vertex2[2][0])/2-(vertex[0][0]+vertex[2][0])/2;
		/*矩形一第一条边的向量*/
		int x11=vertex[0][0]-vertex[1][0];
		int y11=vertex[0][1]-vertex[1][1];
		/*矩形一在第一条边的投影*/
		double le1=Math.sqrt(x11*x11+y11*y11);
		/*矩形2相邻两条边在矩形1第一条边上的投影,例如projection211表示第2个矩形的第1条边在矩形1上第1条边的投影*/
		double projection2111=this.getProjection(wx1, wy1, x11, y11);
		double projection2211=this.getProjection(wx2, wy2, x11, y11);
		/*中心点连接向量的投影*/
		double centerProjection1=this.getProjection(centerX, centerY, x11, y11);
		/*两个矩形投影之和*/
		double total=projection2111+projection2211+le1;
		/*如果中心点向量投影大于矩形投影之和的一半那肯定没有碰撞*/
		if(centerProjection1>total/2){
			return false;
		}

		int x12=vertex[1][0]-vertex[2][0];
		int y12=vertex[1][1]-vertex[2][1];
		double le2=Math.sqrt(x12*x12+y12*y12);
		double projection2112=this.getProjection(wx1, wy1, x12, y12);
		double projection2212=this.getProjection(wx2, wy2, x12, y12);
		double centerProjection2=this.getProjection(centerX, centerY, x12, y12);
		if(centerProjection2>(projection2112+projection2212+le2)/2){
			return false;
		}
		/*反过来矩形1在矩形2相邻两条边的投影的一半跟中心点向量的投影大小对比,不一一写注释了*/
		int wx11=vertex[0][0]-vertex[1][0];
		int wy11=vertex[0][1]-vertex[1][1];
		int wx12=vertex[1][0]-vertex[2][0];
		int wy12=vertex[1][1]-vertex[2][1];

		int x21=vertex2[1][0]-vertex2[2][0];
		int y21=vertex2[1][1]-vertex2[2][1];
		double le3=Math.sqrt(x21*x21+y21*y21);
		double projection1121=this.getProjection(wx11, wy11, x21, y21);
		double projection1221=this.getProjection(wx12, wy12, x21, y21);
		double centerProjection3=this.getProjection(centerX, centerY, x21, y21);
		if(centerProjection3>(projection1121+projection1221+le3)/2){
			return false;
		}

		int x22=vertex2[1][0]-vertex2[2][0];
		int y22=vertex2[1][1]-vertex2[2][1];
		double le4=Math.sqrt(x22*x22+y22*y22);
		double projection1122=this.getProjection(wx11, wy11, x22, y22);
		double projection1222=this.getProjection(wx12, wy12, x22, y22);
		double centerProjection4=this.getProjection(centerX, centerY, x22, y22);
		if(centerProjection4>(projection1122+projection1222+le4)/2){
			return false;
		}
		return true;
	}
	/**
	 * 求向量1在向量2的投影
	 * @param x1 向量1的x坐标
	 * @param y1 向量1的y坐标
	 * @param x2 向量2的x坐标
	 * @param y2 向量2的y坐标
	 * @return 投影的绝对值
	 */
	private double getProjection(int x1,int y1,int x2,int y2){
		double t=(x1*x2+y1*y2)/(Math.sqrt(x1*x1+y1*y1)*Math.sqrt(x2*x2+y2*y2));
		double length=Math.sqrt(x1*x1+y1*y1)*t;
		return Math.abs(length);
	}

	/**
	 * 是否包含某一点
	 */
	@Override
	public boolean contains(int px, int py) {
		double l = this.getLength(px, py, vertex[0][0], vertex[0][1],
				vertex[1][0], vertex[1][1]);
		double l1 = this.getLength(px, py, vertex[1][0], vertex[1][1],
				vertex[2][0], vertex[2][1]);
		double l2 = this.getLength(px, py, vertex[2][0], vertex[2][1],
				vertex[3][0], vertex[3][1]);
		double l3 = this.getLength(px, py, vertex[3][0], vertex[3][1],
				vertex[0][0], vertex[0][1]);
		double total = l1 + l2 + l3 + l;
		double width = this.getLength(vertex[0][0], vertex[0][1], vertex[1][0],
				vertex[1][1]);
		double height = this.getLength(vertex[3][0], vertex[3][1],
				vertex[0][0], vertex[0][1]);
		return total == (width + height);
	}

	/* 两点间的距离 */
	private double getLength(int x1, int y1, int x2, int y2) {
		return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
	}

	/**
	 * 点到线段的距离
	 *
	 * @param x
	 *            点x坐标
	 * @param y
	 *            点y坐标
	 * @param x1
	 *            线段顶点1x坐标
	 * @param y1
	 *            线段顶点1y坐标
	 * @param x2
	 *            线段顶点2x坐标
	 * @param y2
	 *            线段顶点2y坐标
	 * @return
	 */
	private double getLength(int x, int y, int x1, int y1, int x2, int y2) {
		double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
		if (cross <= 0) {
			return Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
		}
		double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
		if (cross >= d2) {
			return Math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
		}
		double r = cross / d2;
		double px = x1 + (x2 - x1) * r;
		double py = y1 + (y2 - y1) * r;
		return Math.sqrt((x - px) * (x - px) + (py - y) * (py - y));

	}

	public int[][] getVertex() {
		return vertex;
	}

	public void setVertex(int[][] vertex) {
		this.vertex = vertex;
	}

	@Override
	public Rectangle copy() {
		return new Rectangle(x, y, vertex);
	}

	@Override
	public void moveTo(int px, int py) {

		int vx = px - this.x;
		int vy = py - this.y;

		int[][] copyOfVertex = new int[this.vertex.length][2];
		for (int i = 0; i < this.vertex.length; i++) {
			copyOfVertex[i][0] = vx + this.vertex[i][0];
			copyOfVertex[i][1] = vy + this.vertex[i][1];
		}

		this.x = px;
		this.y = py;
	}

}
package game;

public class Test {
    //根据两点可以获取向量
	//根据一点可以获得
	//根据两点获取

	@org.junit.Test
	public  void test() {
	Circle c1=new Circle(0, 0, 71);
	Circle c2=new Circle(0, 76, 2);
	/*圆是否包含圆*/
	System.out.println("圆包含圆"+c1.collision(c2));

	/*矩形的4个点*/
	int[][] in=new int[4][2];
	in[0][0]=0;
	in[0][1]=100;

	in[1][0]=100;
	in[1][1]=0;

	in[2][0]=200;
	in[2][1]=100;

	in[3][0]=100;
	in[3][1]=200;

	Rectangle rectangle1=new Rectangle(100, 100, in);
	/*矩形是否包含圆*/
	System.out.println("矩形是否包含圆:"+(rectangle1.collision(c1)));
	/*圆包含矩形*/
	System.out.println("圆包含矩形:"+(c1.collision(rectangle1)));
	/*矩形形包含点*/
	System.out.println("矩形形包含点:"+rectangle1.contains(55, 49));

	/*矩形的4个点*/
	int[][] in2=new int[4][2];
	in2[0][0]=0;
	in2[0][1]=44;

	in2[1][0]=0;
	in2[1][1]=0;

	in2[2][0]=44;
	in2[2][1]=0;

	in2[3][0]=44;
	in2[3][1]=44;

	Rectangle rectangle2=new Rectangle(24, 24, in2);
	long start=System.currentTimeMillis();
	rectangle2.collision(rectangle1);
	for(int i=0;i<100000;i++){
		//c1.collision(c2);
		//c1.collision(rectangle1);
		rectangle2.collision(rectangle1);
	}
	long end =System.currentTimeMillis();
	System.out.println((end-start));
	System.out.println("矩形包含矩形:"+rectangle2.collision(rectangle1));
	}
}

参考博客

http://blog.csdn.net/i_dovelemon/article/details/31420749

http://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.html

http://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.html

时间: 2024-10-28 10:47:55

游戏碰撞之OBB算法实现(java代码实现)的相关文章

对一致性Hash算法,Java代码实现的深入研究

一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法和一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),接着在

【转载】对一致性Hash算法,Java代码实现的深入研究

原文地址:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这

算法9-5:最大流算法的Java代码

残留网络 在介绍最大流算法之前先介绍一下什么是残留网络.残余网络的概念有点类似于集合中的补集概念. 下图是残余网络的例子.上面的网络是原始网络,下面的网络是计算出的残留网络.残留网络的作用就是用来描述这个网络中还剩下多少可以利用的流量. 流量网络 最大流算法比以前介绍的算法都要复杂.网络中的每一条边需要记录容量和当前流量.容量是固定值,是已知条件,而当前流量在计算过程中会一直发生变化.因此,需要建立一个专门的类,用于最大流算法. public class FlowEdge { private i

利用朴素贝叶斯算法进行分类-Java代码实现

http://www.crocro.cn/post/286.html 利用朴素贝叶斯算法进行分类-Java代码实现 鳄鱼  3个月前 (12-14)  分类:机器学习  阅读(44)  评论(0) Java package cn.crocro.classifier; import java.util.ArrayList; /** * 朴素贝叶斯分类器,只能针对有限个情况的分类下面是实例代码 * * @author 鳄鱼 * */ public class NaiveBayesClassifier

编辑距离和编辑距离的动态规划算法(Java代码)

编辑距离概念描述: 编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数.许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符. 例如将kitten一字转成sitting: sitten (k→s) sittin (e→i) sitting (→g) 俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念. 编辑距离的应用在信息检索.拼写纠错.机器翻译.命名实体抽取.同义词寻找等问题中有较多的应用 问题:找出

几种简单的负载均衡算法及其Java代码实现

什么是负载均衡 负载均衡,英文名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求.负载均衡能够平均分配客户请求到服务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能. 负载均衡分为软件负载均衡和硬件负载均衡,前者的代

初探12306售票算法(二)-java代码实践

周五闲来无事,基于上一篇关于初探12306售票算法(一)-理论,进行了java编码实践供各位读者参考(以下为相关代码的简单描述) 1.订票工具类 1.1初始化一列车厢的票据信息 /** * 生成Ticket信息 * * @param train * @return */ public static List<Ticket> initTicketList(Train train) { List<Ticket> result = new ArrayList<Ticket>(

H5游戏开发之多边形碰撞检测(JAVA代码)

转载至:http://www.cnblogs.com/Kurodo/archive/2012/08/08/2628688.html 对于矩形碰撞,很多人都知道.但面对多边形图形,大多数采用多矩形覆盖的方式. 但是我不是很喜欢这种方式,我所采用的是利用一个经典算法: SAT 一种可以快速检测不规则的凸多边形是否碰撞的算法 给出两个凸多边形体,如果我们能找到一个轴线,使两物体在此轴线上的投影不重叠,则这两个物体之间没有发生碰撞,这个轴线叫做Separating Axis(红色轴线). 对于2D来说,

算法,java代码实现打印万年历

万年历 以1900年1月1号星期一为时间原点 星期日 第一天 星期一 第二天 星期二 第三天 星期三 第四天 星期四 第五天 星期五 第六天 星期六 第七天 1.计算出当前日期距离原点的天数(例:2016/9/18) 2015到1900之间有多少个瑞年和平年-->count1 2016年一月到八月的总天数-->count2 本月的一号 count = count1+count2+1 2.计算出本月的一号是一周的第几天 k = count%7;(打印一号前面有多少空格) 3.计算出该月有多少天