feat/vtk-3d-view #7
|
|
@ -19,7 +19,9 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
@ -3984,61 +3986,209 @@ int cmdViewGallery(const std::string& dir, int frames,
|
||||||
//
|
//
|
||||||
// --preview 离屏出俯视(top) + 斜视(oblique)两张图展示 20 条并排成测区;否则开真窗口可转可缩。
|
// --preview 离屏出俯视(top) + 斜视(oblique)两张图展示 20 条并排成测区;否则开真窗口可转可缩。
|
||||||
|
|
||||||
// 一条线在世界中的摆放(刚体变换 + 该线已加载的整卷体)。
|
// 一条线在世界中的摆放(刚体变换 + 该线的视野自适应引擎源)。
|
||||||
struct LinePlacement {
|
//
|
||||||
|
// P11:每条线不再整卷加载固定层,而是各持一个 ViewAdaptiveVolumeSource(LOD+视锥
|
||||||
|
// 裁剪+异步重组引擎,与单条 view 完全同款)。引擎 exagg=1.0(不烘几何),垂向夸张/
|
||||||
|
// 航向/平移全由世界变换 T 承担(与单条 view 把 exagg 放 actor SetScale 同理)。每帧
|
||||||
|
// 把世界相机逆变换到该线局部帧喂引擎 → selectLod 选层选区(视锥外→引擎不提交)。
|
||||||
|
struct PlacedSource {
|
||||||
std::string name; // 明星路_NNN
|
std::string name; // 明星路_NNN
|
||||||
vtkSmartPointer<vtkImageData> img; // 该线整卷 VTK_SHORT(线局部坐标)
|
std::unique_ptr<geopro::render::ViewAdaptiveVolumeSource> source;
|
||||||
geopro::data::StoreMeta meta; // 量化/几何
|
geopro::data::StoreMeta meta; // 量化/几何
|
||||||
double startX = 0, startY = 0; // 起点局部米(相对公共原点)
|
double startX = 0, startY = 0; // 起点局部米(相对公共原点)
|
||||||
double headingDeg = 0; // 航向角(度,相对 +X 东向,逆时针)
|
double headingDeg = 0; // 航向角(度,相对 +X 东向)
|
||||||
double spreadX = 0, spreadY = 0; // 可选横向铺开偏移(垂直航向,--spread>0 时)
|
double spreadX = 0, spreadY = 0; // 可选横向铺开偏移
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkTransform> world; // T:Scale(1,1,exagg)→RotateZ→Translate
|
||||||
|
vtkSmartPointer<vtkTransform> worldInv; // T⁻¹(相机逆变换到局部帧)
|
||||||
|
vtkSmartPointer<vtkVolumeProperty> prop; // 逐线 2/98 分位标定的传函(底图+高清共用)
|
||||||
|
vtkSmartPointer<vtkVolume> baseVolume; // 常驻粗底图(永在场,套 T)
|
||||||
|
vtkSmartPointer<vtkSmartVolumeMapper> baseMapper;
|
||||||
|
vtkSmartPointer<vtkVolume> hiresVolume; // 高清叠加(就绪后局部覆盖,套 T)
|
||||||
|
vtkSmartPointer<vtkSmartVolumeMapper> hiresMapper;
|
||||||
|
vtkSmartPointer<vtkImageData> currentImg; // 持当前高清单图引用(mapper 仅持裸指针)
|
||||||
|
|
||||||
|
double worldBounds[6] = {0, 0, 0, 0, 0, 0}; // 该线(含 T+底图盒)的世界 AABB(视锥裁剪用)
|
||||||
|
bool culled = false; // 本帧是否被视锥裁掉(两层皆隐 → 真跳过)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 由该线整卷体 + 公共世界原点 + 航向 + Z 夸张 → vtkVolume(套刚体变换 + 传函)。
|
// 由 起点+航向+Z 夸张 → 世界刚体变换 T。
|
||||||
// 变换合成(VTK:先加先内层,作用于点的顺序为 后加先施):Translate→RotateZ→Scale(1,1,exagg),
|
// 合成顺序(VTK PostMultiply:先加先施于点):Scale(1,1,exagg)→RotateZ→Translate,
|
||||||
// 即点先按 exagg 拉伸 Z(局部),再绕 Z 旋到真实航向,再平移到世界起点。
|
// 即点先按 exagg 拉伸 Z(局部深度),再绕竖直轴转真实航向,再平移到世界起点。
|
||||||
vtkSmartPointer<vtkVolume> makePlacedVolume(const LinePlacement& lp, double exagg,
|
vtkSmartPointer<vtkTransform> makeLineTransform(double startX, double startY,
|
||||||
double& vminOut, double& vmaxOut) {
|
double headingDeg,
|
||||||
const GalleryVariant& v = kViewDefaultVariant; // P4 默认醒目版(var4)
|
double spreadX, double spreadY,
|
||||||
const geopro::data::StoreMeta& m = lp.meta;
|
double exagg) {
|
||||||
|
auto xf = vtkSmartPointer<vtkTransform>::New();
|
||||||
|
xf->PostMultiply();
|
||||||
|
xf->Scale(1.0, 1.0, exagg);
|
||||||
|
xf->RotateZ(headingDeg);
|
||||||
|
xf->Translate(startX + spreadX, startY + spreadY, 0.0);
|
||||||
|
return xf;
|
||||||
|
}
|
||||||
|
|
||||||
// 逐体 2/98 分位标定(裁离群,自适应该体值域;退化则回退全量化域)。
|
// 逐线传函:从该线常驻底图(整卷代表)实测 2/98 分位标定色阶/不透明度端点 + 梯度门,
|
||||||
|
// 与单条 view 的传函标定同口径(底图非空恒可标定;退化回退全量化域)。
|
||||||
|
vtkSmartPointer<vtkVolumeProperty> buildLineProperty(
|
||||||
|
const geopro::data::StoreMeta& m, vtkImageData* basis) {
|
||||||
|
const GalleryVariant& v = kViewDefaultVariant; // P4 默认醒目版(var4)
|
||||||
double vmin = m.vminPhys, vmax = m.vmaxPhys;
|
double vmin = m.vminPhys, vmax = m.vmaxPhys;
|
||||||
|
if (basis != nullptr) {
|
||||||
const ScalarPercentiles pc =
|
const ScalarPercentiles pc =
|
||||||
sampleScalarPercentiles(lp.img.Get(), m.quant, 0.02, 0.98);
|
sampleScalarPercentiles(basis, m.quant, 0.02, 0.98);
|
||||||
if (pc.samples > 0) {
|
if (pc.samples > 0) {
|
||||||
vmin = pc.lo;
|
vmin = pc.lo;
|
||||||
vmax = pc.hi;
|
vmax = pc.hi;
|
||||||
}
|
}
|
||||||
vminOut = vmin;
|
}
|
||||||
vmaxOut = vmax;
|
|
||||||
const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax);
|
const geopro::core::ColorScale cs = pickColor(v.color, vmin, vmax);
|
||||||
|
|
||||||
GradStats gs;
|
GradStats gs;
|
||||||
if (v.useGradientOpacity) gs = sampleGradientMagnitude(lp.img.Get());
|
if (v.useGradientOpacity && basis != nullptr) gs = sampleGradientMagnitude(basis);
|
||||||
vtkSmartPointer<vtkVolumeProperty> prop = makeVariantProperty(
|
return makeVariantProperty(v, m.quant, cs, vmin, vmax, v.maxOpacity,
|
||||||
v, m.quant, cs, vmin, vmax, v.maxOpacity,
|
(v.useGradientOpacity && gs.samples > 0) ? &gs
|
||||||
v.useGradientOpacity ? &gs : nullptr);
|
: nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
vtkNew<vtkSmartVolumeMapper> mapper;
|
// 把世界相机参数逆变换到某线局部帧(T⁻¹):pos/focal 是点(含平移逆),up 是方向
|
||||||
mapper->SetInputData(lp.img.Get());
|
// (仅旋转逆,TransformVector 不含平移)。再调引擎 updateView 选层选区(视锥外→引擎
|
||||||
mapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
|
// 内部 selectLod 判 empty → 不提交,保留上一就绪/无图)。
|
||||||
mapper->SetAutoAdjustSampleDistances(0);
|
void viewAllSubmitOneLine(PlacedSource& ps, vtkCamera* worldCam,
|
||||||
mapper->SetInteractiveAdjustSampleDistances(0);
|
double aspect, int viewportH) {
|
||||||
|
if (ps.culled || worldCam == nullptr) return;
|
||||||
|
double wp[3], wf[3], wu[3];
|
||||||
|
worldCam->GetPosition(wp);
|
||||||
|
worldCam->GetFocalPoint(wf);
|
||||||
|
worldCam->GetViewUp(wu);
|
||||||
|
|
||||||
auto volume = vtkSmartPointer<vtkVolume>::New();
|
geopro::render::CameraView c{};
|
||||||
volume->SetMapper(mapper);
|
ps.worldInv->TransformPoint(wp, c.pos);
|
||||||
volume->SetProperty(prop);
|
ps.worldInv->TransformPoint(wf, c.focal);
|
||||||
|
ps.worldInv->TransformVector(wu, c.up);
|
||||||
|
c.fovYDeg = worldCam->GetViewAngle();
|
||||||
|
c.aspect = aspect;
|
||||||
|
c.viewportH = viewportH;
|
||||||
|
|
||||||
// 刚体摆放 + Z 夸张(一并烘进 UserTransform)。
|
// 引擎 exagg=1.0:局部帧几何无夸张(夸张在 T 里),故喂引擎 volumeView 默认即可。
|
||||||
auto xf = vtkSmartPointer<vtkTransform>::New();
|
ps.source->setAspect(aspect);
|
||||||
xf->PostMultiply();
|
ps.source->setViewportHeight(viewportH);
|
||||||
xf->Scale(1.0, 1.0, exagg); // 只 Z 夸张(局部深度)
|
ps.source->updateView(c, geopro::render::VolumeView{
|
||||||
xf->RotateZ(lp.headingDeg); // 绕竖直轴转航向
|
ps.meta.nx, ps.meta.ny, ps.meta.nz,
|
||||||
// 平移到世界起点(+ 可选横向铺开偏移,垂直航向,让重叠的同路多趟可分辨)。
|
ps.meta.brick, ps.source->levelCount(),
|
||||||
xf->Translate(lp.startX + lp.spreadX, lp.startY + lp.spreadY, 0.0);
|
{ps.meta.origin[0], ps.meta.origin[1],
|
||||||
volume->SetUserTransform(xf);
|
ps.meta.origin[2]},
|
||||||
return volume;
|
{ps.meta.spacing[0], ps.meta.spacing[1],
|
||||||
|
ps.meta.spacing[2]},
|
||||||
|
1.0});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非阻塞拉取该线后台已就绪的高清单图,喂高清 mapper(无新结果→沿用上一帧)。
|
||||||
|
// 返回 1=换上新图。
|
||||||
|
int viewAllPickOneLine(PlacedSource& ps) {
|
||||||
|
if (ps.culled) return 0;
|
||||||
|
auto imgs = ps.source->currentImages(); // 内部 takeLatest(非阻塞)
|
||||||
|
if (imgs.empty() || imgs[0] == nullptr) return 0;
|
||||||
|
if (imgs[0] == ps.currentImg) return 0;
|
||||||
|
ps.currentImg = imgs[0];
|
||||||
|
ps.hiresMapper->SetInputData(ps.currentImg);
|
||||||
|
ps.hiresMapper->Update();
|
||||||
|
ps.hiresVolume->SetVisibility(1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视锥裁剪(#2):把该线世界 AABB 与相机 6 个视锥面比对,整盒在任一面外侧 → 裁掉
|
||||||
|
// (base+hires 两层皆隐 → 该线本帧完全不渲,省下解压/重组/ray-march)。盒 8 角全在
|
||||||
|
// 某面负半空间才裁(保守,绝不误裁部分可见的线)。
|
||||||
|
bool aabbOutsideFrustum(const double b[6], const double planes[24]) {
|
||||||
|
for (int p = 0; p < 6; ++p) {
|
||||||
|
const double a = planes[p * 4 + 0], bb = planes[p * 4 + 1],
|
||||||
|
cc = planes[p * 4 + 2], dd = planes[p * 4 + 3];
|
||||||
|
bool allOut = true;
|
||||||
|
for (int cx = 0; cx < 2 && allOut; ++cx)
|
||||||
|
for (int cy = 0; cy < 2 && allOut; ++cy)
|
||||||
|
for (int cz = 0; cz < 2 && allOut; ++cz) {
|
||||||
|
const double x = b[cx], y = b[2 + cy], z = b[4 + cz];
|
||||||
|
if (a * x + bb * y + cc * z + dd >= 0.0) allOut = false; // 该角在面内
|
||||||
|
}
|
||||||
|
if (allOut) return true; // 全 8 角在该面外 → 整盒在视锥外
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// view-all 每帧驱动共享状态(挂 interactor 回调)。
|
||||||
|
struct ViewAllState {
|
||||||
|
std::vector<PlacedSource>* lines = nullptr;
|
||||||
|
vtkRenderer* ren = nullptr;
|
||||||
|
vtkCamera* cam = nullptr;
|
||||||
|
vtkRenderWindow* rw = nullptr;
|
||||||
|
vtkTextActor* fpsText = nullptr;
|
||||||
|
double aspect = 1400.0 / 900.0;
|
||||||
|
int viewportH = 900;
|
||||||
|
bool inCb = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重算各线视锥可见性(裁屏外线)+ 对可见线提交引擎目标(非阻塞)。culled 线两层皆隐。
|
||||||
|
void viewAllRefreshFrustum(ViewAllState* st) {
|
||||||
|
double planes[24];
|
||||||
|
st->cam->GetFrustumPlanes(st->aspect, planes);
|
||||||
|
for (PlacedSource& ps : *st->lines) {
|
||||||
|
const bool outside = aabbOutsideFrustum(ps.worldBounds, planes);
|
||||||
|
ps.culled = outside;
|
||||||
|
ps.baseVolume->SetVisibility(outside ? 0 : 1);
|
||||||
|
if (outside) {
|
||||||
|
ps.hiresVolume->SetVisibility(0);
|
||||||
|
} else {
|
||||||
|
// 可见:提交引擎目标(局部帧),高清可见性由 pick 决定(有就绪图才显)。
|
||||||
|
viewAllSubmitOneLine(ps, st->cam, st->aspect, st->viewportH);
|
||||||
|
ps.hiresVolume->SetVisibility(ps.currentImg != nullptr ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交互进行中:只重算视锥可见性 + 提交目标(非阻塞),主线程立即继续响应输入。
|
||||||
|
void viewAllOnInteracting(vtkObject*, unsigned long, void* clientData, void*) {
|
||||||
|
auto* st = static_cast<ViewAllState*>(clientData);
|
||||||
|
viewAllRefreshFrustum(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时器:非阻塞拉取各可见线后台已就绪的新高清纹理换上 → 有新图才重渲。
|
||||||
|
void viewAllOnTimer(vtkObject*, unsigned long, void* clientData, void*) {
|
||||||
|
auto* st = static_cast<ViewAllState*>(clientData);
|
||||||
|
if (st->inCb) return;
|
||||||
|
int changed = 0;
|
||||||
|
for (PlacedSource& ps : *st->lines) changed += viewAllPickOneLine(ps);
|
||||||
|
if (changed > 0) {
|
||||||
|
st->ren->ResetCameraClippingRange();
|
||||||
|
st->rw->Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交互结束:重算视锥 + 提交 + 拉取 + 刷新 fps(仅松手触发一次)。
|
||||||
|
void viewAllOnInteract(vtkObject*, unsigned long, void* clientData, void*) {
|
||||||
|
auto* st = static_cast<ViewAllState*>(clientData);
|
||||||
|
if (st->inCb) return;
|
||||||
|
st->inCb = true;
|
||||||
|
viewAllRefreshFrustum(st);
|
||||||
|
int culledN = 0, visN = 0;
|
||||||
|
for (PlacedSource& ps : *st->lines) {
|
||||||
|
viewAllPickOneLine(ps);
|
||||||
|
if (ps.culled) ++culledN; else ++visN;
|
||||||
|
}
|
||||||
|
st->ren->ResetCameraClippingRange();
|
||||||
|
|
||||||
|
constexpr int kFpsProbeFrames = 3;
|
||||||
|
Stopwatch swR;
|
||||||
|
for (int i = 0; i < kFpsProbeFrames; ++i) st->rw->Render();
|
||||||
|
const double fps = swR.elapsedMs() > 0
|
||||||
|
? 1000.0 * kFpsProbeFrames / swR.elapsedMs()
|
||||||
|
: 0.0;
|
||||||
|
char buf[256];
|
||||||
|
std::snprintf(buf, sizeof(buf),
|
||||||
|
"fps: %.1f | visible lines: %d | culled: %d", fps, visN,
|
||||||
|
culledN);
|
||||||
|
if (st->fpsText) st->fpsText->SetInput(buf);
|
||||||
|
st->rw->Render();
|
||||||
|
st->inCb = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmdViewAll(int argc, char** argv) {
|
int cmdViewAll(int argc, char** argv) {
|
||||||
|
|
@ -4159,12 +4309,15 @@ int cmdViewAll(int argc, char** argv) {
|
||||||
"如需把各趟铺开成可分辨的并排测区,加 --spread 60。\n";
|
"如需把各趟铺开成可分辨的并排测区,加 --spread 60。\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) 逐线:算起点局部米 + 航向 + 加载整卷体 → LinePlacement。
|
// 3) 逐线:算起点局部米 + 航向 + 建 ViewAdaptiveVolumeSource 引擎 → PlacedSource。
|
||||||
std::vector<LinePlacement> placements;
|
// P11:不再整卷固定层加载(撞 GL 16384 纹理墙),改为每线一个视野自适应引擎,
|
||||||
|
// 引擎恒产 ≤16384 单纹理(LOD+视野选区),exagg/航向/平移由世界变换 T 承担。
|
||||||
|
const int winW = 1400, winH = 900;
|
||||||
|
const double aspect = static_cast<double>(winW) / winH;
|
||||||
|
std::vector<PlacedSource> lines;
|
||||||
int lineIdx = 0;
|
int lineIdx = 0;
|
||||||
for (const LineGps& lg : gpsList) {
|
for (const LineGps& lg : gpsList) {
|
||||||
// 轨迹 → CGCS2000 局部米(投影到公共带号后减公共原点)。XY 约定 x=东、y=北,
|
// 轨迹 → CGCS2000 局部米(投影到公共带号后减公共原点)。XY 约定 x=东、y=北。
|
||||||
// 与下游航向 atan2(hy,hx) / 刚体平移 Translate(x,y) 一致。
|
|
||||||
std::vector<geopro::io::gpr::XY> trackM;
|
std::vector<geopro::io::gpr::XY> trackM;
|
||||||
trackM.reserve(lg.track.pts.size());
|
trackM.reserve(lg.track.pts.size());
|
||||||
for (const auto& p : lg.track.pts) {
|
for (const auto& p : lg.track.pts) {
|
||||||
|
|
@ -4175,11 +4328,9 @@ int cmdViewAll(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const geopro::io::gpr::XY& start = trackM.front();
|
const geopro::io::gpr::XY& start = trackM.front();
|
||||||
// 航向:起→止主方向(直接首尾向量,稳健于逐点抖动)。
|
|
||||||
const geopro::io::gpr::XY& end = trackM.back();
|
const geopro::io::gpr::XY& end = trackM.back();
|
||||||
const double hx = end.x - start.x, hy = end.y - start.y;
|
const double hx = end.x - start.x, hy = end.y - start.y;
|
||||||
const double headingDeg = std::atan2(hy, hx) * 180.0 / 3.14159265358979323846;
|
const double headingDeg = std::atan2(hy, hx) * 180.0 / 3.14159265358979323846;
|
||||||
// 垂直航向的左法向单位向量 (-hy,hx)/|h|,用于 --spread 横向铺开。
|
|
||||||
const double hlen = std::hypot(hx, hy);
|
const double hlen = std::hypot(hx, hy);
|
||||||
double spreadX = 0, spreadY = 0;
|
double spreadX = 0, spreadY = 0;
|
||||||
if (spread > 0.0 && hlen > 0.0) {
|
if (spread > 0.0 && hlen > 0.0) {
|
||||||
|
|
@ -4187,34 +4338,95 @@ int cmdViewAll(int argc, char** argv) {
|
||||||
spreadX = (-hy / hlen) * off;
|
spreadX = (-hy / hlen) * off;
|
||||||
spreadY = (hx / hlen) * off;
|
spreadY = (hx / hlen) * off;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载该线整卷体(按 --level;夹到该 store 实际层数)。
|
|
||||||
const std::string storePath = (fs::path(storesDir) / lg.name).string();
|
|
||||||
geopro::data::ChunkedVolumeStore store(storePath);
|
|
||||||
const int lv = std::max(0, std::min(level, store.levels() - 1));
|
|
||||||
vtkSmartPointer<vtkImageData> img =
|
|
||||||
buildLevelImage(store, lv, store.meta());
|
|
||||||
|
|
||||||
LinePlacement lp;
|
|
||||||
lp.name = lg.name;
|
|
||||||
lp.img = img;
|
|
||||||
lp.meta = store.meta();
|
|
||||||
lp.startX = start.x;
|
|
||||||
lp.startY = start.y;
|
|
||||||
lp.headingDeg = headingDeg;
|
|
||||||
lp.spreadX = spreadX;
|
|
||||||
lp.spreadY = spreadY;
|
|
||||||
++lineIdx;
|
++lineIdx;
|
||||||
std::cout << "[view-all] " << lg.name << " level=" << lv << " 维度="
|
|
||||||
<< img->GetDimensions()[0] << "x" << img->GetDimensions()[1]
|
|
||||||
<< "x" << img->GetDimensions()[2] << " 起点局部米=(" << start.x
|
|
||||||
<< ", " << start.y << ") 航向=" << headingDeg << "°\n";
|
|
||||||
placements.push_back(std::move(lp));
|
|
||||||
}
|
|
||||||
std::cout << "[view-all] 加载并定位线数=" << placements.size() << "\n";
|
|
||||||
|
|
||||||
// 4) 同一 renderer 加全部 placed volume。
|
const std::string storePath = (fs::path(storesDir) / lg.name).string();
|
||||||
const int winW = 1400, winH = 900;
|
|
||||||
|
PlacedSource ps;
|
||||||
|
ps.name = lg.name;
|
||||||
|
// 引擎 exagg=1.0:垂向夸张放进世界变换 T(与单条 view 把 exagg 放 actor 同理)。
|
||||||
|
ps.source = std::make_unique<geopro::render::ViewAdaptiveVolumeSource>(
|
||||||
|
storePath, /*exagg=*/1.0);
|
||||||
|
ps.source->setAspect(aspect);
|
||||||
|
ps.source->setViewportHeight(winH);
|
||||||
|
ps.meta = ps.source->meta();
|
||||||
|
ps.startX = start.x;
|
||||||
|
ps.startY = start.y;
|
||||||
|
ps.headingDeg = headingDeg;
|
||||||
|
ps.spreadX = spreadX;
|
||||||
|
ps.spreadY = spreadY;
|
||||||
|
|
||||||
|
// 世界变换 T + 逆变换(相机逆变换到局部帧)。
|
||||||
|
ps.world = makeLineTransform(start.x, start.y, headingDeg, spreadX, spreadY,
|
||||||
|
exagg);
|
||||||
|
ps.worldInv = vtkSmartPointer<vtkTransform>::New();
|
||||||
|
ps.worldInv->DeepCopy(ps.world);
|
||||||
|
ps.worldInv->Inverse();
|
||||||
|
|
||||||
|
// 逐线传函(从常驻底图标定)+ 底图层 + 高清层,两层皆套世界变换 T。
|
||||||
|
ps.prop = buildLineProperty(ps.meta, ps.source->baseImage());
|
||||||
|
|
||||||
|
ps.baseMapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
|
||||||
|
ps.baseMapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
|
||||||
|
ps.baseMapper->SetAutoAdjustSampleDistances(1);
|
||||||
|
ps.baseMapper->SetInteractiveAdjustSampleDistances(1);
|
||||||
|
ps.baseVolume = vtkSmartPointer<vtkVolume>::New();
|
||||||
|
if (ps.source->baseImage() != nullptr) {
|
||||||
|
ps.baseMapper->SetInputData(ps.source->baseImage());
|
||||||
|
ps.baseMapper->Update();
|
||||||
|
}
|
||||||
|
ps.baseVolume->SetMapper(ps.baseMapper);
|
||||||
|
ps.baseVolume->SetProperty(ps.prop);
|
||||||
|
ps.baseVolume->SetUserTransform(ps.world);
|
||||||
|
|
||||||
|
ps.hiresMapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
|
||||||
|
ps.hiresMapper->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
|
||||||
|
// #1 拖动降采样:交互式采样距离自适应(拖动→大步长降采样跟手,松手→全质量)。
|
||||||
|
ps.hiresMapper->SetAutoAdjustSampleDistances(1);
|
||||||
|
ps.hiresMapper->SetInteractiveAdjustSampleDistances(1);
|
||||||
|
ps.hiresVolume = vtkSmartPointer<vtkVolume>::New();
|
||||||
|
ps.hiresVolume->SetMapper(ps.hiresMapper);
|
||||||
|
ps.hiresVolume->SetProperty(ps.prop);
|
||||||
|
ps.hiresVolume->SetUserTransform(ps.world);
|
||||||
|
ps.hiresVolume->SetVisibility(0); // 无就绪高清前不显(底图兜底)
|
||||||
|
|
||||||
|
// 该线世界 AABB(底图模型盒经 T 变换的 8 角包络)→ 视锥裁剪用。
|
||||||
|
if (ps.source->baseImage() != nullptr) {
|
||||||
|
double mb[6];
|
||||||
|
ps.source->baseImage()->GetBounds(mb);
|
||||||
|
double lo[3] = {std::numeric_limits<double>::infinity(),
|
||||||
|
std::numeric_limits<double>::infinity(),
|
||||||
|
std::numeric_limits<double>::infinity()};
|
||||||
|
double hi[3] = {-std::numeric_limits<double>::infinity(),
|
||||||
|
-std::numeric_limits<double>::infinity(),
|
||||||
|
-std::numeric_limits<double>::infinity()};
|
||||||
|
for (int cx = 0; cx < 2; ++cx)
|
||||||
|
for (int cy = 0; cy < 2; ++cy)
|
||||||
|
for (int cz = 0; cz < 2; ++cz) {
|
||||||
|
double in[3] = {mb[cx], mb[2 + cy], mb[4 + cz]}, out[3];
|
||||||
|
ps.world->TransformPoint(in, out);
|
||||||
|
for (int d = 0; d < 3; ++d) {
|
||||||
|
lo[d] = std::min(lo[d], out[d]);
|
||||||
|
hi[d] = std::max(hi[d], out[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps.worldBounds[0] = lo[0]; ps.worldBounds[1] = hi[0];
|
||||||
|
ps.worldBounds[2] = lo[1]; ps.worldBounds[3] = hi[1];
|
||||||
|
ps.worldBounds[4] = lo[2]; ps.worldBounds[5] = hi[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
int bd[3] = {0, 0, 0};
|
||||||
|
if (ps.source->baseImage()) ps.source->baseImage()->GetDimensions(bd);
|
||||||
|
std::cout << "[view-all] " << lg.name << " 引擎底图 level="
|
||||||
|
<< ps.source->baseLevel() << " 底图维度=" << bd[0] << "x" << bd[1]
|
||||||
|
<< "x" << bd[2] << " 起点局部米=(" << start.x << ", " << start.y
|
||||||
|
<< ") 航向=" << headingDeg << "°\n";
|
||||||
|
lines.push_back(std::move(ps));
|
||||||
|
}
|
||||||
|
std::cout << "[view-all] 加载并定位线数=" << lines.size()
|
||||||
|
<< "(每线一个 ViewAdaptiveVolumeSource 引擎)\n";
|
||||||
|
|
||||||
|
// 4) 同一 renderer 加全部线的底图层 + 高清层。
|
||||||
auto rw = preview ? makeOffscreenWindow(winW, winH)
|
auto rw = preview ? makeOffscreenWindow(winW, winH)
|
||||||
: vtkSmartPointer<vtkRenderWindow>::New();
|
: vtkSmartPointer<vtkRenderWindow>::New();
|
||||||
if (!preview) rw->SetSize(winW, winH);
|
if (!preview) rw->SetSize(winW, winH);
|
||||||
|
|
@ -4226,30 +4438,47 @@ int cmdViewAll(int argc, char** argv) {
|
||||||
auto capWin = vtkSmartPointer<CapturingOutputWindow>::New();
|
auto capWin = vtkSmartPointer<CapturingOutputWindow>::New();
|
||||||
vtkOutputWindow::SetInstance(capWin);
|
vtkOutputWindow::SetInstance(capWin);
|
||||||
|
|
||||||
int placed = 0;
|
for (PlacedSource& ps : lines) {
|
||||||
for (const LinePlacement& lp : placements) {
|
ren->AddVolume(ps.baseVolume); // 先加底图 → 底层常渲
|
||||||
double vmin = 0, vmax = 0;
|
ren->AddVolume(ps.hiresVolume); // 后加高清 → 叠在底图上
|
||||||
vtkSmartPointer<vtkVolume> vol = makePlacedVolume(lp, exagg, vmin, vmax);
|
|
||||||
ren->AddVolume(vol);
|
|
||||||
++placed;
|
|
||||||
}
|
}
|
||||||
std::cout << "[view-all] 已加入场景体数=" << placed << "\n";
|
std::cout << "[view-all] 已加入场景线数=" << lines.size()
|
||||||
|
<< "(底图常驻 + 高清叠加,各 ≤16384 单纹理,绝不撞 GL 纹理墙)\n";
|
||||||
|
|
||||||
// 渲一帧 → 验非空 + 闸门纹理错。
|
ViewAllState st;
|
||||||
|
st.lines = &lines;
|
||||||
|
st.ren = ren.Get();
|
||||||
|
st.rw = rw.Get();
|
||||||
|
st.aspect = aspect;
|
||||||
|
st.viewportH = winH;
|
||||||
|
|
||||||
|
// 首帧:ResetCamera 框全测区 → 概览(各线选粗 LOD 底图)。提交引擎目标 + 阻塞拉首图。
|
||||||
ren->ResetCamera();
|
ren->ResetCamera();
|
||||||
|
st.cam = ren->GetActiveCamera();
|
||||||
|
viewAllRefreshFrustum(&st);
|
||||||
|
// 概览阻塞拉一次(保证首帧高清就绪,离屏/真窗口都从有图起步)。
|
||||||
|
for (PlacedSource& ps : lines) {
|
||||||
|
if (ps.culled) continue;
|
||||||
|
for (int tries = 0; tries < 200; ++tries) {
|
||||||
|
if (viewAllPickOneLine(ps)) break;
|
||||||
|
if (ps.currentImg != nullptr) break;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
rw->Render();
|
rw->Render();
|
||||||
if (capWin->textureError()) {
|
if (capWin->textureError()) {
|
||||||
std::cerr << "[view-all] 警告: 检测到 3D 纹理维度错误(某体超 GL 上限),"
|
std::cerr << "[view-all] 警告: 仍检测到 3D 纹理维度错误(不应发生,引擎契约 "
|
||||||
"可调小 --level 增大 level 取更粗层。\n";
|
"≤16384)。\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preview) {
|
if (preview) {
|
||||||
fs::create_directories(shotDir);
|
fs::create_directories(shotDir);
|
||||||
// 俯视(top):相机沿 -Z 俯看 XY 平面(看 20 条在测区平面如何并排铺开)。
|
|
||||||
|
// (A) 概览俯视(top):相机沿 -Z 俯看 XY 平面(看 20 条在测区平面铺开)。
|
||||||
{
|
{
|
||||||
ren->ResetCamera();
|
ren->ResetCamera();
|
||||||
vtkCamera* cam = ren->GetActiveCamera();
|
vtkCamera* cam = ren->GetActiveCamera();
|
||||||
double fp[3], pos[3];
|
double fp[3];
|
||||||
cam->GetFocalPoint(fp);
|
cam->GetFocalPoint(fp);
|
||||||
const double* b = ren->ComputeVisiblePropBounds();
|
const double* b = ren->ComputeVisiblePropBounds();
|
||||||
const double span = std::max({b[1] - b[0], b[3] - b[2], b[5] - b[4]});
|
const double span = std::max({b[1] - b[0], b[3] - b[2], b[5] - b[4]});
|
||||||
|
|
@ -4257,67 +4486,188 @@ int cmdViewAll(int argc, char** argv) {
|
||||||
cam->SetFocalPoint(fp[0], fp[1], fp[2]);
|
cam->SetFocalPoint(fp[0], fp[1], fp[2]);
|
||||||
cam->SetViewUp(0, 1, 0);
|
cam->SetViewUp(0, 1, 0);
|
||||||
ren->ResetCameraClippingRange();
|
ren->ResetCameraClippingRange();
|
||||||
rw->Render();
|
st.cam = cam;
|
||||||
const std::string p = (fs::path(shotDir) / "view-all-top.png").string();
|
viewAllRefreshFrustum(&st);
|
||||||
savePng(rw.Get(), p);
|
for (PlacedSource& ps : lines)
|
||||||
std::cout << "[view-all] 俯视图存: " << p << "\n";
|
for (int t = 0; t < 100 && !ps.culled && ps.currentImg == nullptr; ++t) {
|
||||||
|
if (viewAllPickOneLine(ps)) break;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||||
}
|
}
|
||||||
// 斜视(oblique):默认 var4 取景(El45/Az30 风格),看测区三维起伏。
|
rw->Render();
|
||||||
|
savePng(rw.Get(), (fs::path(shotDir) / "view-all-top.png").string());
|
||||||
|
std::cout << "[view-all] 俯视图存: "
|
||||||
|
<< (fs::path(shotDir) / "view-all-top.png").string() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// (B) 概览斜视(oblique):var4 取景 + 概览 fps(全部可见、各选粗 LOD)。
|
||||||
|
int ovVisible = 0, ovCulled = 0;
|
||||||
|
double fpsOverview = 0.0;
|
||||||
{
|
{
|
||||||
ren->ResetCamera();
|
ren->ResetCamera();
|
||||||
vtkCamera* cam = ren->GetActiveCamera();
|
vtkCamera* cam = ren->GetActiveCamera();
|
||||||
cam->Elevation(35.0);
|
cam->Elevation(35.0);
|
||||||
cam->Azimuth(30.0);
|
cam->Azimuth(30.0);
|
||||||
ren->ResetCameraClippingRange();
|
ren->ResetCameraClippingRange();
|
||||||
rw->Render();
|
st.cam = cam;
|
||||||
const std::string p =
|
viewAllRefreshFrustum(&st);
|
||||||
(fs::path(shotDir) / "view-all-oblique.png").string();
|
for (PlacedSource& ps : lines) {
|
||||||
savePng(rw.Get(), p);
|
for (int t = 0; t < 100 && !ps.culled && ps.currentImg == nullptr; ++t) {
|
||||||
std::cout << "[view-all] 斜视图存: " << p << "\n";
|
if (viewAllPickOneLine(ps)) break;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||||
}
|
}
|
||||||
|
if (ps.culled) ++ovCulled; else ++ovVisible;
|
||||||
|
}
|
||||||
|
rw->Render();
|
||||||
|
savePng(rw.Get(),
|
||||||
|
(fs::path(shotDir) / "view-all-oblique.png").string());
|
||||||
|
std::cout << "[view-all] 斜视图存: "
|
||||||
|
<< (fs::path(shotDir) / "view-all-oblique.png").string() << "\n";
|
||||||
|
|
||||||
// fps:斜视取景预热后连渲计时(真实多体共场景 fps,不编造)。
|
rw->SetDesiredUpdateRate(15.0); // 拖动态:降采样
|
||||||
rw->Render();
|
rw->Render();
|
||||||
Stopwatch sw;
|
Stopwatch sw;
|
||||||
const int frames = 60;
|
const int frames = 60;
|
||||||
for (int f = 0; f < frames; ++f) rw->Render();
|
for (int f = 0; f < frames; ++f) {
|
||||||
const double ms = sw.elapsedMs();
|
cam->Azimuth(360.0 / frames);
|
||||||
const double fps = ms > 0 ? frames * 1000.0 / ms : 0.0;
|
rw->Render();
|
||||||
const vtkIdType nb = countNonBlackPixels(rw.Get(), winW, winH);
|
}
|
||||||
|
fpsOverview = sw.elapsedMs() > 0 ? frames * 1000.0 / sw.elapsedMs() : 0.0;
|
||||||
std::cout << "\n=== view-all --preview 测区全貌(多体共场景)===\n";
|
|
||||||
std::cout << "参与线数 : " << placements.size() << "\n";
|
|
||||||
std::cout << "level : " << level << "\n";
|
|
||||||
std::cout << "exagg(Z) : " << exagg << "\n";
|
|
||||||
std::cout << "spread(横向铺开): " << spread << " m (0=纯真实位置)\n";
|
|
||||||
std::cout << "非黑像素 : " << nb << " / " << (winW * winH) << "\n";
|
|
||||||
std::cout << "fps(" << frames << "帧连渲) : " << fps << "\n";
|
|
||||||
std::cout << "俯视图 : " << (fs::path(shotDir) / "view-all-top.png").string()
|
|
||||||
<< "\n";
|
|
||||||
std::cout << "斜视图 : "
|
|
||||||
<< (fs::path(shotDir) / "view-all-oblique.png").string() << "\n";
|
|
||||||
|
|
||||||
writeMetricLine(
|
|
||||||
"view-all,lines=" + std::to_string(placements.size()) +
|
|
||||||
",level=" + std::to_string(level) + ",exagg=" + std::to_string(exagg) +
|
|
||||||
",spread=" + std::to_string(spread) +
|
|
||||||
",nonBlack=" + std::to_string(nb) + ",fps=" + std::to_string(fps));
|
|
||||||
return nb > 0 ? 0 : 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 真窗口:可旋转/缩放。
|
// (C) 拉近一段:把焦点对准测区中段一条线的世界中心、近距正对该段 → 大部分线出
|
||||||
rw->SetWindowName("gpr_poc view-all —— 20 条独立体按真实 GPS 摆成测区");
|
// 视锥被裁掉,当前线只渲可见段的合适 LOD。报拉近 fps(与概览对比)。
|
||||||
|
// fps 探针用小幅 azimuth 摆动(±6°,模拟拉近态轻微转动观察),而非整周 orbit
|
||||||
|
// ——整周 orbit 会把全测区转回视野使裁剪失效,不代表真实"拉近看一段"的交互。
|
||||||
|
int nearVisible = 0, nearCulled = 0;
|
||||||
|
double fpsNear = 0.0;
|
||||||
|
{
|
||||||
|
// 选中段一条线(取参与线的中位)作拉近目标,焦点置其世界 AABB 中心。
|
||||||
|
const PlacedSource& tgt = lines[lines.size() / 2];
|
||||||
|
const double cx = 0.5 * (tgt.worldBounds[0] + tgt.worldBounds[1]);
|
||||||
|
const double cy = 0.5 * (tgt.worldBounds[2] + tgt.worldBounds[3]);
|
||||||
|
const double cz = 0.5 * (tgt.worldBounds[4] + tgt.worldBounds[5]);
|
||||||
|
// 该段(含 exagg 后)世界跨度 → 近距视距(贴该段,使其充满视野、邻线出视锥)。
|
||||||
|
const double segLen = std::max({tgt.worldBounds[1] - tgt.worldBounds[0],
|
||||||
|
tgt.worldBounds[3] - tgt.worldBounds[2],
|
||||||
|
tgt.worldBounds[5] - tgt.worldBounds[4]});
|
||||||
|
vtkCamera* cam = ren->GetActiveCamera();
|
||||||
|
const double fovY = cam->GetViewAngle();
|
||||||
|
const double tanH =
|
||||||
|
std::max(1e-3, std::tan(0.5 * fovY * 3.14159265358979 / 180.0));
|
||||||
|
// 近距:只贴该段横截面尺度(取较短轴 ~该段 Y/Z 跨度的 1/4),使邻行线出视锥。
|
||||||
|
const double shortSpan = std::max(
|
||||||
|
1.0, std::min(tgt.worldBounds[3] - tgt.worldBounds[2],
|
||||||
|
tgt.worldBounds[5] - tgt.worldBounds[4]));
|
||||||
|
const double dist = (0.25 * shortSpan) / tanH * 1.4;
|
||||||
|
cam->SetFocalPoint(cx, cy, cz);
|
||||||
|
cam->SetPosition(cx, cy + dist, cz + 0.3 * dist); // 斜上方近观该段
|
||||||
|
cam->SetViewUp(0, 0, 1);
|
||||||
|
ren->ResetCameraClippingRange();
|
||||||
|
st.cam = cam;
|
||||||
|
(void)segLen;
|
||||||
|
viewAllRefreshFrustum(&st);
|
||||||
|
for (PlacedSource& ps : lines) {
|
||||||
|
for (int t = 0; t < 120 && !ps.culled && ps.currentImg == nullptr; ++t) {
|
||||||
|
if (viewAllPickOneLine(ps)) break;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||||
|
}
|
||||||
|
if (ps.culled) ++nearCulled; else ++nearVisible;
|
||||||
|
}
|
||||||
|
rw->Render();
|
||||||
|
savePng(rw.Get(), (fs::path(shotDir) / "view-all-near.png").string());
|
||||||
|
std::cout << "[view-all] 拉近图存: "
|
||||||
|
<< (fs::path(shotDir) / "view-all-near.png").string() << "\n";
|
||||||
|
|
||||||
|
rw->SetDesiredUpdateRate(15.0); // 拖动态:降采样
|
||||||
|
rw->Render();
|
||||||
|
Stopwatch sw;
|
||||||
|
const int frames = 60;
|
||||||
|
for (int f = 0; f < frames; ++f) {
|
||||||
|
cam->Azimuth(f % 2 == 0 ? 0.4 : -0.4); // 小幅摆动(不转回全测区)
|
||||||
|
viewAllRefreshFrustum(&st); // 拖动中持续重算视锥裁剪
|
||||||
|
rw->Render();
|
||||||
|
}
|
||||||
|
fpsNear = sw.elapsedMs() > 0 ? frames * 1000.0 / sw.elapsedMs() : 0.0;
|
||||||
|
rw->SetDesiredUpdateRate(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vtkIdType nb = countNonBlackPixels(rw.Get(), winW, winH);
|
||||||
|
const bool texErr = capWin->textureError();
|
||||||
|
vtkOutputWindow::SetInstance(nullptr);
|
||||||
|
|
||||||
|
std::cout << "\n=== view-all --preview 测区全貌(多体共场景,引擎 LOD+视锥裁剪)===\n";
|
||||||
|
std::cout << "参与线数 : " << lines.size() << "\n";
|
||||||
|
std::cout << "exagg(Z) : " << exagg << "\n";
|
||||||
|
std::cout << "spread(横向铺开) : " << spread << " m (0=纯真实位置)\n";
|
||||||
|
std::cout << "纹理维度错误 : " << (texErr ? "是(!!)" : "否(引擎契约 ≤16384)")
|
||||||
|
<< "\n";
|
||||||
|
std::cout << "概览可见/裁剪线 : " << ovVisible << " / " << ovCulled << "\n";
|
||||||
|
std::cout << "概览 fps(60帧旋) : " << fpsOverview << "\n";
|
||||||
|
std::cout << "拉近可见/裁剪线 : " << nearVisible << " / " << nearCulled
|
||||||
|
<< " (视锥裁剪生效:裁掉屏外线)\n";
|
||||||
|
std::cout << "拉近 fps(60帧旋) : " << fpsNear << "\n";
|
||||||
|
const double speedup = fpsOverview > 0 ? fpsNear / fpsOverview : 0.0;
|
||||||
|
std::cout << "拉近/概览 fps 比 : " << speedup << "x\n";
|
||||||
|
std::cout << "末帧非黑像素 : " << nb << " / " << (winW * winH) << "\n";
|
||||||
|
std::cout << "俯视图 : "
|
||||||
|
<< (fs::path(shotDir) / "view-all-top.png").string() << "\n";
|
||||||
|
std::cout << "斜视图 : "
|
||||||
|
<< (fs::path(shotDir) / "view-all-oblique.png").string() << "\n";
|
||||||
|
std::cout << "拉近图 : "
|
||||||
|
<< (fs::path(shotDir) / "view-all-near.png").string() << "\n";
|
||||||
|
|
||||||
|
writeMetricLine(
|
||||||
|
"view-all,lines=" + std::to_string(lines.size()) +
|
||||||
|
",exagg=" + std::to_string(exagg) + ",spread=" + std::to_string(spread) +
|
||||||
|
",ovVisible=" + std::to_string(ovVisible) +
|
||||||
|
",ovCulled=" + std::to_string(ovCulled) +
|
||||||
|
",nearVisible=" + std::to_string(nearVisible) +
|
||||||
|
",nearCulled=" + std::to_string(nearCulled) +
|
||||||
|
",fpsOverview=" + std::to_string(fpsOverview) +
|
||||||
|
",fpsNear=" + std::to_string(fpsNear) +
|
||||||
|
",nonBlack=" + std::to_string(nb) +
|
||||||
|
",texErr=" + std::to_string(texErr ? 1 : 0));
|
||||||
|
return (nb > 0 && !texErr) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 真窗口:可旋转/缩放(每线引擎 LOD + 视锥裁剪 + 拖动降采样)。
|
||||||
|
vtkOutputWindow::SetInstance(nullptr);
|
||||||
|
rw->SetWindowName("gpr_poc view-all —— 20 条独立体引擎LOD/视锥裁剪/拖动降采样");
|
||||||
|
|
||||||
|
// 屏幕左上角 fps + 可见/裁剪线数文本。
|
||||||
|
vtkNew<vtkTextActor> fpsText;
|
||||||
|
fpsText->SetInput("fps: -- | visible lines: -- | culled: --");
|
||||||
|
fpsText->GetTextProperty()->SetFontSize(20);
|
||||||
|
fpsText->GetTextProperty()->SetColor(1.0, 1.0, 0.4);
|
||||||
|
fpsText->SetDisplayPosition(12, winH - 30);
|
||||||
|
ren->AddViewProp(fpsText);
|
||||||
|
st.fpsText = fpsText.Get();
|
||||||
|
|
||||||
vtkNew<vtkRenderWindowInteractor> iren;
|
vtkNew<vtkRenderWindowInteractor> iren;
|
||||||
iren->SetRenderWindow(rw);
|
iren->SetRenderWindow(rw);
|
||||||
vtkNew<vtkInteractorStyleTrackballCamera> style;
|
vtkNew<vtkInteractorStyleTrackballCamera> style;
|
||||||
iren->SetInteractorStyle(style);
|
iren->SetInteractorStyle(style);
|
||||||
ren->ResetCamera();
|
iren->SetDesiredUpdateRate(15.0); // 拖动态:mapper 降采样
|
||||||
vtkCamera* cam = ren->GetActiveCamera();
|
iren->SetStillUpdateRate(0.5); // 静止态:全质量
|
||||||
cam->Elevation(35.0);
|
|
||||||
cam->Azimuth(30.0);
|
vtkNew<vtkCallbackCommand> cbInteract;
|
||||||
ren->ResetCameraClippingRange();
|
cbInteract->SetCallback(viewAllOnInteracting);
|
||||||
|
cbInteract->SetClientData(&st);
|
||||||
|
iren->AddObserver(vtkCommand::InteractionEvent, cbInteract);
|
||||||
|
|
||||||
|
vtkNew<vtkCallbackCommand> cbEnd;
|
||||||
|
cbEnd->SetCallback(viewAllOnInteract);
|
||||||
|
cbEnd->SetClientData(&st);
|
||||||
|
iren->AddObserver(vtkCommand::EndInteractionEvent, cbEnd);
|
||||||
|
|
||||||
|
vtkNew<vtkCallbackCommand> cbTimer;
|
||||||
|
cbTimer->SetCallback(viewAllOnTimer);
|
||||||
|
cbTimer->SetClientData(&st);
|
||||||
|
iren->AddObserver(vtkCommand::TimerEvent, cbTimer);
|
||||||
|
|
||||||
std::cout << "[view-all] 打开真窗口。左键旋转 / 滚轮缩放 / q 退出。\n";
|
std::cout << "[view-all] 打开真窗口。左键旋转 / 滚轮缩放 / q 退出。\n";
|
||||||
iren->Initialize();
|
iren->Initialize();
|
||||||
|
iren->CreateRepeatingTimer(33); // ~30Hz 非阻塞拉取后台就绪纹理
|
||||||
rw->Render();
|
rw->Render();
|
||||||
iren->Start();
|
iren->Start();
|
||||||
std::cout << "[view-all] 窗口关闭,退出。\n";
|
std::cout << "[view-all] 窗口关闭,退出。\n";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue