#include #include #include "lod/ViewAdaptiveLodPolicy.hpp" using geopro::render::CameraView; using geopro::render::LodSelection; using geopro::render::selectLod; using geopro::render::VolumeView; namespace { // 构造一个规整体:nx=ny=nz=512、brick=64(每轴 8 块)、level0 间距=1、levels=4(L0..L3)。 // 世界范围 [0,512]³(origin 0)。各 level 维度:512/256/128/64;间距 1/2/4/8。 VolumeView makeVol(int n = 512, int brick = 64, int levels = 4, double sp = 1.0) { VolumeView v{}; v.nx = v.ny = v.nz = n; v.brick = brick; v.levels = levels; v.origin[0] = v.origin[1] = v.origin[2] = 0.0; v.spacing[0] = v.spacing[1] = v.spacing[2] = sp; v.exagg = 1.0; return v; } // 相机:从 +X 方向看体中心。dist = 相机到中心距离;fov/viewportH 控分辨率密度。 CameraView lookFromX(const VolumeView& v, double dist, double fovYDeg = 30.0, int viewportH = 1080) { CameraView c{}; const double cx = v.origin[0] + 0.5 * v.nx * v.spacing[0]; const double cy = v.origin[1] + 0.5 * v.ny * v.spacing[1]; const double cz = v.origin[2] + 0.5 * v.nz * v.spacing[2]; c.focal[0] = cx; c.focal[1] = cy; c.focal[2] = cz; c.pos[0] = cx + dist; c.pos[1] = cy; c.pos[2] = cz; c.up[0] = 0; c.up[1] = 0; c.up[2] = 1; c.fovYDeg = fovYDeg; c.aspect = 1.0; c.viewportH = viewportH; return c; } } // namespace // ── empty:体完全在视锥外(相机背对体)→ empty ────────────────────────────── TEST(ViewAdaptiveLod, VolumeBehindCameraIsEmpty) { const VolumeView v = makeVol(); CameraView c = lookFromX(v, 1000.0); // 把焦点设到相机背后:相机仍在 +X 远处,但看向 +X 更远(体在身后)。 c.focal[0] = c.pos[0] + 1000.0; // 视线朝 +X,体在 -X 侧 → 视锥外 const LodSelection sel = selectLod(v, c); EXPECT_TRUE(sel.empty); } TEST(ViewAdaptiveLod, VolumeOffToSideIsEmpty) { const VolumeView v = makeVol(); // 相机看 +Z(体在 -X..+X、相机在远 +X,焦点正上方)→ 体偏出窄视锥。 CameraView c{}; c.pos[0] = 100000; c.pos[1] = 0; c.pos[2] = 0; c.focal[0] = 100000; c.focal[1] = 0; c.focal[2] = 100000; // 看 +Z c.up[0] = 1; c.up[1] = 0; c.up[2] = 0; c.fovYDeg = 5.0; c.aspect = 1.0; c.viewportH = 1080; const LodSelection sel = selectLod(v, c); EXPECT_TRUE(sel.empty); } // ── 远观整体 → 粗层、区间≈全体、各轴 ≤ maxTextureDim ─────────────────────── TEST(ViewAdaptiveLod, FarViewPicksCoarseLevelWholeVolume) { const VolumeView v = makeVol(); // 很远 + 适中 fov:整体入视锥,worldPerPixel 大 → 粗层。 const CameraView c = lookFromX(v, 8000.0); const LodSelection sel = selectLod(v, c); EXPECT_FALSE(sel.empty); EXPECT_GT(sel.level, 0); // 远 → 粗(非最细) // 区间≈全体:覆盖所有块(该 level 块数)。 // 该 level 每轴块数 = ceil(ceil(512/2^L)/64)。 const int dimL = (512 + (1 << sel.level) - 1) >> sel.level; const int bN = (dimL + v.brick - 1) / v.brick; EXPECT_EQ(sel.bx0, 0); EXPECT_EQ(sel.bx1, bN); EXPECT_EQ(sel.by0, 0); EXPECT_EQ(sel.by1, bN); EXPECT_EQ(sel.bz0, 0); EXPECT_EQ(sel.bz1, bN); } // ── 近观局部 → 细层、区间是视锥内小块、各轴 ≤ maxTextureDim ───────────────── TEST(ViewAdaptiveLod, NearViewPicksFineLevelSmallRegion) { const VolumeView v = makeVol(); // 贴近 + 窄 fov:只看到体内一小块,worldPerPixel 小 → 细层(0)、小区间。 const CameraView c = lookFromX(v, 60.0, 20.0, 1080); const LodSelection sel = selectLod(v, c); EXPECT_FALSE(sel.empty); EXPECT_EQ(sel.level, 0); // 近 → 最细 // 区间是子集(不是全 8 块):至少某一垂直于视线的轴被裁小。 const int bNfull = (512 + v.brick - 1) / v.brick; // 8 const bool yShrunk = (sel.by1 - sel.by0) < bNfull; const bool zShrunk = (sel.bz1 - sel.bz0) < bNfull; EXPECT_TRUE(yShrunk || zShrunk); } // ── 选定层重组区间各轴恒 ≤ maxTextureDim(小 maxTextureDim 强制升层/缩区间)── TEST(ViewAdaptiveLod, RespectsMaxTextureDimAlways) { const VolumeView v = makeVol(); // 多组视距 × 极小 maxTextureDim,断言选定层重组单纹理各轴 ≤ maxTextureDim。 for (double dist : {60.0, 300.0, 1500.0, 8000.0, 40000.0}) { const CameraView c = lookFromX(v, dist); const LodSelection sel = selectLod(v, c, /*maxTextureDim=*/64); if (sel.empty) continue; // 选定 level 下,区间重组单纹理各轴体素数 = (块数 × brick) 截到该 level 维度。 const int dimL = (512 + (1 << sel.level) - 1) >> sel.level; auto axisTex = [&](int b0, int b1) { const int start = b0 * v.brick; const int end = std::min(b1 * v.brick, dimL); return end - start; }; EXPECT_LE(axisTex(sel.bx0, sel.bx1), 64); EXPECT_LE(axisTex(sel.by0, sel.by1), 64); EXPECT_LE(axisTex(sel.bz0, sel.bz1), 64); } } TEST(ViewAdaptiveLod, RespectsDefaultMaxTextureDim) { const VolumeView v = makeVol(/*n=*/4096, /*brick=*/256, /*levels=*/1); // levels=1(只有 level0)→ 不能升层,只能靠缩区间满足 16384。4096 < 16384 必满足。 const CameraView c = lookFromX(v, 100000.0); const LodSelection sel = selectLod(v, c, 16384); ASSERT_FALSE(sel.empty); EXPECT_EQ(sel.level, 0); const int texX = std::min((sel.bx1) * v.brick, 4096) - sel.bx0 * v.brick; EXPECT_LE(texX, 16384); } // ── 视距-层单调:拉近 level 不增 ──────────────────────────────────────────── TEST(ViewAdaptiveLod, LevelMonotonicWithDistance) { const VolumeView v = makeVol(); int prev = 1 << 30; for (double dist : {40000.0, 8000.0, 1500.0, 300.0, 60.0}) { // 由远到近 const CameraView c = lookFromX(v, dist); const LodSelection sel = selectLod(v, c); if (sel.empty) continue; EXPECT_LE(sel.level, prev) << "dist=" << dist; // 拉近 level 不增 prev = sel.level; } } // ── 区间半开且合法(b0> sel.level; const int bN = (dimL + v.brick - 1) / v.brick; EXPECT_GE(sel.bx0, 0); EXPECT_LT(sel.bx0, sel.bx1); EXPECT_LE(sel.bx1, bN); EXPECT_GE(sel.by0, 0); EXPECT_LT(sel.by0, sel.by1); EXPECT_LE(sel.by1, bN); EXPECT_GE(sel.bz0, 0); EXPECT_LT(sel.bz0, sel.bz1); EXPECT_LE(sel.bz1, bN); } // ── side 视角:从某一侧斜看,仍非空且区间合法 ───────────────────────────── TEST(ViewAdaptiveLod, ObliqueSideViewNotEmpty) { const VolumeView v = makeVol(); CameraView c{}; const double cx = 256, cy = 256, cz = 256; c.pos[0] = cx + 1200; c.pos[1] = cy + 1200; c.pos[2] = cz + 800; c.focal[0] = cx; c.focal[1] = cy; c.focal[2] = cz; c.up[0] = 0; c.up[1] = 0; c.up[2] = 1; c.fovYDeg = 45.0; c.aspect = 1.6; c.viewportH = 1080; const LodSelection sel = selectLod(v, c); EXPECT_FALSE(sel.empty); EXPECT_GE(sel.level, 0); EXPECT_LT(sel.bx0, sel.bx1); }