碰撞检测系列——图形碰撞/相交的方法

准备

这个系列提供一系列简单图形的碰撞/相交的方法,提供了分块的代码和完整的demo,demo预览和代码,点击下来入口进入。这里会说明一下整个demo的代码结构,下载代码后可以先扫一眼,会清晰些。

快速入口

代码结构

主要文件有:

  • HTML文件, demo入口文件,每个demo一个,存在多个
  • hit.js, 被每个HTML依赖,包含了所有的demo用到的碰撞/相交的方法
  • init.js, 同样被每个HTML依赖,包含了工具方法,绘制逻辑

HTML文件

每一个demo都有独立HTML文件入口,文件中主要调用readInit方法,传入参数是画布绘制时,需要的参数。详情在后面具体使用的方法中说明,返回一个init方法,在onload的时候掉用

xml 复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>POINT/POINT</title>
		<script src="./script/init.js" charset="utf-8" type="text/javascript"></script>
        <script src="./script/hit.js" charset="utf-8" type="text/javascript"></script>
		<link rel="stylesheet" type="text/css" href="./css/style.css"   />
		<script>
			const init = readyInit({
				zoom: 10,radius:1,radius1:1,
				fillRectColliding: true,
				hitFunc: (e, cp,{zoom}) => {
					return hit.pointPoint(cp, utils.toPoint(Math.round(e.x/zoom), Math.round(e.y/zoom)))
				}
			})
		</script>
	</head>
	<body onload="init()">
		<div id="container">
            <canvas id="test" width=100% height=100%></canvas>
        </div>
	</body>
</html>

init.js文件

这个文件内容主要包含四部分:准备方法和重置画布大小方法、图形渲染以及交互、工具对象、绘制工具对象。 最主要的就在图形渲染以及交互里面了,其他的都是辅助工具。这里代码分开说明,但是都是在一个文件中。

initResizeresize负责初始化画布大小和监听浏览器窗口变化,并调整画布大小

ini 复制代码
function readyInit(opt) {
    return () => {
        initResize();
        check(opt)
    }
}
// 初始化画布大小和监听浏览器窗口变化,并调整画布大小
function initResize() {
    resize()
    window.addEventListener('resize', () => {
        resize()
    })
}
// 设置画布大小
function resize() {
    const canvas = document.getElementById('test');
    const width = canvas.parentElement.scrollWidth;
    const height = canvas.parentElement.scrollHeight;
    canvas.width = width
    canvas.height = height
    canvas.style.width = width;
    canvas.style.height = height;
    const ctx = canvas.getContext("2d");
    ctx.width = width;
    ctx.height = height;
}

HTML文件中调用readyInit传入的opt参数就是给这里使用的,opt中属性主要分为三类:

  • 基础图形, 基础图形指的画布固定不动的图形,绘制此需要的相关参数,例如绘制的图形有点、圆、线、矩形、多边形。由baseShape决定。不同的形状需要不同的参数,有的图形的参数在基础图形的绘制参数准备中会程序提供,有的参数就需要开发者提供了。具体参数就不列举了,属性不多,根据demo查看使用到的属性即可。
  • 鼠标图形, 鼠标图形指的是跟着鼠标一起移动的图形,绘制此图形需要的属性,例如cursorShape、cursorStartPoint、radius1等。不同的属性也是根据绘制的图形不同搭配使用。
  • hitFunc, 这是必须配置的方法,此方法的返回true,代表两图形碰撞; false,不碰撞。此方法中,准备参数,并调用各demo自己的检测方法。
ini 复制代码
// 图形渲染以及交互
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();
}

这部分是两个工具对象,没有特别需要说明的。

