JavaScript實現碰撞檢測(分離軸定理)


概述

分離軸定理是一項用於檢測碰撞的算法。其適用范圍較廣,涵蓋檢測圓與多邊形,多邊形與多邊形的碰撞;缺點在於無法檢測凹多邊形的碰撞。本demo使用Js進行算法實現,HTML5 canvas進行渲染。

詳細

一、准備工作,熟悉分離軸定理 算法原理

 

(翻譯至http://www.sevenson.com.au/actionscript/sat/)

 

從根本上來講,分離軸定理(以及其他碰撞算法)的用途就是去檢測並判斷兩個圖形之間是否有間隙。分離軸定理中用到的方法使算法本身顯得十分獨特。

我所聽到過分離軸定理的最好類比方式是這樣的:

假想你拿一個電筒從不同的角度照射到兩個圖形上,那么會有怎樣的一系列的陰影投射到它們之后的牆壁上呢?

sat_2.jpg

如果你用這個方式從每一個角度上對這兩個圖形進行處理,並都找不到任何的間隙,那么這兩個圖形就一定接觸。如果你找到了一個間隙,那么這兩個圖形就顯而易見地沒有接觸。

從編程的角度來講,從每個可能的角度上去檢測會使處理變得十分密集。不過幸運的是,由於多邊形的性質,你只需要檢測其中幾個關鍵的角度。

你需要檢測的角度數量就正是這個多邊形的邊數。也就是說,你所需檢測的角度最大數量就是你要檢測碰撞的兩個多邊形邊數之和。舉個例子,兩個五邊形就需要檢測10個角度。

sat_3.jpg

這是一個簡易但比較啰嗦的方法,以下是基本的步驟:

步驟一:從需要檢測的多邊形中取出一條邊,並找出它的法向量(垂直於它的向量),這個向量將會是我們的一個“投影軸”。

sat_4.jpg

步驟二:循環獲取第一個多邊形的每個點,並將它們投影到這個軸上。(記錄這個多邊形投影到軸上的最高和最低點)

sat_5.jpg

 

步驟三:對第二個多邊形做同樣的處理。

sat_6.jpg

 

步驟四:分別得到這兩個多邊形的投影,並檢測這兩段投影是否重疊。

sat_7.jpg

 

 

如果你發現了這兩個投影到軸上的“陰影”有間隙,那么這兩個圖形一定沒有相交。但如果沒有間隙,那么它們則可能接觸,你需要繼續檢測直到把兩個多邊形的每條邊都檢測完。如果你檢測完每條邊后,都沒有發現任何間隙,那么它們是相互碰撞的。

這個算法基本就是如此的。

順帶提一下,如果你記錄了哪個軸上的投影重疊值最小(以及重疊了多少),那么你就能用這個值來分開這兩個圖形。

那么如何處理圓呢?

 

 

在分離軸定理中,檢測圓與檢測多邊形相比,會有點點奇異,但仍然是可以實現的。

最值得注意的是,圓是沒有任何的邊,所以是沒有明顯的用於投影的軸。但它有一條“不是很明顯的”的投影軸。這條軸就是途經圓心和多邊形上離圓心最近的頂點的直線。

sat_8.jpg

 

 

在這以后就是按套路遍歷另一個多邊形的每條投影軸,並檢測是否有投影重疊。

噢,對了,萬一你想知道如何把圓投影到軸上,那你只用簡單地把圓心投影上去,然后加上和減去半徑就能得到投影長度了。

二、代碼解析

1、html代碼如下:

<canvas width="800" height="500" id="mycanvas">Loading...</canvas>
<div id="select-box"></div>

2、main.js主要是控制位移以及圓圈大小,代碼如下:

var SHAPE_SIZE = 80, SHAPE_HANDLE_SIZE = 10;
var CANVAS_WIDTH, CANVAS_HEIGHT;

var renderer;
var shA = null, shB = null;

window.onload = function () {
	var canvasTag = document.getElementById("mycanvas");
	var canvas = canvasTag.getContext("2d");

	CANVAS_WIDTH = canvasTag.width;
	CANVAS_HEIGHT = canvasTag.height;

	renderer = new Renderer(canvas);

	setInterval(function () {
		renderer.loopDraw();
	}, 30);

	MouseEvent.addEvents(canvasTag);

	main();
};

function main () {
	UIUtils.createSelect();

	shA = UIUtils.createShape(150, 250, "shA-select");
	renderer.add(shA);

	shB = UIUtils.createShape(540, 250, "shB-select");
	renderer.add(shB);
}

function getPolygonVertices (edges, r) {  
	var ca = 0, aiv = 360 / edges, ata = Math.PI / 180, list = new Array();

	for (var k = 0; k < edges; k++) {
		var x = Math.cos(ca * ata) * r,
			y = Math.sin(ca * ata) * r;

		list.push(new Vec2(x, y));

		ca += aiv;
	}

	return list;
}

SHAPE_SIZE = 80, SHAPE_HANDLE_SIZE = 10 (SHAPE_SIZE設置外圓環大小,SHAPE_HANDLE_SIZE設置內圓大小),UIUtils.createShape(150, 250, "shA-select")設置第一個圓的x軸、y軸位移,還有外圓環選擇的形狀是什么,UIUtils.createShape(540, 250, "shB-select")設置第二個圓的x軸、y軸位移,還有外圓環選擇的形狀是什么。

3、SAT.js主要是控制拖動圓點時,外框的顏色等,部分代碼如下:

var SAT = (function () {
	function testCollision (A, B) {
		var res, color = "#333333";

		if (A.type == "polygon" && B.type == "polygon") {
			res = polygonsCollisionTest(A, B);
		} else if (A.type == "circle" && B.type == "circle") {
			res = circlesCollisionTest(A, B);
		} else {
			var c, p;
			if (A.type == "circle") {
				c = A;
				p = B;
			} else {
				c = B;
				p = A;
			}

			res = circlePolygonCollisionTest(c, p);
		}

		if (res) {
			color = "#FF0000";
		}

		A.color = B.color = color;
	}

4、Circle.js是控制第二個圓的外框顏色等,代碼如下:

function Circle (r) {
	this.objectIndex = Renderer.objectIndex++;
	this.type = "circle";
	this.r = r;
	this.x = 0;
	this.y = 0;
	this.color = "#333333";
}

Circle.prototype = {
	draw : function (c) {
		c.arc(0, 0, this.r, 0, Math.PI * 2);
	},

	getProjection : function (axis) {
		var pro = Vec2.dot(new Vec2(this.x, this.y), axis) / axis.length();

		return {min : pro - this.r, max : pro + this.r};
	}
};

 

5、Polygon.js是控制第一個圓的外框顏色等,代碼如下:

function Polygon (list) {
	this.objectIndex = Renderer.objectIndex++;
	this.type = "polygon";
	this.vertices = list;
	this.x = 0;
	this.y = 0;
	this.color = "#333333";
}

Polygon.prototype = {
	getRootCoordinate : function () {
		var list = this.vertices, res = new Array();

		for (var i = 0, l = list.length; i < l; i++) {
			var coord = list[i];

			res.push(new Vec2(coord.x + this.x, coord.y + this.y));
		}

		return res;
	},

	draw : function (c) {
		var list = this.vertices;

		if (list.length <= 1) {
			return;
		}

		c.moveTo(list[0].x, list[0].y);

		for (var i = 1, l = list.length; i < l; i++) {
			var coord = list[i];

			c.lineTo(coord.x, coord.y);
		}

		c.closePath();
	},

	getSides : function () {
		var list = this.vertices,
			l = list.length,
			res = new Array();

		if (l >= 3) {
			for (var j = 1, pre = list[0]; j < l; j++) {
				var p = list[j];

				res.push(Vec2.substract(p, pre));

				pre = p;
			}

			res.push(Vec2.substract(list[0], list[l - 1]));
		}

		return res;
	},

	getProjection : function (axis) {
		var list = this.getRootCoordinate(), min = null, max = null;

		for (var i = 0, l = list.length; i < l; i++) {
			var p = list[i];

			var pro = Vec2.dot(p, axis) / axis.length();

			if (min === null || pro < min) {
				min = pro;
			}

			if (max === null || pro > max) {
				max = pro;
			}
		}

		return {min : min, max : max};
	},

	getNearestPoint : function (p1) {
		var list = this.getRootCoordinate(), rP = list[0], minDis = Vec2.distance(p1, rP);

		for (var i = 1, l = list.length; i < l; i++) {
			var p2 = list[i], d = Vec2.distance(p1, p2);

			if (d < minDis) {
				minDis = d;

				rP = p2;
			}
		}

		return rP;
	}
};

 

6、Renderer.js是控制整體外框的屬性,比如顏色邊框等,代碼如下:

Renderer.prototype = {
	loopDraw : function () {
		var c = this.canvas;

		c.fillStyle = "#ff0000";
		c.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		
		for (var i = 0, l = this.displayList.length; i < l; i++) {
			var o = this.displayList[i];

			c.save();
			c.translate(o.x, o.y);

			c.beginPath();
			c.globalAlpha = 0.6;
			c.arc(0, 0, SHAPE_HANDLE_SIZE, 0, Math.PI * 2);
			c.fillStyle = "#0000FF";
			c.fill();

			c.beginPath();
			c.globalAlpha = 1;

			o.draw(c);

			c.strokeStyle = o.color;
			c.lineWidth = 2;
			c.stroke();
			c.restore();
		}
	},

	add : function (o) {
		this.displayList.push(o);
	},

	remove : function (o) {
		for (var i = 0, l = this.displayList.length; i < l; i++) {
			var child = this.displayList[i];

			if (child.objectIndex == o.objectIndex) {
				this.displayList.splice(i, 1);

				break;
			}
		}
	}
};

 

 

三、文件以及演示截圖

1、文件截圖

222.gif

2、演示截圖

blob.png

3、雙擊index.html文件即可運行看效果

四、兼容性

兼容主流瀏覽器

注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM