220 lines
7.4 KiB
C++
220 lines
7.4 KiB
C++
#include <gtest/gtest.h>
|
||
|
||
#include <vtkActor.h>
|
||
#include <vtkCamera.h>
|
||
#include <vtkConeSource.h>
|
||
#include <vtkPolyDataMapper.h>
|
||
#include <vtkRenderer.h>
|
||
#include <vtkSmartPointer.h>
|
||
|
||
#include "CameraPreset.hpp"
|
||
|
||
using namespace geopro::render;
|
||
|
||
namespace {
|
||
|
||
// 造一个带包围盒的 renderer(一个 cone actor),使 ResetCamera 有内容可重定位。
|
||
vtkSmartPointer<vtkRenderer> rendererWithContent() {
|
||
auto cone = vtkSmartPointer<vtkConeSource>::New();
|
||
cone->SetCenter(0, 0, 0);
|
||
cone->SetHeight(2.0);
|
||
cone->SetRadius(1.0);
|
||
auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||
mapper->SetInputConnection(cone->GetOutputPort());
|
||
auto actor = vtkSmartPointer<vtkActor>::New();
|
||
actor->SetMapper(mapper);
|
||
auto r = vtkSmartPointer<vtkRenderer>::New();
|
||
r->AddActor(actor);
|
||
return r;
|
||
}
|
||
|
||
// 相机的视线方向单位向量 = focalPoint - position(归一化)。
|
||
void viewDir(vtkRenderer* r, double out[3]) {
|
||
auto* c = r->GetActiveCamera();
|
||
double p[3], f[3];
|
||
c->GetPosition(p);
|
||
c->GetFocalPoint(f);
|
||
double d[3] = {f[0] - p[0], f[1] - p[1], f[2] - p[2]};
|
||
double n = std::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
|
||
out[0] = d[0] / n; out[1] = d[1] / n; out[2] = d[2] / n;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// Top:相机在焦点上方(pos.z>focal.z),视线朝 -Z,viewUp=+Y。
|
||
TEST(CameraPreset, TopLooksDown) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Top);
|
||
auto* c = r->GetActiveCamera();
|
||
double p[3], f[3], up[3];
|
||
c->GetPosition(p); c->GetFocalPoint(f); c->GetViewUp(up);
|
||
EXPECT_GT(p[2], f[2]); // 相机在上方
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[2], -1.0, 1e-6); // 视线向下
|
||
EXPECT_NEAR(up[1], 1.0, 1e-6); // 北朝上
|
||
}
|
||
|
||
// Bottom:相机在焦点下方,视线朝 +Z。
|
||
TEST(CameraPreset, BottomLooksUp) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Bottom);
|
||
auto* c = r->GetActiveCamera();
|
||
double p[3], f[3];
|
||
c->GetPosition(p); c->GetFocalPoint(f);
|
||
EXPECT_LT(p[2], f[2]);
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[2], 1.0, 1e-6);
|
||
}
|
||
|
||
// Front:相机在 -Y,视线朝 +Y,viewUp=+Z。
|
||
TEST(CameraPreset, FrontLooksNorth) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Front);
|
||
auto* c = r->GetActiveCamera();
|
||
double p[3], f[3], up[3];
|
||
c->GetPosition(p); c->GetFocalPoint(f); c->GetViewUp(up);
|
||
EXPECT_LT(p[1], f[1]);
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[1], 1.0, 1e-6);
|
||
EXPECT_NEAR(up[2], 1.0, 1e-6);
|
||
}
|
||
|
||
// Back:相机在 +Y,视线朝 -Y。
|
||
TEST(CameraPreset, BackLooksSouth) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Back);
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[1], -1.0, 1e-6);
|
||
}
|
||
|
||
// Left:相机在 -X,视线朝 +X。
|
||
TEST(CameraPreset, LeftLooksEast) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Left);
|
||
auto* c = r->GetActiveCamera();
|
||
double p[3], f[3];
|
||
c->GetPosition(p); c->GetFocalPoint(f);
|
||
EXPECT_LT(p[0], f[0]);
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[0], 1.0, 1e-6);
|
||
}
|
||
|
||
// Right:相机在 +X,视线朝 -X。
|
||
TEST(CameraPreset, RightLooksWest) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Right);
|
||
double d[3]; viewDir(r, d);
|
||
EXPECT_NEAR(d[0], -1.0, 1e-6);
|
||
}
|
||
|
||
// zoomBy(>1) 放大:透视下 vtkCamera::Zoom 收窄视角(ViewAngle 变小→画面放大)。
|
||
TEST(CameraPreset, ZoomInNarrowsViewAngle) {
|
||
auto r = rendererWithContent();
|
||
applyFree3D(r);
|
||
auto* c = r->GetActiveCamera();
|
||
const double before = c->GetViewAngle();
|
||
zoomBy(r, 1.2);
|
||
EXPECT_LT(c->GetViewAngle(), before);
|
||
}
|
||
|
||
// zoomBy(<1) 缩小:透视下视角变宽(画面缩小)。
|
||
TEST(CameraPreset, ZoomOutWidensViewAngle) {
|
||
auto r = rendererWithContent();
|
||
applyFree3D(r);
|
||
auto* c = r->GetActiveCamera();
|
||
const double before = c->GetViewAngle();
|
||
zoomBy(r, 1.0 / 1.2);
|
||
EXPECT_GT(c->GetViewAngle(), before);
|
||
}
|
||
|
||
// 正交投影下 zoomBy 改 parallelScale(放大缩小可视范围)。
|
||
TEST(CameraPreset, ZoomInOrthoReducesParallelScale) {
|
||
auto r = rendererWithContent();
|
||
applyView(r, ViewDir::Top); // Top 不改投影模式;显式打开正交
|
||
auto* c = r->GetActiveCamera();
|
||
c->ParallelProjectionOn();
|
||
r->ResetCamera();
|
||
const double before = c->GetParallelScale();
|
||
zoomBy(r, 2.0);
|
||
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);
|
||
zoomBy(nullptr, 1.2);
|
||
fitView(nullptr);
|
||
auto r = rendererWithContent();
|
||
const double before = r->GetActiveCamera()->GetDistance();
|
||
zoomBy(r, 0.0); // 非法 factor 忽略
|
||
zoomBy(r, -1.0);
|
||
EXPECT_DOUBLE_EQ(r->GetActiveCamera()->GetDistance(), before);
|
||
}
|