From 0d2dc2228c31536c8148788cfbe373da2c5f8c8b Mon Sep 17 00:00:00 2001 From: que01 Date: Mon, 3 Aug 2026 15:17:51 +0800 Subject: [PATCH] Improve Cesium cruise smoothness --- .trellis/spec/blender/asset-generation.md | 20 ++++++ .trellis/spec/preview/index.md | 18 +++++ .../check.jsonl | 1 + .../implement.jsonl | 1 + .../prd.md | 67 +++++++++++++++++++ .../report.md | 57 ++++++++++++++++ .../task.json | 26 +++++++ blender/generate_scene.py | 4 +- docs/changelog.md | 8 +++ scripts/lib/cesium-preview.js | 35 +++++++--- 10 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 .trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/check.jsonl create mode 100644 .trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/implement.jsonl create mode 100644 .trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/prd.md create mode 100644 .trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/report.md create mode 100644 .trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/task.json diff --git a/.trellis/spec/blender/asset-generation.md b/.trellis/spec/blender/asset-generation.md index 3fa9f5d..be0bee2 100644 --- a/.trellis/spec/blender/asset-generation.md +++ b/.trellis/spec/blender/asset-generation.md @@ -121,6 +121,26 @@ catalog.MATERIALS 声明「是什么」 纯 Python,无 bpy **这类"试过、不行、为什么"的记录要保留。** 删掉它,下一个人会重新引入同一个资产。 +## 灌木边缘实例有性能预算 + +`generate_scene.py` 的 scrub 不是只有贴地面:`natural=scrub` 会先生成 +`Scrub_` 地面覆盖,再沿边界实例化 `assets/models/custom/bush/bush.glb` +(`add_scrub_edge_bushes()`),并在大 scrub 面内部稀疏补树。 + +这些 bush 是共享 mesh 的多 node 实例。共享 mesh 能压 GLB 体积,但 Cesium 近景 Follow +仍要处理每个 node / draw。`nantaizi-lake-innovation-valley` 曾经用 +`SCRUB_BUSH_SPACING = 0.82`、`SCRUB_BUSH_LIMIT_PER_PATCH = 180`,结果一个场景有约 +`707` 个 bush nodes;车辆 Follow 到灌木密集区域时明显卡顿。当前预算是: + +```python +SCRUB_BUSH_SPACING = 1.8 +SCRUB_BUSH_LIMIT_PER_PATCH = 60 +``` + +在 nantaizi 上约 `266` 个 bush nodes,scrub 地面覆盖仍保留。不要只为了“更满”把间距 +调回 1m 以下;那是在把预览流畅度换成近景装饰密度。若确实需要更密的灌木,先换低模 +bush 或做显式 LOD / instancing 方案,再跑 Cesium preview 验证。 + --- ## 确定性:用无理数周期代替 RNG diff --git a/.trellis/spec/preview/index.md b/.trellis/spec/preview/index.md index 44a5a88..075376c 100644 --- a/.trellis/spec/preview/index.md +++ b/.trellis/spec/preview/index.md @@ -162,6 +162,21 @@ let baseStatus = ""; 相机相关的读数必须跟着渲染循环刷新并**做节流**。用 `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 @@ -207,6 +222,9 @@ python3 -m http.server 8765 | 用定时器代替 `waitForStableFrames` | 遮罩在材质编译完成前就收起,画面是花的 | | 相机读数用 `setInterval` | 快速移动时读到过期值 | | 诊断刷新不节流 | 拖慢帧率 | +| 在车辆每帧回调里创建 `JulianDate` / HPR / 矩阵对象 | 巡航时触发 GC 抖动 | +| 默认打开半透明路线辅助线 | 增加透明合成成本,主场景可见时车辆更不顺 | +| 默认降低 `resolutionScale` 或关抗锯齿 | 预览变糊,无法判断材质/模型质量 | | 再开一个全局变量 | 已有 `window.osmPreview` | | 用 `file://` 打开 | fetch 被拦,卡在加载中 | diff --git a/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/check.jsonl b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/check.jsonl new file mode 100644 index 0000000..9dd3234 --- /dev/null +++ b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/check.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/implement.jsonl b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/implement.jsonl new file mode 100644 index 0000000..9dd3234 --- /dev/null +++ b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/implement.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/prd.md b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/prd.md new file mode 100644 index 0000000..675dd03 --- /dev/null +++ b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/prd.md @@ -0,0 +1,67 @@ +# Smooth Cesium preview vehicle cruise + +## Goal + +Improve perceived smoothness of vehicle cruise in the Cesium preview for +`nantaizi-lake-innovation-valley` while the scene model is visible, especially +when Follow passes through scrub-heavy areas. + +The initial preview-layer optimization helped but did not solve the root cause: +closing `Scene` helps a lot, and the current nantaizi GLB contains about 707 +scrub bush nodes. The root fix is allowed to reduce scrub bush instance density +while preserving the flat scrub ground cover and existing preview controls. + +## Requirements + +1. Optimize `scripts/lib/cesium-preview.js` so vehicle animation does less + per-frame work during cruise without reducing default visual clarity. +2. Preserve existing controls and behavior: + - Play/Pause still drives `viewer.clock.shouldAnimate`. + - Speed slider still maps m/s to clock multiplier. + - Vehicle selection still chooses the follow target. + - Scene/routes/vehicles/FPS/diagnostics toggles still work. + - Follow mode still tracks the selected vehicle. +3. Keep the preview layer dependency-free and browser-native; no build step, + npm dependency, module system, or hard-coded area filenames. +4. Reduce scrub edge bush instance density in `blender/generate_scene.py` to + lower Cesium render load in scrub-heavy Follow views. Preserve deterministic + sampling, scrub ground polygons, scrub tree sampling, and scene stats fields. +5. Regenerate or copy the updated preview JS into + `outputs/nantaizi-lake-innovation-valley/cesium-preview.js` only if needed + for local manual verification; do not commit generated outputs. +6. Rebuild `nantaizi-lake-innovation-valley` `blender,cesium,preview` outputs + for manual validation. This task intentionally changes generated GLB + structure, so full parity-identical output is not expected. + +## Acceptance Criteria + +- [ ] `scripts/lib/cesium-preview.js` has a small, reviewable optimization that + reduces per-frame allocations or render overhead for vehicle cruise. +- [ ] `blender/generate_scene.py` reduces nantaizi scrub bush node count by at + least 50% while keeping scrub polygons present. +- [ ] Vehicle controls listed in Requirements continue to work by inspection + and/or local smoke check. +- [ ] Blender/Cesium rebuild for `nantaizi-lake-innovation-valley` completes + and generated metadata still includes scrub counts. +- [ ] `node --check scripts/lib/cesium-preview.js` passes. +- [ ] `python3 -m py_compile blender/generate_scene.py` passes. +- [ ] If local output preview JS is updated for testing, it remains ignored and + is not committed. + +## Out Of Scope + +- Full scene geometry/material optimization beyond scrub edge bush density. +- Reducing tree count or changing road/building/water assets. +- Replacing Cesium or adding profiling libraries. +- Visual design changes to the preview UI. + +## Technical Notes + +- User confirmed closing `Scene` improves smoothness, so the scene render load + is a real contributor. +- Current route data is small: `nantaizi-lake-innovation-valley` has 9 route + segments and the preview uses at most 5 vehicles. +- Current nantaizi GLB has 1271 nodes; about 707 are scrub bush nodes. Lowering + scrub edge sampling from `0.82m / 180 per patch` to `1.8m / 60 per patch` + should reduce nantaizi bush samples to about 266 while leaving scrub ground + cover intact. diff --git a/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/report.md b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/report.md new file mode 100644 index 0000000..f8f4763 --- /dev/null +++ b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/report.md @@ -0,0 +1,57 @@ +# Smooth Cesium preview vehicle cruise report + +## Root Cause + +The first preview-only optimization helped but did not address the main load. +User testing showed Follow still stuttered in scrub-heavy areas, while disabling +`Scene` improved smoothness substantially. + +Current nantaizi GLB before the scrub-density change: + +- GLB nodes: `1271` +- Scrub bush nodes: `707` +- Scrub polygons: `5` + +The route data is small (`9` route segments, preview uses at most `5` vehicles), +so route interpolation is not the primary bottleneck. + +## Changes + +- `scripts/lib/cesium-preview.js` + - Route helper polyline is now opaque instead of alpha blended. + - Route helper polyline uses `arcType: Cesium.ArcType.NONE`. + - Vehicle orientation and chase-follow camera callbacks reuse scratch + `JulianDate`, `HeadingPitchRoll`, `Quaternion`, and `Matrix4` objects. + - Default Cesium resolution and antialiasing are preserved; no blurry + performance mode is enabled by default. +- `blender/generate_scene.py` + - `SCRUB_BUSH_SPACING`: `0.82 -> 1.8` + - `SCRUB_BUSH_LIMIT_PER_PATCH`: `180 -> 60` + - Scrub ground cover remains unchanged. + +## Validation + +Commands run: + +```bash +node --check scripts/lib/cesium-preview.js +python3 -m py_compile blender/generate_scene.py +python3 -m unittest blender/tests/test_pure.py +npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender,cesium,preview +node scripts/glb-digest.js outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.glb --out /tmp/nantaizi-after-scrub-glb-digest.json +``` + +Results after rebuild: + +- `SCENE_DONE` reports `scrub_bushes: 266`. +- GLB nodes: `830` +- Scrub bush nodes: `266` +- Scrub polygons in metadata: `5` +- Route segments in preview route JSON: `9` +- GLB size: about `31.96 MB` + +## Deferred + +If Follow still stutters after this density reduction, the next root-level work +should target the bush asset itself: lower-poly bush mesh, explicit LOD, or a +proper instancing path supported by Cesium/glTF. diff --git a/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/task.json b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/task.json new file mode 100644 index 0000000..a45a0a9 --- /dev/null +++ b/.trellis/tasks/08-03-smooth-cesium-preview-vehicle-cruise/task.json @@ -0,0 +1,26 @@ +{ + "id": "smooth-cesium-preview-vehicle-cruise", + "name": "smooth-cesium-preview-vehicle-cruise", + "title": "Smooth Cesium preview vehicle cruise", + "description": "Optimize Cesium preview vehicle cruise smoothness without changing Blender or GLB output.", + "status": "in_progress", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "dingkang", + "assignee": "dingkang", + "createdAt": "2026-08-03", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/blender/generate_scene.py b/blender/generate_scene.py index 85b4350..10f1caa 100644 --- a/blender/generate_scene.py +++ b/blender/generate_scene.py @@ -90,9 +90,9 @@ TUFT_TINT = (0.15, 0.52, 0.09) TUFT_TINT_FACTOR = 0.66 SCRUB_BUSH_MODEL = os.path.join(CUSTOM_MODEL_ROOT, "bush", "bush.glb") -SCRUB_BUSH_SPACING = 0.82 +SCRUB_BUSH_SPACING = 1.8 SCRUB_BUSH_INSET = 0.38 -SCRUB_BUSH_LIMIT_PER_PATCH = 180 +SCRUB_BUSH_LIMIT_PER_PATCH = 60 SCRUB_BUSH_HEIGHT = 1.575 SCRUB_BUSH_SCALE_JITTER = 0.16 SCRUB_BUSH_LEAF_TINT = (0.075, 0.31, 0.055) diff --git a/docs/changelog.md b/docs/changelog.md index 8178b52..fcb206a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -36,6 +36,14 @@ - 清理 D3 过期注释债:`tuft_density_wave()` 的说明不再引用已删除的 hedge banding 对比,`.trellis/spec/guides/artifact-parity-guide.md` 也不再把 D3 列为待修缺陷; 只改注释 / spec / changelog,未改 Blender 场景生成逻辑。D1 / D2 继续保留为已知约束。 +- 优化 Cesium 预览车辆巡航流畅度:路线辅助线改为不透明非测地线,并复用车辆朝向与 + 跟车相机热路径里的 `JulianDate` / HPR / 矩阵对象,减少巡航时的透明合成和 GC 抖动。 + 默认保留 Cesium 原分辨率和抗锯齿,避免预览变糊;只影响 + `scripts/lib/cesium-preview.js`,不改变 Blender / GLB / metadata 产物。 +- 从根因降低灌木密集区 Follow 卡顿:`SCRUB_BUSH_SPACING` 从 `0.82m` 调到 `1.8m`, + `SCRUB_BUSH_LIMIT_PER_PATCH` 从 `180` 降到 `60`,nantaizi 的 scrub bush nodes + `707 -> 266`,GLB 总 nodes `1271 -> 830`;scrub 地面覆盖仍保留 5 个面,默认画质 + 不降级。 ## 2026-07-31(三)远看发黑的真正原因:反照率没被提亮 diff --git a/scripts/lib/cesium-preview.js b/scripts/lib/cesium-preview.js index 49822a6..6bfae7b 100644 --- a/scripts/lib/cesium-preview.js +++ b/scripts/lib/cesium-preview.js @@ -365,7 +365,8 @@ polyline: { positions: Cesium.Cartesian3.fromDegreesArrayHeights(flat), width: 2, - material: routeColor.withAlpha(0.75), + material: routeColor, + arcType: Cesium.ArcType.NONE, clampToGround: false } }); @@ -434,9 +435,12 @@ const up = new Cesium.Cartesian3(); const east = new Cesium.Cartesian3(); const north = new Cesium.Cartesian3(); + const aheadTime = new Cesium.JulianDate(); + const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0); + const base = new Cesium.Quaternion(); return new Cesium.CallbackProperty((time, result) => { routePosition(route, start, time, speed, current); - const aheadTime = Cesium.JulianDate.addSeconds(time, 0.8, new Cesium.JulianDate()); + Cesium.JulianDate.addSeconds(time, 0.8, aheadTime); routePosition(route, start, aheadTime, speed, ahead); Cesium.Cartesian3.subtract(ahead, current, direction); if (Cesium.Cartesian3.magnitudeSquared(direction) < 0.0001) { @@ -454,10 +458,13 @@ Cesium.Cartesian3.normalize(north, north); const eastComponent = Cesium.Cartesian3.dot(direction, east); const northComponent = Cesium.Cartesian3.dot(direction, north); - const heading = Math.atan2(eastComponent, northComponent); - const base = Cesium.Transforms.headingPitchRollQuaternion( + hpr.heading = Math.atan2(eastComponent, northComponent); + Cesium.Transforms.headingPitchRollQuaternion( current, - new Cesium.HeadingPitchRoll(heading, 0.0, 0.0) + hpr, + undefined, + undefined, + base ); return Cesium.Quaternion.multiply(base, correction, result || new Cesium.Quaternion()); }, false); @@ -470,6 +477,9 @@ const scratchEast = new Cesium.Cartesian3(); const scratchNorth = new Cesium.Cartesian3(); const scratchUp = new Cesium.Cartesian3(); + const scratchPreviousTime = new Cesium.JulianDate(); + const scratchHpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0); + const scratchTransform = new Cesium.Matrix4(); const offset = new Cesium.Cartesian3(); const state = { enabled: false, distance: 36.0, height: 18.0 }; @@ -478,8 +488,8 @@ const positions = positionsProvider(); const position = positions.getValue(time, scratchPosition); if (!position) return; - const previousTime = Cesium.JulianDate.addSeconds(time, -0.8, new Cesium.JulianDate()); - const previous = positions.getValue(previousTime, scratchPrevious); + Cesium.JulianDate.addSeconds(time, -0.8, scratchPreviousTime); + const previous = positions.getValue(scratchPreviousTime, scratchPrevious); if (previous) { Cesium.Cartesian3.subtract(position, previous, scratchDirection); } else { @@ -501,13 +511,16 @@ const eastComponent = Cesium.Cartesian3.dot(scratchDirection, scratchEast); const northComponent = Cesium.Cartesian3.dot(scratchDirection, scratchNorth); - const heading = Math.atan2(eastComponent, northComponent); + scratchHpr.heading = Math.atan2(eastComponent, northComponent); Cesium.Cartesian3.fromElements(0.0, -state.distance, state.height, offset); - const transform = Cesium.Transforms.headingPitchRollToFixedFrame( + Cesium.Transforms.headingPitchRollToFixedFrame( position, - new Cesium.HeadingPitchRoll(heading, 0.0, 0.0) + scratchHpr, + undefined, + undefined, + scratchTransform ); - viewer.camera.lookAtTransform(transform, offset); + viewer.camera.lookAtTransform(scratchTransform, offset); } function onWheel(event) {