碰撞检测系列——线与点碰撞/相交

此文章是碰撞检测系列的第七篇,圆和矩形碰撞检测/相交,此系列主要包含了多种形状的碰撞/相交检测方法。

预览

先查看效果吧,点击这里

碰撞/相交检测方法

本系列之前的碰撞检测主要是逻辑和一点点加法。线碰撞稍微有点复杂,需要你对高中几何课还有点印象。

这里的线,我们用两点表示法表示,由两个点组成,我们能利用勾股定理计算出两点之间的距离,由于这里会多次使用它,我们先将其封装成工具方法。

javascript 复制代码
/**
 * 计算两点之间距离
 * @param {Object} p1 点对象{x,y}
 * @param {Object} p2 点对象{x,y}
 * @returns number
 */
dist(p1, p2) {
    return Math.sqrt(Math.pow(p2.x - p1.x,2) + Math.pow(p2.y - p1.y,2))
}

检测思路是,先计算出表示线的两点间距离,再计算出点到线的两点距离

ini 复制代码
const lineLen = utils.dist(l[0], l[1]);
const d1 = utils.dist(p, l[0])
const d2 = utils.dist(p, l[1])

如果d1+d2等于直线的长度,那么我们就在直线上。为了更直观,来张图看看:

由于线宽太小,点也只有一个像素,实际应用中,可以考虑增加一个容差值,完整代码如下:

javascript 复制代码
/**
 * 
 * @param {Array} l line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
 * @param {Object} p 点对象{x,y}
 * @returns boolean
 */
function linePoint(l,p) {
    // 计算点到线的两个端点距离
    const d1 = utils.dist(p, l[0])
    const d2 = utils.dist(p, l[1])

    // 计算线的两个端点之间距离,即线的长度
    const lineLen = utils.dist(l[0], l[1]);

    // 由于线宽太小,点也只有一个像素,可以考虑增加一个容差值
    const buffer = 0.1;    

    // 如果点到两个端点的距离等于线长度,说明点在线上,反之,不在
    return Math.abs(d1 + d2 - lineLen) <= buffer;
}

主要代码

在我的demo中,当点与矩形碰撞/相交改变固定圆的颜色,可以点上面预览进去试试。这里是部分核心代码,详细代码结构解析点击这里 这里主要是渲染和交互代码,由于baseShape和cursorShape默认形状是圆,这里opt中参数需设置baseShape为line,关于line的起始点和结束点,配置在drawOpt中;还有必须配置的hitFunc函数

ini 复制代码
const init = readyInit({
	baseShape: "line",
	drawOpt: [{x: 100, y:400},{x: 800, y:100}],
	hitFunc: (e, drawOpt) => {
		return hit.linePoint(drawOpt,e)
	}
})

// 图形渲染以及交互
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 line(ctx, [p1, p2]) {
    ctx.beginPath();
    ctx.moveTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.stroke();
}

代码下载

以上代码只是主要代码并不是完整代码,由于完整代码较多就不贴出来了,有需要可以点击这里,这是GitHub的代码库,详细代码结构解析点击这里

相关推荐
PineappleCoder6 小时前
SVG 适合静态图,Canvas 适合大数据?图表库的场景选择
前端·面试·canvas
德育处主任1 天前
p5.js 用 cylinder() 绘制 3D 圆柱体
前端·数据可视化·canvas
蛋蛋_dandan3 天前
Fabric.js从0到1实现图片框选功能
canvas
wayhome在哪5 天前
用 fabric.js 搞定电子签名拖拽合成图片
javascript·产品·canvas
德育处主任5 天前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
德育处主任6 天前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas
德育处主任8 天前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
掘金安东尼8 天前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
百万蹄蹄向前冲9 天前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae
用户25191624271111 天前
Canvas之画图板
前端·javascript·canvas