diff --git a/docs/superpowers/plans/2026-06-07-m1-phase4-render.md b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md new file mode 100644 index 0000000..fb785fb --- /dev/null +++ b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md @@ -0,0 +1,64 @@ +# M1 Phase 4:三维渲染扩展(render 层 + 2D/3D + voxel + 散点/异常 + DEM)实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development。Steps 用 `- [ ]`。 + +**Goal:** 把工作台中央视图从"内联单一网格渲染"升级为正式 `render` 层(Scene + actor 工厂 + 相机预设),并补齐 M1 三维内容:2D/3D 切换、dd_voxel 体绘制与切片、散点(#17)、异常叠加、DEM 地形。 + +**Architecture:** 新建 `src/render/`(依赖 VTK + core,不依赖 Qt 业务;由 app 的 QVTK widget 承载)。`Scene` 独占 `vtkRenderer`/`vtkRenderWindow`,actor 工厂按 core 模型产 actor;相机预设 Top2D(正交俯视)/Free3D(透视轨道);UI 加 2D/3D 切换。逐步把 `main.cpp::renderGrid` 迁入 `render/actors/GridContourActor`。 + +**Tech Stack:** VTK 9.6 / core 模型 / Qt(仅 app 层 UI)。统一 Release,构建经 `cmd /c "...\external\dev.bat "`(PowerShell 调)。app 构建前先 `taskkill /IM geopro_desktop.exe /F`。 + +**已就绪输入:** `geopro_data` 的 `LocalSampleRepository`(loadGrid/loadScatter/loadColorScale/loadAnomalies);`geopro_core` 的 `IdwInterpolator`(→ScalarVolume)、`ColorScale`、`Grid/ScatterField/Anomaly`;Python 已验证 voxel(`tools/validate_voxel.py`:两交叉剖面 IDW 成体、15.9% 有约束)与 #17/#18(`tools/validate_samples.py`)。 + +--- + +## Task 1:render 层基础 + 2D/3D 相机切换 + +**Files:** `src/render/CMakeLists.txt`、`src/render/Scene.{hpp,cpp}`、`src/render/ColorLutBuilder.{hpp,cpp}`、`src/render/actors/GridContourActor.{hpp,cpp}`、`src/render/CameraPreset.{hpp,cpp}`、`src/CMakeLists.txt`、`src/app/main.cpp`(改用 render 层)、`src/app/CMakeLists.txt`(链 geopro_render)、`tests/render/test_color_lut.cpp` + +- [ ] **Step 1:** `src/render/CMakeLists.txt`:`find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingOpenGL2 InteractionStyle)`;`add_library(geopro_render STATIC ...)` 链 `geopro_core ${VTK_LIBRARIES}`;`vtk_module_autoinit(TARGETS geopro_render MODULES ${VTK_LIBRARIES})`;**AUTOMOC OFF**。`src/CMakeLists.txt` 加 `add_subdirectory(render)`(net 之后、app 之前)。 +- [ ] **Step 2:** `ColorLutBuilder`:`vtkSmartPointer build(const core::ColorScale&, double vmin, double vmax, int n=256)` —— 用 `cs.colorAt(val)` 填 N 级 LUT。可单测:给已知 colorBar,断言某档颜色。(render 测试需 VTK,tests 已链 VTK 模块——按需在 tests/CMakeLists 补 VTK 组件。)单测 `tests/render/test_color_lut.cpp`:构造 ColorScale 两档,build LUT,`lut->GetColor(val,rgb)` 断言取下界色。 +- [ ] **Step 3:** `GridContourActor`:`struct GridActors{vtkSmartPointer bands, edges;}`;`GridActors build(const core::Grid&, const core::ColorScale&)`——把 `main.cpp::renderGrid` 的管线(vtkImageData→DataSetSurfaceFilter→BandedPolyDataContourFilter(GenerateContourEdges)→mapper+LUT)迁过来,返回 actors(不直接操作 renderer)。 +- [ ] **Step 4:** `Scene`:持有 `vtkSmartPointer`;`void clear()`(RemoveAllViewProps)、`void add(vtkActor*)`、`vtkRenderer* renderer()`;`CameraPreset`:`void applyTop2D(vtkRenderer*)`(ParallelProjectionOn + 俯视 XY:相机沿 +Z 看 -Z,up=+Y,ResetCamera)、`void applyFree3D(vtkRenderer*)`(ParallelProjectionOff + 斜视,ResetCamera)。 +- [ ] **Step 5:** `main.cpp` 改:`buildWorkbench` 里渲染数据集时用 `Scene::clear()` + `GridContourActor::build` + `Scene::add`;中央工具条加两个按钮/QActionGroup「二维」「三维」,点击调 `CameraPreset::applyTop2D/applyFree3D(scene.renderer())` + `renderWindow->Render()`。默认二维。 +- [ ] **Step 6:** 构建(先 taskkill)+ 冒烟运行;ctest `-R ColorLut` 过。**人工验证**:登录进工作台 → 点数据集出剖面 → 点"三维"可旋转、"二维"回正交俯视。 +- [ ] **Step 7:** 提交 `feat(render): render 层(Scene/ColorLut/GridContourActor/相机预设) + 2D/3D 切换`。 + +--- + +## Task 2:dd_voxel 体绘制 + 交互切片(核心难点) + +**Files:** `src/render/actors/VoxelActor.{hpp,cpp}`、`src/app/main.cpp`(3D 模式可加载体素)、`tests/render/test_voxel_build.cpp` + +- [ ] `VoxelActor::build(const core::ScalarVolume&, const core::ColorScale&)` → `vtkSmartPointer`:`ScalarVolume`→`vtkImageData`(dims/spacing/origin,标量;NaN 留空→不透明度 0)→ `vtkGPUVolumeRayCastMapper` + `vtkColorTransferFunction`(由 ColorScale)+ `vtkPiecewiseFunction`(NaN/低值透明)。render 的 VTK 组件加 `RenderingVolumeOpenGL2`。 +- [ ] 体素数据来源:`main.cpp` 用 `repo.loadScatter("grid1")`(剖面1)+ 若有剖面2 一并;调 `IdwInterpolator`(GridSpec 由散点包络 + maxDist)→ ScalarVolume → VoxelActor。(M1 数据为两交叉剖面,体素为"十字片",已知;可信满体需更多线,记 §14。) +- [ ] 切片:`vtkImagePlaneWidget` 或 `vtkResliceCursorWidget`,挂 interactor,3D 模式启用、2D 模式禁用;拖动出 dd_slice 截面。 +- [ ] 单测 `test_voxel_build`:小 PointSet → IdwInterpolator → VoxelActor::build 不崩、vtkImageData dims 正确。 +- [ ] 构建+冒烟;**人工验证**:3D 模式显示体素 + 可拖切片。提交 `feat(render): dd_voxel 体绘制 + 交互切片`。 + +--- + +## Task 3:散点(#17) + 异常叠加 + +**Files:** `src/render/actors/ScatterActor.{hpp,cpp}`、`src/render/actors/AnomalyActor.{hpp,cpp}`、`main.cpp` + +- [ ] `ScatterActor::build(const core::ScatterField&, const core::ColorScale&)`→`vtkActor`:`vtkPolyData`(verts,点用 x/y/(z))+ 标量 v + LUT;点大小适中。(#17 倒三角彩色散点) +- [ ] `AnomalyActor::build(const std::vector&)`→`vtkActor`(或多 actor):按 markType——点(`vtkGlyph3D`)/线(polydata,dashed,lineColor/width)/面(`vtkPolygon`填充+边);坐标用 localPts。叠加在剖面上。 +- [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`。 + +--- + +## Task 4:DEM 地形 + 影像(④) + +**Files:** `src/render/actors/TerrainActor.{hpp,cpp}`、`vcpkg.json`(加 gdal)、`main.cpp` + +- [ ] `vcpkg.json` 加 `"gdal"`(重依赖,首次编译久)。`TerrainActor::build(demPath, imagePath)`:GDAL 读 `dem.tif`(高程栅格)+ `image.tif`+`image.tfw`;影像 **EPSG:3857**、DEM/剖面坐标不同 → 用 `core::CrsTransform` 重投影到世界系(设计 §5/§M-1);`vtkImageData`→`vtkWarpScalar`(高程抬升)+ 影像作纹理贴面。 +- [ ] main.cpp:3D 模式可叠加地形面。**人工验证**:地形起伏 + 影像贴图(图 #05 样)。提交 `feat(render): DEM 地形 + 影像叠加(GDAL+PROJ 重投影)`。 + +--- + +## Self-Review 备注 +- 覆盖设计 §4(单一场景/相机预设/数据→actor 管线)、§K-5(① 散点/异常 + ② voxel/切片 + ④ DEM)、§10(IDW→ScalarVolume→VTK 在 render 转换)。 +- 铁律:render 依赖 VTK+core,不碰 Qt 业务;core 仍纯净。 +- voxel 可信度依赖输入(两交叉剖面→十字片,§14);DEM 影像异源 CRS 必重投影(§5)。 +- 颜色精确映射(colorBar 非均匀)在 ColorLutBuilder/VoxelActor 用 ColorScale.colorAt 落实(修正 spike S3 的偏蓝)。