准备
这个系列提供一系列简单图形的碰撞/相交的方法,提供了分块的代码和完整的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文件
这个文件内容主要包含四部分:准备方法和重置画布大小方法、图形渲染以及交互、工具对象、绘制工具对象。 最主要的就在图形渲染以及交互里面了,其他的都是辅助工具。这里代码分开说明,但是都是在一个文件中。
initResize
和resize
负责初始化画布大小和监听浏览器窗口变化,并调整画布大小
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