13 KiB
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 注入的全局对象读:
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 是唯一对外句柄
// 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 和地球全部关闭:
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 的两类消息
// 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。
诊断读数跟渲染循环,不跟定时器
// 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 多资产的开关
// 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)负责这一步。改动导出侧的坐标约定必须同步改这里。
交通信号动态覆盖层
metadata 的动态资产契约如下:
category="dynamic":灯珠节点,继续按相位切换红/黄/绿 lens 的show。category="countdown"且phaseGroup为0或1:对应相位组的倒计时模型;模型内 共享 20 个数字节点,不按每个信号复制数字。
三个模型必须使用完全相同的 placement.modelMatrix。倒计时颜色只能通过模型级
model.color 配合 Cesium.ColorBlendMode.REPLACE 设置;普通 glTF PBR 材质的
getMaterial().setValue() 在本项目验证中不能可靠修改运行时字色,禁止作为实现路径。
倒计时数字的显示逻辑只改变当前数字节点的 show,颜色由该 phase group 的当前灯色
统一设置。加载失败属于部分资产失败:应进入诊断而不清空主场景。
语义检查资产
1. 范围与触发条件
cesium 阶段除完整主 GLB 外,会按 Blender 顶层集合导出可选检查资产:道路、建筑、
绿化与设施、水体。它们只服务于预览检查;主 GLB 仍是下游兼容基线,不能被替换。
2. 调用形式
不新增 CLI 参数。正常运行 Cesium 阶段即可:
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 一起绘制。
{ "id": "roads", "enabled": true, "category": "semantic" }
正确:默认关闭并在检查模式按需加载。
{ "id": "roads", "enabled": false, "category": "semantic" }
本地预览必须走 HTTP
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 被拦,卡在加载中 |