From 7ec4ea7de9fb062f83da549c73d23e33b4ca4def Mon Sep 17 00:00:00 2001 From: xudan Date: Tue, 17 Jun 2025 16:09:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E9=85=8D=E7=BD=AE=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8C=BA=E5=9F=9F=E5=92=8C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=AC=A6=E5=90=88?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Canvas绘制技术详解.md | 440 +++++++++++++++++++++++++++++++ Meta2D引擎作用详解.md | 600 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1040 insertions(+) create mode 100644 Canvas绘制技术详解.md create mode 100644 Meta2D引擎作用详解.md diff --git a/Canvas绘制技术详解.md b/Canvas绘制技术详解.md new file mode 100644 index 0000000..0a8cf91 --- /dev/null +++ b/Canvas绘制技术详解.md @@ -0,0 +1,440 @@ +# Canvas 2D 绘制技术详解 + +## 📖 概述 + +本文档详细分析场景编辑器中的自定义绘制函数,这些函数基于 **HTML5 Canvas 2D API** 在浏览器页面上绘制各种图形元素(点位、路线、区域、机器人)。 + +## 🎯 核心技术栈 + +### 1. HTML5 Canvas 2D API + +- **技术原理**:Canvas 是 HTML5 提供的位图绘制 API +- **绘制方式**:使用 JavaScript 在 Canvas 画布上逐像素绘制 +- **坐标系统**:左上角为原点 (0,0),X轴向右,Y轴向下 +- **绘制上下文**:通过 `CanvasRenderingContext2D` 对象进行所有绘制操作 + +### 2. Meta2D 引擎集成 + +- **自定义绘制**:通过 `registerCanvasDraw()` 注册自定义绘制函数 +- **图形对象**:每个绘制函数接收 `MapPen` 对象,包含图形的所有属性 +- **渲染时机**:引擎在每次重绘时自动调用对应的绘制函数 + +--- + +## 🎨 绘制函数详细分析 + +### 1. 点位绘制函数 `drawPoint()` + +#### 函数签名和参数 + +```typescript +function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void; +``` + +#### 代码逐行分析 + +```typescript +// 1. 获取全局主题配置 +const theme = sTheme.editor; +``` + +**分析**:从全局主题服务获取编辑器主题配置,用于确定颜色、样式等视觉属性。 + +```typescript +// 2. 从计算属性中提取绘制参数 +const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {}; +``` + +**分析**: + +- `active`:图形是否处于选中状态 +- `iconSize`:图标大小,重命名为 `r`(半径) +- `fontSize/lineHeight/fontFamily`:文本绘制参数 + +```typescript +// 3. 获取世界坐标系下的矩形区域 +const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {}; +``` + +**分析**:Meta2D 引擎会自动计算图形在世界坐标系下的实际位置和大小。 + +```typescript +// 4. 获取业务属性 +const { type } = pen.point ?? {}; +const { label = '' } = pen ?? {}; +``` + +```typescript +// 5. 保存当前画布状态 +ctx.save(); +``` + +**分析**:`save()` 保存当前的绘制状态(变换矩阵、样式等),避免影响其他图形。 + +#### 小点位绘制(类型1-9) + +```typescript +switch (type) { + case MapPointType.普通点: + case MapPointType.等待点: + case MapPointType.避让点: + case MapPointType.临时避让点: + // 绘制圆角菱形 + ctx.beginPath(); + ctx.moveTo(x + w / 2 - r, y + r); + ctx.arcTo(x + w / 2, y, x + w - r, y + h / 2 - r, r); + ctx.arcTo(x + w, y + h / 2, x + w / 2 + r, y + h - r, r); + ctx.arcTo(x + w / 2, y + h, x + r, y + h / 2 + r, r); + ctx.arcTo(x, y + h / 2, x + r, y + h / 2 - r, r); + ctx.closePath(); +``` + +**分析**: + +- `beginPath()`:开始新的绘制路径 +- `moveTo()`:移动画笔到起始点 +- `arcTo()`:绘制圆弧连接线,创建圆角效果 +- `closePath()`:闭合路径形成完整图形 + +```typescript +// 填充背景色 +ctx.fillStyle = get(theme, `point-s.fill-${type}`) ?? ''; +ctx.fill(); + +// 绘制边框 +ctx.strokeStyle = get(theme, active ? 'point-s.strokeActive' : 'point-s.stroke') ?? ''; +``` + +**分析**:根据点位类型和激活状态设置不同的填充色和边框色。 + +#### 临时避让点特殊标记 + +```typescript +if (type === MapPointType.临时避让点) { + ctx.lineCap = 'round'; // 设置线条端点为圆形 + ctx.beginPath(); + // 绘制8个短线标记,形成放射状效果 + ctx.moveTo(x + 0.66 * r, y + h / 2 - 0.66 * r); + ctx.lineTo(x + r, y + h / 2 - r); + // ... 其他7个方向的短线 +} +``` + +**分析**:在菱形的8个方向绘制短线,形成特殊的视觉标识。 + +#### 大点位绘制(类型11+) + +```typescript +case MapPointType.电梯点: +case MapPointType.自动门点: +case MapPointType.充电点: +case MapPointType.停靠点: +case MapPointType.动作点: +case MapPointType.禁行点: + ctx.roundRect(x, y, w, h, r); // 绘制圆角矩形 + ctx.strokeStyle = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke') ?? ''; + ctx.stroke(); +``` + +**分析**:大点位使用圆角矩形,通过 `roundRect()` API 一次性绘制。 + +#### 文本标签绘制 + +```typescript +// 设置文本样式 +ctx.fillStyle = get(theme, 'color') ?? ''; +ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`; +ctx.textAlign = 'center'; // 水平居中 +ctx.textBaseline = 'top'; // 垂直顶部对齐 + +// 在点位上方绘制标签 +ctx.fillText(label, x + w / 2, y - fontSize * lineHeight); +``` + +```typescript +// 恢复画布状态 +ctx.restore(); +``` + +--- + +### 2. 路线绘制函数 `drawLine()` + +#### 核心绘制逻辑 + +```typescript +// 1. 获取路线的两个端点坐标 +const [p1, p2] = pen.calculative?.worldAnchors ?? []; +const { x: x1 = 0, y: y1 = 0 } = p1 ?? {}; +const { x: x2 = 0, y: y2 = 0 } = p2 ?? {}; +``` + +```typescript +// 2. 获取路线属性 +const { type, direction = 1, pass = 0, c1, c2 } = pen.route ?? {}; +const { x: dx1 = 0, y: dy1 = 0 } = c1 ?? {}; // 控制点1偏移 +const { x: dx2 = 0, y: dy2 = 0 } = c2 ?? {}; // 控制点2偏移 +``` + +#### 路线类型绘制 + +```typescript +ctx.moveTo(x1, y1); // 移动到起点 +switch (type) { + case MapRouteType.直线: + ctx.lineTo(x2, y2); // 直接连线到终点 + break; + + case MapRouteType.二阶贝塞尔曲线: + // 使用一个控制点绘制曲线 + ctx.quadraticCurveTo(x1 + dx1 * s, y1 + dy1 * s, x2, y2); + break; + + case MapRouteType.三阶贝塞尔曲线: + // 使用两个控制点绘制更复杂的曲线 + ctx.bezierCurveTo(x1 + dx1 * s, y1 + dy1 * s, x2 + dx2 * s, y2 + dy2 * s, x2, y2); + break; +} +``` + +**贝塞尔曲线原理**: + +- **二阶贝塞尔曲线**:由起点、一个控制点、终点定义的曲线 +- **三阶贝塞尔曲线**:由起点、两个控制点、终点定义的更灵活曲线 +- **数学公式**:基于参数方程计算曲线上的每个点 + +#### 禁行路线绘制 + +```typescript +if (pass === MapRoutePassType.禁行) { + ctx.setLineDash([s * 5]); // 设置虚线样式 +} +ctx.stroke(); // 绘制路线 +``` + +#### 方向箭头绘制 + +```typescript +// 1. 计算箭头角度 +let r = (() => { + switch (type) { + case MapRouteType.直线: + return Math.atan2(y2 - y1, x2 - x1); // 直线的角度 + case MapRouteType.二阶贝塞尔曲线: + // 根据控制点计算切线角度 + return direction < 0 ? Math.atan2(dy1 * s, dx1 * s) : Math.atan2(y2 - y1 - dy1 * s, x2 - x1 - dx1 * s); + // ... + } +})(); +``` + +```typescript +// 2. 移动坐标系到箭头位置 +if (direction < 0) { + ctx.translate(x1, y1); // 反向箭头在起点 +} else { + ctx.translate(x2, y2); // 正向箭头在终点 + r += Math.PI; // 旋转180度 +} + +// 3. 绘制箭头(两条线段形成尖角) +ctx.moveTo(Math.cos(r + Math.PI / 5) * s * 10, Math.sin(r + Math.PI / 5) * s * 10); +ctx.lineTo(0, 0); +ctx.lineTo(Math.cos(r - Math.PI / 5) * s * 10, Math.sin(r - Math.PI / 5) * s * 10); +``` + +**箭头绘制原理**: + +- 使用三角函数计算箭头两条边的端点 +- `Math.PI / 5` (36度) 是箭头的张开角度 +- 通过坐标变换将箭头定位到正确位置和角度 + +--- + +### 3. 区域绘制函数 `drawArea()` + +#### 矩形区域绘制 + +```typescript +// 1. 绘制填充矩形 +ctx.rect(x, y, w, h); // 定义矩形路径 +ctx.fillStyle = get(theme, `area.fill-${type}`) ?? ''; // 设置填充色 +ctx.fill(); // 填充矩形 + +// 2. 绘制边框 +ctx.strokeStyle = get(theme, active ? 'area.strokeActive' : `area.stroke-${type}`) ?? ''; +ctx.stroke(); // 绘制边框 +``` + +#### 区域标签 + +```typescript +// 在区域上方居中显示标签 +ctx.fillStyle = get(theme, 'color') ?? ''; +ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`; +ctx.textAlign = 'center'; +ctx.textBaseline = 'top'; +ctx.fillText(label, x + w / 2, y - fontSize * lineHeight); +``` + +--- + +### 4. 机器人绘制函数 `drawRobot()` + +#### 机器人本体绘制 + +```typescript +const ox = x + w / 2; // 机器人中心X坐标 +const oy = y + h / 2; // 机器人中心Y坐标 + +// 绘制椭圆形机器人 +ctx.ellipse(ox, oy, w / 2, h / 2, 0, 0, Math.PI * 2); +ctx.fillStyle = get(theme, 'robot.fill') ?? ''; +ctx.fill(); +ctx.strokeStyle = get(theme, 'robot.stroke') ?? ''; +ctx.stroke(); +``` + +#### 路径轨迹绘制 + +```typescript +if (path?.length) { + // 设置路径样式 + ctx.strokeStyle = get(theme, 'robot.line') ?? ''; + ctx.lineCap = 'round'; // 圆形线帽 + ctx.lineWidth = s * 4; // 粗线条 + ctx.setLineDash([s * 5, s * 10]); // 虚线样式 + + // 坐标变换:移动到机器人中心并旋转 + ctx.translate(ox, oy); + ctx.rotate((-deg * Math.PI) / 180); + + // 绘制路径线条 + ctx.beginPath(); + ctx.moveTo(0, 0); + path.forEach((d) => ctx.lineTo(d.x * s, d.y * s)); + ctx.stroke(); +} +``` + +#### 路径终点箭头 + +```typescript +// 计算路径最后两个点的方向 +const { x: ex1 = 0, y: ey1 = 0 } = nth(path, -1) ?? {}; // 最后一个点 +const { x: ex2 = 0, y: ey2 = 0 } = nth(path, -2) ?? {}; // 倒数第二个点 +const r = Math.atan2(ey1 - ey2, ex1 - ex2) + Math.PI; + +// 在路径终点绘制箭头 +ctx.translate(ex1 * s, ey1 * s); +ctx.beginPath(); +ctx.moveTo(Math.cos(r + Math.PI / 4) * s * 10, Math.sin(r + Math.PI / 4) * s * 10); +ctx.lineTo(0, 0); +ctx.lineTo(Math.cos(r - Math.PI / 4) * s * 10, Math.sin(r - Math.PI / 4) * s * 10); +ctx.stroke(); +``` + +--- + +## 🔧 Canvas 2D API 核心方法说明 + +### 路径绘制方法 + +| 方法 | 功能 | 示例 | +| --------------------------------------------- | ------------------ | -------------------- | +| `beginPath()` | 开始新的绘制路径 | 每次绘制新图形前调用 | +| `moveTo(x, y)` | 移动画笔到指定位置 | 设置绘制起点 | +| `lineTo(x, y)` | 画直线到指定位置 | 绘制线段 | +| `arcTo(x1, y1, x2, y2, r)` | 绘制圆弧连接 | 创建圆角效果 | +| `quadraticCurveTo(cpx, cpy, x, y)` | 二阶贝塞尔曲线 | 简单曲线 | +| `bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)` | 三阶贝塞尔曲线 | 复杂曲线 | +| `rect(x, y, w, h)` | 矩形路径 | 绘制矩形 | +| `ellipse(x, y, rx, ry, rotation, start, end)` | 椭圆路径 | 绘制椭圆/圆形 | +| `closePath()` | 闭合当前路径 | 连接起点和终点 | + +### 样式设置方法 + +| 属性/方法 | 功能 | 示例 | +| -------------------- | ---------------- | ------------------------------- | +| `fillStyle` | 设置填充颜色 | `ctx.fillStyle = '#ff0000'` | +| `strokeStyle` | 设置描边颜色 | `ctx.strokeStyle = '#0000ff'` | +| `lineWidth` | 设置线条宽度 | `ctx.lineWidth = 2` | +| `lineCap` | 设置线条端点样式 | `'round'`, `'square'`, `'butt'` | +| `setLineDash([...])` | 设置虚线样式 | `ctx.setLineDash([5, 5])` | + +### 变换方法 + +| 方法 | 功能 | 说明 | +| -------------------------------- | ------------ | -------------- | +| `translate(x, y)` | 平移坐标系 | 移动原点位置 | +| `rotate(angle)` | 旋转坐标系 | 按弧度旋转 | +| `setTransform(a, b, c, d, e, f)` | 重置变换矩阵 | 恢复标准坐标系 | + +### 状态管理方法 + +| 方法 | 功能 | 说明 | +| ----------- | ------------ | ---------- | +| `save()` | 保存当前状态 | 压入状态栈 | +| `restore()` | 恢复之前状态 | 弹出状态栈 | + +--- + +## 🎯 绘制流程总结 + +### 1. 标准绘制流程 + +```typescript +function customDraw(ctx: CanvasRenderingContext2D, pen: MapPen): void { + // 1. 保存画布状态 + ctx.save(); + + // 2. 提取绘制参数 + const { x, y, width, height } = pen.calculative?.worldRect ?? {}; + + // 3. 设置样式属性 + ctx.fillStyle = '填充色'; + ctx.strokeStyle = '边框色'; + + // 4. 创建绘制路径 + ctx.beginPath(); + // ... 具体绘制操作 + + // 5. 执行绘制 + ctx.fill(); // 填充 + ctx.stroke(); // 描边 + + // 6. 恢复画布状态 + ctx.restore(); +} +``` + +### 2. 性能优化要点 + +- **状态管理**:及时调用 `save()` 和 `restore()` 避免状态污染 +- **路径复用**:合理使用 `beginPath()` 清除之前的路径 +- **批量绘制**:同类型图形可以合并绘制操作 +- **避免重复计算**:缓存复杂的数学计算结果 + +--- + +## 📊 技术优势 + +### 1. Canvas 2D 的优势 + +- **高性能**:直接操作像素,渲染速度快 +- **灵活性**:可以绘制任意复杂的图形 +- **交互性**:支持鼠标事件检测和处理 +- **兼容性**:现代浏览器完全支持 + +### 2. 自定义绘制的优势 + +- **个性化**:完全定制化的视觉效果 +- **主题支持**:动态切换颜色主题 +- **状态反馈**:不同状态显示不同样式 +- **扩展性**:易于添加新的图形类型 + +--- + +## 🔚 结语 + +本场景编辑器通过 Canvas 2D API 实现了丰富的图形绘制功能,每个绘制函数都经过精心设计,既保证了视觉效果,又兼顾了性能表现。理解这些绘制原理对于进一步扩展和优化编辑器功能具有重要意义。 diff --git a/Meta2D引擎作用详解.md b/Meta2D引擎作用详解.md new file mode 100644 index 0000000..a68e4c2 --- /dev/null +++ b/Meta2D引擎作用详解.md @@ -0,0 +1,600 @@ +# Meta2D引擎作用详解 + +## 🤔 您的疑问:Meta2D到底做了什么? + +您的观察很准确!确实,具体的绘制操作都是通过HTML5 Canvas原生API实现的。那么Meta2D引擎到底在做什么呢? + +**简单类比**:如果说Canvas API是"画笔和颜料",那么Meta2D就是"画师的大脑和手" - 它决定什么时候画、画在哪里、画什么样式,以及如何响应用户的操作。 + +--- + +## 🎯 Meta2D引擎的核心作用 + +Meta2D引擎并不是替代Canvas API,而是在Canvas API之上构建了一个完整的**图形管理和渲染框架**。它的主要作用包括: + +### 1. 🎨 渲染管理系统 + +```typescript +// 我们只需要注册绘制函数 +this.registerCanvasDraw({ + point: drawPoint, + line: drawLine, + area: drawArea, + robot: drawRobot, +}); + +// Meta2D会自动调用这些函数 +``` + +**Meta2D负责**: + +- **何时渲染**:自动检测数据变化,决定何时重绘 +- **渲染顺序**:管理图层顺序,确保正确的绘制层级 +- **性能优化**:只重绘需要更新的部分,避免全量重绘 +- **调用时机**:在正确的时机调用我们的绘制函数 + +### 2. 🎮 图形对象管理 + +```typescript +// 创建一个点位 - 我们只需要定义数据结构 +const pen: MapPen = { + id: 'point1', + name: 'point', + x: 100, + y: 100, + width: 24, + height: 24, + point: { type: MapPointType.普通点 }, +}; + +// Meta2D会管理这个对象的整个生命周期 +await this.addPen(pen, false, true, true); +``` + +**Meta2D负责**: + +- **对象存储**:维护所有图形对象的数据结构 +- **坐标转换**:从逻辑坐标转换为屏幕坐标 +- **状态管理**:追踪每个对象的状态(选中、激活、可见等) +- **生命周期**:管理对象的创建、更新、删除 + +### 3. 🖱️ 事件处理系统 + +```typescript +// 我们只需要监听高级事件 +this.on('click', (e, data) => { + // Meta2D已经处理了鼠标点击的复杂逻辑 + console.log('点击了图形:', data); +}); +``` + +**Meta2D负责**: + +- **事件捕获**:监听原生DOM事件(mousedown、mousemove、mouseup等) +- **坐标转换**:将屏幕坐标转换为画布坐标 +- **碰撞检测**:判断点击了哪个图形对象 +- **事件分发**:将事件分发给正确的处理器 + +### 4. 📐 坐标系统管理 + +```typescript +// 我们在绘制函数中使用的坐标 +const { x, y, width, height } = pen.calculative?.worldRect ?? {}; +``` + +**Meta2D负责**: + +- **坐标计算**:自动计算`worldRect`(世界坐标) +- **缩放处理**:处理画布缩放时的坐标转换 +- **视口管理**:管理可视区域和裁剪 +- **变换矩阵**:处理复杂的坐标变换 + +--- + +## 🔄 Meta2D的工作流程 + +### 1. 初始化阶段 + +```typescript +export class EditorService extends Meta2d { + constructor(container: HTMLDivElement) { + // 1. 创建Meta2D实例,传入配置 + super(container, EDITOR_CONFIG); + + // 2. 注册自定义绘制函数 + this.#register(); + + // 3. 监听事件 + this.on('*', (e, v) => this.#listen(e, v)); + } +} +``` + +### 2. 图形创建阶段 + +```typescript +// 用户调用 +await this.addPoint({ x: 100, y: 100 }, MapPointType.普通点); + +// Meta2D内部流程: +// 1. 创建图形对象数据结构 +// 2. 分配唯一ID +// 3. 计算坐标和尺寸 +// 4. 添加到图形列表 +// 5. 触发重绘 +``` + +### 3. 渲染阶段 + +```typescript +// Meta2D的渲染循环(简化版) +function render() { + // 1. 清空画布 + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // 2. 遍历所有图形对象 + for (const pen of this.store.data.pens) { + // 3. 计算世界坐标 + this.updateWorldRect(pen); + + // 4. 调用对应的绘制函数 + const drawFn = this.canvasDrawMap[pen.name]; // 获取我们注册的绘制函数 + if (drawFn) { + drawFn(ctx, pen); // 调用 drawPoint、drawLine 等 + } + } +} +``` + +### 4. 事件处理阶段 + +```typescript +// 用户点击画布 +canvas.addEventListener('click', (e) => { + // Meta2D内部处理: + // 1. 获取点击坐标 + const point = { x: e.offsetX, y: e.offsetY }; + + // 2. 转换为画布坐标 + const worldPoint = this.screenToWorld(point); + + // 3. 碰撞检测 - 判断点击了哪个图形 + const hitPen = this.hitTest(worldPoint); + + // 4. 触发相应事件 + if (hitPen) { + this.emit('click', e, hitPen); + } +}); +``` + +--- + +## 🎯 Meta2D的核心价值 + +### 1. 抽象层次提升 + +```typescript +// 没有Meta2D,我们需要手动处理: +canvas.addEventListener('mousedown', (e) => { + // 计算点击坐标 + // 检测点击了哪个图形 + // 处理拖拽逻辑 + // 重绘画布 + // ... 大量底层代码 +}); + +// 有了Meta2D,我们只需要: +this.on('mousedown', (e, pen) => { + // 直接处理业务逻辑 + console.log('点击了图形:', pen.id); +}); +``` + +### 2. 数据驱动渲染 + +```typescript +// 数据变化自动触发重绘 +this.setValue({ id: 'point1', x: 200 }); // Meta2D会自动重绘 +``` + +### 3. 复杂交互支持 + +```typescript +// 选择、拖拽、缩放、旋转等复杂交互 +this.active(['point1', 'point2']); // 多选 +this.inactive(); // 取消选择 +this.delete([pen]); // 删除图形 +``` + +### 4. 性能优化 + +- **脏矩形重绘**:只重绘变化的区域 +- **离屏渲染**:复杂图形使用离屏Canvas +- **层级管理**:合理的图层分离 +- **事件优化**:高效的碰撞检测算法 + +--- + +## 🏗️ 架构分层对比 + +### 传统Canvas开发 + +``` +┌─────────────────────┐ +│ 业务逻辑层 │ +├─────────────────────┤ +│ 手动管理层 │ ← 需要自己实现 +│ (对象管理/事件/渲染) │ +├─────────────────────┤ +│ Canvas 2D API │ +├─────────────────────┤ +│ 浏览器引擎 │ +└─────────────────────┘ +``` + +### 使用Meta2D + +``` +┌─────────────────────┐ +│ 业务逻辑层 │ ← 我们专注于这里 +├─────────────────────┤ +│ Meta2D 引擎 │ ← 引擎处理复杂逻辑 +├─────────────────────┤ +│ Canvas 2D API │ ← 底层绘制API +├─────────────────────┤ +│ 浏览器引擎 │ +└─────────────────────┘ +``` + +--- + +## 🎨 实际代码示例 + +### 没有Meta2D的代码(复杂) + +```typescript +class ManualCanvas { + private pens: MapPen[] = []; + private selectedPens: MapPen[] = []; + + constructor(private canvas: HTMLCanvasElement) { + this.canvas.addEventListener('click', this.onClick.bind(this)); + this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this)); + // ... 更多事件监听 + } + + onClick(e: MouseEvent) { + const rect = this.canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // 手动碰撞检测 + for (const pen of this.pens) { + if (this.isPointInPen(x, y, pen)) { + this.selectPen(pen); + this.render(); // 手动重绘 + break; + } + } + } + + render() { + const ctx = this.canvas.getContext('2d')!; + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // 手动绘制每个图形 + for (const pen of this.pens) { + this.drawPen(ctx, pen); + } + } + + // ... 大量的手动管理代码 +} +``` + +### 使用Meta2D的代码(简洁) + +```typescript +class EditorService extends Meta2d { + constructor(container: HTMLDivElement) { + super(container, EDITOR_CONFIG); + + // 注册绘制函数 + this.registerCanvasDraw({ point: drawPoint }); + + // 监听事件 + this.on('click', (e, pen) => { + // 直接处理业务逻辑 + this.handlePenClick(pen); + }); + } + + async addPoint(p: Point, type: MapPointType) { + const pen: MapPen = { + // ... 定义数据结构 + }; + + // Meta2D自动处理渲染 + await this.addPen(pen); + } +} +``` + +--- + +## 🎯 总结 + +Meta2D引擎的作用就像是一个**智能管家**: + +1. **您专注于业务**:定义数据结构和绘制逻辑 +2. **引擎处理细节**:坐标转换、事件处理、渲染优化 +3. **原生API执行**:最终通过Canvas API完成绘制 + +这种分工让您可以: + +- 🎯 **专注业务逻辑**:不需要处理复杂的底层细节 +- 🚀 **提高开发效率**:大量重复的工作由引擎完成 +- 🎨 **获得更好的性能**:引擎内置了各种优化策略 +- 🔧 **更容易维护**:清晰的架构分层 + +**Meta2D = 图形管理框架 + 事件处理系统 + 渲染优化引擎** + +它不是替代Canvas API,而是在Canvas API之上构建了一个完整的企业级图形编辑解决方案! + +--- + +## 📱 项目中的实际应用 + +### 1. 响应式数据流集成 + +```typescript +export class EditorService extends Meta2d { + // Meta2D处理底层变化,我们用RxJS处理业务逻辑 + readonly #change$$ = new Subject(); + + public readonly current = useObservable( + this.#change$$.pipe( + debounceTime(100), + map(() => clone(this.store.active?.[0])), + ), + ); + + // Meta2D的事件 → RxJS流 → Vue响应式数据 + #listen(e: unknown, v: any) { + switch (e) { + case 'add': + case 'delete': + case 'update': + this.#change$$.next(true); // 通知数据变化 + break; + } + } +} +``` + +### 2. 复杂业务逻辑简化 + +```typescript +// 创建区域时的智能关联 +public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string) { + // Meta2D自动处理选中状态 + const selected = this.store.active; + + // 根据区域类型自动关联相关元素 + switch (type) { + case MapAreaType.库区: + selected?.filter(({ point }) => point?.type === MapPointType.动作点) + .forEach(({ id }) => points.push(id!)); + break; + case MapAreaType.互斥区: + selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!)); + selected?.filter(({ route }) => route?.type).forEach(({ id }) => routes.push(id!)); + break; + } + + // Meta2D自动处理图形创建和渲染 + const area = await this.addPen(pen, true, true, true); + this.bottom(area); // 自动层级管理 +} +``` + +### 3. 主题系统集成 + +```typescript +// 监听主题变化,Meta2D自动重绘 +watch( + () => sTheme.theme, + (theme) => { + this.setTheme(theme); // Meta2D内置主题系统 + + // 重新应用主题到自定义绘制 + this.find('point').forEach((pen) => { + if (pen.point?.type >= 10) { + this.canvas.updateValue(pen, this.#mapPointImage(pen.point.type)); + } + }); + + this.render(); // 触发重绘 + }, + { immediate: true }, +); +``` + +### 4. 实时数据更新 + +```typescript +// 机器人实时位置更新 +public refreshRobot(id: RobotInfo['id'], info: Partial): void { + const { x: cx = 37, y: cy = 37, active, angle, path: points } = info; + + // Meta2D自动处理坐标转换和重绘 + this.setValue({ + id, + x: cx - 37, + y: cy - 37, + rotate: angle, + robot: { ...robot, active, path }, + visible: true + }, { render: true, history: false, doEvent: false }); +} +``` + +### 5. 事件系统的实际使用 + +```typescript +constructor(container: HTMLDivElement) { + super(container, EDITOR_CONFIG); + + // Meta2D统一事件处理 + this.on('*', (e, v) => this.#listen(e, v)); + + // 具体事件映射到业务逻辑 + #listen(e: unknown, v: any) { + switch (e) { + case 'click': + case 'mousedown': + case 'mouseup': + // 转换为响应式数据流 + this.#mouse$$.next({ type: e, value: pick(v, 'x', 'y') }); + break; + case 'active': + case 'inactive': + // 选中状态变化 + this.#change$$.next(false); + break; + } + } +} +``` + +--- + +## 🎯 如果没有Meta2D会怎样? + +假设我们要自己实现同样的功能,需要处理的复杂度: + +### 1. 坐标系统管理 + +```typescript +// 需要手动处理缩放、平移、坐标转换 +class CoordinateSystem { + private scale = 1; + private offsetX = 0; + private offsetY = 0; + + screenToWorld(screenPoint: Point): Point { + return { + x: (screenPoint.x - this.offsetX) / this.scale, + y: (screenPoint.y - this.offsetY) / this.scale, + }; + } + + worldToScreen(worldPoint: Point): Point { + return { + x: worldPoint.x * this.scale + this.offsetX, + y: worldPoint.y * this.scale + this.offsetY, + }; + } + + // 还需要处理矩阵变换、旋转等复杂情况... +} +``` + +### 2. 碰撞检测系统 + +```typescript +// 需要为每种图形实现碰撞检测 +class HitTest { + hitTestPoint(point: Point, pen: MapPen): boolean { + const rect = this.getPenRect(pen); + return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; + } + + hitTestLine(point: Point, pen: MapPen): boolean { + // 需要实现点到线段的距离计算 + // 还要考虑贝塞尔曲线的复杂情况 + } + + hitTestArea(point: Point, pen: MapPen): boolean { + // 矩形碰撞检测 + } + + // 还需要处理旋转、缩放后的碰撞检测... +} +``` + +### 3. 渲染管理系统 + +```typescript +// 需要手动管理渲染队列和优化 +class RenderManager { + private dirtyRects: Rect[] = []; + private renderQueue: MapPen[] = []; + + markDirty(pen: MapPen) { + this.dirtyRects.push(this.getPenRect(pen)); + this.renderQueue.push(pen); + } + + render() { + // 计算需要重绘的区域 + const mergedRect = this.mergeDirtyRects(); + + // 清空脏区域 + this.ctx.clearRect(mergedRect.x, mergedRect.y, mergedRect.width, mergedRect.height); + + // 重绘相关图形 + for (const pen of this.getIntersectingPens(mergedRect)) { + this.drawPen(pen); + } + } + + // 大量的优化逻辑... +} +``` + +**这些复杂的底层逻辑,Meta2D都已经帮我们处理好了!** + +--- + +## 🚀 Meta2D带来的开发体验提升 + +### 开发效率对比 + +| 功能 | 纯Canvas开发 | 使用Meta2D | +| -------------------- | ------------ | ----------------------------- | +| 创建一个可点击的图形 | ~100行代码 | ~10行代码 | +| 实现拖拽功能 | ~200行代码 | 内置支持 | +| 多选和批量操作 | ~300行代码 | `this.active([...])` | +| 撤销重做 | ~500行代码 | 内置支持 | +| 图形层级管理 | ~100行代码 | `this.top()`, `this.bottom()` | +| 响应式数据绑定 | 需要自己实现 | 内置事件系统 | + +### 维护成本对比 + +| 场景 | 纯Canvas | Meta2D | +| -------------- | ---------------- | ------------------ | +| 添加新图形类型 | 修改多个系统 | 添加绘制函数即可 | +| 性能优化 | 需要深度优化 | 引擎已优化 | +| Bug修复 | 涉及多个底层模块 | 通常只涉及业务逻辑 | +| 功能扩展 | 可能需要重构架构 | 基于现有API扩展 | + +--- + +## 🏆 总结 + +Meta2D引擎就像是为Canvas开发者提供的一个**超级工具箱**: + +- 🎨 **您负责创意**:定义什么样的图形、什么样的交互 +- 🔧 **Meta2D负责实现**:处理所有复杂的底层逻辑 +- 🚀 **Canvas负责绘制**:最终的像素级渲染 + +这种架构让我们的场景编辑器项目能够: + +- ✅ 快速开发复杂的图形编辑功能 +- ✅ 获得企业级的性能和稳定性 +- ✅ 专注于业务逻辑而不是底层实现 +- ✅ 轻松维护和扩展功能 + +**Meta2D不是画笔,而是整个画室的管理系统!**