Add optional GLB compression stage

This commit is contained in:
2026-08-03 17:50:21 +08:00
parent 5ef128c6f9
commit 7ba85946dc
13 changed files with 738 additions and 15 deletions

View File

@@ -12,7 +12,7 @@
```
config/areas/<id>.json ← 你写的
│ build-area.js: normalizeAreaConfig() 补默认值 + 推导 14 个输出路径
│ build-area.js: normalizeAreaConfig() 补默认值 + 推导输出路径
<areaDir>/_pipeline/osm2streets-qgis.config.json ← 生成的,不要手改
@@ -52,6 +52,7 @@ cp config/examples/template.json config/areas/my-area.json
| `qgis` | | 见下 | QGIS/osm2streets 旋钮 |
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
| `blender` | | 见下 | Blender 侧选项 |
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
| `outputs` | | 从 `id` 推导 | 输出路径覆盖,逃生舱 |
**路径一律绝对**`normalizeAreaConfig` 对每一项都做 `path.resolve`,相对路径会
@@ -65,10 +66,11 @@ cp config/examples/template.json config/areas/my-area.json
| `blender` | `true` | |
| `cesium` | `true` | |
`reimport``preview` **在这里配也没用**——`normalizeAreaConfig:117-118` 把它们
`reimport``preview``compress` **在这里配也没用**——`normalizeAreaConfig` 把它们
硬编码为 `false`,只能靠 `--stages` 显式请求。
> 恢复动作reimport补丁动作preview不该被一份配置文件变成默认行为。
> 恢复动作reimport补丁动作preview和替代产物动作compress不该被一份
> 配置文件变成默认行为。
`--stages` 会整体覆盖这里的默认值。
@@ -117,6 +119,18 @@ cp config/examples/template.json config/areas/my-area.json
| `treeStyle` | `"natural"` | 合法值见 `generate_scene.py``TREE_STYLES``natural``procedural``shapespark` |
| `officeOverrides` | `""` | 旧名 `office_overrides` 仍被接受 |
### `compress`
只影响显式 `--stages compress`。默认压缩链是 texture resize + WebP transcode
不覆盖默认 `<area-id>.glb`
| 字段 | 默认 | 说明 |
|---|---|---|
| `textureSize` | `768` | 最大纹理宽高,范围 `64..4096` |
| `quality` | `82` | WebP 质量,范围 `1..100` |
| `effort` | `80` | WebP 编码 effort范围 `0..100` |
| `meshopt` | `false` | 是否追加 `EXT_meshopt_compression`。开启前要单独验证 Cesium 兼容性 |
### `outputs`(逃生舱)
默认全部从 `id` 推导为 `<outputRoot>/<id>/<fileStem>.<ext>`。需要定制时逐项覆盖:
@@ -134,6 +148,7 @@ cp config/examples/template.json config/areas/my-area.json
可覆盖的键(`build-area.js:87-102``areaDir``fileStem``geojsonDir``gpkg`
`qgisProject``qgisPreview``blend``render``glb``metadata``cesiumPreview`
`compressedFileStem``compressedGlb``compressedMetadata``compressedCesiumPreview`
`vehicleRoute``vehicleModel``pipelineDir`
**优先改 `fileStem` 或 `areaDir`**——它们能一次性影响全部派生路径。逐个覆盖容易漏。
@@ -175,7 +190,7 @@ cp config/examples/template.json config/areas/my-area.json
| 布尔字段用 `\|\|` 兜底 | `false` 被翻转 |
| 给新字段造顶层平铺别名 | 扩大历史包袱 |
| 逐个覆盖 `outputs` 而不用 `fileStem` | 漏掉某个产物路径 |
| 在 `stages` 里配 `reimport` / `preview` | 无效,被硬编码为 false |
| 在 `stages` 里配 `reimport` / `preview` / `compress` | 无效,被硬编码为 false |
| 加数值字段不做范围校验 | 错配置在中途才崩,输出已被破坏 |
---

View File

@@ -18,6 +18,105 @@
全部是 CommonJS`package.json``"type": "commonjs"`),无构建步骤、无 TypeScript、
零运行时依赖(唯一依赖 `osm2streets-js-node` 只被 `build-osm2streets-qgis.js` 用)。
## 可选 GLB 压缩阶段
### 1. Scope / Trigger
`compress` 是显式请求的替代产物阶段,不属于 `all`,也不覆盖默认 `<area-id>.glb`
它用于在已有 Cesium GLB 上生成并列压缩 GLB、metadata 和预览页。当前主路径是
texture resize + WebP transcode。
`scripts/compress-glb.js` 是该阶段调用的低层脚本,也可单独运行做实验。
### 2. Signatures
阶段入口:
```bash
npm run build:area -- --config config/areas/<area>.json --stages compress
npm run build:area -- --config config/areas/<area>.json --stages cesium,compress
```
低层脚本入口:
```bash
npm run compress:glb -- --input in.glb --output out.glb [options]
```
可选参数:
| 参数 | 默认 | 语义 |
|---|---:|---|
| `--texture-size` | `1024` | 最大纹理宽高,范围 `64..4096` |
| `--quality` | `82` | WebP 质量,范围 `1..100` |
| `--effort` | `80` | WebP 编码 effort范围 `0..100` |
| `--meshopt` | off | 追加 `EXT_meshopt_compression` |
| `--metadata source.json` | none | 复制伴生 metadata 并指向压缩 GLB |
| `--metadata-output out.json` | `<output>.json` | 覆盖 metadata 输出路径 |
| `--preview source.html` | none | 复制 Cesium preview 并指向压缩 metadata |
| `--preview-output out.html` | `<output>-cesium-preview.html` | 覆盖 preview 输出路径 |
### 3. Contracts
- 输入必须是现有 `.glb` 文件;`--output` 必须不同于 `--input`
- 输出是并列压缩 GLB默认构建产物不被替换。
- `compress` 阶段依赖默认 `glb``metadata``cesiumPreview` 已存在。
- 默认链固定为 `gltf-transform resize -> gltf-transform webp`
- `compress` 阶段默认 `textureSize=768`;低层脚本单独运行时默认 `--texture-size 1024`
- `build-area.js` 默认写:
- `<fileStem>-compressed-webp768.glb`
- `<fileStem>-compressed-webp768.json`
- `<fileStem>-compressed-webp768-cesium-preview.html`
- 伴生 metadata 的 `asset``assets[0].url` 改为压缩 GLB 文件名。
- preview HTML 只替换 `window.OSM_ASSET_PREVIEW_CONFIG``glbName` /
`metadataName` 和 loading 文案,不改 preview runtime。
- 成功时 stdout 打印 `GLB_COMPRESS_DONE <json>`包含压缩前后大小、image /
non-image bytes、结构计数、扩展、metadata / preview 输出路径。
### 4. Validation & Error Matrix
| 条件 | 结果 |
|---|---|
| 缺 `--input` / `--output` | 打印 usage 并退出非零 |
| 输入文件不存在 | `Input GLB not found: <path>` |
| `--output` 等于 `--input` | 抛错,避免覆盖源 GLB |
| 数值参数超范围 | 抛错并指出合法范围 |
| `gltf-transform` 退出非零 | 抛错并带上 status / signal |
| `--preview` 没有 `--metadata` | 抛错,因为 preview 必须指向存在的 metadata |
| preview HTML 找不到配置块 | 抛错,不做猜测替换 |
| `--stages compress` 但默认 preview 不存在 | `Cesium preview not found: <path>` |
### 5. Good/Base/Bad Cases
- Good: `--stages cesium,compress` 先重导默认 GLB再生成并列压缩产物。
- Good: `--texture-size 768 --metadata --preview` 生成压缩 GLB、metadata、HTML
源 GLB 保持不变。
- Base: 只传 `--input --output` 生成压缩 GLB不生成伴生文件。
- Bad: 使用 `--meshopt` 后没有做 Cesium 兼容性验证就当默认产物发布。
### 6. Tests Required
- `node --check scripts/compress-glb.js`
- `node --check scripts/build-area.js`
- 对目标区域跑一次 `npm run compress:glb -- ... --metadata --preview`
- 对目标区域跑一次 `npm run build:area -- --stages compress`
- `node scripts/glb-digest.js <compressed.glb>` 确认可解析结构和扩展
- 浏览器/Cesium 预览压缩 HTML确认 `EXT_texture_webp` 在目标环境可加载
### 7. Wrong vs Correct
Wrong:
```bash
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a.glb
```
Correct:
```bash
npm run compress:glb -- --input outputs/a/a.glb --output outputs/a/a-compressed-webp768.glb
```
---
## CLI 参数解析
@@ -94,7 +193,7 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
---
## 五个阶段
## 阶段
| 阶段 | 做什么 | 读 | 写 |
|---|---|---|---|
@@ -103,8 +202,9 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
| `blender` | OSM + GeoJSON → 场景 | `.osm``osm2streets_web_out/` | `.blend``.png` |
| `cesium` | 场景 → GLB + 元数据 + 预览页 | `.blend` | `.glb``.json`、预览 HTML 及其静态资源 |
| `preview` | 只补生成预览页 | `.glb``.json` | 预览 HTML 及其静态资源 |
| `compress` | 生成并列压缩 Cesium 产物 | `.glb``.json`、默认预览 HTML | `-compressed-webp*.glb/json/html` |
调度是顶层的五个 `if``build-area.js:32-46`),顺序固定,**阶段之间不传内存状态,
调度是顶层的阶段 `if``build-area.js` 开头),顺序固定,**阶段之间不传内存状态,
只通过磁盘产物耦合**。这就是单跑某个阶段能work 的原因。
`cesium` 阶段结束时会直接调 `writeCesiumPreview(area)``build-area.js:285`),所以
@@ -126,7 +226,8 @@ gpkg: path.resolve(outputOverrides.gpkg || path.join(areaDir, `${fileStem}.gpkg`
all: ["intermediates", "blender", "cesium"],
```
`preview` 同样不在 `all` 里——`cesium` 已经包含它。
`preview` 同样不在 `all` 里——`cesium` 已经包含它。`compress` 也不在 `all` 里——
它生成的是替代压缩产物,不是 baseline GLB。
### `intermediates` 与 `reimport` 互斥
@@ -136,9 +237,9 @@ all: ["intermediates", "blender", "cesium"],
这是**显式拒绝而不是警告**——两者同时开,无论谁先跑,另一个的工作都白做。
`normalizeAreaConfig``stages.reimport``stages.preview` 硬编码为 `false`
`build-area.js:117-118`**不能从配置文件打开**,只能靠 `--stages` 显式请求。
恢复动作补丁动作都不该被一份配置文件变成默认行为。
`normalizeAreaConfig``stages.reimport``stages.preview``stages.compress` 硬编码为 `false`
**不能从配置文件打开**,只能靠 `--stages` 显式请求。
恢复动作补丁动作和替代产物动作都不该被一份配置文件变成默认行为。
---