html5碰撞计算,JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】...

本文介绍JS/HTML5游戏中碰撞检测的包围盒算法,利用分离轴定理检测凸多边形间的碰撞,通过实例演示算法原理及实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文实例讲述了JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法。分享给大家供大家参考,具体如下:

概述

分离轴定理是一项用于检测碰撞的算法。其适用范围较广,涵盖检测圆与多边形,多边形与多边形的碰撞;缺点在于无法检测凹多边形的碰撞。本demo使用Js进行算法实现,HTML5 canvas进行渲染。

详细

一、准备工作,熟悉分离轴定理 算法原理

从根本上来讲,分离轴定理(以及其他碰撞算法)的用途就是去检测并判断两个图形之间是否有间隙。分离轴定理中用到的方法使算法本身显得十分独特。

我所听到过分离轴定理的最好类比方式是这样的:

假想你拿一个电筒从不同的角度照射到两个图形上,那么会有怎样的一系列的阴影投射到它们之后的墙壁上呢?

bf2e4ead47a195e2db52940d4e7b4910.png

如果你用这个方式从每一个角度上对这两个图形进行处理,并都找不到任何的间隙,那么这两个图形就一定接触。如果你找到了一个间隙,那么这两个图形就显而易见地没有接触。

从编程的角度来讲,从每个可能的角度上去检测会使处理变得十分密集。不过幸运的是,由于多边形的性质,你只需要检测其中几个关键的角度。

你需要检测的角度数量就正是这个多边形的边数。也就是说,你所需检测的角度最大数量就是你要检测碰撞的两个多边形边数之和。举个例子,两个五边形就需要检测10个角度。

86b261763b18511105f2d35bf94fb73d.png

这是一个简易但比较啰嗦的方法,以下是基本的步骤:

步骤一:从需要检测的多边形中取出一条边,并找出它的法向量(垂直于它的向量),这个向量将会是我们的一个“投影轴”。

58b119147351cdccc7fe496747eb1e76.png

步骤二:循环获取第一个多边形的每个点,并将它们投影到这个轴上。(记录这个多边形投影到轴上的最高和最低点)

1d657ed4ec9d480eb62ddb4d075633a9.png

步骤三:对第二个多边形做同样的处理。

36235f8c12f90fd011398ea62aa15eaa.png

步骤四:分别得到这两个多边形的投影,并检测这两段投影是否重叠。

0a96c3bbaec0a7c266197e1a368899f8.png

如果你发现了这两个投影到轴上的“阴影”有间隙,那么这两个图形一定没有相交。但如果没有间隙,那么它们则可能接触,你需要继续检测直到把两个多边形的每条边都检测完。如果你检测完每条边后,都没有发现任何间隙,那么它们是相互碰撞的。

这个算法基本就是如此的。

顺带提一下,如果你记录了哪个轴上的投影重叠值最小(以及重叠了多少),那么你就能用这个值来分开这两个图形。

那么如何处理圆呢?

在分离轴定理中,检测圆与检测多边形相比,会有点点奇异,但仍然是可以实现的。

最值得注意的是,圆是没有任何的边,所以是没有明显的用于投影的轴。但它有一条“不是很明显的”的投影轴。这条轴就是途经圆心和多边形上离圆心最近的顶点的直线。

00a24c96405d44bd732a737a04d9fe45.png

在这以后就是按套路遍历另一个多边形的每条投影轴,并检测是否有投影重叠。

噢,对了,万一你想知道如何把圆投影到轴上,那你只用简单地把圆心投影上去,然后加上和减去半径就能得到投影长度了。

二、完整实例:

盒包围碰撞算法-凸多边形分离轴检测算法

#stage {

border: 1px solid lightgray;

}

是否碰撞:

