#include "VtkSceneView.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CameraPreset.hpp" #include "Scene.hpp" #include "Theme.hpp" #include "actors/AxesActor.hpp" #include "actors/CurtainActor.hpp" #include "actors/MapLineActor.hpp" #include "actors/TerrainActor.hpp" #include "actors/VoxelActor.hpp" #include "geo/GeoLocalFrame.hpp" namespace geopro::app { namespace { // 控制器层枚举 → render 层枚举(保持控制器不依赖 render)。 geopro::render::AxesMode toRenderMode(geopro::controller::AxesMode m) { switch (m) { case geopro::controller::AxesMode::Standard: return geopro::render::AxesMode::Standard; case geopro::controller::AxesMode::Stereo: return geopro::render::AxesMode::Stereo; case geopro::controller::AxesMode::None: return geopro::render::AxesMode::None; } return geopro::render::AxesMode::Standard; } geopro::render::AxesUnit toRenderUnit(geopro::controller::AxesUnit u) { switch (u) { case geopro::controller::AxesUnit::None: return geopro::render::AxesUnit::None; case geopro::controller::AxesUnit::Meter: return geopro::render::AxesUnit::Meter; case geopro::controller::AxesUnit::Feet: return geopro::render::AxesUnit::Feet; case geopro::controller::AxesUnit::LatLon: return geopro::render::AxesUnit::LatLon; } return geopro::render::AxesUnit::Meter; } geopro::render::ViewDir toRenderViewDir(geopro::controller::ViewDir d) { switch (d) { case geopro::controller::ViewDir::Front: return geopro::render::ViewDir::Front; case geopro::controller::ViewDir::Back: return geopro::render::ViewDir::Back; case geopro::controller::ViewDir::Left: return geopro::render::ViewDir::Left; case geopro::controller::ViewDir::Right: return geopro::render::ViewDir::Right; case geopro::controller::ViewDir::Top: return geopro::render::ViewDir::Top; case geopro::controller::ViewDir::Bottom: return geopro::render::ViewDir::Bottom; } return geopro::render::ViewDir::Front; } } // namespace VtkSceneView::VtkSceneView(geopro::render::Scene& scene, vtkRenderWindow* renderWindow, std::shared_ptr frame, double zRefElev) : scene_(scene), renderWindow_(renderWindow), frame_(std::move(frame)), zRefElev_(zRefElev) { // 近裁剪容差调小:场景含近处剖面 + 远处底图(几十km),默认容差会把近裁剪面随远面推出去、 // 切掉离相机近的剖面。调小后近面可贴近,剖面不被切(代价:远处深度精度略降,不可见层无所谓)。 scene_.renderer()->SetNearClippingPlaneTolerance(1e-5); } void VtkSceneView::removeProps(std::vector>& props) { for (auto& p : props) if (p) scene_.renderer()->RemoveViewProp(p); props.clear(); } bool VtkSceneView::computeDataBounds(double out[6]) const { vtkBoundingBox bb; for (const auto& kv : dsProps_) for (const auto& p : kv.second) if (p) { if (double* b = p->GetBounds()) bb.AddBounds(b); } for (const auto& p : miscProps_) if (p) { if (double* b = p->GetBounds()) bb.AddBounds(b); } if (!bb.IsValid()) return false; bb.GetBounds(out); return true; } double VtkSceneView::dataHorizontalRadius() const { double b[6]; if (!computeDataBounds(b)) return 0.0; const double dx = b[1] - b[0], dy = b[3] - b[2]; return 0.5 * std::sqrt(dx * dx + dy * dy); // 水平对角线半径 } void VtkSceneView::clear() { // 只移除数据 prop(按 ds 跟踪)+ 杂项(地形/测线)+ 坐标轴;不动底图(TileBasemap 自管)→ 重建不丢图。 for (auto& kv : dsProps_) removeProps(kv.second); dsProps_.clear(); removeProps(miscProps_); if (currentAxes_) { scene_.renderer()->RemoveViewProp(currentAxes_); currentAxes_ = nullptr; } // 体素 image 失效:置空并通知上层关闭切片(防切片附着到已移除的 image)。 currentVolumeImage_ = nullptr; volumeOwnerDs_.clear(); frameAnchoredToData_ = false; // 新一轮选择重新按其首个真实剖面重锚原点 if (onVolumeChanged) onVolumeChanged(); } void VtkSceneView::setVerticalExaggeration(double ve) { verticalExaggeration_ = ve; } void VtkSceneView::addSurveyLine(const geopro::core::Grid& grid) { auto line = geopro::render::buildSurveyLine(grid, *frame_); if (line) { scene_.addActor(line); miscProps_.push_back(line); } } void VtkSceneView::addCurtain(const std::string& dsId, const geopro::core::Grid& grid, const geopro::core::ColorScale& cs) { // 首个带经纬度的剖面到达 → 把 GeoLocalFrame 原点重锚到该剖面 lat/lon 中心:使局部坐标从 0 附近起 // (轴刻度有意义),同一选择内多条剖面共用此原点 → 相互地理配准。无经纬剖面是平面、不受原点影响。 const int nx = grid.nx(); if (!frameAnchoredToData_ && nx > 0 && static_cast(grid.lat.size()) >= nx && static_cast(grid.lon.size()) >= nx) { double la0 = grid.lat[0], la1 = grid.lat[0], lo0 = grid.lon[0], lo1 = grid.lon[0]; for (int i = 1; i < nx; ++i) { la0 = std::min(la0, grid.lat[i]); la1 = std::max(la1, grid.lat[i]); lo0 = std::min(lo0, grid.lon[i]); lo1 = std::max(lo1, grid.lon[i]); } // 就地重锚共享 frame(不换对象)→ 同持此 frame 的底图层等随即一致对齐。 frame_->reanchor((la0 + la1) / 2.0, (lo0 + lo1) / 2.0); frameAnchoredToData_ = true; if (onFrameReanchored) onFrameReanchored(); // 通知底图刷新到数据位置 } auto curtain = geopro::render::buildCurtain(grid, cs, *frame_); if (curtain) { curtain->SetScale(1.0, 1.0, verticalExaggeration_); // 纵向夸张成墙 scene_.addActor(curtain); dsProps_[dsId].push_back(curtain); } } void VtkSceneView::addVolume(const std::string& dsId, const geopro::data::VolumeGrid& vol, const geopro::core::ColorScale& cs) { // 纵向夸张烤进 image 的 z 原点/间距(与帘面 SetScale 同倍,保证纵向一致)。 // 用暴露 image 的 buildVoxel 重载:保留 currentVolumeImage_ 供 P3 切片附着(几何含 VE)。 vtkSmartPointer image; auto volume = geopro::render::buildVoxel( vol.vol, cs, vol.origin[0], vol.origin[1], vol.origin[2] * verticalExaggeration_, vol.spacing[0], vol.spacing[1], vol.spacing[2] * verticalExaggeration_, vol.vmin, vol.vmax, image); if (volume) { scene_.addViewProp(volume); dsProps_[dsId].push_back(volume); currentVolumeImage_ = image; currentColorScale_ = cs; currentVmin_ = vol.vmin; currentVmax_ = vol.vmax; volumeOwnerDs_ = dsId; if (onVolumeChanged) onVolumeChanged(); } } void VtkSceneView::addTerrain(const geopro::data::TerrainPaths& paths) { auto terrain = geopro::render::buildTerrain(paths.demPath, paths.imagePath, *frame_, zRefElev_, verticalExaggeration_); if (terrain) { scene_.addActor(terrain); miscProps_.push_back(terrain); } } void VtkSceneView::removeDataset(const std::string& dsId) { auto it = dsProps_.find(dsId); if (it == dsProps_.end()) return; removeProps(it->second); dsProps_.erase(it); if (volumeOwnerDs_ == dsId) { // 该 ds 的体素被移除 → 切片源失效 currentVolumeImage_ = nullptr; volumeOwnerDs_.clear(); if (onVolumeChanged) onVolumeChanged(); } } void VtkSceneView::toggleDatasetVisibility(const std::string& dsId) { auto it = dsProps_.find(dsId); if (it == dsProps_.end() || it->second.empty()) return; // 切片(非 dsProps_)等无图元 → 忽略 // 以首个 prop 当前可见性取反,统一作用于该 ds 全部 prop。 const bool show = it->second.front()->GetVisibility() == 0; for (auto& p : it->second) p->SetVisibility(show ? 1 : 0); if (renderWindow_) renderWindow_->Render(); } void VtkSceneView::setAxes(geopro::controller::AxesMode mode, geopro::controller::AxesUnit unit, int fontSize) { axesMode_ = mode; axesUnit_ = unit; axesFontSize_ = fontSize; } void VtkSceneView::applyCameraView(geopro::controller::ViewDir dir) { geopro::render::applyView(scene_.renderer(), toRenderViewDir(dir)); // 设朝向(内部 ResetCamera 含底图) double bounds[6]; if (computeDataBounds(bounds)) scene_.renderer()->ResetCamera(bounds); // 重新取景到数据(否则被~公里级底图推到超远) scene_.renderer()->ResetCameraClippingRange(); // 裁剪面含底图 if (renderWindow_) renderWindow_->Render(); if (onCameraChanged) onCameraChanged(); // 相机变了 → 底图按新视锥重算覆盖 } void VtkSceneView::zoom(double factor) { geopro::render::zoomBy(scene_.renderer(), factor); if (renderWindow_) renderWindow_->Render(); if (onCameraChanged) onCameraChanged(); } void VtkSceneView::fitView() { double bounds[6]; if (computeDataBounds(bounds)) scene_.renderer()->ResetCamera(bounds); // 取景到数据(不含底图) else geopro::render::fitView(scene_.renderer()); scene_.renderer()->ResetCameraClippingRange(); // 裁剪面含底图 → 不被"蒙版"切掉 if (renderWindow_) renderWindow_->Render(); if (onCameraChanged) onCameraChanged(); // 取景后 → 底图按新视锥重算覆盖(治首帧部分瓦片不出) } void VtkSceneView::rebuildAxes() { // 先移除上一次的坐标轴 prop:render 可能在一次 rebuild 内多次调用(末尾统一 render + // 异步回灌 render),不先移除会叠加坐标轴(评审 HIGH)。移除后再算 bounds(仅数据图元)。 if (currentAxes_) { scene_.renderer()->RemoveViewProp(currentAxes_); currentAxes_ = nullptr; } // 坐标轴随数据包围盒重建:仅按数据图元算 bounds(不含底图,否则被~公里级底图撑大), // 再造 vtkCubeAxesActor 入场。None 模式或无数据 → buildAxes 返回 nullptr,场景无坐标轴。 double bounds[6]; if (!computeDataBounds(bounds)) return; // 无数据 → 不建坐标轴 geopro::render::AxesOptions opts; opts.mode = toRenderMode(axesMode_); opts.unit = toRenderUnit(axesUnit_); opts.fontSize = axesFontSize_; opts.frame = frame_.get(); auto axes = geopro::render::buildAxes(bounds, opts, scene_.renderer()); if (axes) { scene_.addViewProp(axes); currentAxes_ = axes; } } void VtkSceneView::render(bool is2D) { // 视图区背景永远深色(规范 §0.5:不随明暗切换),让色阶数据更突出。 double bgR, bgG, bgB; geopro::app::vtkBackground(bgR, bgG, bgB); scene_.renderer()->SetBackground(bgR, bgG, bgB); // 坐标轴仅三维视图显示(2D 俯视测线不需要立体坐标轴)。 if (!is2D) rebuildAxes(); if (is2D) geopro::render::applyTop2D(scene_.renderer()); else geopro::render::applyFree3D(scene_.renderer()); double bounds[6]; if (computeDataBounds(bounds)) scene_.renderer()->ResetCamera(bounds); // 取景到数据(不含底图,否则数据缩成小点) else scene_.renderer()->ResetCamera(); scene_.renderer()->ResetCameraClippingRange(); // 裁剪面含底图 → 不被"蒙版"切掉 if (renderWindow_) renderWindow_->Render(); if (onCameraChanged) onCameraChanged(); // 取景后 → 底图按新视锥重算覆盖 } void VtkSceneView::renderIncremental() { // 增量渲染:仅按新包围盒重建坐标轴并提交,不动相机(勾选/取消时视角不跳)。 rebuildAxes(); scene_.renderer()->ResetCameraClippingRange(); // 数据/底图变化后扩裁剪面,防被切 if (renderWindow_) renderWindow_->Render(); } } // namespace geopro::app