javascript 复制代码
const utils = {
    // 获取2D 渲染上下文
    getCtx() {
        const canvas = document.getElementById('test');
        return canvas.getContext("2d");
    },
    // 清除画布
    cleanCanvas(ctx) {
        const canvas = ctx.canvas;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    },
    // 生成point对象的工具方法
    toPoint(x,y) {
        return {x,y}
    },
    // 生成circle对象的工具方法
    toCircle(x, y, r) {
        return {x,y,r}
    },
    // 生成rect对象的工具方法
    toRect(x, y, w,h) {
        return {x,y,w,h}
    },
    /**
     * 计算两点之间距离
     * @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))
    }
}
// 图形绘制工具方法
const drawUtils = {
    circle(ctx, {x, y, r}) {
        ctx.beginPath();
        ctx.arc(x, y, r, 0, 2 * Math.PI);
        ctx.fill();
    },
    rect(ctx, {x, y, w, h}) {
        ctx.fillRect(x, y, w, h);
    },
    line(ctx, [p1, p2]) {
        ctx.beginPath();
        ctx.moveTo(p1.x, p1.y);
        ctx.lineTo(p2.x, p2.y);
        ctx.stroke();
    },
    polygon(ctx, points) {
        points = [...points];
        ctx.beginPath();
        const first = points.shift();
        ctx.moveTo(first.x, first.y);
        points.forEach(p => ctx.lineTo(p.x, p.y))
        // ctx.closePath();
        ctx.fill();
    }
}

hit.js文件

此文件是本系列的核心代码了,包含了多种图形碰撞/相交的检测方法。这部分代码太多,就不全部贴到这里了。可以根据需要在,快速入口中找到需要的图形碰撞/相交,里面有demo预览路径和源码下载路径。

javascript 复制代码
const hit = {
    /**
     * 
     * @param {Object} p1 点对象{x,y}
     * @param {Object} p2 点对象{x,y}
     * @returns boolean
     */
    pointPoint(p1, p2) {},
    /**
     * 
     * @param {Object} p 点对象{x,y}
     * @param {Object} c 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @returns boolean
     */
    pointCircle(p, c) {},
    /**
     * 
     * @param {Object} c1 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @param {Object} c2 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @returns boolean
     */
    circleCircle(c1, c2) {},
    /**
     * 
     * @param {Object} p 点对象{x,y}
     * @param {Object} r 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @returns boolean
     */
    pointRectangle(p,r) {},
    /**
     * 
     * @param {Object} r1 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @param {Object} r2 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @returns boolean
     */
    rectangleRectangle(r1,r2) {},
    /**
     * 
     * @param {Object} c 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @param {Object} r 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @returns boolean
     */
    circleRect(c,r) {},
    /**
     * 
     * @param {Array} l line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @param {Object} p 点对象{x,y}
     * @returns boolean
     */
    linePoint(l,p) {},
    /**
     * 
     * @param {Array} l line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @param {Object} c 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @returns boolean
     */
    lineCircle(l,c) {},
    /**
     * 
     * @param {Array} l1 line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @param {Array} l2 line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @returns boolean
     */
    lineLine([p1,p2],[p3,p4]) {},
    /**
     * 
     * @param {Array} l line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @param {Object} r 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @returns boolean
     */
    lineRectangle(l,r) {},
    /**
     * 
     * @param {Array} vertices poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @param {Object} p 点对象{x,y}
     * @returns boolean
     */
    polygonPoint(vertices, p) {},
    /**
     * 
     * @param {Array} vertices poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @param {Object} c 圆对象{x,y,r} x/y: 圆心坐标; r:圆半径
     * @returns boolean
     */
    polygonCirecle(vertices,c) {},
    /**
     * 
     * @param {Array} vertices poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @param {Object} rect 矩形对象{x,y,w,h} x/y: 矩形左上角坐标; w:宽; h:高
     * @returns boolean
     */
    polygonRectangle(vertices,rect) {},
    /**
     * 
     * @param {Array} vertices poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @param {Array} l line对象/线对象 结构[{x,y},{x,y}] 元素1:起始点; 元素2:结束点;
     * @returns boolean
     */
    polygonLine(vertices,l) {},
    /**
     * 
     * @param {Array} p1 poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @param {Array} p2 poly对象/多边形对象 结构[{x,y},{x,y},...]
     * @returns boolean
     */
    polygonPolygon(p1, p2) {},
}

结尾

希望这个系列能对大家有所帮助

长期从事2D、3D及2D编辑器、3D编辑器、大屏编辑器工作,如有需要欢迎沟通交流,微信号:blackmugger

相关推荐
余生H2 天前
即时可玩web小游戏(二):打砖块(支持移动端版) - 集成InsCode快来阅读并即时体验吧~
前端·javascript·inscode·canvas·h5游戏
黑猫很白6 天前
计算机图形学-动画Animation-仿真物理模拟Simulation
计算机图形学
普兰店拉马努金9 天前
【Canvas与图标】牛皮纸文件袋图标
canvas·图标·文件袋·牛皮纸
德育处主任11 天前
前端啊,拿Lottie炫个动画吧
前端·svg·canvas
GDAL12 天前
深入剖析Canvas的getBoundingClientRect:精准定位与交互事件实现
canvas
剑亦未配妥14 天前
使用js和canvas、html实现简单的俄罗斯方块小游戏
前端·javascript·canvas·1024程序员节
howard200515 天前
2.1 HTML5 - Canvas标签
html5·canvas
字节流动19 天前
Vulkan 开发(三):Vulkan 物理设备
计算机图形学
字节流动20 天前
Vulkan 开发(二):Vulkan 实例
计算机图形学
普兰店拉马努金1 个月前
【Canvas与标牌】立入禁止标牌
canvas·警示·标识·立入禁止