此文章是碰撞检测系列的第九篇,线和线碰撞检测/相交,此系列主要包含了多种形状的碰撞/相交检测方法。
预览
先查看效果吧,点击这里
碰撞/相交检测方法
为了检查两条线是否相交,我们需要计算到交点的距离:
const uA = ((p4.x-p3.x)*(p1.y-p3.y) - (p4.y-p3.y)*(p1.x-p3.x)) / ((p4.y-p3.y)*(p2.x-p1.x) - (p4.x-p3.x)*(p2.y-p1.y));
const uB = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
关于计算方法的推导方法如下:
/**
*
* P1 P2 直线 1 上的两个点
* A1 代表直线 1 的向量
* t1 直线 1 的系数
*
* P3 P4 直线 2 上的两个点
* A2 代表直线 2 的向量
* t2 直线 2 的系数
*
* Pa = P1 + t1*A1
* Pb = P3 + t2*A2
*
* 相交时,Pa = Pb
* x1 + t1*(x2-x1) = x3 + t2*(x4-x3)
* y1 + t1*(y2-y1) =y3 + t2*(y4-y3)
*
* 剩下就是二元一次方程求解
* t1 = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3))/((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
* t2 = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
*
*/
如果发生相交,uA和uB都应该在0-1的范围内。
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
return true;
}
return false;
我们可以计算出两条线的交叉点,在canvas上绘制出交叉的点
const intersectionX = p1.x + (uA * (p2.x - p1.x));
const intersectionY = p1.y + (uA * (p2.y - p1.y));
完整代码如下:
/**
*
* @param {Array} l1 line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
* @param {Array} l2 line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
* @returns boolean
*/
function lineLine([p1,p2],[p3,p4]) {
// calculate the distance to intersection point
const uA = ((p4.x-p3.x)*(p1.y-p3.y) - (p4.y-p3.y)*(p1.x-p3.x)) / ((p4.y-p3.y)*(p2.x-p1.x) - (p4.x-p3.x)*(p2.y-p1.y));
const uB = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
// optionally, draw a circle where the lines meet
const intersectionX = p1.x + (uA * (p2.x - p1.x));
const intersectionY = p1.y + (uA * (p2.y - p1.y));
const hitPoints = hit.hitPoints || (hit.hitPoints = [])
hitPoints.push({x: intersectionX, y: intersectionY})
return true;
}
return false;
}
主要代码
在我的demo中,当点与矩形碰撞/相交改变固定圆的颜色,可以点上面预览进去试试。这里是部分核心代码,详细代码结构解析点击这里 这里主要是渲染和交互代码,由于baseShape和cursorShape默认形状是圆,这里opt中参数需设置baseShape和cursorShape为line,关于line的起始点和结束点,配置在drawOpt中;cursorStartPoint为cursorShape线段的起始点;还有必须配置的hitFunc函数
const init = readyInit({
baseShape: "line",
cursorShape: 'line',
cursorStartPoint: {x: 50, y:50},
drawOpt: [{x: 100, y:400},{x: 800, y:100}],
hitFunc: (e, drawOpt,{cursorStartPoint}) => {
return hit.lineLine([cursorStartPoint,{ x:e.x, y:e.y}], drawOpt)
}
})
// 图形渲染以及交互
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的代码库,详细代码结构解析点击这里