feat/vtk-merged-dataset-column #10
|
|
@ -9,6 +9,7 @@
|
|||
#include <QString>
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkBoundingBox.h>
|
||||
#include <vtkCubeAxesActor.h>
|
||||
|
|
@ -424,6 +425,44 @@ void VtkSceneView::fitView() {
|
|||
if (onCameraChanged) onCameraChanged(); // 取景后 → 底图按新视锥重算覆盖(治首帧部分瓦片不出)
|
||||
}
|
||||
|
||||
bool VtkSceneView::datasetBounds(const std::vector<std::string>& dsIds, double outB[6]) const {
|
||||
// computeDataBounds 的按 dsId 版:只并集给定 dsIds 的已渲染 actor 包围盒(同样仅计可见 prop)。
|
||||
vtkBoundingBox bb;
|
||||
for (const auto& id : dsIds) {
|
||||
auto it = dsProps_.find(id);
|
||||
if (it == dsProps_.end()) continue;
|
||||
for (const auto& p : it->second)
|
||||
if (p && p->GetVisibility()) { if (double* b = p->GetBounds()) bb.AddBounds(b); }
|
||||
}
|
||||
if (!bb.IsValid()) return false;
|
||||
bb.GetBounds(outB);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VtkSceneView::fitToBounds(const double b[6]) {
|
||||
if (!scene_.renderer()) return;
|
||||
scene_.renderer()->ResetCamera(b); // 保持朝向,仅重定位+缩放到该盒(区别于 fitView 的全场景)
|
||||
scene_.renderer()->ResetCameraClippingRange(); // 裁剪面含底图 → 不被"蒙版"切掉
|
||||
if (renderWindow_) renderWindow_->Render();
|
||||
if (onCameraChanged) onCameraChanged(); // 相机变了 → 底图按新视锥重算覆盖
|
||||
}
|
||||
|
||||
void VtkSceneView::orbitToAxis(geopro::controller::ViewDir dir, const double pivot[3]) {
|
||||
auto* r = scene_.renderer();
|
||||
if (!r) return;
|
||||
auto* c = r->GetActiveCamera();
|
||||
// 保留当前缩放:取现距离 d=|cam−focal|,绕 pivot 转到 dir 轴(focal=pivot、pos=pivot+off*d)。
|
||||
const double d = c->GetDistance();
|
||||
const auto pose = geopro::render::orbitPose(toRenderViewDir(dir), pivot, d);
|
||||
c->SetFocalPoint(pose.focal[0], pose.focal[1], pose.focal[2]);
|
||||
c->SetPosition(pose.pos[0], pose.pos[1], pose.pos[2]);
|
||||
c->SetViewUp(pose.up[0], pose.up[1], pose.up[2]);
|
||||
c->OrthogonalizeViewUp();
|
||||
r->ResetCameraClippingRange(); // 只转向不改距离 → 不 ResetCamera;仅扩裁剪面
|
||||
if (renderWindow_) renderWindow_->Render();
|
||||
if (onCameraChanged) onCameraChanged(); // 相机变了 → 底图按新视锥重算覆盖
|
||||
}
|
||||
|
||||
void VtkSceneView::rebuildAxes() {
|
||||
// 先移除上一次的坐标轴 prop:render 可能在一次 rebuild 内多次调用(末尾统一 render +
|
||||
// 异步回灌 render),不先移除会叠加坐标轴(评审 HIGH)。移除后再算 bounds(仅数据图元)。
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ public:
|
|||
void applyCameraView(geopro::controller::ViewDir dir) override;
|
||||
void zoom(double factor) override;
|
||||
void fitView() override;
|
||||
|
||||
// ── 视图导航基元(spec §3.1;T1)──────────────────────────────────────────────
|
||||
// 给定 dsIds 的已渲染 actor 世界包围盒并集;无有效返回 false,否则填 out=[xmin,xmax,…,zmax]。
|
||||
bool datasetBounds(const std::vector<std::string>& dsIds, double outB[6]) const;
|
||||
// 相机适配到指定包围盒,保持当前朝向(ResetCamera(b)),用于双击适配/贴合。
|
||||
void fitToBounds(const double b[6]);
|
||||
// 绕 pivot 转到沿 dir 轴看向 pivot,保留当前 focal-to-camera 距离(缩放不变)。
|
||||
void orbitToAxis(geopro::controller::ViewDir dir, const double pivot[3]);
|
||||
void render(bool is2D, bool resetCamera = true) override;
|
||||
void renderIncremental() override;
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,29 @@ void applyView(vtkRenderer* r, ViewDir dir)
|
|||
r->ResetCamera();
|
||||
}
|
||||
|
||||
CameraPose orbitPose(ViewDir dir, const double pivot[3], double distance)
|
||||
{
|
||||
// 方向偏移(pos = pivot + offset*distance)与 up 约定须与 applyView 完全一致:
|
||||
// Top +Z/up+Y、Bottom -Z/up+Y、Front -Y/up+Z、Back +Y/up+Z、Left -X/up+Z、Right +X/up+Z。
|
||||
double off[3] = {0, 0, 0};
|
||||
double up[3] = {0, 0, 1};
|
||||
switch (dir) {
|
||||
case ViewDir::Top: off[2] = 1; up[0] = 0; up[1] = 1; up[2] = 0; break;
|
||||
case ViewDir::Bottom: off[2] = -1; up[0] = 0; up[1] = 1; up[2] = 0; break;
|
||||
case ViewDir::Front: off[1] = -1; break; // 从 -Y 看 +Y,up=+Z
|
||||
case ViewDir::Back: off[1] = 1; break;
|
||||
case ViewDir::Left: off[0] = -1; break; // 从 -X 看 +X,up=+Z
|
||||
case ViewDir::Right: off[0] = 1; break;
|
||||
}
|
||||
CameraPose pose;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pose.focal[i] = pivot[i];
|
||||
pose.pos[i] = pivot[i] + off[i] * distance;
|
||||
pose.up[i] = up[i];
|
||||
}
|
||||
return pose;
|
||||
}
|
||||
|
||||
void zoomBy(vtkRenderer* r, double factor)
|
||||
{
|
||||
if (!r || factor <= 0.0) return;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@ enum class ViewDir { Front, Back, Left, Right, Top, Bottom };
|
|||
// 应用 6 向正交快捷视图:设 position/focalPoint/viewUp 后 ResetCamera。
|
||||
void applyView(vtkRenderer* r, ViewDir dir);
|
||||
|
||||
// 绕支点转到某轴的相机位姿(纯数学,可单测):focal=pivot,pos=pivot+dir_offset*distance,
|
||||
// up 按 dir 预设。方向偏移/up 约定与 applyView 完全一致(Top=+Z 看下、+Y 朝上;Front 从 -Y
|
||||
// 看 +Y、+Z 朝上;…)。用于 orbitToAxis:保留当前缩放距离、只改朝向绕 pivot 转。
|
||||
struct CameraPose {
|
||||
double pos[3];
|
||||
double focal[3];
|
||||
double up[3];
|
||||
};
|
||||
CameraPose orbitPose(ViewDir dir, const double pivot[3], double distance);
|
||||
|
||||
// 相机缩放:factor>1 拉近(放大),factor<1 推远(缩小)。透视下改距离、正交下改 parallelScale。
|
||||
void zoomBy(vtkRenderer* r, double factor);
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,73 @@ TEST(CameraPreset, ZoomInOrthoReducesParallelScale) {
|
|||
EXPECT_LT(c->GetParallelScale(), before);
|
||||
}
|
||||
|
||||
// ── orbitPose(纯数学,供 orbitToAxis 用)──────────────────────────────────────
|
||||
// 各方向:focal==pivot、|pos-pivot|==distance、pos 沿正确轴偏移、up 与 applyView 约定一致。
|
||||
namespace {
|
||||
constexpr double kPivot[3] = {10.0, -20.0, 5.0};
|
||||
constexpr double kDist = 7.0;
|
||||
|
||||
double dist3(const double a[3], const double b[3]) {
|
||||
const double dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2];
|
||||
return std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Top:pos 在 pivot 的 +Z、距离保持,up=+Y(对齐 applyView Top)。
|
||||
TEST(OrbitPose, TopOffsetsPlusZUpY) {
|
||||
auto pose = orbitPose(ViewDir::Top, kPivot, kDist);
|
||||
EXPECT_NEAR(pose.focal[0], kPivot[0], 1e-9);
|
||||
EXPECT_NEAR(pose.focal[1], kPivot[1], 1e-9);
|
||||
EXPECT_NEAR(pose.focal[2], kPivot[2], 1e-9);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[0], kPivot[0], 1e-9);
|
||||
EXPECT_NEAR(pose.pos[1], kPivot[1], 1e-9);
|
||||
EXPECT_NEAR(pose.pos[2], kPivot[2] + kDist, 1e-9); // +Z
|
||||
EXPECT_NEAR(pose.up[0], 0.0, 1e-9);
|
||||
EXPECT_NEAR(pose.up[1], 1.0, 1e-9);
|
||||
EXPECT_NEAR(pose.up[2], 0.0, 1e-9);
|
||||
}
|
||||
|
||||
// Bottom:pos 在 -Z,up=+Y。
|
||||
TEST(OrbitPose, BottomOffsetsMinusZUpY) {
|
||||
auto pose = orbitPose(ViewDir::Bottom, kPivot, kDist);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[2], kPivot[2] - kDist, 1e-9); // -Z
|
||||
EXPECT_NEAR(pose.up[1], 1.0, 1e-9);
|
||||
}
|
||||
|
||||
// Front:从 -Y 看 +Y → pos 在 -Y,up=+Z。
|
||||
TEST(OrbitPose, FrontOffsetsMinusYUpZ) {
|
||||
auto pose = orbitPose(ViewDir::Front, kPivot, kDist);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[1], kPivot[1] - kDist, 1e-9); // -Y
|
||||
EXPECT_NEAR(pose.up[2], 1.0, 1e-9);
|
||||
}
|
||||
|
||||
// Back:pos 在 +Y,up=+Z。
|
||||
TEST(OrbitPose, BackOffsetsPlusYUpZ) {
|
||||
auto pose = orbitPose(ViewDir::Back, kPivot, kDist);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[1], kPivot[1] + kDist, 1e-9); // +Y
|
||||
EXPECT_NEAR(pose.up[2], 1.0, 1e-9);
|
||||
}
|
||||
|
||||
// Left:从 -X 看 +X → pos 在 -X,up=+Z。
|
||||
TEST(OrbitPose, LeftOffsetsMinusXUpZ) {
|
||||
auto pose = orbitPose(ViewDir::Left, kPivot, kDist);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[0], kPivot[0] - kDist, 1e-9); // -X
|
||||
EXPECT_NEAR(pose.up[2], 1.0, 1e-9);
|
||||
}
|
||||
|
||||
// Right:pos 在 +X,up=+Z。
|
||||
TEST(OrbitPose, RightOffsetsPlusXUpZ) {
|
||||
auto pose = orbitPose(ViewDir::Right, kPivot, kDist);
|
||||
EXPECT_NEAR(dist3(pose.pos, kPivot), kDist, 1e-9);
|
||||
EXPECT_NEAR(pose.pos[0], kPivot[0] + kDist, 1e-9); // +X
|
||||
EXPECT_NEAR(pose.up[2], 1.0, 1e-9);
|
||||
}
|
||||
|
||||
// 空指针/非法 factor 安全。
|
||||
TEST(CameraPreset, NullAndInvalidAreSafe) {
|
||||
applyView(nullptr, ViewDir::Top);
|
||||
|
|
|
|||
Loading…
Reference in New Issue