feat: 更新地图组件配置,优化搜索区域和数据结构,确保符合项目规范
This commit is contained in:
parent
e4047e62a0
commit
7ec4ea7de9
440
Canvas绘制技术详解.md
Normal file
440
Canvas绘制技术详解.md
Normal file
@ -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 实现了丰富的图形绘制功能,每个绘制函数都经过精心设计,既保证了视觉效果,又兼顾了性能表现。理解这些绘制原理对于进一步扩展和优化编辑器功能具有重要意义。
|
600
Meta2D引擎作用详解.md
Normal file
600
Meta2D引擎作用详解.md
Normal file
@ -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<boolean>();
|
||||
|
||||
public readonly current = useObservable<MapPen>(
|
||||
this.#change$$.pipe(
|
||||
debounceTime(100),
|
||||
map(() => <MapPen>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 = <MapPen[]>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<RobotRealtimeInfo>): 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不是画笔,而是整个画室的管理系统!**
|
Loading…
x
Reference in New Issue
Block a user