react-babylon-map 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +78 -0
- package/demo.html +161 -0
- package/dist/cjs/main.js +520 -0
- package/dist/es/main.mjs +20 -0
- package/dist/es/main.mjs.map +1 -0
- package/dist/es/main10.mjs +33 -0
- package/dist/es/main10.mjs.map +1 -0
- package/dist/es/main11.mjs +12 -0
- package/dist/es/main11.mjs.map +1 -0
- package/dist/es/main12.mjs +14 -0
- package/dist/es/main12.mjs.map +1 -0
- package/dist/es/main13.mjs +12 -0
- package/dist/es/main13.mjs.map +1 -0
- package/dist/es/main14.mjs +5 -0
- package/dist/es/main14.mjs.map +1 -0
- package/dist/es/main15.mjs +12 -0
- package/dist/es/main15.mjs.map +1 -0
- package/dist/es/main16.mjs +25 -0
- package/dist/es/main16.mjs.map +1 -0
- package/dist/es/main17.mjs +54 -0
- package/dist/es/main17.mjs.map +1 -0
- package/dist/es/main18.mjs +88 -0
- package/dist/es/main18.mjs.map +1 -0
- package/dist/es/main19.mjs +18 -0
- package/dist/es/main19.mjs.map +1 -0
- package/dist/es/main2.mjs +9 -0
- package/dist/es/main2.mjs.map +1 -0
- package/dist/es/main20.mjs +21 -0
- package/dist/es/main20.mjs.map +1 -0
- package/dist/es/main21.mjs +61 -0
- package/dist/es/main21.mjs.map +1 -0
- package/dist/es/main3.mjs +46 -0
- package/dist/es/main3.mjs.map +1 -0
- package/dist/es/main4.mjs +23 -0
- package/dist/es/main4.mjs.map +1 -0
- package/dist/es/main5.mjs +69 -0
- package/dist/es/main5.mjs.map +1 -0
- package/dist/es/main6.mjs +35 -0
- package/dist/es/main6.mjs.map +1 -0
- package/dist/es/main7.mjs +65 -0
- package/dist/es/main7.mjs.map +1 -0
- package/dist/es/main8.mjs +14 -0
- package/dist/es/main8.mjs.map +1 -0
- package/dist/es/main9.mjs +26 -0
- package/dist/es/main9.mjs.map +1 -0
- package/dist/maplibre/cjs/main.js +520 -0
- package/dist/maplibre/es/main.mjs +20 -0
- package/dist/maplibre/es/main.mjs.map +1 -0
- package/dist/maplibre/es/main10.mjs +33 -0
- package/dist/maplibre/es/main10.mjs.map +1 -0
- package/dist/maplibre/es/main11.mjs +12 -0
- package/dist/maplibre/es/main11.mjs.map +1 -0
- package/dist/maplibre/es/main12.mjs +14 -0
- package/dist/maplibre/es/main12.mjs.map +1 -0
- package/dist/maplibre/es/main13.mjs +12 -0
- package/dist/maplibre/es/main13.mjs.map +1 -0
- package/dist/maplibre/es/main14.mjs +5 -0
- package/dist/maplibre/es/main14.mjs.map +1 -0
- package/dist/maplibre/es/main15.mjs +12 -0
- package/dist/maplibre/es/main15.mjs.map +1 -0
- package/dist/maplibre/es/main16.mjs +25 -0
- package/dist/maplibre/es/main16.mjs.map +1 -0
- package/dist/maplibre/es/main17.mjs +54 -0
- package/dist/maplibre/es/main17.mjs.map +1 -0
- package/dist/maplibre/es/main18.mjs +88 -0
- package/dist/maplibre/es/main18.mjs.map +1 -0
- package/dist/maplibre/es/main19.mjs +18 -0
- package/dist/maplibre/es/main19.mjs.map +1 -0
- package/dist/maplibre/es/main2.mjs +9 -0
- package/dist/maplibre/es/main2.mjs.map +1 -0
- package/dist/maplibre/es/main20.mjs +61 -0
- package/dist/maplibre/es/main20.mjs.map +1 -0
- package/dist/maplibre/es/main21.mjs +21 -0
- package/dist/maplibre/es/main21.mjs.map +1 -0
- package/dist/maplibre/es/main3.mjs +46 -0
- package/dist/maplibre/es/main3.mjs.map +1 -0
- package/dist/maplibre/es/main4.mjs +23 -0
- package/dist/maplibre/es/main4.mjs.map +1 -0
- package/dist/maplibre/es/main5.mjs +69 -0
- package/dist/maplibre/es/main5.mjs.map +1 -0
- package/dist/maplibre/es/main6.mjs +35 -0
- package/dist/maplibre/es/main6.mjs.map +1 -0
- package/dist/maplibre/es/main7.mjs +65 -0
- package/dist/maplibre/es/main7.mjs.map +1 -0
- package/dist/maplibre/es/main8.mjs +14 -0
- package/dist/maplibre/es/main8.mjs.map +1 -0
- package/dist/maplibre/es/main9.mjs +26 -0
- package/dist/maplibre/es/main9.mjs.map +1 -0
- package/dist/maplibre/types/api/canvas-props.d.ts +9 -0
- package/dist/maplibre/types/api/coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/maplibre/types/api/coords.d.ts +5 -0
- package/dist/maplibre/types/api/index.d.ts +7 -0
- package/dist/maplibre/types/api/near-coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/use-map.d.ts +3 -0
- package/dist/maplibre/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/maplibre/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/maplibre/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/maplibre/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/maplibre/types/core/earth-radius.d.ts +1 -0
- package/dist/maplibre/types/core/generic-map.d.ts +49 -0
- package/dist/maplibre/types/core/matrix-utils.d.ts +7 -0
- package/dist/maplibre/types/core/sync-camera.d.ts +7 -0
- package/dist/maplibre/types/core/use-babylon-map.d.ts +32 -0
- package/dist/maplibre/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/maplibre/types/core/use-coords.d.ts +5 -0
- package/dist/maplibre/types/core/use-function.d.ts +1 -0
- package/dist/maplibre/types/maplibre/canvas.d.ts +4 -0
- package/dist/maplibre/types/maplibre.index.d.ts +4 -0
- package/dist/types/api/canvas-props.d.ts +9 -0
- package/dist/types/api/coordinates.d.ts +13 -0
- package/dist/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/types/api/coords.d.ts +5 -0
- package/dist/types/api/index.d.ts +7 -0
- package/dist/types/api/near-coordinates.d.ts +13 -0
- package/dist/types/api/use-map.d.ts +3 -0
- package/dist/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/types/core/earth-radius.d.ts +1 -0
- package/dist/types/core/generic-map.d.ts +49 -0
- package/dist/types/core/matrix-utils.d.ts +7 -0
- package/dist/types/core/sync-camera.d.ts +7 -0
- package/dist/types/core/use-babylon-map.d.ts +32 -0
- package/dist/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/types/core/use-coords.d.ts +5 -0
- package/dist/types/core/use-function.d.ts +1 -0
- package/dist/types/mapbox/canvas.d.ts +4 -0
- package/dist/types/mapbox.index.d.ts +4 -0
- package/package.json +58 -0
- package/plan.md +719 -0
- package/src/api/canvas-props.ts +10 -0
- package/src/api/coordinates.tsx +83 -0
- package/src/api/coords-to-vector-3.ts +39 -0
- package/src/api/coords.tsx +6 -0
- package/src/api/index.ts +7 -0
- package/src/api/near-coordinates.tsx +87 -0
- package/src/api/use-map.ts +8 -0
- package/src/api/vector-3-to-coords.ts +13 -0
- package/src/core/canvas-in-layer/use-canvas-in-layer.tsx +27 -0
- package/src/core/canvas-in-layer/use-render.ts +43 -0
- package/src/core/canvas-in-layer/use-root.tsx +82 -0
- package/src/core/canvas-overlay/canvas-portal.tsx +98 -0
- package/src/core/canvas-overlay/init-canvas-fc.tsx +45 -0
- package/src/core/canvas-overlay/render.tsx +1 -0
- package/src/core/canvas-overlay/sync-camera-fc.tsx +83 -0
- package/src/core/coords-to-matrix.ts +21 -0
- package/src/core/earth-radius.ts +1 -0
- package/src/core/events.ts +55 -0
- package/src/core/generic-map.ts +59 -0
- package/src/core/map-engine.tsx +70 -0
- package/src/core/matrix-utils.ts +22 -0
- package/src/core/sync-camera.ts +29 -0
- package/src/core/use-babylon-map.ts +46 -0
- package/src/core/use-coords-to-matrix.ts +13 -0
- package/src/core/use-coords.tsx +22 -0
- package/src/core/use-function.ts +10 -0
- package/src/mapbox/canvas.tsx +59 -0
- package/src/mapbox.index.ts +7 -0
- package/src/maplibre/canvas.tsx +59 -0
- package/src/maplibre.index.ts +7 -0
- package/src/vite-env.d.ts +1 -0
- package/stories/.ladle/components.tsx +50 -0
- package/stories/.ladle/style.css +63 -0
- package/stories/package.json +31 -0
- package/stories/pnpm-lock.yaml +5450 -0
- package/stories/sandbox.config.json +3 -0
- package/stories/src/adaptive-dpr.tsx +34 -0
- package/stories/src/billboard.stories.tsx +111 -0
- package/stories/src/buildings-3d.stories.tsx +280 -0
- package/stories/src/canvas/mapbox.stories.tsx +113 -0
- package/stories/src/canvas/maplibre.stories.tsx +93 -0
- package/stories/src/comparison.stories.tsx +161 -0
- package/stories/src/extrude/chaillot.ts +8 -0
- package/stories/src/exude-coordinates.stories.tsx +139 -0
- package/stories/src/free-3d-buildings/get-buildings-data.ts +49 -0
- package/stories/src/html-on-top.stories.tsx +156 -0
- package/stories/src/ifc/ifc-to-babylon.ts +97 -0
- package/stories/src/ifc/ifc.main.ts +904 -0
- package/stories/src/ifc/ifc2bb.ts +343 -0
- package/stories/src/ifc/model.ifc +14155 -0
- package/stories/src/ifc.stories.tsx +276 -0
- package/stories/src/mapbox/story-mapbox.tsx +97 -0
- package/stories/src/maplibre/story-maplibre.tsx +36 -0
- package/stories/src/multi-coordinates.stories.tsx +115 -0
- package/stories/src/pivot-controls.stories.tsx +148 -0
- package/stories/src/postprocessing.stories.tsx +125 -0
- package/stories/src/render-on-demand.stories.tsx +76 -0
- package/stories/src/story-map.tsx +44 -0
- package/stories/src/sunlight.stories.tsx +215 -0
- package/stories/src/vite-env.d.ts +1 -0
- package/stories/tsconfig.json +32 -0
- package/stories/tsconfig.node.json +10 -0
- package/stories/vite.config.ts +27 -0
- package/tsconfig.json +31 -0
- package/tsconfig.mapbox.json +7 -0
- package/tsconfig.maplibre.json +7 -0
- package/tsconfig.node.json +10 -0
- package/tsconfig.types.json +25 -0
- package/vite.config.ts +65 -0
package/plan.md
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: react-babylon-map
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
任务目标:基于 react-three-map 代码迁移基于react-babylon的react-babylon-map, 不改变底层对maplibregl, react-map-gl的依赖
|
|
6
|
+
|
|
7
|
+
文件目录:
|
|
8
|
+
|
|
9
|
+
react-three-map: C:\Users\neychang\workspace\react-three-map
|
|
10
|
+
|
|
11
|
+
react-babylon-map: C:\Users\neychang\workspace\react-babylon-map
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# react-babylon-map: 基于 react-babylonjs 替换 Three.js 的迁移工程规划
|
|
15
|
+
|
|
16
|
+
> 使用 react-babylonjs 替换 react-three-map 中的 Three.js/R3F,创建 `react-babylon-map`。
|
|
17
|
+
|
|
18
|
+
## 1. 项目概述
|
|
19
|
+
|
|
20
|
+
### 1.1 当前架构
|
|
21
|
+
|
|
22
|
+
`react-three-map` 将 **React Three Fiber (R3F)** 与 **MapLibre GL JS / Mapbox GL JS** 桥接,在地图上渲染声明式 3D 内容。核心设计:
|
|
23
|
+
|
|
24
|
+
- **双渲染模式**: `overlay={true}`(覆盖画布)和 `overlay={false}`(共享 WebGL 上下文的自定义图层)
|
|
25
|
+
- **相机同步**: MapLibre 的 view-projection 矩阵每帧驱动 Three.js 相机
|
|
26
|
+
- **Provider 抽象**: 泛型接口隐藏 Mapbox/MapLibre 差异
|
|
27
|
+
- **双构建目标**: 同一代码库输出 MapLibre 和 Mapbox 版本
|
|
28
|
+
|
|
29
|
+
### 1.2 react-babylonjs 能力评估
|
|
30
|
+
|
|
31
|
+
react-babylonjs 是基于 `react-reconciler` 的自定义 React 渲染器,将 JSX 映射到 Babylon.js 对象。
|
|
32
|
+
|
|
33
|
+
**可用能力:**
|
|
34
|
+
|
|
35
|
+
| 能力 | API | 说明 |
|
|
36
|
+
|------|-----|------|
|
|
37
|
+
| 声明式场景图 | `<Engine>`, `<Scene>`, `<box>`, `<freeCamera>` 等 | 自动生成 JSX 组件映射 |
|
|
38
|
+
| 帧级拦截 | `useBeforeRender(cb)`, `useAfterRender(cb)` | 订阅 `scene.onBeforeRenderObservable` |
|
|
39
|
+
| 场景/引擎访问 | `useScene()`, `useEngine()`, `useCanvas()` | 从 React context 获取实例 |
|
|
40
|
+
| 场景回调 | `<Scene onCreated={...} onSceneMount={...}>` | 场景初始化和挂载回调 |
|
|
41
|
+
| 指针拾取 | `<Scene onMeshPicked={...} onScenePointerDown={...}>` | 基于 Babylon 内置 picking |
|
|
42
|
+
| 相机创建 | `useCamera(fn, autoAttach?)` | 可通过 `autoAttach=false` 禁止输入绑定 |
|
|
43
|
+
| 多场景 | 多个 `<Scene>` 嵌套在 `<Engine>` 下 | 共享引擎,独立场景 |
|
|
44
|
+
| Portal | `createPortal(children, target)` | 渲染子树到不同 Babylon 容器 |
|
|
45
|
+
|
|
46
|
+
**需扩展的能力:**
|
|
47
|
+
|
|
48
|
+
| 限制 | 影响 | 解决方案 |
|
|
49
|
+
|------|------|----------|
|
|
50
|
+
| `<Engine>` 总创建自己的 `<canvas>` | 无法直接使用 MapLibre 的画布/GL 上下文 | 扩展 Engine 组件,增加 `externalContext` prop |
|
|
51
|
+
| 渲染循环不可外部控制 | 无 `frameloop: 'never'` 等价物 | 调用 `engine.stopRenderLoop()` + 手动 `scene.render()` |
|
|
52
|
+
| 相机 JSX 自动 `attachControl()` | 与 MapLibre 输入处理冲突 | `onCreated` 中立即 `detachControl()` 或用 `useCamera(fn, false)` |
|
|
53
|
+
| 无手动帧推进 API | 无 `advance()` 等价物 | 通过 `useEngine()` 获取引擎后手动管理渲染 |
|
|
54
|
+
|
|
55
|
+
### 1.3 Three.js 集成点分析
|
|
56
|
+
|
|
57
|
+
| 类别 | 当前 Three.js 用法 | 涉及文件 |
|
|
58
|
+
|------|-------------------|----------|
|
|
59
|
+
| 数学原语 | `Matrix4`, `Vector3`, `Quaternion`, `Euler` | `sync-camera.ts`, `coords-to-matrix.ts`, `events.ts` |
|
|
60
|
+
| 相机 | `PerspectiveCamera`, `projectionMatrix` | `sync-camera.ts`, `sync-camera-fc.tsx`, `coordinates.tsx` |
|
|
61
|
+
| 场景 | `Scene`, `Object3D`, `createPortal` | `coordinates.tsx`, `canvas-portal.tsx` |
|
|
62
|
+
| R3F 集成 | `Canvas`, `useFrame`, `useThree`, `createRoot`, `_roots`, `extend`, `advance` | 所有核心文件 |
|
|
63
|
+
| WebGL 上下文 | `gl: { context, autoClear }`, `gl.resetState()` | `use-root.tsx`, `use-render.ts` |
|
|
64
|
+
| 事件/射线 | 自定义事件系统 + `Raycaster` | `events.ts` |
|
|
65
|
+
|
|
66
|
+
## 2. 技术方案
|
|
67
|
+
|
|
68
|
+
### 2.1 架构决策:扩展 react-babylonjs
|
|
69
|
+
|
|
70
|
+
**决策**: 以 react-babylonjs 为基础,扩展其 Engine 组件以支持外部 WebGL 上下文和渲染控制。
|
|
71
|
+
|
|
72
|
+
**具体策略**:
|
|
73
|
+
|
|
74
|
+
1. **Fork react-babylonjs 的 Engine 组件**,创建 `MapEngine` 组件,支持:
|
|
75
|
+
- `externalContext?: WebGLRenderingContext` -- 接受 MapLibre 的 GL 上下文
|
|
76
|
+
- `renderMode?: 'auto' | 'manual'` -- 手动模式下不启动渲染循环
|
|
77
|
+
2. **保留 react-babylonjs 的 reconciler 和 JSX 组件系统**,用于场景图管理
|
|
78
|
+
3. **使用 react-babylonjs 的 hooks**(`useBeforeRender`, `useScene`, `useEngine`)进行相机同步和帧级控制
|
|
79
|
+
4. **使用 Babylon.js 原生 API** 仅处理 MapLibre 特有的矩阵转换和相机覆盖
|
|
80
|
+
|
|
81
|
+
**架构对比**:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
当前 react-three-map:
|
|
85
|
+
R3F createRoot(canvas) → configure({ gl: { context }, frameloop: 'never' })
|
|
86
|
+
→ useFrame() 同步相机
|
|
87
|
+
→ advance() 手动渲染
|
|
88
|
+
|
|
89
|
+
新 react-babylon-map:
|
|
90
|
+
react-babylonjs <MapEngine externalContext={gl} renderMode="manual">
|
|
91
|
+
→ useBeforeRender() 同步相机
|
|
92
|
+
→ scene.render() 手动渲染
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2.2 react-babylonjs 扩展方案
|
|
96
|
+
|
|
97
|
+
#### MapEngine 组件
|
|
98
|
+
|
|
99
|
+
基于 react-babylonjs 的 `<Engine>` 组件 fork 并扩展:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// src/core/map-engine.tsx
|
|
103
|
+
import { Engine } from '@babylonjs/core';
|
|
104
|
+
import { EngineCanvasContext } from 'react-babylonjs';
|
|
105
|
+
|
|
106
|
+
interface MapEngineProps {
|
|
107
|
+
/** MapLibre 的 WebGL 上下文,用于共享渲染 */
|
|
108
|
+
externalContext?: WebGLRenderingContext;
|
|
109
|
+
/** 渲染模式:auto=自动循环,manual=外部控制 */
|
|
110
|
+
renderMode?: 'auto' | 'manual';
|
|
111
|
+
children: React.ReactNode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const MapEngine: React.FC<MapEngineProps> = ({
|
|
115
|
+
externalContext, renderMode = 'manual', children
|
|
116
|
+
}) => {
|
|
117
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
118
|
+
const engineRef = useRef<Engine>();
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (externalContext) {
|
|
122
|
+
// 使用 MapLibre 的 GL 上下文创建 Babylon Engine
|
|
123
|
+
engineRef.current = new Engine(
|
|
124
|
+
externalContext as WebGL2RenderingContext,
|
|
125
|
+
false, // antialias
|
|
126
|
+
{ preserveDrawingBuffer: true, stencil: true },
|
|
127
|
+
false // adaptToDeviceRatio
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
// overlay 模式:使用独立 canvas
|
|
131
|
+
engineRef.current = new Engine(canvasRef.current!, true);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (renderMode === 'manual') {
|
|
135
|
+
// 不启动自动渲染循环
|
|
136
|
+
// 外部(MapLibre render callback)负责调用 scene.render()
|
|
137
|
+
} else {
|
|
138
|
+
engineRef.current.runRenderLoop(() => {
|
|
139
|
+
engineRef.current!.scenes.forEach(s => s.render());
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return () => { engineRef.current?.dispose(); };
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<EngineCanvasContext.Provider value={engineRef.current}>
|
|
148
|
+
{externalContext ? null : <canvas ref={canvasRef} style={{position:'absolute', top:0, left:0}} />}
|
|
149
|
+
{children}
|
|
150
|
+
</EngineCanvasContext.Provider>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### 渲染控制 Hook
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
// src/core/use-manual-render.ts
|
|
159
|
+
import { useEngine, useScene } from 'react-babylonjs';
|
|
160
|
+
|
|
161
|
+
/** 手动渲染一帧,供 MapLibre 自定义图层的 render() 调用 */
|
|
162
|
+
export function useManualRender() {
|
|
163
|
+
const engine = useEngine();
|
|
164
|
+
const scene = useScene();
|
|
165
|
+
|
|
166
|
+
return useCallback(() => {
|
|
167
|
+
if (!scene || !engine) return;
|
|
168
|
+
// 重置 GL 状态,避免与 MapLibre 冲突
|
|
169
|
+
engine.resetCustomCache?.();
|
|
170
|
+
scene.render();
|
|
171
|
+
}, [engine, scene]);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 2.3 相机同步方案
|
|
176
|
+
|
|
177
|
+
利用 react-babylonjs 的 `useBeforeRender` hook 和 Babylon.js 相机 API:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// src/core/sync-camera-babylon.ts
|
|
181
|
+
import { FreeCamera, Matrix, Vector3, Matrix } from '@babylonjs/core';
|
|
182
|
+
|
|
183
|
+
/** 将 MapLibre 的 view-projection 矩阵应用到 Babylon 相机 */
|
|
184
|
+
export function syncCamera(
|
|
185
|
+
camera: FreeCamera,
|
|
186
|
+
origin: Matrix, // row-major Babylon matrix
|
|
187
|
+
mapViewProjMx: number[] // column-major from MapLibre
|
|
188
|
+
) {
|
|
189
|
+
// 转置:MapLibre 列主序 → Babylon 行主序
|
|
190
|
+
const projView = columnMajorToBabylonMatrix(mapViewProjMx)
|
|
191
|
+
.multiply(origin);
|
|
192
|
+
const projViewInv = projView.clone().invert();
|
|
193
|
+
|
|
194
|
+
// 从逆矩阵提取相机位置
|
|
195
|
+
const position = Vector3.TransformCoordinates(
|
|
196
|
+
Vector3.Zero(), projViewInv
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// 提取前方向
|
|
200
|
+
const forward = Vector3.TransformCoordinates(
|
|
201
|
+
new Vector3(0, 0, 1), projViewInv
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// 提取上方向
|
|
205
|
+
const up = Vector3.TransformCoordinates(
|
|
206
|
+
new Vector3(0, 1, 0), projViewInv
|
|
207
|
+
).subtract(position).normalize();
|
|
208
|
+
|
|
209
|
+
camera.position.copyFrom(position);
|
|
210
|
+
camera.setTarget(forward);
|
|
211
|
+
camera.upVector.copyFrom(up);
|
|
212
|
+
|
|
213
|
+
// 冻结投影矩阵,使用地图的投影
|
|
214
|
+
camera.freezeProjectionMatrix(projView);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// src/core/sync-camera-fc.tsx
|
|
220
|
+
import { useBeforeRender, useScene } from 'react-babylonjs';
|
|
221
|
+
import { FreeCamera } from '@babylonjs/core';
|
|
222
|
+
|
|
223
|
+
/** 在每帧前同步 Babylon 相机与 MapLibre 视图 */
|
|
224
|
+
export const SyncCameraFC: React.FC<SyncCameraFCProps> = ({
|
|
225
|
+
latitude, longitude, altitude, mapViewProjMx
|
|
226
|
+
}) => {
|
|
227
|
+
const scene = useScene();
|
|
228
|
+
const origin = useCoordsToMatrix({ latitude, longitude, altitude });
|
|
229
|
+
|
|
230
|
+
useBeforeRender(() => {
|
|
231
|
+
if (!scene?.activeCamera) return;
|
|
232
|
+
syncCamera(
|
|
233
|
+
scene.activeCamera as FreeCamera,
|
|
234
|
+
origin,
|
|
235
|
+
mapViewProjMx
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2.4 矩阵转换工具
|
|
244
|
+
|
|
245
|
+
MapLibre 提供列主序矩阵(OpenGL/WebGL 约定),Babylon.js 内部存储行主序。
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// src/core/matrix-utils.ts
|
|
249
|
+
import { Matrix } from '@babylonjs/core';
|
|
250
|
+
|
|
251
|
+
/** 列主序 → 行主序转置 */
|
|
252
|
+
export function transposeMatrix(m: number[]): number[] {
|
|
253
|
+
return [
|
|
254
|
+
m[0], m[4], m[8], m[12],
|
|
255
|
+
m[1], m[5], m[9], m[13],
|
|
256
|
+
m[2], m[6], m[10], m[14],
|
|
257
|
+
m[3], m[7], m[11], m[15],
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** MapLibre 列主序矩阵 → Babylon.js Matrix(行主序) */
|
|
262
|
+
export function columnMajorToBabylonMatrix(m: number[]): Matrix {
|
|
263
|
+
return Matrix.FromArray(transposeMatrix(m));
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 2.5 两种渲染模式的实现
|
|
268
|
+
|
|
269
|
+
#### Canvas-in-Layer 模式 (`overlay={false}`)
|
|
270
|
+
|
|
271
|
+
共享 MapLibre 的 WebGL 上下文,通过自定义图层渲染:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// src/core/canvas-in-layer/use-canvas-in-layer.tsx
|
|
275
|
+
export function useCanvasInLayer(props: CanvasProps, fromLngLat: FromLngLat, map: MapInstance) {
|
|
276
|
+
const { latitude, longitude, altitude, frameloop } = props;
|
|
277
|
+
const origin = useCoordsToMatrix({ latitude, longitude, altitude, fromLngLat });
|
|
278
|
+
|
|
279
|
+
// MapLibre 自定义图层接口
|
|
280
|
+
const layerProps = {
|
|
281
|
+
id: props.id,
|
|
282
|
+
beforeId: props.beforeId,
|
|
283
|
+
type: 'custom',
|
|
284
|
+
renderingMode: '3d',
|
|
285
|
+
onAdd: (map: MapInstance, gl: WebGLRenderingContext) => {
|
|
286
|
+
// 创建 Babylon Engine(使用 MapLibre 的 GL 上下文)
|
|
287
|
+
engine = new Engine(gl as WebGL2RenderingContext, false, {
|
|
288
|
+
preserveDrawingBuffer: true, stencil: true
|
|
289
|
+
}, false);
|
|
290
|
+
scene = new Scene(engine);
|
|
291
|
+
scene.useRightHandedSystem = true;
|
|
292
|
+
scene.autoClear = false;
|
|
293
|
+
scene.clearColor = new Color4(0, 0, 0, 0);
|
|
294
|
+
|
|
295
|
+
camera = new FreeCamera("cam", Vector3.Zero(), scene);
|
|
296
|
+
// 禁止相机输入控制——地图拥有输入
|
|
297
|
+
camera.inputs.clear();
|
|
298
|
+
},
|
|
299
|
+
render: (gl: WebGLRenderingContext, matrix: number[]) => {
|
|
300
|
+
syncCamera(camera, origin, matrix);
|
|
301
|
+
scene.render();
|
|
302
|
+
if (frameloop !== 'demand') map.triggerRepaint();
|
|
303
|
+
},
|
|
304
|
+
onRemove: () => {
|
|
305
|
+
scene.dispose();
|
|
306
|
+
engine.dispose();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
return layerProps;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Canvas-Overlay 模式 (`overlay={true}`)
|
|
315
|
+
|
|
316
|
+
独立 canvas 覆盖在地图上方:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// src/core/canvas-overlay/canvas-portal.tsx
|
|
320
|
+
import { MapEngine } from '../map-engine';
|
|
321
|
+
import { Scene as BabylonScene } from 'react-babylonjs';
|
|
322
|
+
|
|
323
|
+
export const CanvasPortal: React.FC<CanvasPortalProps> = ({
|
|
324
|
+
latitude, longitude, altitude, children
|
|
325
|
+
}) => {
|
|
326
|
+
return (
|
|
327
|
+
<div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
|
|
328
|
+
<MapEngine renderMode="manual">
|
|
329
|
+
<BabylonScene
|
|
330
|
+
onCreated={(scene) => {
|
|
331
|
+
scene.useRightHandedSystem = true;
|
|
332
|
+
scene.autoClear = false;
|
|
333
|
+
scene.clearColor = new Color4(0, 0, 0, 0);
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
<freeCamera
|
|
337
|
+
name="cam"
|
|
338
|
+
position={Vector3.Zero()}
|
|
339
|
+
onCreated={(cam) => {
|
|
340
|
+
cam.inputs.clear(); // 禁用输入
|
|
341
|
+
}}
|
|
342
|
+
/>
|
|
343
|
+
<SyncCameraFC latitude={latitude} longitude={longitude} altitude={altitude} />
|
|
344
|
+
{children}
|
|
345
|
+
</BabylonScene>
|
|
346
|
+
</MapEngine>
|
|
347
|
+
</div>
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## 3. 文件迁移映射
|
|
353
|
+
|
|
354
|
+
### 3.1 核心文件
|
|
355
|
+
|
|
356
|
+
| 源文件 | 操作 | react-babylonjs 替代方案 |
|
|
357
|
+
|--------|------|------------------------|
|
|
358
|
+
| `src/core/generic-map.ts` | **保留** | 无 Three.js 依赖 |
|
|
359
|
+
| `src/core/earth-radius.ts` | **保留** | 纯常量 |
|
|
360
|
+
| `src/core/use-function.ts` | **保留** | 纯 React 工具 |
|
|
361
|
+
| `src/core/coords-to-matrix.ts` | **重写** | `BABYLON.Matrix.Compose()` 替代 `Matrix4.compose()` |
|
|
362
|
+
| `src/core/sync-camera.ts` | **重写** | `FreeCamera` + `freezeProjectionMatrix()` 替代 `PerspectiveCamera` |
|
|
363
|
+
| `src/core/events.ts` | **重写** | `scene.onPointerObservable` + `scene.pick()` 替代 `Raycaster` |
|
|
364
|
+
| `src/core/use-r3m.ts` | **重写** | 使用 `useScene()`/`useEngine()` 替代 `useThree()`;保留 R3M 状态管理逻辑 |
|
|
365
|
+
| `src/core/use-coords.tsx` | **重写** | 使用 `useScene()` 获取场景上下文 |
|
|
366
|
+
| `src/core/use-coords-to-matrix.ts` | **重写** | 适配 Babylon 矩阵类型 |
|
|
367
|
+
| `src/core/canvas-overlay/render.tsx` | **适配** | Render 类型签名变更 |
|
|
368
|
+
|
|
369
|
+
### 3.2 新增文件
|
|
370
|
+
|
|
371
|
+
| 文件 | 用途 |
|
|
372
|
+
|------|------|
|
|
373
|
+
| `src/core/map-engine.tsx` | 扩展 react-babylonjs Engine,支持外部 GL 上下文和手动渲染模式 |
|
|
374
|
+
| `src/core/matrix-utils.ts` | 列主序↔行主序矩阵转换 |
|
|
375
|
+
| `src/core/use-manual-render.ts` | 手动帧渲染 hook |
|
|
376
|
+
|
|
377
|
+
### 3.3 Canvas-in-Layer 文件
|
|
378
|
+
|
|
379
|
+
| 源文件 | 操作 | react-babylonjs 替代方案 |
|
|
380
|
+
|--------|------|------------------------|
|
|
381
|
+
| `use-root.tsx` | **重写** | `new Engine(externalGL)` 替代 `createRoot(canvas).configure({gl:{context}})` |
|
|
382
|
+
| `use-render.ts` | **重写** | `scene.render()` + `engine.resetCustomCache()` 替代 `advance()` + `gl.resetState()` |
|
|
383
|
+
| `use-canvas-in-layer.tsx` | **重写** | 自定义图层内创建 Babylon Engine/Scene |
|
|
384
|
+
|
|
385
|
+
### 3.4 Canvas-Overlay 文件
|
|
386
|
+
|
|
387
|
+
| 源文件 | 操作 | react-babylonjs 替代方案 |
|
|
388
|
+
|--------|------|------------------------|
|
|
389
|
+
| `canvas-portal.tsx` | **重写** | `<MapEngine>` + `<Scene>` 替代 R3F `<Canvas>` |
|
|
390
|
+
| `sync-camera-fc.tsx` | **重写** | `useBeforeRender()` 替代 `useFrame()` |
|
|
391
|
+
| `init-r3m.tsx` | **重写** | 使用 `useScene()`/`useEngine()` 初始化上下文 |
|
|
392
|
+
| `init-canvas-fc.tsx` | **重写** | 适配 MapEngine |
|
|
393
|
+
|
|
394
|
+
### 3.5 API 文件
|
|
395
|
+
|
|
396
|
+
| 源文件 | 操作 | 说明 |
|
|
397
|
+
|--------|------|------|
|
|
398
|
+
| `canvas-props.ts` | **重写** | 替换 `RenderProps` 为 Babylon 相关 props |
|
|
399
|
+
| `coords.tsx` | **保留** | 纯接口定义 |
|
|
400
|
+
| `coordinates.tsx` | **重写** | 使用 react-babylonjs `createPortal` + Babylon Scene/Camera |
|
|
401
|
+
| `near-coordinates.tsx` | **重写** | 适配 Babylon 场景坐标 |
|
|
402
|
+
| `coords-to-vector-3.ts` | **重写** | 替换 `three.MathUtils` |
|
|
403
|
+
| `vector-3-to-coords.ts` | **重写** | 同上 |
|
|
404
|
+
| `use-map.ts` | **保留** | 纯 react-map-gl 封装 |
|
|
405
|
+
|
|
406
|
+
### 3.6 入口文件
|
|
407
|
+
|
|
408
|
+
| 源文件 | 操作 | 说明 |
|
|
409
|
+
|--------|------|------|
|
|
410
|
+
| `src/maplibre/canvas.tsx` | **重写** | 移除 `extend(THREE)`,使用 react-babylonjs 组件 |
|
|
411
|
+
| `src/mapbox/canvas.tsx` | **重写** | 同上 |
|
|
412
|
+
| `src/maplibre.index.ts` | **更新导出** | |
|
|
413
|
+
| `src/mapbox.index.ts` | **更新导出** | |
|
|
414
|
+
|
|
415
|
+
## 4. 分阶段计划
|
|
416
|
+
|
|
417
|
+
### Phase 1: 基础设施
|
|
418
|
+
|
|
419
|
+
**目标**: 搭建 Babylon.js + react-babylonjs 基础,矩阵工具,相机同步。
|
|
420
|
+
|
|
421
|
+
1. **依赖替换**
|
|
422
|
+
- 安装 `react-babylonjs`, `@babylonjs/core`, `@babylonjs/materials`
|
|
423
|
+
- 移除 `three`, `@react-three/fiber`, `@react-three/drei` 等
|
|
424
|
+
- 更新 `vite.config.ts`、`tsconfig.json`、`package.json`
|
|
425
|
+
|
|
426
|
+
2. **矩阵工具**
|
|
427
|
+
- `src/core/matrix-utils.ts` -- 列主序↔行主序转换
|
|
428
|
+
|
|
429
|
+
3. **坐标数学重写**
|
|
430
|
+
- `src/core/coords-to-matrix.ts` -- `BABYLON.Matrix.Compose()`
|
|
431
|
+
- `src/api/coords-to-vector-3.ts` -- 移除 `three.MathUtils`
|
|
432
|
+
- `src/api/vector-3-to-coords.ts` -- 同上
|
|
433
|
+
|
|
434
|
+
4. **相机同步**
|
|
435
|
+
- `src/core/sync-camera.ts` -- Babylon `FreeCamera` + `freezeProjectionMatrix()`
|
|
436
|
+
|
|
437
|
+
5. **MapEngine 组件**
|
|
438
|
+
- `src/core/map-engine.tsx` -- 扩展 Engine 支持外部上下文
|
|
439
|
+
|
|
440
|
+
### Phase 2: Canvas-in-Layer 模式
|
|
441
|
+
|
|
442
|
+
**目标**: `overlay={false}` 模式工作——共享 WebGL 上下文。
|
|
443
|
+
|
|
444
|
+
1. **Babylon Engine 创建**
|
|
445
|
+
- `src/core/canvas-in-layer/use-root.tsx` -- 从 MapLibre GL 上下文创建 Engine/Scene
|
|
446
|
+
|
|
447
|
+
2. **渲染循环**
|
|
448
|
+
- `src/core/canvas-in-layer/use-render.ts` -- `scene.render()` 从 MapLibre `render()` 回调触发
|
|
449
|
+
|
|
450
|
+
3. **自定义图层**
|
|
451
|
+
- `src/core/canvas-in-layer/use-canvas-in-layer.tsx` -- 返回 MapLibre `LayerProps`
|
|
452
|
+
|
|
453
|
+
4. **Provider 入口**
|
|
454
|
+
- `src/maplibre/canvas.tsx` -- 使用 react-babylonjs 的 Canvas 组件
|
|
455
|
+
|
|
456
|
+
### Phase 3: Canvas-Overlay 模式
|
|
457
|
+
|
|
458
|
+
**目标**: `overlay={true}` 模式工作——独立 canvas。
|
|
459
|
+
|
|
460
|
+
1. **Overlay 画布包装器**
|
|
461
|
+
- `src/core/canvas-overlay/canvas-portal.tsx` -- `<MapEngine>` + react-babylonjs `<Scene>`
|
|
462
|
+
|
|
463
|
+
2. **Overlay 相机同步**
|
|
464
|
+
- `src/core/canvas-overlay/sync-camera-fc.tsx` -- 使用 `useBeforeRender()` 替代 `useFrame()`
|
|
465
|
+
|
|
466
|
+
3. **Overlay 初始化**
|
|
467
|
+
- `src/core/canvas-overlay/init-canvas-fc.tsx`
|
|
468
|
+
- `src/core/canvas-overlay/init-r3m.tsx`
|
|
469
|
+
|
|
470
|
+
### Phase 4: 事件与交互
|
|
471
|
+
|
|
472
|
+
**目标**: 点击/悬停事件正常工作。
|
|
473
|
+
|
|
474
|
+
1. **事件系统**
|
|
475
|
+
- `src/core/events.ts` -- 使用 react-babylonjs `onMeshPicked` + `scene.onPointerObservable`
|
|
476
|
+
- 指针坐标映射:地图 canvas → Babylon 场景
|
|
477
|
+
|
|
478
|
+
2. **Coordinates 组件**
|
|
479
|
+
- `src/api/coordinates.tsx` -- 使用 react-babylonjs `createPortal`
|
|
480
|
+
- `src/api/near-coordinates.tsx`
|
|
481
|
+
|
|
482
|
+
### Phase 5: 完善与测试
|
|
483
|
+
|
|
484
|
+
1. **更新示例**
|
|
485
|
+
- 移植 `example-maplibre/` 到 Babylon.js mesh 组件
|
|
486
|
+
- 移植 `example-mapbox/`
|
|
487
|
+
- 移植 `stories/` Ladle stories
|
|
488
|
+
|
|
489
|
+
2. **测试**
|
|
490
|
+
- `src/test/coords-to-matrix.test.ts` -- 适配 Babylon 矩阵输出
|
|
491
|
+
- 相机同步单元测试
|
|
492
|
+
- 矩阵转换测试
|
|
493
|
+
|
|
494
|
+
3. **文档**
|
|
495
|
+
- 更新 README
|
|
496
|
+
- API 文档
|
|
497
|
+
- react-three-map 迁移指南
|
|
498
|
+
|
|
499
|
+
## 5. 依赖变更
|
|
500
|
+
|
|
501
|
+
### 移除
|
|
502
|
+
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"three": "^0.159.0",
|
|
506
|
+
"@types/three": "^0.159.0",
|
|
507
|
+
"@react-three/fiber": "^8.15.12",
|
|
508
|
+
"@react-three/drei": "^9.97.6",
|
|
509
|
+
"@react-three/postprocessing": "^2.15.11",
|
|
510
|
+
"three-stdlib": "^2.28.7",
|
|
511
|
+
"web-ifc-three": "^0.0.125"
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### 新增
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"react-babylonjs": "^3.1.0",
|
|
520
|
+
"@babylonjs/core": "^7.0.0",
|
|
521
|
+
"@babylonjs/materials": "^7.0.0",
|
|
522
|
+
"@babylonjs/loaders": "^7.0.0"
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Peer 依赖变更
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
// 移除
|
|
530
|
+
"@react-three/fiber": ">=8.13",
|
|
531
|
+
"three": ">=0.133",
|
|
532
|
+
|
|
533
|
+
// 新增
|
|
534
|
+
"react-babylonjs": ">=3.0.0",
|
|
535
|
+
"@babylonjs/core": ">=7.0.0"
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### 保持不变
|
|
539
|
+
|
|
540
|
+
```json
|
|
541
|
+
{
|
|
542
|
+
"maplibre-gl": ">=4.0.0",
|
|
543
|
+
"mapbox-gl": ">=3.5.0",
|
|
544
|
+
"react": ">=18.0",
|
|
545
|
+
"react-map-gl": ">=8.0.0"
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## 6. 坐标系映射
|
|
550
|
+
|
|
551
|
+
### Three.js → Babylon.js
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
Three.js (右手坐标系): Babylon.js (右手坐标系模式):
|
|
555
|
+
Y ↑ Y ↑
|
|
556
|
+
| Z (朝向观察者) | Z (前进方向,进入屏幕)
|
|
557
|
+
| / | /
|
|
558
|
+
| / | /
|
|
559
|
+
+------→ X +------→ X
|
|
560
|
+
|
|
561
|
+
设置 scene.useRightHandedSystem = true 后,
|
|
562
|
+
Babylon.js 坐标轴与 Three.js 一致。
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### 矩阵布局转换
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
MapLibre (列主序): → Babylon.js (行主序):
|
|
569
|
+
[m0, m1, m2, m3, [m0, m4, m8, m12,
|
|
570
|
+
m4, m5, m6, m7, → m1, m5, m9, m13,
|
|
571
|
+
m8, m9, m10,m11, m2, m6, m10, m14,
|
|
572
|
+
m12,m13,m14,m15] m3, m7, m11, m15]
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### coordsToMatrix 翻译
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// Three.js 版本(当前):
|
|
579
|
+
quat.setFromEuler(euler.set(-Math.PI * .5, 0, 0));
|
|
580
|
+
m4.compose(pos, quat, scale).toArray(); // 列主序
|
|
581
|
+
|
|
582
|
+
// Babylon.js 版本:
|
|
583
|
+
const rotation = Quaternion.FromEulerAngles(-Math.PI * .5, 0, 0);
|
|
584
|
+
const composed = Matrix.Compose(
|
|
585
|
+
new Vector3(scaleUnit, -scaleUnit, scaleUnit),
|
|
586
|
+
rotation,
|
|
587
|
+
new Vector3(center.x, center.y, center.z || 0)
|
|
588
|
+
);
|
|
589
|
+
// 结果已是行主序 Babylon 矩阵
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## 7. API 兼容性策略
|
|
593
|
+
|
|
594
|
+
### 公共 API
|
|
595
|
+
|
|
596
|
+
库名变更为 `react-babylon-map`,Breaking Change 预期之中。组件结构保持相似:
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
// 之前 (react-three-map + Three.js):
|
|
600
|
+
import { Canvas } from 'react-three-map/maplibre';
|
|
601
|
+
<Map longitude={-122.4} latitude={37.8}>
|
|
602
|
+
<Canvas>
|
|
603
|
+
<mesh position={[0, 0, 0]}>
|
|
604
|
+
<boxGeometry args={[100, 100, 100]} />
|
|
605
|
+
<meshStandardMaterial color="red" />
|
|
606
|
+
</mesh>
|
|
607
|
+
</Canvas>
|
|
608
|
+
</Map>
|
|
609
|
+
|
|
610
|
+
// 之后 (react-babylon-map + react-babylonjs):
|
|
611
|
+
import { Canvas } from 'react-babylon-map/maplibre';
|
|
612
|
+
<Map longitude={-122.4} latitude={37.8}>
|
|
613
|
+
<Canvas>
|
|
614
|
+
<box name="myBox" position={new Vector3(0, 0, 0)} size={100}>
|
|
615
|
+
<standardMaterial diffuseColor={Color3.Red()} />
|
|
616
|
+
</box>
|
|
617
|
+
</Canvas>
|
|
618
|
+
</Map>
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Hook API
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// 之前:
|
|
625
|
+
import { useR3M, Coordinates } from 'react-three-map/maplibre';
|
|
626
|
+
// useR3M 返回 { map, viewProjMx, fromLngLat }
|
|
627
|
+
|
|
628
|
+
// 之后:
|
|
629
|
+
import { useBabylonMap, Coordinates } from 'react-babylon-map/maplibre';
|
|
630
|
+
// useBabylonMap 内部使用 useScene() + useEngine()
|
|
631
|
+
// 返回 { map, viewProjMx, fromLngLat, engine, scene }
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### 保持不变的组件
|
|
635
|
+
|
|
636
|
+
- `Canvas` -- 相同 props(`longitude`, `latitude`, `altitude`, `overlay`, `frameloop`, `id`, `beforeId`)
|
|
637
|
+
- `Coordinates` -- 相同 props(`longitude`, `latitude`, `altitude`, `children`)
|
|
638
|
+
- `useMap` -- 相同接口
|
|
639
|
+
|
|
640
|
+
### 变更的组件
|
|
641
|
+
|
|
642
|
+
- `<Canvas>` 内部子组件使用 react-babylonjs JSX(`<box>`, `<freeCamera>` 等)
|
|
643
|
+
- `coordsToVector3` / `vector3ToCoords` -- 函数签名不变,内部实现变更
|
|
644
|
+
|
|
645
|
+
## 8. 风险评估
|
|
646
|
+
|
|
647
|
+
| 风险 | 缓解措施 |
|
|
648
|
+
|------|----------|
|
|
649
|
+
| Babylon Engine 无法正确共享 GL 状态 | Phase 2 优先原型验证;降级为仅 overlay 模式 |
|
|
650
|
+
| 相机同步抖动/偏移 | 逐像素对比测试 Three.js 版本输出 |
|
|
651
|
+
| react-babylonjs 的 reconciler 与自定义渲染循环冲突 | `engine.stopRenderLoop()` 已被 `RenderOnDemand` 组件验证可行 |
|
|
652
|
+
| 矩阵转置错误 | 穷举单元测试,与已知 Three.js 输出对比 |
|
|
653
|
+
| 坐标系不匹配 | `scene.useRightHandedSystem = true` + 视觉验证 |
|
|
654
|
+
| react-babylonjs 版本升级导致的 break | fork Engine 组件,与上游保持松耦合 |
|
|
655
|
+
| Babylon 相机 `attachControl` 冲突 | `onCreated` 中 `cam.inputs.clear()` 禁用输入 |
|
|
656
|
+
|
|
657
|
+
## 9. react-babylonjs API 速查
|
|
658
|
+
|
|
659
|
+
### 常用 Hooks
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
import { useBeforeRender, useAfterRender, useScene, useEngine } from 'react-babylonjs';
|
|
663
|
+
|
|
664
|
+
// 帧回调
|
|
665
|
+
useBeforeRender((scene, eventState) => { /* 每帧前执行 */ });
|
|
666
|
+
useAfterRender((scene, eventState) => { /* 每帧后执行 */ });
|
|
667
|
+
|
|
668
|
+
// 获取实例
|
|
669
|
+
const scene = useScene(); // => Scene | null
|
|
670
|
+
const engine = useEngine(); // => Engine | null
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### 常用 JSX 组件
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
<Scene onCreated={(scene) => { /* 初始化场景 */ }}>
|
|
677
|
+
<freeCamera name="cam" position={new Vector3(0, 0, -10)}
|
|
678
|
+
onCreated={(cam) => cam.inputs.clear()} />
|
|
679
|
+
<hemisphericLight name="light" direction={new Vector3(0, 1, 0)} />
|
|
680
|
+
<box name="box" size={2} position={new Vector3(0, 0, 0)}>
|
|
681
|
+
<standardMaterial name="mat" diffuseColor={Color3.Red()} />
|
|
682
|
+
</box>
|
|
683
|
+
</Scene>
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Three.js → react-babylonjs 对应表
|
|
687
|
+
|
|
688
|
+
| react-three-map (R3F) | react-babylon-map (react-babylonjs) |
|
|
689
|
+
|----------------------|-------------------------------------|
|
|
690
|
+
| `<Canvas>` | `<MapEngine>` + `<Scene>` |
|
|
691
|
+
| `useFrame(cb)` | `useBeforeRender(cb)` |
|
|
692
|
+
| `useThree(s => s.camera)` | `useScene()?.activeCamera` |
|
|
693
|
+
| `useThree(s => s.gl)` | `useEngine()` |
|
|
694
|
+
| `useThree(s => s.scene)` | `useScene()` |
|
|
695
|
+
| `createRoot(canvas)` | `<MapEngine externalContext={gl}>` |
|
|
696
|
+
| `advance(timestamp)` | `scene.render()` |
|
|
697
|
+
| `gl.resetState()` | `engine.resetCustomCache()` |
|
|
698
|
+
| `<perspectiveCamera>` | `<freeCamera>` |
|
|
699
|
+
| `<mesh>` + `<boxGeometry>` | `<box>` |
|
|
700
|
+
| `<meshStandardMaterial>` | `<standardMaterial>` |
|
|
701
|
+
| R3F `createPortal` | react-babylonjs `createPortal` |
|
|
702
|
+
| `Raycaster` | `scene.pick()` / `onMeshPicked` |
|
|
703
|
+
|
|
704
|
+
## 10. 测试策略
|
|
705
|
+
|
|
706
|
+
### 单元测试
|
|
707
|
+
- 矩阵转换工具(列主序 ↔ 行主序)
|
|
708
|
+
- `coordsToMatrix` 输出与 Three.js 版本对比(转置后应一致)
|
|
709
|
+
- `coordsToVector3` / `vector3ToCoords` 往返精度
|
|
710
|
+
- 相机同步矩阵分解
|
|
711
|
+
|
|
712
|
+
### 集成测试
|
|
713
|
+
- Canvas-in-layer: 模拟 MapLibre map,验证 Babylon Engine 在共享上下文上创建
|
|
714
|
+
- Canvas-overlay: 验证 overlay canvas 定位
|
|
715
|
+
- 事件: 指针事件映射到正确的 Babylon pick
|
|
716
|
+
|
|
717
|
+
### 视觉回归测试
|
|
718
|
+
- Three.js 版本与 Babylon.js 版本截图对比
|
|
719
|
+
- 不同缩放级别和坐标的相机同步精度
|