標籤:演算法 遊戲 數學 圖形 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;} /** * 跟別的映像是否有重疊 */@Overridepublic 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;}/** * 是否包含某個點 */@Overridepublic 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;}@Overridepublic Circle copy() {return new Circle(radius, radius, radius);}@Overridepublic 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條邊的距離小於圓的半徑並且圓心不在矩形內那麼這個矩形跟這個圓重疊 */@Overridepublic 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);}/** * 是否包含某一點 */@Overridepublic 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;}@Overridepublic Rectangle copy() {return new Rectangle(x, y, vertex);}@Overridepublic 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.Testpublic 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/31420749http://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.htmlhttp://www.cnblogs.com/iamzhanglei/archive/2012/06/07/2539751.html
遊戲碰撞之OBB演算法實現(java代碼實現)