此文章是碰撞检测系列的第十一篇,线和矩形碰撞检测/相交,此系列主要包含了多种形状的碰撞/相交检测方法。
预览
先查看效果吧,点击这里
碰撞/相交检测方法
之前的章节,我们主要介绍了圆形和矩形的碰撞/相交,它们是很有用的,可以作为边界框或者简化复杂形状的碰撞/相交。但是在某些情况下,我们需要更高的精确度,从本章节开始就介绍这些方法。
当前章节,我们将检查一个点是否在一个复杂多边形内。我们使用一组称为顶点(vector)的X/Y点来定义多边形。为了存储这些点,我们将使用一个point对象数组。point只是存储X/Y(或X/Y/Z)坐标。
为了检查碰撞,我们将使用一个单独的布尔变量。这将在稍后的函数中出现,如果你感到困惑,可以到下面看的完整示例。
ini
let collision = false;
遍历多边形中的每个顶点,并同时拿到下个顶点,形成一个线(多边形中的一条边)
ini
// 遍历多边形中的每个顶点,并同时拿到下个顶点,形成一个line
let next = 0;
for (let current = 0; current < vertices.length; current++) {
// 计算下个顶点的下标,如果当前是最后一个顶点,下一个点的下标是0
next = current + 1;
if (next == vertices.length) next = 0;
// 从多边形点列表中获取点
const vc = vertices[current]; // c 代表 "current"
const vn = vertices[next]; // n 代表 "next"
}
在循环中,拿到vc当前顶点和vn下一个顶点,开始比较
css
if (((vc.y >= p.y && vn.y < p.y) || (vc.y < p.y && vn.y >= p.y)) &&
(p.x < (vn.x-vc.x)*(p.y-vc.y) / (vn.y-vc.y)+vc.x)) {
collision = !collision;
}
上面代码中包含了两个检查,第一个,检测点的位置是否在两个顶点的Y方向之间
css
(vc.y >= p.y && vn.y < p.y) || (vc.y < p.y && vn.y >= p.y)
用图片说明,更能清晰表达
此检查语句,还可以简化为:
css
(vc.y > p.y) != (vn.y > p.y)
下一个检测,复杂些,是基于若尔当曲线定理(Jordan Curve Theorem),这里就不解释算法了。
scss
p.x < (vn.x-vc.x)*(p.y-vc.y) / (vn.y-vc.y)+vc.x)
如果上面两个检查都为真,我们将碰撞转换为相反的值。这与我们之前的测试不同,在之前的测试中,我们将碰撞简单地设置为true或false。在我们遍历了所有顶点之后,碰撞的最终状态就是结果。
完整代码如下:
ini
/**
*
* @param {Array} vertices poly对象/多边形对象 结构[{x,y},{x,y},...]
* @param {Object} p 点对象{x,y}
* @returns boolean
*/
function polygonPoint(vertices, p) {
let collision = false;
// 遍历多边形中的每个点,并同时拿到下个点,形成一个line
let next = 0;
for (let current = 0; current < vertices.length; current++) {
// 计算下个点的下标,如果当前是最后一个点,下一个点的下标是0
next = current + 1;
if (next == vertices.length) next = 0;
// 从多边形点列表中获取点
const vc = vertices[current]; // c 代表 "current"
const vn = vertices[next]; // n 代表 "next"
// compare position, flip 'collision' variable
// back and forth
if (((vc.y >= p.y && vn.y < p.y) || (vc.y < p.y && vn.y >= p.y)) &&
(p.x < (vn.x-vc.x)*(p.y-vc.y) / (vn.y-vc.y)+vc.x)) {
collision = !collision;
}
}
return collision;
}
主要代码
在我的demo中,当点与矩形碰撞/相交改变固定圆的颜色,可以点上面预览进去试试。这里是部分核心代码,详细代码结构解析点击这里 这里主要是渲染和交互代码,由于baseShape和cursorShape默认形状是圆,polygon, drawOpt中是绘制多边形的顶点位置;还有必须配置的hitFunc函数
ini
const init = readyInit({
baseShape: "polygon",
drawOpt: [{x:600,y:100},{x:800,y:100},{x:1000,y:300},{x:400,y:300}],
hitFunc: (e, drawOpt) => {
return hit.polygonPoint(drawOpt,{ x:e.x, y:e.y})
}
})
// 图形渲染以及交互
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();
}
代码下载
以上代码只是主要代码并不是完整代码,由于完整代码较多就不贴出来了,有需要可以点击这里,这是GitHub的代码库,详细代码结构解析点击这里