diff --git a/docs/superpowers/STATUS.md b/docs/superpowers/STATUS.md index 28f027f..c6c6482 100644 --- a/docs/superpowers/STATUS.md +++ b/docs/superpowers/STATUS.md @@ -17,9 +17,9 @@ - **中央 二维地图 / 三维视图**(两个**真内容**,非相机切换): - 二维地图 = `MapLineActor`:测线 `lat/lon` 轨迹**红线**俯视(浅底),像地图。 - 三维视图 = `CurtainActor`:沿测线的**竖直断面墙**(分段色带,z 纵向夸张×3,沿弯曲测线弯)。 - - **下方 数据详情**:单击数据集 → `GridContourActor` 平面反演剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5)。 + - **下方 数据详情**:工具条「反演剖面/原数据」切换。单击数据集 → 反演剖面=`GridContourActor` 平面剖面(#18,colorBar 真实非均匀分段值上色,纵向夸张×1.5);原数据=`ScatterActor` 彩色方块散点(#17,x=距离/y=深度取负,用散点自带色阶)。 - **右 属性**:名称/网格 nx×ny/vmin·vmax。 -- 单元测试累计 ~28 个全绿(core/data/net/render);离屏 `verify_section/map/curtain_3d.png` 均核对正确。 +- 单元测试累计 **31 个全绿**(core/data/net/render;含 Scatter 2 个、修复了陈旧的 Curtain mapper 类型断言);离屏 `verify_section/map/curtain_3d/scatter.png` 均核对正确(scatter 与 Python 真值 ref_17 吻合)。 ## 2. 各 Phase 完成度 @@ -29,7 +29,7 @@ | P1 | core(LocalFrame/模型/ColorScale/IDW/CrsTransform/GeoLocalFrame) | ✅ | | P2 | data(解析器/LocalSampleRepository)+ 对象树 | ✅ | | P3 | 登录(RsaEncryptor/ApiClient/AuthService/LoginWindow) | ✅(**Credential 记住免登录未做**) | -| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18) | 🔶 **核心三视图已对**;**散点#17 / 异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** | +| P4 | 渲染:render 层 + 二维地图(线)+ 三维视图(帘面)+ 数据详情(#18/#17) | 🔶 **核心三视图 + 散点#17 已对**;**异常叠加 / DEM地形 / dd_voxel回归 / 底图瓦片 未做** | ## 3. 构建约定(**机器本地**) @@ -60,8 +60,8 @@ ## 6. 已知问题 / 待办(P4 下次会话) -1. **散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),可作数据详情的"原数据"视图。 -2. **异常叠加**:`AnomalyActor`(markType 点/线/面),叠加在剖面/帘面上(异常数据已能解析)。 +1. ~~**散点 #17**:`ScatterActor`(剖面原数据 2597 点彩色散点),数据详情"原数据"视图~~ ✅ **已完成**(2026-06-08,离屏 PNG 核对吻合 Python 真值,接入数据详情「反演剖面/原数据」切换;app 待人工登录肉眼复核交互)。 +2. **异常叠加**:`AnomalyActor`(markType 点/线/面),叠加在剖面/帘面上(异常数据已能解析)。**下一项**。 3. **DEM/影像地形**:加 vcpkg `gdal`;GDAL 读 dem.tif/image.tif;**影像 EPSG:3857 必须 PROJ 重投影到世界系**;`vtkWarpScalar` 地形面 + 纹理。 4. **dd_voxel 回归**:需先确认项目 CRS,使散点 projX/Y 能转到 lat/lon 世界系,与帘面配准;VoxelActor 已就绪。 5. **底图瓦片**(二维地图,天地图/Mapbox):M1.5。 @@ -84,4 +84,5 @@ cmd /c "...\external\dev.bat cmake --build build/release --target render_verify" - 计划:`plans/` 下 phase0-4 + `2026-06-07-m1-view-redesign.md`(正确视图模型)+ spike-report - 环境:`../ENV_SETUP_Windows.md` - 验证脚本:`tools/validate_samples.py`(#17/#18 真值)、`tools/validate_voxel.py`(voxel)、`tests/spike/render_verify.cpp`(app 渲染积木离屏核对) -- 渲染积木(render 层):`MapLineActor`(测线线)、`CurtainActor`(断面墙)、`GridContourActor`(#18 平面)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。 +- 渲染积木(render 层):`MapLineActor`(测线线)、`CurtainActor`(断面墙)、`GridContourActor`(#18 平面)、`ScatterActor`(#17 散点)、`VoxelActor`(体,未接 UI)、`ColorLutBuilder`、`CameraPreset`、`Scene`;`core::GeoLocalFrame`(经纬→局部米)。 +- 散点 #17 专属色阶经 `LocalSampleRepository::loadScatterColorScale`(剖面原数据自带色阶,范围/分段与网格色阶不同)。 diff --git a/docs/superpowers/plans/2026-06-07-m1-phase4-render.md b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md index d53a130..8c7ee62 100644 --- a/docs/superpowers/plans/2026-06-07-m1-phase4-render.md +++ b/docs/superpowers/plans/2026-06-07-m1-phase4-render.md @@ -5,7 +5,7 @@ > **⚠️ 状态(2026-06-07,务必先读 `2026-06-07-m1-view-redesign.md` + STATUS.md):** > - **Task 1(render 层 + 相机预设)**:✅ 已完成。但中央"单场景平躺剖面 + 2D/3D 仅换相机"的做法**已被 view-redesign 取代** —— 现在中央是 **二维地图(测线线)/ 三维视图(竖直帘面)两种内容**(详见 view-redesign 的"实施结果")。 > - **Task 2(dd_voxel 体绘制+切片)**:🔶 VoxelActor 代码完成,但**已从 UI 移除/搁置**(散点 projX/Y 真实 CRS 未确认,无法与 lat/lon 帘面配准)。**CRS 确认后再回归**。 -> - **Task 3(散点#17 + 异常叠加)**:⬜ 未做(P4 下次)。 +> - **Task 3(散点#17 + 异常叠加)**:🔶 **散点#17 ✅ 已完成**(2026-06-08,`ScatterActor` 离屏 PNG 核对吻合 Python 真值 ref_17,接入数据详情「反演剖面/原数据」切换,+2 单测,全 31 测试绿;app 待人工登录复核)。**异常叠加 ⬜ 未做(下一项)**。 > - **Task 4(DEM/影像地形)**:⬜ 未做(P4 下次,需先确认 CRS + 加 gdal)。 > 渲染必须用 `tests/spike/render_verify.cpp` 离屏 PNG 核对(本会话教训)。 @@ -49,7 +49,7 @@ **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 倒三角彩色散点) +- [x] `render::buildScatter(const core::ScatterField&, const core::ColorScale&, float pointSize)`→`vtkActor`:`vtkPolyData`(verts,点 x/-y/0,与 #18 同坐标系)+ 点标量 v + LUT(色阶范围优先 colorBar 真实分段值);方块点 SetPointSize。色阶用散点自带文件(`loadScatterColorScale`)。离屏 `verify_scatter.png` 核对吻合 ref_17。 - [ ] `AnomalyActor::build(const std::vector&)`→`vtkActor`(或多 actor):按 markType——点(`vtkGlyph3D`)/线(polydata,dashed,lineColor/width)/面(`vtkPolygon`填充+边);坐标用 localPts。叠加在剖面上。 - [ ] main.cpp:数据集详情可切换"网格/散点"显示;异常按需叠加。**人工验证**:散点图 ~#17;异常虚线叠在剖面上。提交 `feat(render): 散点(#17) + 异常叠加`。 diff --git a/src/app/main.cpp b/src/app/main.cpp index 736792d..7fffc0e 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -4,8 +4,10 @@ // 二维地图 = 对每个勾选数据集 buildSurveyLine(lat/lon 红线俯视,z=0)+ applyTop2D(浅底背景)。 // 三维视图 = 对每个勾选数据集 buildCurtain(竖直断面墙),actor SetScale(1,1,3) 纵向夸张 + applyFree3D(白底)。 // 切视图 / 勾选变化 → 按当前勾选集重建对应内容。 -// - 下方「数据详情」:独立 QVTK 小视图。单击某 DS → 显示该数据集平面反演剖面(#18 banded 等值面+等值线, -// 两 actor SetScale(1,1.5,1) 纵向夸张,平躺俯视正交)+ 属性。 +// - 下方「数据详情」:独立 QVTK 小视图 + 工具条「反演剖面 / 原数据」切换。单击某 DS → 显示该数据集: +// 反演剖面 = #18 banded 等值面+等值线(两 actor SetScale(1,1.5,1) 纵向夸张)。 +// 原数据 = #17 彩色散点(buildScatter,x=距离/y=深度,按散点自带色阶上色)。 +// 两者皆平躺俯视正交 + 属性。 // - 右 属性:选中数据集属性文本。 // 世界系:启动 loadGrid("grid1") 取一次,用其 lat/lon 中位/均值作 GeoLocalFrame(全项目共享,保证多视图配准)。 @@ -43,6 +45,7 @@ #include "actors/CurtainActor.hpp" #include "actors/GridContourActor.hpp" #include "actors/MapLineActor.hpp" +#include "actors/ScatterActor.hpp" #include "geo/GeoLocalFrame.hpp" @@ -108,6 +111,12 @@ double median(std::vector v) // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 enum class ViewMode { Map2D, View3D }; +// 数据详情显示内容(默认反演剖面)。反演剖面=#18 banded;原数据=#17 散点。 +enum class DetailMode { Section18, Scatter17 }; + +// #17 散点屏幕像素方块边长。 +constexpr float kScatterPointSize = 4.0F; + // 纵向夸张倍数:三维断面墙沿 z 拉伸成墙;数据详情 #18 沿 y 拉伸填面板。 constexpr double kCurtainZScale = 3.0; constexpr double kDetailYScale = 1.5; @@ -176,8 +185,27 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re vtkRenderer* detailRendererPtr = detailRenderer.Get(); vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get(); + // 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。 + auto* detailContainer = new QWidget(); + auto* detailLayout = new QVBoxLayout(detailContainer); + detailLayout->setContentsMargins(0, 0, 0, 0); + detailLayout->setSpacing(0); + + auto* detailToolBar = new QToolBar(); + auto* detailGroup = new QActionGroup(detailToolBar); + detailGroup->setExclusive(true); + auto* actSection = detailToolBar->addAction(QStringLiteral("反演剖面")); + auto* actScatter = detailToolBar->addAction(QStringLiteral("原数据")); + actSection->setCheckable(true); + actScatter->setCheckable(true); + detailGroup->addAction(actSection); + detailGroup->addAction(actScatter); + actSection->setChecked(true); // 默认反演剖面 (#18) + detailLayout->addWidget(detailToolBar); + detailLayout->addWidget(detailWidget, 1); + auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情")); - detailDock->setWidget(detailWidget); + detailDock->setWidget(detailContainer); // 放在中央视图下方。 dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea); @@ -252,24 +280,25 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re rebuildCentral(); }); - // ── 单击 DS → 下方数据详情显示平面反演剖面 + 右侧属性(与勾选区分;不改帘面可见性)── - QObject::connect( - tree, &QTreeWidget::itemClicked, tree, - [&repo, propLabel, detailRendererPtr, detailRenderWindowPtr](QTreeWidgetItem* item, int) { - const QString dsId = item->data(0, kRoleDsId).toString(); - if (dsId.isEmpty()) return; // GS/TM 节点无详情 - const QString ddType = item->data(0, kRoleDdType).toString(); - const QString name = item->text(0); - if (ddType != "dd_section") return; + // ── 数据详情共享状态 + 重建 ────────────────────────────────────────── + // 当前选中数据集 id(空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。 + auto currentDsId = std::make_shared(); + auto detailMode = std::make_shared(DetailMode::Section18); - const std::string id = dsId.toStdString(); + // 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。 + auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, currentDsId, + detailMode]() { + detailRendererPtr->RemoveAllViewProps(); + if (currentDsId->isEmpty()) { // 未选数据集:清空即可 + detailRenderWindowPtr->Render(); + return; + } + const std::string id = currentDsId->toStdString(); + if (*detailMode == DetailMode::Section18) { + // 反演剖面:#18 banded 等值面 + 等值线,两 actor 纵向夸张 1.5x(沿 y)。 const auto g = repo.loadGrid(id); const auto cs = repo.loadColorScale(id); - - // 下方数据详情:平面反演剖面(#18 banded 等值面 + 等值线),平躺俯视正交。 - // 两个 actor 都纵向夸张 1.5x(沿 y)填满面板。 const auto actors = geopro::render::buildGridContour(g, cs); - detailRendererPtr->RemoveAllViewProps(); if (actors.bands) { actors.bands->SetScale(1.0, kDetailYScale, 1.0); detailRendererPtr->AddViewProp(actors.bands); @@ -278,17 +307,54 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re actors.edges->SetScale(1.0, kDetailYScale, 1.0); detailRendererPtr->AddViewProp(actors.edges); } - geopro::render::applyTop2D(detailRendererPtr); - detailRendererPtr->ResetCamera(); - detailRenderWindowPtr->Render(); + } else { + // 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。 + const auto s = repo.loadScatter(id); + const auto scs = repo.loadScatterColorScale(id); + auto a = geopro::render::buildScatter(s, scs, kScatterPointSize); + if (a) { + a->SetScale(1.0, kDetailYScale, 1.0); + detailRendererPtr->AddViewProp(a); + } + } + geopro::render::applyTop2D(detailRendererPtr); + detailRendererPtr->ResetCamera(); + detailRenderWindowPtr->Render(); + }; - // 右侧属性。 + // ── 单击 DS → 记选中 + 重建数据详情 + 右侧属性(与勾选区分;不改帘面可见性)── + QObject::connect( + tree, &QTreeWidget::itemClicked, tree, + [&repo, propLabel, currentDsId, rebuildDetail](QTreeWidgetItem* item, int) { + const QString dsId = item->data(0, kRoleDsId).toString(); + if (dsId.isEmpty()) return; // GS/TM 节点无详情 + const QString ddType = item->data(0, kRoleDdType).toString(); + if (ddType != "dd_section") return; + const QString name = item->text(0); + + *currentDsId = dsId; + rebuildDetail(); + + // 右侧属性(数据集级,与详情模式无关)。 + const auto g = repo.loadGrid(dsId.toStdString()); propLabel->setText( QStringLiteral("数据集: %1\n类型: 剖面网格 (dd_section)\n网格: %2 x %3\n" "vmin / vmax: %4 / %5") .arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)); }); + // ── 数据详情工具条「反演剖面/原数据」:切模式 → 重建数据详情 ── + QObject::connect(actSection, &QAction::triggered, detailWidget, + [detailMode, rebuildDetail]() { + *detailMode = DetailMode::Section18; + rebuildDetail(); + }); + QObject::connect(actScatter, &QAction::triggered, detailWidget, + [detailMode, rebuildDetail]() { + *detailMode = DetailMode::Scatter17; + rebuildDetail(); + }); + // ── 工具条「二维地图/三维视图」:切换互斥视图 → 按当前勾选集重建对应内容 ── QObject::connect(act2D, &QAction::triggered, vtkWidget, [viewMode, rebuildCentral]() { *viewMode = ViewMode::Map2D; diff --git a/src/data/repo/LocalSampleRepository.cpp b/src/data/repo/LocalSampleRepository.cpp index 30971d5..f2cf2af 100644 --- a/src/data/repo/LocalSampleRepository.cpp +++ b/src/data/repo/LocalSampleRepository.cpp @@ -20,6 +20,7 @@ constexpr const char* kDsId = "grid1"; constexpr const char* kGridFile = u8"剖面网格数据1.txt"; constexpr const char* kColorScaleFile = u8"剖面网格数据的色阶数据1.txt"; constexpr const char* kScatterFile = u8"剖面原数据1.txt"; +constexpr const char* kScatterColorScaleFile = u8"剖面原数据的色阶数据1.txt"; // dd_voxel:两条交叉剖面散点(约 78° 相交),合并为体素插值输入。 constexpr const char* kVoxelScatterFile1 = u8"剖面原数据1.txt"; constexpr const char* kVoxelScatterFile2 = u8"剖面原数据2.txt"; @@ -93,6 +94,11 @@ std::vector LocalSampleRepository::loadAnomalies(const std::string& dsI return parseAnomalies(readFile(kAnomalyFile)); } +ColorScale LocalSampleRepository::loadScatterColorScale(const std::string& dsId) { + requireKnownDs(dsId); + return parseColorScale(readFile(kScatterColorScaleFile)); +} + std::vector LocalSampleRepository::loadVoxelScatters() { std::vector out; out.push_back(parseScatter(readFile(kVoxelScatterFile1))); diff --git a/src/data/repo/LocalSampleRepository.hpp b/src/data/repo/LocalSampleRepository.hpp index 026ddc8..bc73d53 100644 --- a/src/data/repo/LocalSampleRepository.hpp +++ b/src/data/repo/LocalSampleRepository.hpp @@ -17,6 +17,10 @@ public: geopro::core::ColorScale loadColorScale(const std::string& dsId) override; std::vector loadAnomalies(const std::string& dsId) override; + // 散点原数据(#17)专属色阶:剖面原数据自带的色阶文件(与网格色阶不同:分段值范围更大)。 + // 具体类专有方法(不进 IDatasetRepository 接口);散点点为不透明,alpha 量纲差异无影响。 + geopro::core::ColorScale loadScatterColorScale(const std::string& dsId); + // dd_voxel 输入:读两条交叉剖面散点(剖面原数据1.txt + 剖面原数据2.txt), // 返回两者,供体素插值合并。具体类专有方法(不进 IDatasetRepository 接口)。 std::vector loadVoxelScatters(); diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index 5c817d0..7650917 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets) add_library(geopro_render STATIC - Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp) + Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp) target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES}) target_compile_features(geopro_render PUBLIC cxx_std_17) diff --git a/src/render/actors/ScatterActor.cpp b/src/render/actors/ScatterActor.cpp new file mode 100644 index 0000000..1e77729 --- /dev/null +++ b/src/render/actors/ScatterActor.cpp @@ -0,0 +1,87 @@ +#include "actors/ScatterActor.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ColorLutBuilder.hpp" + +namespace geopro::render { + +namespace { +// LUT 级数。 +constexpr int kLutLevels = 256; +} // namespace + +vtkSmartPointer buildScatter(const geopro::core::ScatterField& s, + const geopro::core::ColorScale& cs, + float pointSize) +{ + const std::size_t n = s.v.size(); + + // 退化输入:无值或 x/y 长度不足 → 空 actor(调用方仍可安全 add,mapper 无输入则不绘制)。 + if (n == 0 || s.x.size() < n || s.y.size() < n) { + return vtkSmartPointer::New(); + } + + vtkNew points; + points->SetNumberOfPoints(static_cast(n)); + + vtkNew verts; // 每点一个 vtkVertex(GL_POINTS),渲染为方块点 + + vtkNew sc; + sc->SetName("v"); + sc->SetNumberOfTuples(static_cast(n)); + + for (std::size_t i = 0; i < n; ++i) { + const auto id = static_cast(i); + // y=深度(越大越深)→取负,使深部在下、剖面不倒置(与 GridContourActor 一致)。 + points->SetPoint(id, s.x[i], -s.y[i], 0.0); + verts->InsertNextCell(1, &id); + sc->SetValue(id, s.v[i]); + } + + vtkNew poly; + poly->SetPoints(points); + poly->SetVerts(verts); + poly->GetPointData()->SetScalars(sc); + + // 色阶范围:优先 colorBar 真实分段值(非均匀),否则回退到 v 实测范围。 + const std::vector stops = cs.stopValues(); + double vmin, vmax; + if (stops.size() >= 2) { + vmin = stops.front(); + vmax = stops.back(); + } else { + vmin = s.v.front(); + vmax = vmin; + for (double v : s.v) { + if (v < vmin) vmin = v; + if (v > vmax) vmax = v; + } + if (vmin >= vmax) vmax = vmin + 1.0; + } + + auto lut = buildLut(cs, vmin, vmax, kLutLevels); + + vtkNew mapper; + mapper->SetInputData(poly); + mapper->SetScalarModeToUsePointData(); + mapper->SetLookupTable(lut); + mapper->SetScalarRange(vmin, vmax); + + auto actor = vtkSmartPointer::New(); + actor->SetMapper(mapper); + actor->GetProperty()->SetPointSize(pointSize); + return actor; +} + +} // namespace geopro::render diff --git a/src/render/actors/ScatterActor.hpp b/src/render/actors/ScatterActor.hpp new file mode 100644 index 0000000..57f583f --- /dev/null +++ b/src/render/actors/ScatterActor.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +#include "model/ColorScale.hpp" +#include "model/Field.hpp" + +namespace geopro::render { + +// 剖面原数据散点(#17):把 ScatterField 的每个采样点画成按 v 着色的方块点。 +// 坐标与数据详情 #18 一致:x=xlist(距离)、y=-ylist(深度向下)、z=0(face-on 俯视读)。 +// pointSize 为屏幕像素方块边长。色阶范围优先取 colorBar 真实分段值(非均匀), +// 否则回退到 v 的实际 min/max。退化输入(无点)返回空 actor,调用方可安全 add。 +vtkSmartPointer buildScatter(const geopro::core::ScatterField& s, + const geopro::core::ColorScale& cs, + float pointSize = 4.0F); + +} // namespace geopro::render diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 300b12c..c60e3f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -64,6 +64,8 @@ target_sources(geopro_tests PRIVATE render/test_color_lut.cpp) target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp) # Curtain:buildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) 非空 actor + 点数=nx*ny。 target_sources(geopro_tests PRIVATE render/test_curtain.cpp) +# Scatter(#17):buildScatter(ScatterField+ColorScale->vtkPolyData 彩色散点) 点数/verts/上色/y取负。 +target_sources(geopro_tests PRIVATE render/test_scatter.cpp) target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES}) vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES}) diff --git a/tests/render/test_curtain.cpp b/tests/render/test_curtain.cpp index ddb3a18..ccae4eb 100644 --- a/tests/render/test_curtain.cpp +++ b/tests/render/test_curtain.cpp @@ -1,8 +1,7 @@ #include -#include -#include -#include +#include +#include #include "actors/CurtainActor.hpp" #include "geo/GeoLocalFrame.hpp" @@ -11,7 +10,8 @@ using namespace geopro::core; -// buildCurtain: 小 Grid(3,2) + lat/lon/y/values -> 非空 actor,mapper 输入点数=6。 +// buildCurtain: 小 Grid(3,2) + lat/lon/y/values -> 非空 actor,经 banded contour 管线 +// (vtkDataSetSurfaceFilter→vtkBandedPolyDataContourFilter→vtkPolyDataMapper) 产出非空 polydata。 TEST(Curtain, BuildsStructuredGridFromSmallGrid) { Grid g(3, 2); // nx=3 (lat/lon 列), ny=2 (深度行) g.lat = {22.50, 22.50, 22.50}; @@ -32,9 +32,14 @@ TEST(Curtain, BuildsStructuredGridFromSmallGrid) { auto actor = geopro::render::buildCurtain(g, cs, frame); ASSERT_NE(actor.GetPointer(), nullptr); - auto* mapper = vtkDataSetMapper::SafeDownCast(actor->GetMapper()); + auto* mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); ASSERT_NE(mapper, nullptr); - auto* input = mapper->GetInput(); - ASSERT_NE(input, nullptr); - EXPECT_EQ(input->GetNumberOfPoints(), 6); + ASSERT_NE(mapper->GetLookupTable(), nullptr); + + // 运行管线,断言 banded contour 产出非空 polydata(色带面)。 + mapper->Update(); + auto* out = mapper->GetInput(); + ASSERT_NE(out, nullptr); + EXPECT_GT(out->GetNumberOfPoints(), 0); + EXPECT_GT(out->GetNumberOfCells(), 0); } diff --git a/tests/render/test_scatter.cpp b/tests/render/test_scatter.cpp new file mode 100644 index 0000000..b6d6b7d --- /dev/null +++ b/tests/render/test_scatter.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include + +#include "actors/ScatterActor.hpp" +#include "model/ColorScale.hpp" +#include "model/Field.hpp" + +using namespace geopro::core; + +// buildScatter: 小散点场 -> 非空 actor,polydata 点数=点数、含 verts、按 v 上色(LUT)。 +TEST(Scatter, BuildsColoredPointsFromScatterField) { + ScatterField s; + s.x = {0.0, 10.0, 20.0}; + s.y = {1.0, 2.0, 3.0}; // 深度,渲染时取负 + s.v = {0.0, 2.5, 5.0}; + + ColorScale cs; + cs.addStop(0.0, Rgba{0, 0, 255, 255}); + cs.addStop(5.0, Rgba{255, 0, 0, 255}); + + auto actor = geopro::render::buildScatter(s, cs); + ASSERT_NE(actor.GetPointer(), nullptr); + + auto* mapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper()); + ASSERT_NE(mapper, nullptr); + ASSERT_NE(mapper->GetLookupTable(), nullptr); + + auto* poly = vtkPolyData::SafeDownCast(mapper->GetInput()); + ASSERT_NE(poly, nullptr); + EXPECT_EQ(poly->GetNumberOfPoints(), 3); + EXPECT_EQ(poly->GetNumberOfVerts(), 3); // 每点一个 vtkVertex + ASSERT_NE(poly->GetPointData()->GetScalars(), nullptr); + + // y 取负:深度向下(与剖面 #18 一致)。 + double p1[3]; + poly->GetPoint(1, p1); + EXPECT_DOUBLE_EQ(p1[0], 10.0); + EXPECT_DOUBLE_EQ(p1[1], -2.0); +} + +// 退化输入(无值)返回非空 actor 但无 polydata 输入(调用方可安全 add)。 +TEST(Scatter, EmptyFieldYieldsSafeActor) { + ScatterField s; // 空 + ColorScale cs; + cs.addStop(0.0, Rgba{0, 0, 255, 255}); + auto actor = geopro::render::buildScatter(s, cs); + ASSERT_NE(actor.GetPointer(), nullptr); + EXPECT_EQ(actor->GetMapper(), nullptr); // 未设 mapper +} diff --git a/tests/spike/render_verify.cpp b/tests/spike/render_verify.cpp index 625bf5f..4e48f39 100644 --- a/tests/spike/render_verify.cpp +++ b/tests/spike/render_verify.cpp @@ -20,6 +20,7 @@ #include "actors/CurtainActor.hpp" #include "actors/GridContourActor.hpp" #include "actors/MapLineActor.hpp" +#include "actors/ScatterActor.hpp" #include "geo/GeoLocalFrame.hpp" #include "parse/SampleParsers.hpp" @@ -78,6 +79,18 @@ int main() { render::applyFree3D(ren); renderToPng(ren, (dir + "verify_curtain_3d.png").c_str(), 700, 500); } + // 4) 散点 (#17) — 剖面原数据彩色方块散点; x=距离, y=深度(取负向下), face-on 俯视 + { + core::ScatterField s = data::parseScatter(slurp((dir + "scatter.json").c_str())); + core::ColorScale scs = data::parseColorScale(slurp((dir + "scatter_colorbar.json").c_str())); + auto a = render::buildScatter(s, scs, 4.0F); + vtkNew ren; ren->SetBackground(1, 1, 1); + ren->AddActor(a); + render::applyTop2D(ren); + renderToPng(ren, (dir + "verify_scatter.png").c_str(), 1100, 360); + std::printf("SCATTER pts=%zu\n", s.v.size()); + } + std::printf("RENDER_VERIFY_DONE grid=%dx%d lat0=%.5f lon0=%.5f\n", g.nx(), g.ny(), lat0, lon0); return 0; }