geopro/tests/controller/test_vtk_scene_controller.cpp

270 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <functional>
#include <string>
#include <vector>
#include "I3dSceneView.hpp"
#include "VtkSceneController.hpp"
#include "model/ColorScale.hpp"
#include "model/Field.hpp"
#include "repo/I3dSceneRepository.hpp"
#include "repo/IDatasetRepository.hpp"
using namespace geopro;
using namespace geopro::controller;
namespace {
// 记录视图收到的图元调用类型/数量。
struct FakeView : I3dSceneView {
int clears = 0;
int surveyLines = 0;
int curtains = 0;
int volumes = 0;
int terrains = 0;
int renders = 0;
bool lastIs2D = false;
double ve = -1.0;
// P2 记录。
int setAxesCalls = 0;
AxesMode lastAxesMode = AxesMode::None;
AxesUnit lastAxesUnit = AxesUnit::None;
int lastAxesFont = -1;
int cameraViewCalls = 0;
ViewDir lastViewDir = ViewDir::Front;
int zoomCalls = 0;
double lastZoomFactor = 0.0;
int fitCalls = 0;
// clear 模型化"移除所有图元"图元计数归零反映当前场景状态clears 累加。
void clear() override {
++clears;
surveyLines = curtains = volumes = terrains = 0;
}
void setVerticalExaggeration(double v) override { ve = v; }
void addSurveyLine(const core::Grid&) override { ++surveyLines; }
void addCurtain(const core::Grid&, const core::ColorScale&) override { ++curtains; }
void addVolume(const data::VolumeGrid&, const core::ColorScale&) override { ++volumes; }
void addTerrain(const data::TerrainPaths&) override { ++terrains; }
void setAxes(AxesMode mode, AxesUnit unit, int fontSize) override {
++setAxesCalls;
lastAxesMode = mode; lastAxesUnit = unit; lastAxesFont = fontSize;
}
void applyCameraView(ViewDir dir) override { ++cameraViewCalls; lastViewDir = dir; }
void zoom(double factor) override { ++zoomCalls; lastZoomFactor = factor; }
void fitView() override { ++fitCalls; }
void render(bool is2D) override { ++renders; lastIs2D = is2D; }
int props() const { return surveyLines + curtains + volumes + terrains; }
};
// 同步小数据仓储loadGrid 返回 2x2 gridloadColorScale 返回两段色阶。
struct FakeDsRepo : data::IDatasetRepository {
std::vector<data::GsNode> loadStructure() override { return {}; }
core::Grid loadGrid(const std::string&) override {
core::Grid g(2, 2);
g.lat = {22.0, 22.001};
g.lon = {114.0, 114.001};
return g;
}
core::ScatterField loadScatter(const std::string&) override { return {}; }
core::ColorScale loadColorScale(const std::string&) override {
core::ColorScale cs;
cs.addStop(0.0, core::Rgba{0, 0, 255, 255});
cs.addStop(1.0, core::Rgba{255, 0, 0, 255});
return cs;
}
core::ColorScale loadScatterColorScale(const std::string&) override { return loadColorScale(""); }
std::vector<core::Anomaly> loadAnomalies(const std::string&) override { return {}; }
};
// 同步三维仓储dimensionOf 全当 3DloadVolume 立即回调一个最小有效体。
struct FakeSceneRepo : data::I3dSceneRepository {
data::DsDimension dimensionOf(const data::DsRow&) const override {
return data::DsDimension::Dim3D;
}
void loadVolume(const std::string&, std::function<void(data::VolumeGrid)> onOk,
OnError) override {
data::VolumeGrid g;
g.vol = core::ScalarVolume(2, 2, 2);
g.spacing = {{1.0, 1.0, 1.0}};
g.vmin = 0.0; g.vmax = 1.0;
onOk(std::move(g)); // 同步回调(异步壳)
}
void loadTerrainPaths(std::function<void(data::TerrainPaths)> onOk, OnError) override {
onOk(data::TerrainPaths{"dem.tif", "image.tif"});
}
// 切片/异常/任务 stub满足纯虚行为同 LocalSample3dRepository
void createSlice(const SliceSpec&, const std::string&,
std::function<void(std::string)> onOk, OnError) override { onOk("slice-0"); }
void saveSlice(const std::string&, const SliceSpec&,
std::function<void()> onOk, OnError) override { onOk(); }
void deleteSlice(const std::string&,
std::function<void()> onOk, OnError) override { onOk(); }
void loadAnomalyTree(const std::string&,
std::function<void(AnomalyTree)> onOk, OnError) override { onOk({}); }
void saveAnomaly(const core::Anomaly&, const std::string&,
std::function<void(std::string)> onOk, OnError) override { onOk("anomaly-0"); }
void deleteAnomaly(const std::string&,
std::function<void()> onOk, OnError) override { onOk(); }
void deleteAnomalyGroup(const std::string&,
std::function<void()> onOk, OnError) override { onOk(); }
void loadTaskRecords(const std::string&,
std::function<void(std::vector<TaskRecord>)> onOk, OnError) override { onOk({}); }
void loadUsableTasks(const std::string&,
std::function<void(std::vector<UsableTask>)> onOk, OnError) override { onOk({}); }
};
} // namespace
// 2D 模式 + 勾选 1 ds → 1 个测线 actor无帘面/体素/地形。
TEST(VtkSceneController, Map2DWithOneDatasetAddsSurveyLine) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::Map2D);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.surveyLines, 1);
EXPECT_EQ(view.curtains, 0);
EXPECT_EQ(view.volumes, 0);
EXPECT_GE(view.renders, 1);
EXPECT_TRUE(view.lastIs2D);
}
// 3D 模式 + 帘面图层 → 1 帘面 actor。
TEST(VtkSceneController, View3DCurtainAddsCurtain) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.curtains, 1);
EXPECT_EQ(view.surveyLines, 0);
EXPECT_FALSE(view.lastIs2D);
}
// 3D + 帘面 + 体素 → 帘面 1 + 体素 1体素经异步回调进场
TEST(VtkSceneController, View3DWithVoxelAddsVolume) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setLayer(SceneLayer::Voxel, true);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.curtains, 1);
EXPECT_EQ(view.volumes, 1);
}
// 3D + 地形 → 地形 1与勾选数据集无关地形是场景图层
TEST(VtkSceneController, View3DWithTerrainAddsTerrain) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setLayer(SceneLayer::Terrain, true);
c.setCheckedDatasets({"ds1"});
EXPECT_EQ(view.terrains, 1);
EXPECT_EQ(view.curtains, 1);
}
// 取消勾选 → clear 后无任何图元。
TEST(VtkSceneController, UncheckAllClearsScene) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1"});
ASSERT_EQ(view.curtains, 1);
c.setCheckedDatasets({}); // 取消全部勾选
EXPECT_EQ(view.curtains, 0);
EXPECT_EQ(view.volumes, 0);
// 最后一次重建仍调用 clear。
EXPECT_GE(view.clears, 2);
}
// 纵向比例传到视图。
TEST(VtkSceneController, VerticalExaggerationForwarded) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setVerticalExaggeration(3.5);
c.setCheckedDatasets({"ds1"});
EXPECT_DOUBLE_EQ(view.ve, 3.5);
}
// 多个数据集 → 每个一个帘面。
TEST(VtkSceneController, MultipleDatasetsAddMultipleCurtains) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setCheckedDatasets({"ds1", "ds2", "ds3"});
EXPECT_EQ(view.curtains, 3);
}
// ── P2坐标轴 / 快捷视图 / Zoom 编排 ──
// 每次重建都把当前坐标轴设置下发给视图clear 后须重设)。
TEST(VtkSceneController, RebuildForwardsAxesSettings) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D); // 触发一次重建
EXPECT_GE(view.setAxesCalls, 1);
// 默认 = 标准 + 米 + 字号 12。
EXPECT_EQ(view.lastAxesMode, AxesMode::Standard);
EXPECT_EQ(view.lastAxesUnit, AxesUnit::Meter);
EXPECT_EQ(view.lastAxesFont, 12);
}
// setAxesMode 改模式并重建下发。
TEST(VtkSceneController, SetAxesModeForwardedOnRebuild) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
c.setAxesMode(AxesMode::None);
EXPECT_EQ(view.lastAxesMode, AxesMode::None);
const int rebuilds = view.setAxesCalls;
c.setAxesMode(AxesMode::Stereo);
EXPECT_EQ(view.lastAxesMode, AxesMode::Stereo);
EXPECT_GT(view.setAxesCalls, rebuilds); // 又触发一次重建
}
// setAxesUnit 改单位并重建下发。
TEST(VtkSceneController, SetAxesUnitForwarded) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setAxesUnit(AxesUnit::Feet);
EXPECT_EQ(view.lastAxesUnit, AxesUnit::Feet);
c.setAxesUnit(AxesUnit::LatLon);
EXPECT_EQ(view.lastAxesUnit, AxesUnit::LatLon);
}
// applyView 转发方向,不重建场景(不增 clear
TEST(VtkSceneController, ApplyViewForwardsDirectionWithoutRebuild) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.setViewMode(ViewMode::View3D);
const int clearsBefore = view.clears;
c.applyView(ViewDir::Top);
EXPECT_EQ(view.cameraViewCalls, 1);
EXPECT_EQ(view.lastViewDir, ViewDir::Top);
EXPECT_EQ(view.clears, clearsBefore); // 不重建
c.applyView(ViewDir::Left);
EXPECT_EQ(view.lastViewDir, ViewDir::Left);
}
// zoomIn/zoomOut 用 1.2 / (1/1.2)fit 调 fitView。
TEST(VtkSceneController, ZoomAndFitForwarded) {
FakeDsRepo ds; FakeSceneRepo sc; FakeView view;
VtkSceneController c(ds, sc, view);
c.zoomIn();
EXPECT_EQ(view.zoomCalls, 1);
EXPECT_DOUBLE_EQ(view.lastZoomFactor, 1.2);
c.zoomOut();
EXPECT_DOUBLE_EQ(view.lastZoomFactor, 1.0 / 1.2);
c.fit();
EXPECT_EQ(view.fitCalls, 1);
}