此文章是碰撞检测系列的第二篇,点和圆碰撞检测/相交,或者说是判断点是否在圆中,此系列主要包含了多种形状的碰撞/相交检测方法。
预览
先查看效果吧,点击这里
碰撞/相交检测方法
点和点碰撞非常简单,但从这里开始,我们需要一些基本的数学来检查物体是否相互接触。测试一个点是否在圆内需要我们回想起中学数学课上的勾股定理:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a 2 + b 2 = c 2 a2 + b2 = c2 </math>a2+b2=c2
我们可以求出三角形c的长边的长度已知另外两条边的长度。伪代码,它看起来像这样:
css
c = Math.sqrt( (a*a) + (b*b) );
a的平方加b的平方,得到结果的平方根。
我们为什么需要这个?我们可以用勾股定理得到二维空间中两个物体之间的距离!在这种情况下,a和b是点到圆心之间的水平和垂直距离。
我们可以这样计算X和Y的距离:
ini
const distX = px - cx;
const distY = py - cy;
然后,我们能够利用勾股定理计算出两点之间的距离:
ini
const distance = Math.sqrt((distX * distX) + (distY * distY));
如果点在(10,10)圆心在(40,50)我们得到的距离是50。你可能会想,"如果结果是负的呢?"不用担心:因为我们用距离值相乘,即使它们是负的,结果也会是正的。
接下来,我们如何用它来测试碰撞呢?如果点到圆心的距离小于圆的半径,我们就会相撞!
kotlin
if (distance <= r) {
return true;
}
return false;
我们可以将这些代码封装在一个函数中,以使其更可用。参数是一个点对象(包含x、y坐标)和圆对象(包含圆心坐标和半径)。该函数返回一个布尔值true或false,取决于是否发生碰撞。
javascript
/**
*
* @param {Object} p 点对象{x,y}
* @param {Object} c 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
* @returns boolean
*/
function pointCircle(p, c) {
// get distance between the point and circle's center
// using the Pythagorean Theorem
const distX = p.x - c.x;
const distY = p.y - c.y;
const distance = Math.sqrt((distX * distX) + (distY * distY));
// if the distance is less than the circle's
// radius the point is inside!
return distance <= c.r;
}
主要代码
在我的demo中,当点与圆碰撞/相交改变圆的颜色,可以点上面预览进去试试。这里是部分核心代码,详细代码结构解析点击这里
这里主要是渲染和交互代码,由于baseShape和cursorShape默认形状是圆,这里opt中参数仅设置各自圆的半径即可,radius是baseShape的半径,radius1是cursorShape的半径,还有必须配置的hitFunc函数
ini
const init = readyInit({
radius:40,radius1:5,
hitFunc: (e, cp,{radius}) => {
const p = utils.toPoint(e.x, e.y)
return hit.pointCircle(p,utils.toCircle(cp.x, cp.y, radius))
}
})
// 图形渲染以及交互
function check(opt) {
const ctx = utils.getCtx();
const canvas = ctx.canvas;
const zoom = opt.zoom || 1;
const width = canvas.width / zoom;
const height = canvas.height / zoom;
const cp = { x: Math.round(width / 2), y: Math.round(height / 2) }
ctx.scale(zoom, zoom)
// 基础图形的绘制参数准备开始
const radius = opt.radius || 10;
const baseShape = opt.baseShape || 'circle'
let drawOpt = opt.drawOpt;;
if (baseShape === 'circle') {
drawOpt = {...cp, r:radius}
} else if (baseShape === 'rect') {
const w = opt.w || 400;
const h = opt.h || 200;
drawOpt = {x:(width - w) / 2, y:(height - h) / 2, w, h}
}
// 基础图形的绘制参数准备结束
// 渲染方法
function render(colliding) {
utils.cleanCanvas(ctx)
ctx.fillStyle = '#0095d9E0';
ctx.strokeStyle = '#0095d9E0';
if (colliding) {
// 碰撞时绘制效果
if (opt.fillRectColliding) {
// 碰撞时,改变背景图颜色(两点碰撞时使用,由于点太小,效果不明显)
ctx.save()
ctx.fillStyle = "#f6ad49";
ctx.fillRect(0, 0, width, height);
ctx.restore()
} else {
// 碰撞时,改变基础图形绘制颜色
ctx.fillStyle = "#f6ad49E0";
ctx.strokeStyle = "#f6ad49E0";
}
}
// 相交的辅助点绘制,不是每个demo都会有
const hitPoints = hit.hitPoints;
if (hitPoints) {
ctx.save()
ctx.fillStyle = "red";
hitPoints.forEach(p => {
drawUtils.circle(ctx, { x: p.x, y: p.y, r: 16 })
});
ctx.restore()
}
ctx.lineWidth = 20;
ctx.lineJoin = "round";
ctx.lineCap = "round";
// 基础图形绘制
const drawFunc = drawUtils[baseShape];
if (drawFunc) {
drawFunc(ctx,drawOpt)
}
delete hit.hitPoints;
}
const radius1 = opt.radius1 || 10;
const cursorShape = opt.cursorShape || 'circle'
canvas.addEventListener('mousemove', (e) => {
// 调用每个demo配置的hitFunc,检测碰撞结果
const colliding = opt.hitFunc ? opt.hitFunc(e, drawOpt, opt) : false;
// 移动鼠标重绘
render(colliding);
// 绘制鼠标图形,也就是移动的图形
ctx.fillStyle = '#6a6868E0';
if (cursorShape === 'rect') {
const w = opt.cursorW || 20;
const h = opt.cursorH || 20;
drawUtils.rect(ctx, { x:e.x / zoom - w/2, y:e.y / zoom - h/2, w, h })
} else if (cursorShape === 'line') {
ctx.strokeStyle = "#6a6868E0";
ctx.lineWidth = 20;
ctx.lineJoin = "round";
ctx.lineCap = "round";
drawUtils.line(ctx, [opt.cursorStartPoint,{ x:e.x, y:e.y}])
} else if (cursorShape === 'polygon') {
const { x, y } = e;
const points = [
{ x: x - 20, y: y - 20 },
{ x: x + 40, y: y - 10 },
{ x: x + 60, y: y + 20 },
{ x: x - 20, y: y + 20 },
{x: x - 40, y: y},
]
drawUtils.polygon(ctx, points)
} else {
drawUtils.circle(ctx, { x:e.x / zoom, y:e.y / zoom, r:radius1 })
}
})
render();
}
代码涉及到圆的绘制,被我抽取为一个工具方法,放在init.js文件中的drawUtils工具对象中
scss
function circle(ctx, {x, y, r}) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
这里面用到勾股定理计算两点距离很多次,我将其抽取为一个工具方法,放在init.js文件中的utils工具对象中
javascript
/**
* 计算两点之间距离
* @param {Object} p1 点对象{x,y}
* @param {Object} p2 点对象{x,y}
* @returns number
*/
function dist(p1, p2) {
return Math.sqrt(Math.pow(p2.x - p1.x,2) + Math.pow(p2.y - p1.y,2))
}
代码下载
以上代码只是主要代码并不是完整代码,由于完整代码较多就不贴出来了,有需要可以点击这里,这是GitHub的代码库,详细代码结构解析点击这里