geopro/src/app/VtkSceneView.cpp

335 lines
14 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 "VtkSceneView.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
#include <utility>
#include <QDebug>
#include <QString>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkBoundingBox.h>
#include <vtkCubeAxesActor.h>
#include <vtkProp.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkVolume.h>
#include "CameraPreset.hpp"
#include "Scene.hpp"
#include "Theme.hpp"
#include "actors/AnomalyActor.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<geopro::core::GeoLocalFrame> frame, double zRefElev)
: scene_(scene),
renderWindow_(renderWindow),
frame_(std::move(frame)),
zRefElev_(zRefElev) {
// 近裁剪容差调小:场景含近处剖面 + 远处底图(几十km),默认容差会把近裁剪面随远面推出去、
// 切掉离相机近的剖面。调小后近面可贴近,剖面不被切(代价:远处深度精度略降,不可见层无所谓)。
scene_.renderer()->SetNearClippingPlaneTolerance(1e-5);
}
void VtkSceneView::removeProps(std::vector<vtkSmartPointer<vtkProp>>& 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_);
clearAnomalies(); // 异常 actor 随清场一并移除
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::anchorFrameIfNeeded(const std::vector<double>& lat,
const std::vector<double>& lon, int n) {
// 首个带经纬数据到达 → 把 GeoLocalFrame 原点重锚到其 lat/lon 包围盒中心:使局部坐标从 0 附近起
// (轴刻度有意义),同一选择内多条剖面/足迹共用此原点 → 相互地理配准。已锚或无经纬则保持不动。
if (frameAnchoredToData_ || n < 1) return;
if (static_cast<int>(lat.size()) < n || static_cast<int>(lon.size()) < n) return;
double la0 = lat[0], la1 = lat[0], lo0 = lon[0], lo1 = lon[0];
for (int i = 1; i < n; ++i) {
la0 = std::min(la0, lat[i]); la1 = std::max(la1, lat[i]);
lo0 = std::min(lo0, lon[i]); lo1 = std::max(lo1, lon[i]);
}
// 就地重锚共享 frame不换对象→ 同持此 frame 的底图层等随即一致对齐。
frame_->reanchor((la0 + la1) / 2.0, (lo0 + lo1) / 2.0);
frameAnchoredToData_ = true;
if (onFrameReanchored) onFrameReanchored(); // 通知底图刷新到数据位置
}
void VtkSceneView::addCurtain(const std::string& dsId, const geopro::core::Grid& grid,
const geopro::core::ColorScale& cs) {
anchorFrameIfNeeded(grid.lat, grid.lon, grid.nx()); // 首个带经纬剖面 → 重锚原点
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<vtkImageData> 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::addMapLine(const std::string& dsId, const geopro::data::MapLine& line,
double worldZ) {
// 2D 足迹:经共享 frame 投影到世界 XY、Z=worldZ。按 dsId 跟踪(与帘面同 dsProps_ → removeDataset 复用)。
// worldZ 已是最终世界高程(含摆放语义),不再施加 VE足迹是水平线非随深度的竖直图元
// 足迹可能是首个(且唯一)带经纬的数据 → 与帘面同样重锚原点,否则按样本默认原点投到数百公里外不可见。
anchorFrameIfNeeded(line.lat, line.lon, static_cast<int>(line.lat.size()));
auto actor = geopro::render::buildMapLine(line.lat, line.lon, worldZ, *frame_);
if (actor) {
scene_.addActor(actor);
dsProps_[dsId].push_back(actor);
}
}
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::addAnomaly(const geopro::core::Anomaly& a) {
if (a.id.empty()) return;
removeAnomaly(a.id); // 幂等:同 id 先移除旧 actor避免重复
auto actor = geopro::render::buildAnomaly3D(a);
if (!actor) return;
scene_.addActor(actor); // worldPts 已是世界系(含 VE),不再 SetScale
anomalyProps_[a.id] = actor;
}
void VtkSceneView::removeAnomaly(const std::string& anomalyId) {
auto it = anomalyProps_.find(anomalyId);
if (it == anomalyProps_.end()) return;
if (it->second) scene_.renderer()->RemoveViewProp(it->second);
anomalyProps_.erase(it);
}
void VtkSceneView::clearAnomalies() {
for (auto& kv : anomalyProps_)
if (kv.second) scene_.renderer()->RemoveViewProp(kv.second);
anomalyProps_.clear();
}
void VtkSceneView::setAnomalyVisible(const std::string& anomalyId, bool visible) {
auto it = anomalyProps_.find(anomalyId);
if (it != anomalyProps_.end() && it->second) it->second->SetVisibility(visible ? 1 : 0);
}
void VtkSceneView::setSelectedAnomaly(const std::string& anomalyId) {
// 选中者加粗高亮、其余恢复常态列表↔VTK 联动 R84
for (auto& kv : anomalyProps_) {
if (!kv.second) continue;
const bool sel = (kv.first == anomalyId);
kv.second->GetProperty()->SetLineWidth(sel ? 5.0 : 2.0);
kv.second->GetProperty()->SetPointSize(sel ? 12.0 : 8.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() {
// 先移除上一次的坐标轴 proprender 可能在一次 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