window.onload = function () {

var stage = document.querySelector('#stage'),

ctx = stage.getContext('2d');

stage.width = 400;

stage.height = 400;

var starPointArr = [];

// 绘制五角星

function drawStar(ctx,r,R,x,y,rot,c){

ctx.beginPath();

for(var i =0;i<5;i++){

var startPosX = Math.cos((18 + i*72 - rot)/180 * Math.PI )*R + x,

startPosY = - Math.sin((18 + i*72 - rot)/180 * Math.PI)*R + y,

endPosX = Math.cos((54 + i*72 - rot)/180 * Math.PI )*r + x,

endPosY = - Math.sin((54 + i*72 - rot)/180 * Math.PI)*r + y;

ctx.lineTo(startPosX,startPosY);

ctx.lineTo(endPosX, endPosY);

starPointArr.push(startPosX,startPosY,endPosX,endPosY);

}

ctx.closePath();

ctx.fillStyle = c;

ctx.lineWidth = 3;

ctx.lineJoin = "round";

ctx.fill();

ctx.stroke();

}

var polygonPointArr = [];

// 绘制多边形

function drawpolygon(numSides,radius,x,y){

ctx.beginPath();

for(i = 1;i<=numSides; i++){

var xPos = x+radius*Math.cos(2*Math.PI*i/numSides);

var yPos = x+radius*Math.sin(2*Math.PI*i/numSides);

polygonPointArr.push(xPos,yPos);

ctx.lineTo(xPos,yPos);

}

//创建完成 闭合路径

ctx.closePath();

ctx.lineWidth = 3; //线宽

ctx.lineJoin = "round";

ctx.fillStyle = '#00f';

ctx.fill();

ctx.stroke();

}

//两个向量的点积

function dotV2(v1,v2) {

return v1.x*v2.x+v1.y*v2.y;

}

//计算polyArr在轴线axis上的投影,polyArr是一系列点坐标的集合,数组表示

function calcProj(axis,polyArr) {

var v = {"x":polyArr[0],"y":polyArr[1]};

var d,min,max;

min = max = dotV2(axis,v);//计算投影轴与第一个坐标点的点积

for(var i=2;i

v.x=polyArr[i];

v.y=polyArr[i+1];

d = dotV2(axis,v);//计算v到投影轴的距离,遍历出最小和最大区间

min = (d

max = (d>max)?d:max;

}

return [min,max];

}

//计算同一个轴上线段的距离s1(min1,max1),s2(min2,max2),如果距离小于0则表示两线段有相交;

function segDist(min1,max1,min2,max2) {

if(min1

{

return min2-max1;

}

else

{

return min1-max2;

}

}

//判断两个多边形是否相交碰撞,p1,p2用于保存多边形点的数组

function isCollide(p1,p2) {

//定义法向量

var e = {"x":0,"y":0};

var p = p1,idx=0,len1=p1.length,len2=p2.length,px,py;//p缓存形状p1的数据

for(var i=0,len = len1+len2;i

{

idx = i;

//计算两个多边形每条边

if(i>len1) {//当p1遍历完毕后,p缓存形状p2的数据,从新遍历

p=p2;

idx=(i-len1);//len2

}

if(i===p.length-2) {//p包含的点数据组成的最后一个坐标点

px=p[0]-p[idx];//首尾的x轴相连

py=p[1]-p[idx+1];//首尾的y轴相连

} else {

px = p[idx+2]-p[idx];//递增的x轴相连

py = p[idx+3]-p[idx+1];//递减的y轴相连

}

//得到边的法向量【垂直相交】,即投影轴

e.x = -py;

e.y = px;

//计算两个多边形在法向量上的投影

var pp1 = calcProj(e,p1);//涵盖到投影轴的最小值与最大值

var pp2 = calcProj(e,p2);

//计算两个线段在法向量上距离,如果大于0则可以退出,表示无相交

if(segDist(pp1[0],pp1[1],pp2[0],pp2[1])>0) {

return false;

}

}

return true;

}

document.onkeydown = function (event) {

var e = event || window.event || arguments.callee.caller.arguments[0];

//根据地图数组碰撞将测

switch (e.keyCode) {

case 37:

console.log("Left");

if (starPosX > 0) {

starPosX -= 2;

}

break;

case 38:

console.log("Top");

if (starPosY > 0) {

starPosY -= 2;

}

break;

case 39:

console.log("Right");

if (starPosX < stage.width) {

starPosX += 2;

}

break;

case 40:

console.log("Bottom");

if (starPosY < stage.height) {

starPosY += 2;

}

break;

default:

return false;

}

};

var starPosX = stage.width/2,starPosY = stage.height/2;

stage.addEventListener('click', function (event) {

var x = event.clientX - stage.getBoundingClientRect().left;

var y = event.clientY - stage.getBoundingClientRect().top;

starPosX = x;

starPosY = y;

});

function update() {

ctx.clearRect(0, 0, 400, 400);

starPointArr = [];

polygonPointArr = [];

drawpolygon(7,50,300,300);

drawStar(ctx,30,50,starPosX,starPosY,30,"yellow");

document.querySelector('.hitTest').innerHTML = "否";

var flag = isCollide(starPointArr, polygonPointArr);

if (flag) {

document.querySelector('.hitTest').innerHTML = "是";

}

requestAnimationFrame(update);

}

update();

};

这里使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun 测试上述代码运行结果如下:

4b196ca57e30de4d9793e29edbaf2041.gif

希望本文所述对大家JavaScript程序设计有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值