180 lines
8.1 KiB
C++
180 lines
8.1 KiB
C++
// 离屏渲染验证:把真实数据渲成 PNG,肉眼核对方向/2D-3D 差异(不靠"进程活着"自欺)。
|
||
// 产出(D:/dev/spike_data/):
|
||
// verify_section.png —— 平面反演剖面(#18, 数据详情), 浅部在上、深部在下, 纵向夸张填满
|
||
// verify_curtain_top.png —— 帘面俯视(二维)
|
||
// verify_curtain_3d.png —— 帘面透视(三维)
|
||
#include <fstream>
|
||
#include <numeric>
|
||
#include <sstream>
|
||
#include <string>
|
||
|
||
#include <vtkActor.h>
|
||
#include <vtkNew.h>
|
||
#include <vtkOpenGLRenderWindow.h>
|
||
#include <vtkPNGWriter.h>
|
||
#include <vtkRenderWindow.h>
|
||
#include <vtkRenderer.h>
|
||
#include <vtkSmartVolumeMapper.h>
|
||
#include <vtkWindowToImageFilter.h>
|
||
|
||
#include "CameraPreset.hpp"
|
||
#include "ColorLutBuilder.hpp"
|
||
#include "VoxelFromScatters.hpp"
|
||
#include "actors/AnomalyActor.hpp"
|
||
#include "actors/CurtainActor.hpp"
|
||
#include "actors/ElectrodeActor.hpp"
|
||
#include "actors/GridContourActor.hpp"
|
||
#include "actors/MapLineActor.hpp"
|
||
#include "actors/ScatterActor.hpp"
|
||
#include "actors/TerrainActor.hpp"
|
||
#include "geo/CrsTransform.hpp"
|
||
#include "geo/GeoLocalFrame.hpp"
|
||
#include "parse/SampleParsers.hpp"
|
||
|
||
#include <vtkVolume.h>
|
||
|
||
static std::string slurp(const char* p) {
|
||
std::ifstream f(p);
|
||
std::stringstream s; s << f.rdbuf(); return s.str();
|
||
}
|
||
|
||
static void renderToPng(vtkRenderer* ren, const char* path, int w, int h) {
|
||
vtkNew<vtkRenderWindow> rw;
|
||
rw->SetOffScreenRendering(1);
|
||
rw->AddRenderer(ren);
|
||
rw->SetSize(w, h);
|
||
rw->Render();
|
||
vtkNew<vtkWindowToImageFilter> w2i; w2i->SetInput(rw);
|
||
vtkNew<vtkPNGWriter> png; png->SetFileName(path);
|
||
png->SetInputConnection(w2i->GetOutputPort()); png->Write();
|
||
}
|
||
|
||
int main() {
|
||
using namespace geopro;
|
||
const std::string dir = "D:/dev/spike_data/";
|
||
core::Grid g = data::parseGrid(slurp((dir + "grid.json").c_str()));
|
||
core::ColorScale cs = data::parseColorScale(slurp((dir + "colorbar.json").c_str()));
|
||
|
||
// 1) 平面剖面 (#18) — 俯视看 XY 平面(face-on); 纵向夸张 1.5x 填满宽面板(物探惯例)
|
||
{
|
||
auto a = render::buildGridContour(g, cs);
|
||
const double exag = 1.5;
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(1, 1, 1);
|
||
if (a.bands) { a.bands->SetScale(1.0, exag, 1.0); ren->AddActor(a.bands); }
|
||
if (a.edges) { a.edges->SetScale(1.0, exag, 1.0); ren->AddActor(a.edges); }
|
||
render::applyTop2D(ren);
|
||
renderToPng(ren, (dir + "verify_section.png").c_str(), 1100, 360);
|
||
}
|
||
|
||
// GeoLocalFrame: 原点取 lat/lon 均值
|
||
double lat0 = std::accumulate(g.lat.begin(), g.lat.end(), 0.0) / (g.lat.empty() ? 1 : g.lat.size());
|
||
double lon0 = std::accumulate(g.lon.begin(), g.lon.end(), 0.0) / (g.lon.empty() ? 1 : g.lon.size());
|
||
core::GeoLocalFrame frame(lat0, lon0);
|
||
|
||
// 2) 二维地图:测线折线俯视(浅灰底)
|
||
{
|
||
auto line = render::buildSurveyLine(g, frame);
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(0.96, 0.97, 0.99);
|
||
ren->AddActor(line);
|
||
render::applyTop2D(ren);
|
||
renderToPng(ren, (dir + "verify_map.png").c_str(), 700, 500);
|
||
}
|
||
// 3) 帘面透视(三维);纵向夸张 z*3,使 15m 深度相对 62m 长度更像一道墙
|
||
{
|
||
auto a = render::buildCurtain(g, cs, frame);
|
||
a->SetScale(1.0, 1.0, 3.0);
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(1, 1, 1);
|
||
ren->AddActor(a);
|
||
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<vtkRenderer> 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());
|
||
}
|
||
|
||
// 5) 异常叠加 — #18 平面剖面 + 异常 dashed 折线叠加(同纵向夸张 1.5x 对齐)
|
||
{
|
||
std::vector<core::Anomaly> anomalies =
|
||
data::parseAnomalies(slurp((dir + "anomaly.json").c_str()));
|
||
auto a = render::buildGridContour(g, cs);
|
||
const double exag = 1.5;
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(1, 1, 1);
|
||
if (a.bands) { a.bands->SetScale(1.0, exag, 1.0); ren->AddActor(a.bands); }
|
||
if (a.edges) { a.edges->SetScale(1.0, exag, 1.0); ren->AddActor(a.edges); }
|
||
auto anomActors = render::buildAnomalies(anomalies);
|
||
for (auto& act : anomActors) {
|
||
act->SetScale(1.0, exag, 1.0);
|
||
ren->AddActor(act);
|
||
}
|
||
// 顶部电极标记 ▼(同纵向夸张对齐)。
|
||
auto elec = render::buildElectrodes(g);
|
||
if (elec) { elec->SetScale(1.0, exag, 1.0); ren->AddActor(elec); }
|
||
render::applyTop2D(ren);
|
||
renderToPng(ren, (dir + "verify_section_anomaly.png").c_str(), 1100, 360);
|
||
std::printf("ANOMALY n=%zu\n", anomalies.size());
|
||
}
|
||
|
||
// 6) dd_voxel — 两交叉剖面散点经 EPSG:4547→4326→GeoLocalFrame 配准 + IDW 成体素,
|
||
// 叠一条帘面(grid1,同系)做空间参照; 透视核对"十字片"与帘面配准(profile1 片应贴合帘面)。
|
||
{
|
||
std::vector<core::ScatterField> profs;
|
||
profs.push_back(data::parseScatter(slurp((dir + "scatter.json").c_str())));
|
||
profs.push_back(data::parseScatter(slurp((dir + "scatter2.json").c_str())));
|
||
core::ColorScale scs = data::parseColorScale(slurp((dir + "scatter_colorbar.json").c_str()));
|
||
core::CrsTransform crs("EPSG:4547", "EPSG:4326");
|
||
auto vr = render::buildVoxelFromScatters(profs, scs, crs, frame);
|
||
|
||
// 灰底使半透明体绘制可见(白底会冲淡)。3D:体素 + 帘面(同系)核对 profile1 片贴合帘面。
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(0.22, 0.22, 0.26);
|
||
if (vr.valid()) ren->AddVolume(vr.volume);
|
||
auto curt = render::buildCurtain(g, cs, frame); // 原始米(不夸张)与体素同系
|
||
if (curt) ren->AddActor(curt);
|
||
render::applyFree3D(ren);
|
||
renderToPng(ren, (dir + "verify_voxel_3d.png").c_str(), 700, 500);
|
||
|
||
// 俯视:体素 footprint 应呈两测线相交的"X"(十字片)。
|
||
vtkNew<vtkRenderer> renTop; renTop->SetBackground(0.22, 0.22, 0.26);
|
||
if (vr.valid()) renTop->AddVolume(vr.volume);
|
||
render::applyTop2D(renTop);
|
||
renderToPng(renTop, (dir + "verify_voxel_top.png").c_str(), 600, 500);
|
||
|
||
int dims[3] = {0, 0, 0};
|
||
if (vr.valid()) vr.image->GetDimensions(dims);
|
||
std::printf("VOXEL valid=%d dims=%dx%dx%d pts=%zu\n", vr.valid() ? 1 : 0,
|
||
dims[0], dims[1], dims[2], profs[0].v.size() + profs[1].v.size());
|
||
|
||
// [实测诊断] 体绘制实际渲染模式 + OpenGL 渲染器(查是否软件渲染/CPU 回退)。
|
||
if (vr.valid()) {
|
||
vtkNew<vtkRenderer> rd; rd->AddVolume(vr.volume);
|
||
vtkNew<vtkRenderWindow> rwd; rwd->SetOffScreenRendering(1);
|
||
rwd->AddRenderer(rd); rwd->SetSize(400, 400); rwd->Render();
|
||
auto* svm = vtkSmartVolumeMapper::SafeDownCast(vr.volume->GetMapper());
|
||
const int mode = svm ? svm->GetLastUsedRenderMode() : -99;
|
||
std::printf("VOXEL_RENDER_MODE=%d (0=Default,1=RayCast/CPU,2=GPU,3=OSPRay)\n", mode);
|
||
auto* gl = vtkOpenGLRenderWindow::SafeDownCast(rwd.Get());
|
||
if (gl) std::printf("GL_CAPS:\n%s\n", gl->ReportCapabilities());
|
||
}
|
||
}
|
||
|
||
// 7) DEM 地形 + 影像贴图 — GDAL 读 + 重投影到世界系 + warp 面 + 纹理
|
||
{
|
||
auto terr = render::buildTerrain(dir + "dem.tif", dir + "image.tif", frame, 0.0, 1.0);
|
||
vtkNew<vtkRenderer> ren; ren->SetBackground(0.50, 0.60, 0.72);
|
||
if (terr) ren->AddActor(terr);
|
||
render::applyFree3D(ren);
|
||
renderToPng(ren, (dir + "verify_terrain_3d.png").c_str(), 800, 600);
|
||
std::printf("TERRAIN actor=%d\n", terr ? 1 : 0);
|
||
}
|
||
|
||
std::printf("RENDER_VERIFY_DONE grid=%dx%d lat0=%.5f lon0=%.5f\n", g.nx(), g.ny(), lat0, lon0);
|
||
return 0;
|
||
}
|