311 lines
12 KiB
Markdown
311 lines
12 KiB
Markdown
# Preview:Cesium 预览层
|
||
|
||
> 覆盖浏览器运行时 `scripts/lib/cesium-preview.js`(672 行)与
|
||
> `cesium-preview.css`(230 行),以及 Node 侧的 `scripts/lib/area-preview.js`。
|
||
> 运行时:浏览器。全仓唯一的 DOM 环境。
|
||
|
||
---
|
||
|
||
## 定位
|
||
|
||
预览层是**验证性的,不是产物本身**。它加载 `cesium` 阶段导出的 `.glb` + `.json`,
|
||
用来确认资产在真实 Cesium 里的样子。改这一层**不会**改变 Blender/GLB 主资产。
|
||
|
||
车辆巡航同理——README 里写明它是"用于验证高精度巡航可用性的预览层功能"。
|
||
|
||
---
|
||
|
||
## 没有构建步骤
|
||
|
||
```
|
||
scripts/lib/cesium-preview.js ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.js
|
||
scripts/lib/cesium-preview.css ─── 原样 copyFileSync ──▶ outputs/<area>/cesium-preview.css
|
||
(area-preview.js:writeCesiumPreviewSupportFiles)
|
||
<area>-cesium-preview.html ─── 模板字符串生成 ────▶ 同目录
|
||
(area-preview.js:cesiumPreviewHtml)
|
||
```
|
||
|
||
所以:**没有打包、没有转译、没有 npm 依赖、没有模块系统**。浏览器直接吃。
|
||
写代码时只能用目标浏览器原生支持的语法,`Cesium` 从 CDN 全局引入。
|
||
|
||
整个文件是一个 IIFE + `"use strict"`(`cesium-preview.js:1-2`)。
|
||
|
||
---
|
||
|
||
## 参数注入
|
||
|
||
JS 不硬编码任何文件名,全部从 HTML 注入的全局对象读:
|
||
|
||
```js
|
||
const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; // :4
|
||
// config.areaId / .glbName / .metadataName / .routeName / .vehicleModelName
|
||
```
|
||
|
||
生成侧在 `scripts/lib/area-preview.js` 的 `cesiumPreviewHtml()`,注入时**必须转义**:
|
||
|
||
| 场景 | 用 |
|
||
|---|---|
|
||
| HTML 文本/属性 | `escapeHtml()` |
|
||
| `<script>` 里的 JSON | `escapeScriptJson()` |
|
||
|
||
`|| {}` 的兜底不能删——它让 JS 在没有配置块时也不至于在第一行就崩。
|
||
|
||
**加一个新的可配置项**:`cesiumPreviewHtml()` 里加进注入的 JSON,JS 侧从 `config` 读,
|
||
两边都要动。
|
||
|
||
`build-area.js` 只保留 GLB / metadata 依赖检查、写入顺序和 preview manifest ownership;
|
||
不要把 HTML 模板、runtime copy 或转义实现移回阶段调度器。路线 JSON 与车辆 glTF 分别由
|
||
`vehicle-route.js` 和 `vehicle-model.js` 生成,二者都是不启动外部工具的 Node 模块。
|
||
|
||
---
|
||
|
||
## 加载流程
|
||
|
||
`main()`(`:30-52`)的顺序是刻意的:
|
||
|
||
```
|
||
setLoadingMessage("Loading scene")
|
||
→ fetchJson(metadata) 必需,失败即终止
|
||
→ fetchOptionalJson(route) 可选,失败降级
|
||
→ scenePlacement(metadata)
|
||
→ createViewer()
|
||
setLoadingMessage("Loading model")
|
||
→ loadSceneAssets() 逐个资产加载,失败收集不中断
|
||
→ addVehicleCruises() / createCameraPresets()
|
||
→ buildAssetToggles() / bindRuntimeControls() / startDiagnostics()
|
||
→ cameras.overview()
|
||
→ baseStatus = summaryText(...)
|
||
setLoadingMessage("Preparing view")
|
||
→ await waitForStableFrames() 等画面稳定
|
||
→ document.body.classList.add("scene-ready") ← CSS 靠这个类收起遮罩
|
||
→ window.osmPreview = {...}
|
||
```
|
||
|
||
### 三档失败语义
|
||
|
||
这一层的错误处理分得很清楚,**新增加载逻辑时要先想清楚落在哪一档**:
|
||
|
||
| 档 | 做法 | 出处 |
|
||
|---|---|---|
|
||
| **必需** | `fetchJson` 直接抛,预览起不来 | `:55-62` |
|
||
| **可选** | `fetchOptionalJson` 捕获 → `console.warn` → 返回 `null` | `:63-73` |
|
||
| **部分** | 逐条收集失败,汇总到诊断面板,其余照常显示 | `:163-165` |
|
||
|
||
两段注释把理由写清楚了:
|
||
|
||
> The route file is an extra on top of the scene, not a precondition for it.
|
||
> A missing or unreadable route costs the cruise controls, not the preview.
|
||
|
||
> One broken entry in metadata.assets should not blank the whole preview, so
|
||
> failures are collected and surfaced in the diagnostics panel instead.
|
||
|
||
### `scene-ready` 是加载态的唯一开关
|
||
|
||
`waitForStableFrames()`(`:107`)等若干帧稳定后才加 `scene-ready` 类,CSS 据此
|
||
收起遮罩。**不要改成定时器或 `load` 事件**——材质编译完成之前画面是花的。
|
||
|
||
### `window.osmPreview` 是唯一对外句柄
|
||
|
||
```js
|
||
// Handle for the browser console and for headless checks: everything else
|
||
// in here is closed over by the IIFE and unreachable from outside. :50-51
|
||
window.osmPreview = { viewer, metadata, placement, assets, cruise, cameras };
|
||
```
|
||
|
||
调试和无头检查都靠它。**加新的顶层对象就往这里挂**,不要再开新全局。
|
||
|
||
---
|
||
|
||
## Viewer 配置:一切都关掉
|
||
|
||
`createViewer()`(`:74-97`)把 Cesium 的默认 UI 和地球全部关闭:
|
||
|
||
```js
|
||
animation, timeline, baseLayerPicker, geocoder,
|
||
navigationHelpButton, sceneModePicker, infoBox,
|
||
selectionIndicator, baseLayer ← 全 false
|
||
globe.show = false ← 不显示地球
|
||
skyAtmosphere / skyBox / sun / moon ← 全 false
|
||
backgroundColor = globe.baseColor = "#d9e0e2" ← PREVIEW_BACKGROUND
|
||
```
|
||
|
||
理由:这是**看单个园区资产**的预览,不是地图应用。留着地球和大气会干扰对
|
||
材质与几何的判断,也让背景色不可控。
|
||
|
||
`depthTestAgainstTerrain = false` —— 没有地形,开着只会让模型被裁。
|
||
|
||
保留的只有 `homeButton` 和 `fullscreenButton`。
|
||
|
||
---
|
||
|
||
## DOM 与状态
|
||
|
||
### DOM 句柄集中在顶部
|
||
|
||
`:5-20` 一次性取完全部元素引用,**不在函数里现查**。新增控件时加在这一批里。
|
||
|
||
### status 的两类消息
|
||
|
||
```js
|
||
// Status carries two kinds of message: the scene summary, which is what the
|
||
// panel should read whenever nothing else is going on, and transient notes
|
||
// from a control the user just touched. Keep the summary so the transient
|
||
// note can be replaced instead of destroying it. :21-24
|
||
let baseStatus = "";
|
||
```
|
||
|
||
`baseStatus` 存场景摘要,瞬时提示用完要能回到它。**加新的瞬时提示时不要直接覆写
|
||
`baseStatus`**。
|
||
|
||
### 诊断读数跟渲染循环,不跟定时器
|
||
|
||
```js
|
||
// Camera-dependent readouts have to track the camera, so refresh off the
|
||
// render loop rather than a fixed timer, throttled to stay off the hot path. :589-590
|
||
```
|
||
|
||
相机相关的读数必须跟着渲染循环刷新并**做节流**。用 `setInterval` 会在相机快速移动时
|
||
读到过期值,不节流会拖慢帧率。
|
||
|
||
### 车辆巡航热路径要少分配
|
||
|
||
车辆巡航是预览层里唯一持续动画的功能。`scripts/lib/cesium-preview.js` 的
|
||
`routeOrientation()` 和 `createChaseFollow()` 每帧都会运行,里面的 `JulianDate`、
|
||
`HeadingPitchRoll`、`Quaternion`、`Matrix4` 等临时对象必须在闭包外复用,不要在
|
||
`CallbackProperty` 或 `viewer.clock.onTick` 回调里 `new`。
|
||
|
||
预览页面加载的是 30MB+ GLB,动画时 GPU 已经很忙;巡航辅助线也要避免额外透明合成。
|
||
路线 polyline 使用不透明 `Color` 和 `arcType: Cesium.ArcType.NONE`,不要为了视觉效果
|
||
随手改回半透明 geodesic 线。
|
||
|
||
不要默认降低 `viewer.resolutionScale` 或关闭 antialias/MSAA 来换取巡航流畅度。
|
||
这会让用户误判材质和模型质量。若确实需要性能模式,应做成显式开关,而不是默认牺牲
|
||
预览清晰度。
|
||
|
||
### 单资产 vs 多资产的开关
|
||
|
||
```js
|
||
// A single-asset scene keeps the plain "Scene" checkbox; a multi-asset one
|
||
// gets a child checkbox per model with "Scene" acting as the master. :194-195
|
||
```
|
||
|
||
`buildAssetToggles()` 按资产数量决定 UI 形态,`syncSceneMaster()` 维护主从关系。
|
||
|
||
---
|
||
|
||
## 坐标系
|
||
|
||
GLB 停留在**局部 ENU 坐标系**(X 东、Y 北、Z 上),靠伴生 JSON 的放置信息配合
|
||
`Cesium.Transforms.eastNorthUpToFixedFrame` 摆到地球上
|
||
(`blender/export_cesium.py:8-9`)。
|
||
|
||
`scenePlacement(metadata)`(`:131`)负责这一步。**改动导出侧的坐标约定必须同步改这里。**
|
||
|
||
## 语义检查资产
|
||
|
||
### 1. 范围与触发条件
|
||
|
||
`cesium` 阶段除完整主 GLB 外,会按 Blender 顶层集合导出可选检查资产:道路、建筑、
|
||
绿化与设施、水体。它们只服务于预览检查;主 GLB 仍是下游兼容基线,不能被替换。
|
||
|
||
### 2. 调用形式
|
||
|
||
不新增 CLI 参数。正常运行 Cesium 阶段即可:
|
||
|
||
```bash
|
||
npm run build:area -- --config config/areas/<area>.json --stages cesium
|
||
```
|
||
|
||
### 3. 契约
|
||
|
||
- `blender/export_cesium.py:SEMANTIC_ASSETS` 是集合名、稳定资产 ID 与展示名的唯一映射:
|
||
`03_Roads → roads`、`04_Buildings → buildings`、`02_Green + 05_Props → vegetation`、
|
||
`01_Water → water`。
|
||
- 每个有几何的类别额外写 `<stem>-<id>.glb`,且必须保留与主 GLB 相同的局部 ENU 坐标和
|
||
已处理的 Cesium 材质。
|
||
- metadata 的主资产保持 `id="main"`、`enabled=true`;辅助项设
|
||
`category="semantic"`、`enabled=false`,并提供 `id`、`label`、`type="model"`、`url`。
|
||
- `build-area.js:semanticAssetRecords()` 必须验证 metadata 声明的每个语义文件存在后才写
|
||
Cesium manifest。
|
||
- 浏览器先加载非语义资产;只有点 `Inspect` 才加载辅助 GLB。检查模式必须隐藏主场景,
|
||
返回 `Scene` 必须隐藏辅助模型,禁止两套几何重叠渲染。
|
||
- 没有 `category="semantic"` 的旧 metadata 仍按单资产预览打开,`Inspect` 按钮禁用。
|
||
|
||
### 4. 校验与错误矩阵
|
||
|
||
| 条件 | 结果 |
|
||
|---|---|
|
||
| 类别集合没有可导出 mesh | metadata 不声明该类别,预览不显示该开关 |
|
||
| metadata 声明语义资产但文件不存在 | Cesium 阶段失败,不能写成功 manifest |
|
||
| 辅助 GLB 浏览器加载失败 | 该开关禁用并写入诊断;主场景继续可用 |
|
||
| 旧 metadata 没有语义项 | 完整场景照常显示,`Inspect` 不可点击 |
|
||
|
||
### 5. 正常、基础与错误示例
|
||
|
||
- 正常:进入 `Inspect` 后道路、建筑、绿化与设施、水体全部显示,再单独取消任一类别。
|
||
- 基础:旧的只有 `main` 资产的 metadata 不展示分类控件,所有原有控制仍可用。
|
||
- 错误:主 GLB 和语义 GLB 同时可见,导致道路、建筑等重复渲染和闪烁。
|
||
|
||
### 6. 必需测试
|
||
|
||
- `node scripts/test-preview-assets.js`:断言生成页包含模式切换与语义开关挂载点。
|
||
- `node --check scripts/build-area.js`、`node --check scripts/lib/area-preview.js`、
|
||
`node --check scripts/lib/cesium-preview.js`。
|
||
- 目标区域运行 `--stages cesium`,确认 metadata 的语义 `assets` 与同名辅助 GLB 一一对应。
|
||
- 浏览器在桌面及窄屏分别切换 `Scene` / `Inspect`,确认不重叠且控制不溢出。
|
||
|
||
### 7. 错误与正确写法
|
||
|
||
错误:把辅助模型标为默认启用,页面加载时把它们与主 GLB 一起绘制。
|
||
|
||
```json
|
||
{ "id": "roads", "enabled": true, "category": "semantic" }
|
||
```
|
||
|
||
正确:默认关闭并在检查模式按需加载。
|
||
|
||
```json
|
||
{ "id": "roads", "enabled": false, "category": "semantic" }
|
||
```
|
||
|
||
---
|
||
|
||
## 本地预览必须走 HTTP
|
||
|
||
```bash
|
||
cd outputs/<area-id>
|
||
python3 -m http.server 8765
|
||
# → http://localhost:8765/<area-id>-cesium-preview.html
|
||
```
|
||
|
||
`file://` 会被浏览器的同源策略拦掉 `fetch`,页面停在加载遮罩上。
|
||
|
||
---
|
||
|
||
## 反模式
|
||
|
||
| 反模式 | 后果 |
|
||
|---|---|
|
||
| 引入需要打包/转译的语法或 npm 依赖 | 没有构建步骤,直接跑不起来 |
|
||
| 在 JS 里硬编码 `.glb` / `.json` 文件名 | 换区域就失效,绕过 config 注入 |
|
||
| 注入 HTML 时不转义 | 区域名带特殊字符就破页面 |
|
||
| 把可选资源当必需资源加载 | 缺一个路线文件整个预览打不开 |
|
||
| 单个资产加载失败就中断全部 | 一条坏 metadata 让预览全白 |
|
||
| 用定时器代替 `waitForStableFrames` | 遮罩在材质编译完成前就收起,画面是花的 |
|
||
| 相机读数用 `setInterval` | 快速移动时读到过期值 |
|
||
| 诊断刷新不节流 | 拖慢帧率 |
|
||
| 在车辆每帧回调里创建 `JulianDate` / HPR / 矩阵对象 | 巡航时触发 GC 抖动 |
|
||
| 默认打开半透明路线辅助线 | 增加透明合成成本,主场景可见时车辆更不顺 |
|
||
| 默认降低 `resolutionScale` 或关抗锯齿 | 预览变糊,无法判断材质/模型质量 |
|
||
| 再开一个全局变量 | 已有 `window.osmPreview` |
|
||
| 用 `file://` 打开 | fetch 被拦,卡在加载中 |
|
||
|
||
---
|
||
|
||
## 相关
|
||
|
||
- [CLI 与阶段](../pipeline/cli-and-stages.md):`cesium` / `preview` 阶段如何生成这些文件
|
||
- [资产生成](../blender/asset-generation.md):GLB 里的材质为什么要单独调色
|
||
- [车辆连续路线](vehicle-routes.md):路线 JSON、转向选择与预览标签契约
|
||
- README「实验:车辆巡航」节:面向使用者的说明
|